Building a Ruby on Rails API with GraphQL — Writing Queries

Building a Ruby on Rails API with GraphQL — Writing Queries

GraphQL describes itself as a query language for your API and at its core lies a schema describing the underlying data in the form of a directed graph. GraphQL execution engine - runtime executes queries according to the schema and perhaps some general rules from the specification.

Previously, I shared an introduction to GraphQL alongside the comparison of GraphQL vs REST.

In this article, we will set up a Rails API-only application with graphql for our blog app. The blog app will include the following model.

  • User model
  • Post model and
  • Comments model

Let’s begin by running installing our rails API-only application using

rails new blog-graphql -T -d postgresql

The -T skips unit test, -d specifies PostgreSQL as the database engine since by default Rails uses SQLite.

Now let’s add GraphQL to our application, at the time of this article, the current version is gem 'graphql', '~> 2.0', '>= 2.0.2'. See rubygems page for latest version.

Let's add it to our Gemfile and run

bundle install.

Next, run

rails generate graphql:install

This command generates the following:

  • Set up a folder structure in app/graphql/
  • Add schema definition
  • Add base type classes
  • Add a Query type definition
  • Add a Mutation type definition with a base mutation class
  • Add a route and controller for executing queries
  • Set up a folder structure in app/graphql/
  • Add schema definition
  • Add base type classes
  • Add a Query type definition
  • Add a Mutation type definition with a base mutation class
  • Add a route and controller for executing queries
  • Install graphiql-rails

Run bundle install again to install the added graphiql-rails gem added.

Now, that you’ve successfully installed your GraphiQL, let's set up our blog models.

rails generate model User first_name last_name address postcode city country
rails generate model Post user:references body
rails generate model Comment user:references post:references body
rails db:create db:migrate

Let’s make sure our associations are in place within the models. Update User model associations to

# app/models/user
class User < ApplicationRecord
 has_many :posts
 has_many :comments
end
# app/models/post
class Post < ApplicationRecord
 belongs_to :user
 has_many :comments
end

To make sure that everything works correctly on your end, fire up your rails server using

rails server

Open http://localhost:3000/graphiql on your rails server, you should get the GraphQL playground. Go ahead and query the example testField.

test1.png

If you’re probably wondering, where does the testField come from? Well, wonder no more, it came from #app/graphql/types/query_type.rb, one of the files generated when we did rails generate graphql:install

Great job! So far.

Let’s generate sample data to play with. Add the code below to #db/seeds.rb

users = User.create([
 {
   first_name: "John",
   last_name: "Doe",
   address: "123 Main Street",
   postcode: "AB12 3CD",
   city: "London",
   country: "UK"
 },
 {
   first_name: "Jane",
   last_name: "Doe",
   address: "24 High Street",
   postcode: "AB12 45CD",
   city: "New York",
   country: "US"
 }
])

posts = Post.create!([
 {
   body: "This is a post",
   user: users.first
 },
 {
   body: "This is another post",
   user: users.first
 },
 {
   body: "This is yet another post",
   user: users.last
 }
])

Comment.create!([
 {
   body: "This is a comment",
   post: posts.first,
   user: users.first
 },
 {
   body: "This is another comment",
   post: posts.first,
   user: users.last
 }
])

Run

rails db:seed

Implementing the R (Read) from CRUD Operations

Let’s see how we can read or query our users as well as posts and comments. If you pay attention to the test_field in #app/graphql/types/query_type.rb

field :test_field, String, null: false, description: "An example field added by the generator"

The word field is followed by a name, the type then its nullability. This means the return type should be a String and cannot be null. The description allows you to document your field types as you develop, giving API users to understand the purpose/function of each field. Just below it, there is a method that matches the field name, it has to be the same, this gives the return value when the query is executed.

Let’s create a field to return all users, in our case we would like to return a user object containing all user properties. GraphQL comes with a generator to easily achieve that.

rails generate graphql:object user

This should generate user_type.rb under #app/graphql/types for you

module Types
 class UserType < Types::BaseObject
   field :id, ID, null: false
   field :first_name, String
   field :last_name, String
   field :address, String
   field :postcode, String
   field :city, String
   field :country, String
   field :created_at, GraphQL::Types::ISO8601DateTime, null: false
   field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
 end
end

Now, let’s add the code below to our

#app/graphql/types/query_type.rb file.

field :users, [UserType], null: false, description: 'List all users'

def users
   User.all
end

Navigate back to http://localhost:3000/graphiql, you should have the same output.

test2.png

As stated earlier with the test_field, the GraphQL execution engine resolves fields using the following algorithm:

  • First, it looks for the method with the same name defined in the type class itself (like we did earlier in the QueryType for users); we can access the object being resolved using the object method.
  • If no such method is defined, it tries to call the method with the same name on the object itself.

Let’s demonstrate this example by adding the full_name and full_address methods to the user model.

def full_name
   "#{first_name} #{last_name}"
 end

 def full_address
   "#{address}, #{postcode}, #{city}, #{country}"
 end

Then let’s quickly update our user_type.rb with defined fields. Your #app/graphql/types/user_type.rb should now be looking like this

module Types
 class UserType < Types::BaseObject
   field :full_name, String
   field :full_address, String
   [...]
 end
end

Navigate back to http://localhost:3000/graphiql, let’s make sure we can query the defined method in our model as part of the user field.

test3.png

The beautiful thing about GraphQL is there is no over/under fetching which is a concept describing how we often get less or more data than we need with REST because there is only so much that has been made available by the backend, this is avoided by GraphQL, all the data are always available but you can just select the field you need at any point in time in any particular context so that you don’t ever ask for more than you actually need and you always have access to what you need.

Before we wrap up, let’s quickly generate post and comment types, and also establish relationships between user posts and comments.

rails generate graphql:object post
rails generate graphql:object comment

Now that we have our PostType and CommentType let’s include that in our UserType and PostType respectively.

#app/graphql/types/user_type.rb
module Types
 class UserType < Types::BaseObject
   field :posts, [Types::PostType]
   [...]
 end
end

#app/graphql/types/post_type.rb
module Types
 class PostType < Types::BaseObject
   field :comments, [Types::CommentType]
   [...]
 end
end

Navigate back to http://localhost:3000/graphiql, let’s make sure our associations work properly.

test4.png

Finally, we will add fields for posts and and associated comments. #app/graphql/types/query_type.rb file.

field :posts, [PostType], null: false, description: "List all posts"

   def posts
     Post.all
   end

Navigate back to http://localhost:3000/graphiql and test your query

test5.png

We can add a few tweaks, by including author of the posts, also handling N+1 query by preloading each request for posts in users and comments in posts request.

The easiest way to avoid N+1 queries is to use eager loading. In our case, we need to preload users when making a query to fetch items in QueryType: This may not be the most efficient solution, however, if you’re keen on the solution look at the following.

#app/graphql/types/query_type.rb
def users
  User.preload(:posts, :comments)
end

def posts
  Post.preload(:comments, :user)
end

#app/models/post.rb
 def author
   user.full_name
 end

Then update our PostType

#app/graphql/types/post_type.rb 
module Types
 class PostType < Types::BaseObject
    field :author, String
 end
end

test6.png

We have learned a lot about GraphQL, created our blog API models, add GraphQL to our Rails only-API application, and implemented read operations for Users, Posts, and Comments.

In our next article, we will talk about how to manipulate data using GraphQL mutations and keep it up-to-date with subscriptions.

Source code: https://github.com/acushlakoncept/blog-graphql

Video: https://www.youtube.com/watch?v=clCq23KM05c