Getting started with GraphQL in Rails

22 Apr, 2022

In this guide, we are going to delve into setting up a GraphQL server on Rails. Our focus areas will include setting up GraphQL types for your models, installing the GraphQL Ruby gem along with creating mutations for logging in, registering an account and creating a query for fetching details about the currently logged in user. So without further ado, let's dive right in.

You'll find the docs for the graphql-ruby gem here.

Installation of GraphQL Ruby Gem

Let's kick off the process by installing the GraphQL gem. To do that, we'll use the bundle add command to add the GraphQL gem. Following that, we're going to initiate the installation with this command:

bundle add graphql
rails generate graphql:install

Configuration Changes and Initialization

Having installed the gem, we proceed to initializers, where we'll create a new initializer for GraphiQL. This will enable us to set the authorization header in the GraphiQL editor.

# config/initializers/graphiql.rb
GraphiQL::Rails.config.header_editor_enabled = true

With the configuration set, we run our Rails server and visit GraphiQL, an in-browser IDE for exploring GraphQL, it’s available at localhost:3000/graphiql.

Introduction to Queries and Mutations

Next up are queries and mutations. As the name suggests, queries fetch data while mutations alter it. For this demonstration, both our login and register functions will be mutations. However, before exploring these mutations, we'll first set up an account type.

Setting up Account Type

To set up an account type, we will use a GraphQL generator. Our command essentially looks like:

rails generate graphql object account

In the created object, it's evident that all the fields from ActiveRecord are automatically copied across for us. We only need to make an adjustment to the account type, which has come through as types enum type. This will not work and thus we need to define the enum explicitly by creating a new type with individual and company as values.

# Create app/graphql/types/account_type_type.rb
module Types
  class AccountTypeType < BaseEnum
    value "individual", "An individual"
    value "company", "A company"
  end
end

# Update app/graphql/types/account_type.rb
module Types
  class AccountType < Types::BaseObject
    field :id, ID, null: false
    # This is the type that needs changed...
    field :account_type, Types::AccountTypeType, null: false
    field :name, String, null: false
    field :company_number, String
    field :date_of_birth, GraphQL::Types::ISO8601Date
    field :email, String, null: false
    field :token, String
    field :created_at, GraphQL::Types::ISO8601DateTime, null: false
    field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
  end
end

Exploring Login and Register Mutations

Our mutations, login and register are now ready to be defined. We kick this off by creating a field login from within the mutation type. This is our mutation, and we need a Ruby method to implement it. Our second step involves defining the return type – account type – which GraphQL will insist must be non-null. It will either return an account or throw an error. Login mutations have two crucial inputs: email and password, both of which are strings. Right below are the steps we follow to authenticate a user.

# Added to app/graphql/types/mutation_type.rb
field :login, Types::AccountType, null: false do
  argument :email, String, required: true
  argument :password, String, required: true
end
def login(email:, password:)
  account = Account.find_by(email: email)
  if account&.authenticate(password)
    account.token = account.to_sgid(expires_in: 12.hours, for: 'graphql')
    account
  else
    raise GraphQL::ExecutionError.new("Invalid email or password")
  end
end

Assuming a user keys in a wrong password, an error 'Invalid email or password' will be thrown. So, what happens if a user keys in the right credentials? The server returns data, including login, with the user's ID, email and name and a token set that can be used for future requests. The registration mutation is similar to login, except for the fact that it calls for a lot more arguments, almost as many as the registration form does, and, it also yields back an account in response.

# Added to app/graphql/types/mutation_type.rb
field :register, Types::AccountType, null: false do
  argument :account_type, Types::AccountTypeType, required: true
  argument :name, String, required: true
  argument :company_number, String, required: false
  argument :date_of_birth, String, required: false
  argument :email, String, required: true
  argument :password, String, required: true
end
def register(**kwargs)
  account = Account.new(kwargs)
  if account.save
    account.token = account.to_sgid(expires_in: 12.hours, for: 'graphql')
    account
  else
    raise GraphQL::ExecutionError.new("Register failed.")
  end
end

Generating a Token for Authentication

To be able to stay authenticated, we need to generate a token. In our case, we're going to use a Signed GlobalID. This will enable us to create a cryptographically secure and signed GlobalID that can be sent in plain text across the internet, to be used later for looking up an account.

# You'll see this in the login and register mutations above
account.token = account.to_sgid(expires_in: 12.hours, for: 'graphql')

With this Global ID, we are able to generate a token that can be exposed to the public. The scope of the Global ID is set to graphql. The token expires in 12 hours, after which a user would need to log in again.

Querying the current user

Next we create a query called me that will fetch the current logged in users account.

# Add to app/graphql/types/query_type.rb
field :me, Types::AccountType, null: false,
  description: "Details on the authenticated account"
def me
  authenticate!
  current_account
end

You’ll see we have authenticate! and current_account methods being called, we’ll need to add those to our BaseObject type in GraphQL that all of our queries and mutations inherit from, this is GraphQL’s version of ApplicationController.

# Update app/graphql/types/base_object.rb
module Types
  class BaseObject < GraphQL::Schema::Object
    edge_type_class(Types::BaseEdge)
    connection_type_class(Types::BaseConnection)
    field_class Types::BaseField

    private

     def current_account
       context[:current_account]
     end

     def authenticate!
       if current_account.blank?
         raise GraphQL::ExecutionError, "Authentication failed, you must be signed in!"
       end
     end
  end
end

Finally, with the correct token, a user can put a query and get information about their own account details. Voila!

query {
  me {
    id
    name
    email
  }
}

Be sure to stick around for our next guide in which we will tackle testing GraphQL queries and mutations with RSpec. Don't forget to subscribe to receive updates. Happy coding!

Discussion (0)

To comment you need to sign up for a free account or sign in.

Get new videos in your inbox weekly!

Be the first to hear about new courses and videos launching. You’ll only receive relevant news, no spam.