Graphql APIs in Rails

330
0

For an effective application, we need an effective API which can get and update relevant data in an optimized manner. Graphql is becoming more popular because it does exactly that.

How is graphql different: 

GraphQL is a query language for APIs and runtime, for fulfilling those queries with the existing data. Client queries what they want and that’s exactly what they get. There is no need to deal with irrelevant data.

How does this work:

 The APIs define a schema according to the available information. For example, if you have models called User and Product in your system, you can define a schema that contains available fields for the defined models(Types in Graphql). Types and their mutations are defined and only operations respecting the schema are allowed to be performed. Graphql schema is strongly typed so that there are no mismatched fields.

Schema definition can be seen using an interactive GUI like graphiql:

Working with Rails:

Since the underlying principle behind Rails is convention over configuration, we can spin up our own GraphQL API within a few minutes. By the end of this article, we will have a working api. We need two gems:

  1. graphql: To add boilerplate for setting up API.
  2. graphiql-rails: It provides a GUI interface. We can add this gem to the development environment to visualize the requests and schema.

Note:

I am using ruby 2.5.8 and rails 6.1.4

Setup instructuion for ruby and rails:

1. Install ruby using any version manager.

   I use frum so the corresponding command is:

    frum install 2.5.8  

2. Install rails

    gem install rails -v 6.1.4

Starting Rails Project and initializing models:

  1. After the rails gem is installed in the environment, we can use the following command to start a new project:
rails new users_graphql -d postgresql --api --skip-tests --skip-action-mailbox --skip-action-text --skip-spring -T --skip-turbolinks --skip-active-storage

2. Genaerate models for demo application:

rails generate model User name:string email:string
rails generate model Product user:belongs_to name:string description:text 

3. Update config/database.yml file with username and password for database access: 

default: &default
 adapter: postgresql
 encoding: unicode
 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
 username: postgres
 password:
 
development:
 <<: *default
 database: users_graphql_development
 
test:
 <<: *default
 database: users_graphql_test
 
production:
 <<: *default
 database: users_graphql_production
 username: users_graphql
 password: <%= ENV['USERS_GRAPHQL_DATABASE_PASSWORD'] %>

4. Run commands to create db and run migrations:

rails db:create
rails db:migrate

5. In app/models/user.rb, add has_many relationships for products. The dependent destroy option will ensure that if a user object is deleted, all attached products will also get deleted.

class User < ApplicationRecord
 has_many :products, dependent: :destroy
end

6. In Gemfile, add ‘graphql’, ‘graphiql-rails’ and ‘faker’ gem. Graphql will add boilerplate for graphql queries. With graphiql-rails, we can use the GUI interface. Faker gem will help us create dummy objects for user and product models:

gem 'graphql'
 
# existing gems
 
group :development do
 # existing gems

 gem 'graphiql-rails'
 gem 'faker'
end

 

7. In db/seeds.rb, we can create dummy objects using faker gem:

5.times do
 user = User.create(name: Faker::Name.name, email: Faker::Internet.email)
 5.times do
     user.products.create(name: Faker::Lorem.sentence(word_count: 2), description: Faker::Lorem.paragraph(sentence_count: 3))
 end
end

8. Run bundle install in terminal to install the newly added gems and run rails db:seed to seed the db.

Setting up Graphql:

After the models are in place and databse is ready, we can use the generators provided by graphql gem to set up graphql in the application:

rails generate graphql:install
 
rails generate graphql:object user
 
rails generate graphql:object product

This will automatically add graphql/ to app/ in the project.

Also, it will add the route for queries.

We can update the config/routes.rb to mount the engine to use interactive queries with graphiql.

In config/routes.rb:

Rails.application.routes.draw do
 
 if Rails.env.development?
   mount GraphiQL::Rails::Engine, at: 'graphiql', graphql_path: "graphql#execute"
 end
 
 post "/graphql", to: "graphql#execute"
 # For details on the DSL available within this file, see http

If sprockets engine is not enabled, there might be an issue with graphiql. To prevent this, uncomment require “sprockets/railtie” in application.rb.

In app/assets/config/manifest.js:

//= link graphiql/rails/application.css
//= link graphiql/rails/application.js

Now, we have the environment ready and we can define types for models to query data. We are creating types for Product and User model:

In app/graphql/types/product_type.rb:

module Types
 class ProductType < Types::BaseObject
   field :id, ID, null: false
   field :user_id, Integer, null: false
   field :name, String, null: true
   field :description, String, null: true
   field :created_at, GraphQL::Types::ISO8601DateTime, null: false
   field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
 end
end

In app/graphql/types/user_type.rb:

