This is a series blog post cover above three topics of GraphQL:
- About GraphQL
- Building a basic API with Rails
- Some best practices
GraphQL is not a new thing, it already exist for a while, the reason I decide to investigate it is because Github released their GraphQL API alpha currently. We know Github has been a very good company for a long time, their RESTful API also becomes some kind of standard for developers, so it’s pretty interesting to see what’s new they’ve been released.
This topic is based on my MeetUp talk, to know more you can check my slide and example repo.
Alright, Let’s get started!
Content from slide#4
Before we start writing code, lets define the top level user story and build it step by step:
Top Level User Story:
- As an User, I need an account so I can use API.
- As an User, I can get my account information from API
This is really easy for Rails application, all you need is to install a authentication gem you want, Devise and Clearance are both a good options, in this example, I’ll use Clearance just to try something new, you can choose whatever you want, it does’t matter.
I won’t go through how to install Clearance because you can find out from their doc, but basically you only need 3 commands:
$ rails generate clearance:install
$ rails generate clearance:routes
$ rake db:migrate
Pretty easy, thanks ruby community~! 🙏
Wait, there is one more thing to do, we need a auth_token to authorise User through API, I’ll use has_secure_token (if you use rails5+, it’s built in, you don’t need to install this gem) to simply generate User#api_token
, so our User model would looks like below:
class User < ActiveRecord::Base
include Clearance::User
has_secure_token :api_token
end
# == Schema Information
#
# Table name: users
#
# id :integer not null, primary key
# created_at :datetime not null
# updated_at :datetime not null
# email :string not null
# encrypted_password :string(128) not null
# confirmation_token :string(128)
# remember_token :string(128) not null
# api_token :string
That’s it, we finished story 1! Yay~! 🎉
Ok, time to create GraphQL API.
Our goal is to build a API endpoint that can receive a query like this:
query {
me: viewer {
id
email
created_at
}
}
and return JSON response like this:
{
"data": {
"me": {
"id": 1,
"email": "[email protected]",
"created_at": 1477206061
}
}
}
Settings
# Core gem
gem 'graphql', '~> 1.0.0'
# Awesome gem to build a graphql api explorer, not necessary
gem 'graphiql-rails'
# TDD
group :development, :test do
gem 'rspec-rails', '~> 3.5'
gem 'shoulda-matchers'
gem 'factory_girl_rails'
gem 'faker'
end
Request would be POST /graphql?query=graphql-query
with Authorization header { "Authorization" => "Token #{user.api_token}" }
So, we need to create a route for it
# config/routes.rb
Rails.application.routes.draw do
post "graphql" => "graphqls#create"
end
And then controller
class GraphqlsController < ApplicationController
before_action :authenticate_by_api_token!
def create
query_string = params[:query]
query_variables = JSON.load(params[:variables]) || {}
context = { current_user: current_user }
result = Schema.execute(query_string, variables: query_variables, context: context)
render json: result
end
end
Schema.execute(query_string, variables: query_variables, context: context)
is the basic usage of graphql
gem, we pass query string, graph variables, and context into Schema
which we will define it later.
Before we go any further, let’s take a look of GraphQL type system se we can understand what happened inside our controller action. As you see, the query example I show you before:
query {
me: viewer {
id
email
created_at
}
}
It’s actually contains 3 different scopes: Schema
, QueryType
, and UserType
Each type is defined by us. See these pictures below and you’ll have clearer clue.
Schema Type
The most outer scope is Schema which contains 2 special types Query
and Mutation
, think Query
as GET request and Mutation
as POST request will be better to understand what they are.
Query Type
User Type
Now it’s time to define these types by code. It’s pretty easy to do it by using GraphQL::ObjectType
API:
UserType
To define User type, we define each fields, the resolve
block is the way we customise the response, otherwise it will fetch the method which by field’s name (ex: field id will use user.id
as response)
In this example, we want updated_at
and created_at
to be Integer, so I call obj.updated_at.to_i
in updated_at
field’s resolve block.
# app/graph/types/user_type.rb
UserType = GraphQL::ObjectType.define do
name "User"
description "A user"
field :id, types.Int
field :email, types.String
field :updated_at do
type types.Int
resolve -> (obj, args, ctx) {
obj.updated_at.to_i
}
end
field :created_at do
type types.Int
resolve -> (obj, args, ctx) {
obj.created_at.to_i
}
end
end
**QueryType Same with UserType, we create a viewer field and point it to UserType.
# app/graph/types/query_type.rb
QueryType = GraphQL::ObjectType.define do
name "Query"
description "The query root of this schema"
field :viewer do
type UserType
description "Current user"
resolve ->(obj, args, ctx) {
ctx[:current_user]
}
end
end
The context[:current_user]
is passed in by Schema.execute
method and can be used in every fields under Schema
. Here we don’t actually need to pass current_user
into UserType because UserType itself can also retrieve current_user
, however the example here is to show how we pass user object into UserType by return current_user
inside QueryType#viewer
resolve block.
Schema
# app/graph/schema.rb
Schema = GraphQL::Schema.define do
query QueryType
end
We’re getting there, just add some setups here:
Autoload graph types
# config/application.rb
module GraphBlog
class Application < Rails::Application
# ...
config.autoload_paths << Rails.root.join('app', 'graph', 'types')
end
end
Setting GraphiQL (UI interface to interact with graph API)
First, mount GraphiQL engine into rails routes
# config/routes.rb
Rails.application.routes.draw do
# ...
mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "/graphql"
# ...
end
Then setup default Authorization header
# config/initializers/graphiql.rb
GraphiQL::Rails.config.headers['Authorization'] = -> (context) {
"Token #{context.request.env[:clearance].current_user.try(:api_token)}"
}
Now we can start rails server and go to http://localhost:3000/graphiql to play around.
Here are some example to try:
query {
viewer {
id
email
}
}
query {
me: viewer {
...userFields
}
alsoIsMe: viewer {
...userFields
}
}
fragment userFields on User {
user_id: id
email
}
Yeah! Now we have our first graphql API endpoint on our rails project! 🎉
This is just a rough guide how to start building GraphQL API on Rails, in Building a GraphQL API in Rails - Part 3 - Best Practice we will show some best practices.