Getting started with GraphQL in Rails
Related videos
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!