module Types
 class UserType < Types::BaseObject
   field :id, ID, null: false
   field :name, String, null: true
   field :email, String, null: true
   field :created_at, GraphQL::Types::ISO8601DateTime, null: false
   field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
   field :products, [Types::ProductType], null: true
   field :products_count, Integer, null: true
 
   def products_count
     object.products.size
   end
  
 end
end


We’ll have to add the query fields in app/graphql/types/query_type.rb:

module Types
 class QueryType < Types::BaseObject
   # Users field

   field :users, [Types::UserType], null: false
 
   # Users Query

   def users
     User.all
   end
 
   # Field for one user

   field :user, Types::UserType, null: false do
     # Arguments to identify user

     argument :id, ID, required: true
   end
 
   # Find User Query

   def user(id:)
     User.find(id)
   end
 end
end

We have everything in place for querying user information.

Rails server can be started using the commad: rails s

This will by default start the server on port 3000. 

In the browser, we can access the graphiql UI using:

http://localhost:3000/graphiql 

The UI is divided in two parts. Left part is for writing queries and right part is for results.

There is a Docs option in the top right corner of the screen. This will have the schema for all the queries and mutations defined in the API.

To get all users from the databse, the query will look like:

{
  users {
    name
    email
    productsCount
  }
}

To get user with id 2:

{
  user(id: 2) {
    name
    email
    products {
      name
      description
    }
  }
}

Now, that we can query the api, let’s move on to mutations. Mutations are defined to update the data. Mutations can be writtent to create, update or delete an object.

We can start by adding a create_user mutation.

In app/graphql/mutations/create_user.rb:

class Mutations::CreateUser < Mutations::BaseMutation
 # fields to be updated

 argument :name, String, required: true
 argument :email, String, required: true
 
 # Fields to return after object is created

 field :user, Types::UserType, null: false
 field :errors, [String], null: false
 
 # To pass params to create an object

 def resolve(name:, email:)
     user = User.new(name: name, email: email)
     if user.save
         {
             user: user,
             errors: []
         }
     else
         {
             user: nil,
             errors: users.errors.full_messages
         }
     end
 end
end

Add the created mutation in mutation_type.

In app/graphql/types/mutation_type.rb:

module Types

module Types
 class MutationType < Types::BaseObject
 
   field :create_user, mutation: Mutations::CreateUser
 end
end

After each update in the api, we’ll have to restart the server. Now, we can see the createUser mutation listed in Docs.

For creating a user:

  createUser(input: {name: "John Doe", email: "john.doe@gmail.com"}) {
    user {
      id
      name
      email
    }
    errors
  }
}

We can add users now using Graphql.

A similar process can be used to update and delete users.

In app/graphql/mutations/update_user.rb:

class Mutations::UpdateUser < Mutations::BaseMutation
 argument :id, ID, required: true
 argument :name, String, required: false
 argument :email, String, required: false
 
 field :user, Types::UserType, null: false
 field :errors, [String], null: false
 
 def resolve(id:, name: nil, email: nil)
     user = User.find(id)
     if email == nil
         email = user.email
     end
     if name == nil
         name = user.name
     end
     if user.update(name: name, email: email)
         {
             user: user,
             errors: []
         }
     else
         {
             user: user,
             errors: user.errors.full_messages
         }
     end
 end
end

In app/graphql/mutations/delete_user.rb:

class Mutations::DeleteUser < Mutations::BaseMutation
 argument :id, ID, required: true
 field :user, Types::UserType, null: false
 field :errors, [String], null: false
 
 def resolve(id:)
     user = User.find(id)
     if user.destroy
         {
             user: user,
             errors: []
         }
     else
         {
             user: nil,
             errors: users.errors.full_messages
         }
     end
 end

In app/graphql/types/mutation_type.rb:

module Types
 class MutationType < Types::BaseObject
 
   field :create_user, mutation: Mutations::CreateUser
   field :update_user, mutation: Mutations::UpdateUser
   field :delete_user, mutation: Mutations::DeleteUser
 end
end

For deleting user with id 2:

mutation {
  deleteUser(input: {id: 2}) {
    user {
      id
      name
      email
    }
    errors
  }
}

I hope this tutorial was easy to follow. Graphql provides a lot of flexibility to define how we want to use the APIs. You can try a lot of stuff with the Product model also and add new models with complex mutations. You can find the repository for source code here.

Sweta Sharma
WRITTEN BY

Sweta Sharma

I am a developer with 6 years of experience. I have been involved in various projects ranging from management apps to server-side monitoring. I am proficient in backend development in Ruby and API integrations.