Using GraphQL with Rails 7: Building Efficient APIs

Dhyey Sapara
Simform Engineering
6 min readNov 29, 2023

Harness the GraphQL Query with Rails 7 API

GraphQL is a modern, open-source query language for your API, providing a more efficient, powerful, and flexible alternative to traditional REST APIs. It allows you to request exactly the data you need in a single query.

In this article, we will learn how to leverage GraphQL to create clean, scalable APIs that are a joy for front-end developers to consume.

Let’s start with the basics first:

When building an application using REST, there are two possibilities that can occur when calling an API:

  1. Under-Fetching
  • Under-fetching in APIs occurs when an API endpoint doesn’t provide sufficient data in a single request, forcing the client to make multiple requests to gather the required information.

2. Over-Fetching

  • Over-fetching in APIs happens when an API endpoint provides more data than the client needs for a particular operation.

At the core of every GraphQL application lies a schema. It describes the underlying data in the form of a directed graph.

Getting Started With GraphQL in Rails 7

For this tutorial, we’re going to use:

  • Ruby 3.2.2
  • Rails 7.1.1
  • PostgreSQL

Setting up a new Rails API project

First, let’s create a Rails API project and checkout by running the following command:

rails new rails-graphql --api -d postgresql
cd rails-graphql

Preparing the data model

We’ll need two models for this tutorial: User to describe authors and Blog to represent blog posts. Let’s generate them:

rails g model user first_name last_name email
rails g model blog title description:text user:references

Next, add associations and email downcasing in the user.rb file:

has_many :blogs, dependent: :destroy
before_save { self.email = email.downcase }

Include the Faker gem in the Gemfile for generating fake data:

gem 'faker'

Run bundle install and add seed data to seeds.rb.

5.times do
first_name = Faker::Name.first_name
User.create!(first_name:, last_name: Faker::Name.last_name, email: "#{first_name}@test.com")
end

20.times do
Blog.create!(title: Faker::Book.title, description: Faker::Lorem.paragraphs(number: 3), user: User.find(User.ids.sample))
end

Finally, we’re ready to initialize the database:

rails db:create db:migrate db:seed

Now that we have created models and records, let’s try to access them.

Adding a GraphQL endpoint

To use GraphQL in the project, add the graphql-ruby gem:

bundle add graphql
ra ils generate graphql:install

This will generate the necessary files for a minimal GraphQL setup.

First, let’s take a look at rails_graphql_schema.rb:

class RailsGraphqlSchema < GraphQL::Schema
mutation(Types::MutationType)
query(Types::QueryType)
end

The schema declares that all queries should go to Types::QueryType while mutations should go to Types::MutationType.

Let’s see what’s inside types/query_type.rb class — it is an entry point for all the queries:

module Types
class QueryType < Types::BaseObject
# Add root-level fields here.
# They will be entry points for queries on your schema.

# TODO: remove me
field :test_field, String, null: false,
description: "An example field added by the generator"
def test_field
"Hello World!"
end
end
end

Running a test query

Before writing queries, test the GraphQL setup using the test_field query. Mount the GraphiQL IDE in Ruby on Rails for easy query testing:

First, Mount the GraphiQL IDE in Ruby on Rails.

  1. Add “graphiql-rails” gem to the Gemfile:
gem "graphiql-rails"

2. Add the engine to routes.rb:

if Rails.env.development?
mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "/graphql"
end

For Rails as an API, add the following to access GraphiQL IDE on browser:

  • Add require "sprockets/railtie" to your application.rb.
  • Create app/assets/config/manifest.js file with the following content:
//= link graphiql/rails/application.css
//= link graphiql/rails/application.js

Now, start the server and visit http://localhost:3000/graphiql.

Write the following query:

{
testField
}

Execute the query to test the GraphQL setup.

If we look at our logs we should see what happens when we execute a query.

Requests are sent to GraphqlController, which has also been added to the application by the graphql gem generator.

Take a look at the GraphqlController#execute action:

def execute
variables = prepare_variables(params[:variables])
query = params[:query]
operation_name = params[:operationName]
context = {
# Query context goes here, for example:
# current_user: current_user,
}
result = RailsGraphqlSchema.execute(query, variables: variables, context: context, operation_name: operation_name)
render json: result
rescue StandardError => e
raise e unless Rails.env.development?
handle_error_in_development(e)
end

This action calls for the RailsGraphqlSchema#execute method with the following parameters:

  • query and variables represent a query string and arguments sent by a client respectively;
  • context is an arbitrary hash, which will be available during the query execution everywhere;
  • operation_name picks a named operation from the incoming request to execute (could be empty).

Writing our first GraphQL query

Now, let’s write a query to fetch blog posts and users.

Query for Fetching Posts

Remove example contents from Types::QueryType and register a field blogs that returns all blogs. Add the corresponding resolver method.

The field represents queries that we can execute to obtain data. First, we will create a type for the element we want to return. The GraphQL gem provides a convenient generator for this:

rails g graphql:object blog

This command generates a basic blog type that we can customize to our preferences. If we open the app/graphql/types/blog_type.rb file, we can view the added fields. Let’s make modifications:

module Types
class BlogType < Types::BaseObject
field :id, ID, null: false
field :title, String
field :description, String
field :user_name, String

def user_name
"#{object.user.first_name} #{object.user.last_name}"
end
end
end

We can create custom fields by adding them and defining a method to return the desired name. In this context, the object in our method represents the current object.

Now, let’s add a field in our query type to return all the blogs:

field :blogs, [Types::BlogType], null: true, description: "Fetches all the blogs"
def blogs
Blog.all
end

Here, Types::BlogType is the return type for our element, and ‘[]’ indicates that it will return an array of elements. We use null: true to signify that blogs can be empty. The description provides information about the field’s purpose for developers. Additionally, we need to create a resolver method with the same name as the field to return the desired results.

Now, let’s execute this query:

The left-most pane displays documentation for the field.

In the middle pane, we have written our query:

{
blogs{
id
title
description
userName
}
}

Here, we specify the fields we want from the query, including only the id, title, and description from our blog.

The right-most pane shows the data retrieved from our blog.

Using arguments in query

If you’re interested in a specific blog rather than retrieving all blogs, you can achieve this by passing an argument. Here’s how you can incorporate this in your query_type:

field :blog, Types::BlogType, null: false, description: "Fetch blog for some id" do
argument :id, ID, required: true
end
def blog(id:)
Blog.find(id)
end

We declare the argument we need and pass these arguments to our resolver method for utilization.

The query for this would look like:

query getBlog($id: ID!){
blog(id: $id){
title
description
}
}

Learn more about arguments here.

Here, $id is the variable being passed. It’s strongly typed, so we define the type it will receive.

In GraphiQL, you can pass the variable as follows:

Executing this query would return something like:

{
"data": {
"blog": {
"title": "Dance Dance Dance",
"description": "[\"Et aut nobis. Dolores architecto consequatur. Atque voluptates fuga.\", \"Necessitatibus amet in. Quis ipsa magni. Facilis exercitationem est.\", \"Autem id adipisci. Tempora occaecati sit. Aperiam explicabo dolor.\"]"
}
}
}

Thank you for reading; hopefully, it was helpful to you. In the next part, we will delve into mutations and how to use them.

References:

Stay tuned for more insights and the latest trends in the development ecosystem by following the Simform Engineering.

Connect with us: Twitter | LinkedIn

--

--