Using GraphQL, a Ruby on Rails Introduction
GraphQL has been stirring up quite a buzz ever since it was introduced by Facebook. Since then companies like Github, Shopify and Pinterest have been using it as a core part of their technology stack. A typical GraphQL query would be structured as below:
{
allMovies {
title
description
}
}
GraphQL provides flexibility for querying the necessary fields that are required. This flexibility provides numerous advantages over REST. Perhaps the most compelling advantage is that it eliminates over-fetching and under-fetching.
REST APIs have a fixed structure defined for each API endpoint. They either return too much data on each API call or too little. For example, if only titles need to be displayed on a certain page, returning extra fields doesn’t make much sense. Similarly, what if we wanted to return only the distributor’s name? In traditional REST APIs it would most probably require sending another request to fetch the distributor information for that movie.
In this post, we’ll implement a simplistic API exposing movies and their reviews.
Setting Up
We’ll start by setting up a base Rails project. We’ll be using the graph-ql ruby gem.
gem install bundler
gem install rails
bundle install
rails new learning-graphql
rails db:create
The Rails application should be up and running. Start the server with rails s and verify that it’s up and running. Add the line gem ‘graphql’ to the Gemfile and then run the following commands:
bundle install
rails generate graphql:install
bundle install
The reason for the second bundle install is because the generator for Rails adds another gem to the Gemfile gem ‘graphiql-rails’, group: :development. We’ll be seeing the use of this gem soon.
Now let’s create our Movie model. Enter the following commands in the terminal:
rails generate model movie title description
rails db:migrate
We can now proceed with creating dummy data. This can be done by adding some records into the db/seeds.rb file, followed by executing the rails db:seed command.
Movie.create(
title: ‘Deadly Weapon’,
description: ‘2 retired monks walk into a bar’
)
Movie.create(
title: ‘Deadly Weapon 2 — This time time it’s personal’,
description: ‘Like Deadly Weapon, only deadlier and more personal’
)
Fetching Movies with GraphQL
Now comes the time to create a GraphQL type to represent the schema of our Movie. This can be achieved with the aid of the generators provided by the GraphQL gem.
rails generate graphql:object movie
# In the app/graphql/types/movie_type.rb file
Types::MovieType = GraphQL::ObjectType.define do
name “Movie”
field :id, !types.ID
field :title, !types.String
field :description, types.String
end
Each schema consists of fields; discrete pieces of information that collectively represent our model. GraphQL treats all fields as nullable values by default. In our case, we know that the id and title fields will always exist. Therefore we can declare them as non-nullable fields. This is achieved by prepending ! to the type of each field.
Let’s now create a query mapping to either fetch all the movies or a single movie based off its ID.
Types::QueryType = GraphQL::ObjectType.define do
name “Query”
# Add root-level fields here.
# They will be entry points for queries on your schema.
field :allMovies do
type types[Types::MovieType]
description “A list of all the movies”
resolve -> (obj, args, ctx) {
Movie.all
}
end
field :movie do
type Types::MovieType
description “Return a movie”
argument :id, !types.ID
resolve -> (obj, args, ctx) { Movie.find(args[:id]) }
end
end
A couple of points to observe here:
— The resolve function is responsible for accepting the query payload and implementing the backend logic to fulfill the request and fetch the data required.
— In the movie field an argument has been defined. Since this field should return a single movie we need to select a criteria to uniquely identify each movie. Utilizing the non-nullable id attribute makes the most sense.
Time to see the fruits of our hard work! Remember the gem we installed? It allows us to interact with our API via the route added in the config/routes.rb file.
The default route is /graphiql however it can be modified in the routes file. Head on over to the route and you’ll see a page similar to the one below:
Let’s now try to fetch all the movies. Enter in the following query and execute it. If all goes well, we’ll see something to the image below. Try removing fields and seeing how the response changes.
{
allMovies {
id
title
description
}
}
To retrieve a single Movie, the following payload can be used:
{
movie(id: 2) {
id
title
}
}
Note how the arguments are passed. A good practice exercise would be to try modifying allMovies to take an argument limit that restricts the number of movies in the response based off the value.
Creating a Movie
Until now, we’ve only been looking at how data can be retrieved. Now we’ll talk about using mutations for operations that modify data. Let’s see how we can go ahead with trying to create a movie:
# app/graphql/types/mutation_type.rb
Types::MutationType = GraphQL::ObjectType.define do
name "Mutation"
field :createMovie, Types::MovieType do
argument :title, !types.String
argument :description, !types.String
resolve -> (obj, args, ctx) {
Movie.create(
title: args[:title],
description: args[:description]
)
}
end
end
The implementation accepts two arguments, the title and the description of the movie. Invoking them has the same syntax as queries, however it’s necessary to add the mutation keyword. The invocation below will return the ID of the newly created movie. It can be modified to return any other field as well.
mutation {
createMovie(title: “Deadly Weapon 3”, description: “Even deadlier!”) {
id
}
}
Adding Relationships
To try out linking different types, we need to first create a new model. For this example we’ll be creating a Review model. A movie can have many reviews.
Enter the following commands in the terminal:
rails generate model review content movie_belongs_to
rails db:migrate
rails generate graphql:object review
We’ll also need to add has_many :reviews to the app/models/movie.rb file. Time to implement the Review type representation. While we’re at it, we should also add an additional field in the Movie type to expose reviews as an additional property.
# app/graphql/types/review_type.rb
Types::ReviewType = GraphQL::ObjectType.define do
name "Review"
field :id, types.ID
field :content, types.String
end
# app/graphql/types/movie_type.rb
Types::MovieType = GraphQL::ObjectType.define do
name "Movie"
field :id, types.ID
field :title, types.String
field :description, types.String
field :reviews do
type types[Types::ReviewType]
resolve -> (obj, args, ctx) {
obj.reviews
}
end
end
Congratulations! Reviews have now been exposed as an additional optional field for our movies. Let’s quickly try it out!
Proceeding Further
This concludes our brief look into utilizing GraphQL with Ruby.
Going through the guides at the GraphQL (http://graphql.org/learn/) followed by the graphql-ruby gems (http://graphql-ruby.org/guides) will get you up to speed with everything that GraphQL has to offer. For practical examples of GraphQL, playing with the API of Github might also serve you well.