Using GraphQL with Rails 7: Building Efficient APIs
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:
- 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.
- 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 yourapplication.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
andvariables
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.