Build a GraphQL API in under 20 minutes
Tell your back-end exactly what data you want, when you want it.
GraphQL is exciting.
Why? It’s an innovative way of managing the relationship between client and server, in a way that makes building both sides easier and faster.
Some hail it as the end of REST. Instead of calling individual endpoints to get data—each endpoint tailored to distributing a specific piece of the data— we call a single endpoint and get back all the information we want, structured exactly the way we want it.
In this tutorial, we’ll build a simple Rails and GraphQL API. By the end, you’ll know how to integrate GraphQL into an existing Rails application, and also get glimpse of why the protocol is so exciting.
Let’s get to it.
What We’re Going to Build
GraphQL is all about relationships—picturing your database as literally a graph of related nodes. To match that, let’s create a simple relationship schema.
In fact, we’ll use the best relationship of all: between people and their pets.
Say you have a database with Person models and Pet models. You’re working on the front-end of an application to display each person in the database, along with their pets. But you also want to display the reverse—to show all the pets along with their people.
In the old REST paradigm, you’d need two endpoints to achieve this. We’re going to do it in one.
Here is the JSON data we want to eventually receive.
Call 1, to get people with their pets:
{
data: {
people: [
{
name: 'Scott',
surname: 'Domes',
pets: [{ name: 'Colby' }, { name: 'Jessie' }]
}
]
}
}
Call 2, to get the pets each with their person:
{
data: {
pets: [
{
name: 'Colby',
person: {
name: 'Scott'
}
},
{
name: 'Jessie',
person: {
name: 'Scott'
}
}
]
}
}
We’re going to build our Rails app, and then do the GraphQL good stuff.
Setting Up Rails
Open up a terminal in a working directory and run the following commands:
rails new people --api
cd people/
rails generate model Person name:string surname:string
rails generate model Pet name:string person:references type:string
rake db:migrate
In the above, we create our app (as an API), then create models for people and pets.
If you open up app/models/pet.rb
, you’ll see the model already belongs to a person.
Open up app/models/person.rb
and we can add the other side of the relationship:
class Person < ApplicationRecord
has_many :pets
end
Now we have a relationship between people and pets. Pets belong to people, who have multiple pets.
Before we move to the GraphQL bit, let’s populate our database.
Open up the Rails console with rails c
and then run the following commands (with your name, of course):
scott = Person.create(name: 'Scott', surname: 'Domes')
scott.pets.create(name: 'Colby')
scott.pets.create(name: 'Jessie')
Installing GraphQL
It’s so easy: in your Gemfile add gem 'graphql'
and then run bundle install
. (Make sure to exit your Rails console first!)
Then, inside app/
, create a types
folder. This is where all our GraphQL logic will live.
Schemas & Types
Now we can talk GraphQL.
GraphQL relies on two concepts: schemas and types.
Schemas define what data is available, and what type it is. Types determine the structure of the data (what it looks like).
1
is of the integer type. “Hello” is of the string type. Our Person model will be an object type — a type that has fields. What fields those are depends on how we define our PersonType.
All of the above is pretty abstract, so let’s see what it looks like by creating a PersonType.
Our First Type
Inside types/
create a new file, called person_type.rb
, and type the following:
PersonType = GraphQL::ObjectType.define do
name "Person"
description "A Person"
field :id, types.ID
field :name, types.String
field :surname, types.String
field :pets do
type types[PetType]
resolve -> (person, args, ctx) {
person.pets
}
end
end
We define our PersonType with a name, a description, and then a list of fields. A field is define by a symbol indicating its name, and then what type that field is.
All looks rather straightforward until we get to the field :pets. Here, we see a pet is of the PetType (which we’ll create in a moment). But in order to tell GraphQL where to get the data for the pet, we have to add a resolve function, which grabs the person’s pets.
What resolve does is get the data from person.pets, and then runs that data through the PetType to structure it.
Speaking of which…
Our Second Type
# types/pet_type.rbPetType = GraphQL::ObjectType.define do
name "Pet"
description "A Pet"
field :id, types.ID
field :name, types.String
field :person do
type PersonType
resolve -> (pet, args, ctx) {
pet.person
}
end
end
There’s the other half of the equation.
Here, the :person field will grab the pet.person, and then structure it according to the PersonType. This means we can then access the person’s name, surname, and pet, if we want.
Our Schema and QueryType
Now that we have our Types defined, we need a way to query the data we want.
We need a way to tell our API, “hey, give me all the people and their pets” and “hey, give me all the pets with their owners”.
To do that, we need to do three things:
- Define our QueryType (how our query is going to appear).
- Define our Schema
- Make a route to send the queries to.
After that, we’ll be ready to start testing our API!
Our QueryType
The QueryType is the gateway to our data. It defines how requests can ask for data.
Create a new file in the types/
folder called query_type.rb
and define it as so:
QueryType = GraphQL::ObjectType.define do
name "Query"
description "The query root for this schema" field :pets do
type types[PetType]
resolve -> (obj, args, ctx) {
Pet.all
}
end field :people do
type types[PersonType]
resolve -> (obj, args, ctx) {
Person.all
}
end
end
We open up two fields to our query, :pets and :people. Those fields will then grab the data from Person.all
or Pet.all
, and filter it through the PersonType
and PetType
, respectively.
What does this mean? It means we can structure our query like so:
query {
people {
name
surname
pets {
name
}
}
}
And the GraphQL will process it in the following steps:
QueryType → :people field → PersonType → :name, :surname, :pets fields → PetType → :name field
It will then organize the data exactly as we have defined it above.
Before we see that in action, though, we have to do a little more housekeeping.
Defining Our Schema
Create a new file in the types/
folder called schema.rb
, like so:
Schema = GraphQL::Schema.define do
query QueryType
end
Our Schema is defined so that we can make a query against it. Not much exciting here.
Defining Our Query Route
Lastly, we need a way into our API: a query route.
The beauty of GraphQL is that all requests for data go through a single endpoint.
Let’s create that endpoint now. Inside config/routes.rb
, add the following line before the end
.
post '/query' => 'application#query'
When we send a POST request to ‘http://localhost:3001/query’, it’ll call the #query
method inside our ApplicationController. Next, we’ll create that method.
# app/controller/application_controller
class ApplicationController < ActionController::API
def query
result = Schema.execute(
params[:query]
)
render json: result
end
end
(Make sure to delete the protect_from_forgery
line if it exists)
When this endpoint is called, we execute it according to our GraphQL Schema, based on the query defined in the request params. Then, we send the response back as JSON.
Okay, let’s see it in action!
Testing Our Query
Install and open Postman, and start up our app with rails server -p 3001
.
At the start of this tutorial, we defined two queries as our goal. One was to fetch all the people with their pets, and the second was to fetch all the pets with their people.
Here’s what the two look like in Postman, as the body of our POST request.
Query 1:
{
"query":
"{
pets {
name
person {
name
}
}
}"
}
Query 2:
{
"query":
"{
people {
name
surname
pets {
name
}
}
}"
}
In Postman:
Note that you must set the body to RAW, as JSON! Also, Postman complains about the pretty multiline version above, so condense it to a single line like the below:
{ "query": "{ pets { name person { name } } }" }
.
{ "query": "{ people { name surname pets { name } } }" }
Success!
For our two queries, we get back the following:
We did it! Now we can play around with requesting what data we want. If we only want people with their name:
Or just the owner’s name of each pet:
Or even pets with their name and all their owner’s pets:
These examples are limited by our small dataset, but you can imagine the possibilities with real-life complex models, with dozens of fields and dozens of relationships. All can be managed and traversed with one endpoint.
Welcome to the future!
Call to Action
Learned something from this article? Show your appreciation by hitting those clappin’ hands. Even better, share it with your friends and colleagues. Thanks for reading!
For more, you can follow me on Twitter or check out my personal site: