Designing APIs for mobile and web applications has become a very common problem today. Since the burst of smartphones a decade ago (and with them, the explosion of mobile apps) REST APIs have become the main standard to exchange data between application server and clients.
One of the biggest issues encountered when developing an API is the structure and granularity of the data your backend is giving back to your client. Let’s say you’re building a Twitter-like social network where users can follow other users. While designing your API, you might want to add an endpoint (GET /users/123
) to retrieve data about a specific user. Should the server send data about this user’s followers in the API response? Or should it keep the response very light because what you need is only basic data about this one user? What if your client needs the full data of this user, but only the username and profile pictures of his followers?
Right now you might be thinking about doing some tricks using query parameters when calling the endpoint, something like GET /users/123?full=true&with_followers=true&light_followers=true
. However, I bet you can easily understand how this approach can become a huge source of headaches for you and all the other engineers who need to use your API.
Now imagine that your application is something as complex as GitHub or Facebook, with interleaved data between users, posts, relationships, etc. and the headache is now turning into a nightmare.
Enter GraphQL, a query language created by Facebook some years ago when migrating their main application to a native app. Facebook is a good example of a very complex data architecture. That’s why they designed a better way to deal with data:
Let the client ask for what data it needs from the server.
Going back to our previous example, imagine that our mobile app wants to ask for some data about one specific user but only needs basic information on his 20 first followers. The GraphQL query sent to the server would look like this:
{
user(id: 123) {
id
username,
email,
bio,
profile_picture,
followers(size: 20) {
id
username,
profile_picture
}
}
}
The API implementing the GraphQL protocol would then respond with the following JSON data:
{
"user": {
"id": 123,
"username": "foo",
"email": "foo@myapp.com",
"bio": "I'm just a sample user, nothing much to say."
"profile_picture": "https://mycdn.com/somepicture.jpg",
"followers": [
{
"id": 134,
"username": "bar",
"profile_picture": "https://mycdn.com/someotherpicture.jpg"
},
{
"id": 153,
"username": "baz",
"profile_picture": "https://mycdn.com/anotherpicture.jpg"
},
// and another 18 followers at maximum
]
}
}
In this tutorial, we’ll see how we can implement a simple movies database API using the GraphQL language and Ruby on Rails.
Creating the Project
We’ll create the Rails project using the --api
option to use a lightweight version of the full-stack framework, containing only what we need to build a REST API.
$ gem install rails
$ rails new graphql-tutorial --api
$ cd graphql-tutorial/
Now add the graphql
gem to your Gemfile:
gem 'graphql'
And execute $ bundle install
to install gem. We can now start the application server with $ rails server
.
We need to generate our two models, one to represent movies and the other for movie actors:
$ rails g model Movie title:string summary:string year:integer
$ rails g model Actor name:string bio:string
We’re going to implement a many-to-many relationship between our two models, so we have to generate a migration for the join table:
$ rails g migration CreateActorsMovies
Then update the models:
# app/models/movie.rb
class Movie < ActiveRecord::Base
has_and_belongs_to_many :actors
end
# app/models/actor.rb
class Actor < ActiveRecord::Base
has_and_belongs_to_many :movies
end
We now have a very simple Rails app setup with two models. Let’s now build our API implementing a GraphQL schema.
Building the GraphQL Schema
Before diving straight into the code, let’s talk a bit more about the GraphQL specification. We’ll start by analyzing the following query that we’re going to implement for our application:
{
movie(id: 12) {
title
year
actors(size: 6) {
name
}
}
}
Let’s break it down into the following parts:
- Inside the first opening and the last closing brackets is the body of our GraphQL query, also called the root object or query object. This object has a single field
movie
and takes a single argumentid
. The API is responsible to return with the movie object with the specifiedid
. - Inside this field
movie
, we ask for the scalar fieldstitle
andyear
as well as the collection field ofactors
. To this last one, we’re giving asize
argument, specifying that we want only the first 6 actors associated with this movie. - Finally, we ask for the single field
name
for each actor of the collection held into theactors
field.
Now that we had a brief look of the possibilities of the GraphQL language, we can start the implementation by defining the root query object:
# app/types/query_type.rb
QueryType = GraphQL::ObjectType.define do
name "Query"
description "The query root for this schema"
field :movie do
type MovieType
argument :id, !types.ID
resolve -> (obj, args, ctx) {
Movie.find(args[:id])
}
end
field :actor do
type ActorType
argument :id, !types.ID
resolve -> (obj, args, ctx) {
Actor.find(args[:id])
}
end
end
The root object can have two immediate children types, which are the two models that we have defined in our app, movie
and actor
. For each field definition specify its type, which will be defined in its own class later. We also specify the arguments the field can accept — only an id
which has the special type ID!
(!
indicating that this argument is required).
Finally, we provide a resolve
function, where we get back the ID value given in the query and return the associated model from the database. Note that, in our case, the only thing we do is call the Active Record method Actor::find
, but are free to implement anything we want, as long as we return the data that was requested.
Don’t forget to add the directory containing our types definitions to the list of autoloaded paths:
# config/application.rb
config.autoload_paths < < Rails.root.join("app", "types")
We still need to define the types to handle the movie
and actor
fields. Here’s the code for the definition of MovieType
:
# app/types/movie_type.rb
MovieType = GraphQL::ObjectType.define do
name "Movie"
description "A Movie"
field :id, types.ID
field :title, types.String
field :summary, types.String
field :year, types.Int
field :actors do
type types[ActorType]
argument :size, types.Int, default_value: 10
resolve -> (movie, args, ctx) {
movie.actors.limit(args[:size])
}
end
end
This definition is similar to the one before, except the scalar fields title
, summary
, and year
. We don’t have to provide a resolve
method, as the library will infer the model fields by their name. The actors
field type is [ActorType]
, which is a collection of objects of type ActorType
.
We also indicate that the field actors
can be given an optional size
argument. Defining the resolve
method allows us to handle its value using the Active Record API to limit the size of the collection.
The definition of the ActorType
below is done in very a similar way:
# app/types/actor_type.rb
ActorType = GraphQL::ObjectType.define do
name "Actor"
description "An Actor"
field :id, types.ID
field :name, types.String
field :bio, types.String
field :movies do
type types[MovieType]
argument :size, types.Int, default_value: 10
resolve -> (actor, args, ctx) {
actor.movies.limit(args[:size])
}
end
end
Finally, we can create the schema containing the root query object as its query member:
# app/types/schema.rb
Schema = GraphQL::Schema.define do
query QueryType
end
We can now use it as an entry point for our queries inside the API controllers:
# app/controllers/movies_controller.rb
class MoviesController < ApplicationController
# GET /movies
def query
result = Schema.execute params[:query]
render json: result
end
end
We are now ready to use GraphQL queries inside our API! Let’s add some data …
$ bundle exec rails c
> movie = Movie.create!(title: "Indiana Jones", year: 1981, summary: "Raiders of the Lost Ark")
> actor = Actor.create!(name: "Harrison Ford", bio: "Some long biography about this actor")
> movie.actors < < actor
… and try the endpoint using any HTTP client:
curl -XGET http://localhost:3000/movies -d "query={
movie(id: 1) {
title,
year,
actors {
name
}
}
}"
Going Further
For those of you who’d like to keep learning about using GraphQL with Rails, here are some interesting things to try:
For now, we can only query a specific movie by its ID since we defined the singular
movie
field. What if we wanted the list of movies released in 1993? We could add another fieldmovies
to our rootQuery
, taking filter arguments such asyear
to filter and return a list of records:```ruby # app/types/query_type.rb field :movies do type types[MovieType] argument :year, types.Int resolve -> (obj, args, ctx) { if args[:year].present? Movie.where(year: args[:year]) else Movie.all end } end ```
We could allow the client to specify the order in which lists of
actors
ormovies
are returned, in case we start to have a lot of records. This can be done, again, using optional arguments and handling them inside theresolve
function.At some point, we’ll probably want to limit access to some objects or fields to the clients. For example, we could have users authenticated by some token sent in the client request. We would then check access to records or fields depending on this user permissions. The GraphQL gem also provides some ways to deal with this.
There are many things left to explore. You can check the documentation of the gem, and the official specification of GraphQL to dive deeper into its details.
I hope that you enjoyed discovering the possibilities offered by GraphQL and that you’ll give it a try for your future projects!
Further reading on SitePoint
- Book: Rails Deep Dive
- Screencast: Testing Views, Routes, and Helpers for Problems in Your Rails App
- Screencast: Testing Your Rails App Models with RSpec
- Screencast: Managing Sample Data with Fixtures and Factories
- Screencast: Setting Up Automated Testing with RSpec
Frequently Asked Questions on Building APIs with Ruby on Rails and GraphQL
How do I set up GraphQL in my Ruby on Rails application?
Setting up GraphQL in your Ruby on Rails application involves a few steps. First, you need to add the ‘graphql’ gem to your Gemfile and run ‘bundle install’. Then, you can generate a GraphQL schema by running ‘rails generate graphql:install’. This will create several files and directories in your application, including a schema file, a directory for your types, and a directory for your resolvers. You can then start defining your types and resolvers, and add queries and mutations to your schema.
How do I define a type in GraphQL?
In GraphQL, a type is a way of defining the shape of your data. You can define a type in your Ruby on Rails application by creating a new file in the ‘app/graphql/types’ directory. This file should define a class that inherits from ‘Types::BaseObject’ and defines fields for each piece of data you want to include in the type. Each field should have a type, which can be a built-in GraphQL type like ‘String’ or ‘Int’, or a custom type that you’ve defined.
How do I create a query in GraphQL?
A query in GraphQL is a way of fetching data from your server. You can define a query in your Ruby on Rails application by adding a method to your schema file. This method should have the same name as the query you want to define, and it should return a type that matches the shape of the data you want to return. You can then use this query in your client application to fetch data from your server.
How do I create a mutation in GraphQL?
A mutation in GraphQL is a way of modifying data on your server. You can define a mutation in your Ruby on Rails application by adding a method to your schema file. This method should have the same name as the mutation you want to define, and it should take arguments that match the data you want to modify. It should also return a type that matches the shape of the data you want to return after the mutation is performed. You can then use this mutation in your client application to modify data on your server.
How do I handle errors in GraphQL?
Handling errors in GraphQL can be done in several ways. One common approach is to include an ‘errors’ field in your types, which can be populated with any errors that occur while fetching or modifying data. You can also use the ‘errors’ method on the ‘context’ object in your resolvers to add errors to the response. Additionally, you can raise exceptions in your resolvers, which will be caught and converted into errors by the GraphQL library.
How do I use variables in GraphQL queries?
Variables in GraphQL queries allow you to parameterize your queries, making them more flexible and reusable. You can define a variable in a query by including it in the query definition, like so: ‘query MyQuery($myVariable: String)’. You can then use this variable in your query by prefixing it with a dollar sign, like so: ‘field(arg: $myVariable)’. When you send the query to your server, you should include a ‘variables’ object in the request body that maps variable names to their values.
How do I paginate data in GraphQL?
Pagination in GraphQL can be achieved using the ‘first’ and ‘after’ arguments on fields that return lists of data. The ‘first’ argument specifies how many items to return, and the ‘after’ argument specifies a cursor that points to the start of the data to return. You can generate cursors by encoding the ID of an item, and you can decode cursors to fetch the corresponding item from your database.
How do I sort data in GraphQL?
Sorting data in GraphQL can be done by adding a ‘sort’ argument to fields that return lists of data. This argument should be an enum that defines the possible sort orders for the data. In your resolver, you can then use this argument to sort the data before returning it.
How do I filter data in GraphQL?
Filtering data in GraphQL can be done by adding filter arguments to fields that return lists of data. These arguments should match the fields of the data you want to filter on, and they should have types that match the types of these fields. In your resolver, you can then use these arguments to filter the data before returning it.
How do I secure my GraphQL API?
Securing your GraphQL API involves several steps. First, you should authenticate your users, either by using a traditional session-based approach or by using tokens. You can then use the ‘context’ object in your resolvers to check the authentication status of the user and restrict access to data accordingly. Additionally, you should validate and sanitize all input to prevent injection attacks, and you should rate limit requests to prevent denial-of-service attacks.
Leonard is a web developer from France, currently based in London and working as a backend engineer at Once Dating. Working on more personal projects, he loves to experiment with other technologies like Go or Elixir.