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 argument id. The API is responsible to return with the movie object with the specified id.
Inside this field movie, we ask for the scalar fields title and year as well as the collection field of actors. To this last one, we’re giving a size 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 the actors 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 field movies to our root Query, taking filter arguments such as year 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 or movies are returned, in case we start to have a lot of records. This can be done, again, using optional arguments and handling them inside the resolve 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.
Source: Sitepoint
The Tech Platform
Comments