GraphQL is a query language for APIs. The query language itself is universal and not tied to any frontend or backend technology. This characteristic makes it a great choice for many frameworks or patterns you or your company might follow.
Today, I’m going to build a basic API using GraphQL. This will only cover backend/api concepts. In the future I plan to add an additional tutorial on implementing a front-end around the same concepts. Look for that soon!
GraphQL is a query language for APIs. The query language itself is universal and not tied to any frontend or backend technology. This characteristic makes it a great choice for many frameworks or patterns you or your company might follow.
I'm going to build a basic API using GraphQL. This will only cover backend/api concepts. In the future I plan to add an additional tutorial on implementing a front-end around the same concepts. Look for that soon!
For this app we will leverage the API mode Rails has baked in. This essentially eliminates the view layer and allows you to opt in to middleware as you see fit. Read more about the API mode here.
$ rails new graphql_fun --api--skip-test
GraphQL comes with some concepts that you need to understand to use it effectively. Here are those concepts and their definitions.
Queries – Fetch specific data from the API. Typically these are read-only like GET requests.
Mutations – Some type of modification of data on the API. e.g. CREATE, UPDATE, DESTROY.
Types – Used to define datatypes, or in our case, Rails models. A type contains fields and functions that respond with data based on what is requested. Types can also be static, like String or ID which come from the server side library.
Fields – Represent the attributes for a given type (like attributes on a model).
Functions – Supply the above fields with data (like methods on a model).
Create the models
We’ll use a basic blog concept without any form of authentication layer to keep things simple.
rails g model User email:string name: string
rails g model Post user:belongs_to title:string body:text
rails db:migrate
Update your user.rb model to have the following relation:
Since we need GraphQL itself, that means we need to install the ruby port of GraphQL as a gem. On top of the main ruby implementation of the language we’ll use a nice development utility that allows us to perform queries in the browser called GraphiQL.
I’ll also be using the popular gem called faker to save time seeding some data for us to use.
We create 5 users with 5 posts for each user by running:
$ rails db:seed
Adding GraphQL files
In order to work with GraphQL we need to use different files not typically associated with a rails application. Thanks to the graphql gem we have access to new generators. You can see what generators are in your arsenal by running rails generate inside your application folder on the command line.
A new section should appear that looks similar to the following:
We can generate the necessary files we need by running the following.
# install graphql$ rails generate graphql:install
$ bundle install# generate objects (similar to our typical rails model layer)$ rails generate graphql:object user
$ rails generate graphql:object post
Running the install generator creates quite a few files as well as adds a new route:
$ rails generate graphql:install
Running via Spring preloader in process 30494
create app/graphql/types
create app/graphql/types/.keep
create app/graphql/graphql_fun_schema.rb
create app/graphql/types/base_object.rb
create app/graphql/types/base_argument.rb
create app/graphql/types/base_field.rb
create app/graphql/types/base_enum.rb
create app/graphql/types/base_input_object.rb
create app/graphql/types/base_interface.rb
create app/graphql/types/base_scalar.rb
create app/graphql/types/base_union.rb
create app/graphql/types/query_type.rb
add_root_type query
create app/graphql/mutations
create app/graphql/mutations/.keep
create app/graphql/types/mutation_type.rb
add_root_type mutation
create app/controllers/graphql_controller.rb
route post "/graphql", to: "graphql#execute"
Skipped graphiql, as this rails project is API only
You may wish to use GraphiQL.app for development: https://github.com/skevy/graphiql-app
If you recall a bit before we added a second gem to our development environment that lets you perform and visualize GraphQL queries on the fly. We need to extend our routes.rb file to only load this in the development environment of the app. That would look like the following:
Think of GraphiQL as a GUI for GraphQL. GraphQL only has a single endpoint unlike more conventional RESTful routing which generates quite a few absolute paths for different request types.
Now, visiting localhost:3000/graphiql on your local rails server will give you a nice UI to use.
Small gotcha
Because we are in API mode there’s no concept of an asset pipeline in play. That framework has been commented out by default in config/application.rb If you tried to boot your server right now it would boot but visiting the GraphiQL path would result in an error. To fix this we need to un-comment a line in application.rb and restart your server.
# config/application.rbrequire_relative'boot'require"rails"# Pick the frameworks you want:require"active_model/railtie"require"active_job/railtie"require"active_record/railtie"require"action_controller/railtie"require"action_mailer/railtie"require"action_view/railtie"require"action_cable/engine"require"sprockets/railtie"# <- uncomment this line!# require "rails/test_unit/railtie"# Require the gems listed in Gemfile, including any gems# you've limited to :test, :development, or :production.Bundler.require(*Rails.groups)moduleGraphqlFunclassApplication<Rails::Application# Settings in config/environments/* take precedence over those specified here.# Application configuration should go into files in config/initializers# -- all .rb files in that directory are automatically loaded.# Only loads a smaller set of middleware suitable for API only apps.# Middleware like session, flash, cookies can be added back manually.# Skip views, helpers and assets when generating a new resource.config.api_only=trueendend
On top of this issue we need to provide a manifest.js file within app/assets/config/manifest.js. You’ll need to create those files and folders for this to work.
assets
└── config
└── manifest.js
Inside the manifest file add the following:
//= link graphiql/rails/application.css//= link graphiql/rails/application.js
Boot your server once more and you should hopefully be back in action.
GraphQL Types
For the User and Post models we need to create types so that GraphQL knows what kind of data to send back. Here I can specify what columns, methods, and more that will return to the app.
Starting with the UserType let’s ammend the user_type.rb file to include the following.
Each field gets an object “type” and a null option of whether or not it needs to be present for the query to be consider successful. Passing types and null booleans tells GraphQL what to expect so it knows how to parse data on both the backend and client side parts of the app.
I added a method called posts_count that simply grabs the amount of posts in the database. This doesn’t exist on the model directly so we invented it. In these type of methods we get the word object for free. It refers to the Rails model in mention. In this case, User.
You may notice id and name don’t have functions tied to them. These are already mapped thanks to the models we generated with Rails before hand.
With GraphQL there are two types of requests that get routed to query_type.rb and mutation_type.rb. These have already been referenced when we ran the install generator. That file is called your_appname_schema.rb. You can think of this file like a routing type of file. Mine looks like the following:
Here I’m defining what users and user bring back for us. We need to define these fields and their appropriate ruby methods for determining the response. The users field returns an array of UserType objects (multiple users) and can never be empty (nil). The user field accepts an id argument and returns a single user. It to, can never be nil.
We can test out our work in at localhost:3000/graphiql. A GraphQL query for users looks like the following:
query {users{
name
email
postsCount
}}
If all goes swimmingly you should see a response back of all the users.
And if querying a specific user you should return only one:
query {
user(id: 2){
name
email
posts {
title
}}}
Based on the relationship between our data we can nest posts within the user query and return all the posts of the given user whose ID equals 2.
Mutations
Mutating data is exactly how it sounds. In the RESTful world this is your UPDATE, PUT, POST, DELETE responses.
We can setup a base class and extend it for each future mutation we create. Create a new file in app/graphql/mutations/ called base_mutation.rb
Consider this a shell of all our future mutations.
Some terminology is in order when it comes to understanding mutations
Arguments – arguments to accept as params, which are required, and what object types they are. This is similar to defining strong params in a Rails controller, but with more fine grained control of what’s coming in.
Fields – Same concept as our Query fields from before. In my case, I accepted arguments to create a new user. I want to return a user field with our new model accompanied with an array of errors if any exist.
Resolver – The resolve method is where we execute our ActiveRecord commands. It returns a hash with keys that match the above field names.
In my logs I can see that a user was indeed created:
Started POST "/graphql"for ::1 at 2019-11-03 15:22:20 -0600
Processing by GraphqlController#execute as */*
Parameters: {"query"=>"mutation {\n createUser(input: {name: \"Andy Leverenz\", email: \"andy@web-crunch.com\"}) {\n user {\n id\n name\n email\n }\n errors\n }\n}\n", "variables"=>nil, "graphql"=>{"query"=>"mutation {\n createUser(input: {name: \"Andy Leverenz\", email: \"andy@web-crunch.com\"}) {\n user {\n id\n name\n email\n }\n errors\n }\n}\n", "variables"=>nil}}(0.0ms) begin transaction
SQL (0.7ms) INSERT INTO "users"("email", "name", "created_at", "updated_at") VALUES (?, ?, ?, ?)[["email", "andy@web-crunch.com"], ["name", "Andy Leverenz"], ["created_at", "2019-11-03 21:22:20.095509"], ["updated_at", "2019-11-03 21:22:20.095509"]](0.6ms) commit transaction
Completed 200 OK in 16ms (Views: 0.1ms | ActiveRecord: 1.7ms)
Pretty slick stuff!
Part 2 featuring the front-end coming soon!
For now we have our GraphQL/Rails set up working great. We didn’t need extra routes, controllers, or serializers to achieve the same work done here. What’s great is that we are only returning the data way ask for and type-checking at the same time. GraphQL is very powerful and I’m beginning to see what all the fuss is about.
Look forward to a front-end follow up to this tutorial coming soon. We’ll use similar queries to construct a view layer with a front-end framework (mostly likely React + Apollo). Until then, thanks for following along!
Shameless plug time
I have a new course called Hello Rails. Hello Rails is a modern course designed to help you start using and understanding Ruby on Rails fast. If you’re a novice when it comes to Ruby or Ruby on Rails I invite you to check out the site. The course will be much like these builds but a super more in-depth version with more realistic goals and deliverables. Download your copy today!!