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:
- graphql: To add boilerplate for setting up API.
- 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:
- 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.