Skip to content

Instantly share code, notes, and snippets.

@wayne5540
Last active January 30, 2017 04:51
Show Gist options
  • Save wayne5540/d3140186b68a1ba183e0b328a291e1f7 to your computer and use it in GitHub Desktop.
Save wayne5540/d3140186b68a1ba183e0b328a291e1f7 to your computer and use it in GitHub Desktop.

Building a GraphQL API in Rails - Part 2 - Start Coding

This is a series blog post cover above three topics of GraphQL:

  1. About GraphQL
  2. Building a basic API with Rails
  3. 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!

2. Building a basic API with Rails

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:

  1. As an User, I need an account so I can use API.
  2. As an User, I can get my account information from API

1. As an User, I need an account so I can use API.

Install Authorisation gem

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~! 🙏

Set API token and User model

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~! 🎉

2. As an User, I can get my account information from API

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
    }
  }
}

Install necessary gems

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

Create API endpoint

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.

About GraphQL type

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

Define GraphQL types

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

Last setup

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment