Skip to main content

How to Make Data Fetching Easier Using Ruby on Rails 7 with GraphQL

How to Make Data Fetching Easier Using Ruby on Rails 7 with GraphQL

First introduced in 2004, Ruby on Rails (Rails) is a popular open-source, server-side framework for building web applications. Application Programming Interfaces (API), used to receive and share data from other applications, are commonly built using Representational State Transfer (REST). But RESTful APIs can sometimes cause over fetching of data, extracting all of the data from a specific resource instead of just what’s needed. 

To overcome this and other challenges of RESTful APIs, Meta introduced GraphQL in 2015. GraphQL is a flexible, strongly-typed query language used to deliver data to mobile and web applications. It enables front-end developers to query the database, without requiring many changes to the back-end.

In this blog, we’ll explore how Rails works with GraphQL by creating a simple API without using RESTful API. 

Let’s get started!

First—how does GraphQL work?

GraphQL works with a single endpoint, which means that you can request data from multiple resources with one end point, you have control of the response, and you will get just the information you were looking for.

GraphQL has two common operations:

• Query: used to fetch data, which means read-only

• Mutation: used as the write method (create, update, delete) followed by a fetch

 

To use GraphQL, you need:

Ruby 2.7.0 or newer

Rails 7

Step 1: Create and set up the project

• Run the rails creation command:

rails new rails-graphql

• Install the GraphQL gem:

gem install graphql

• Add the gem to the Gemfile:

# Use GraphQL for API
gem "graphql"

• Run the ‘bundle install’ command.

• Then run the graphql installation:

rails g graphql:install

What does the graphql command do?

This command creates all the structure and data types that we need to start working with GraphQL, including:

Schema file Contains the structure of our GraphQL instance and some configuration methods
Query type file Contains our queries—as many as we want to create
Mutation type file Contains all the mutations we want 
Mutation folder file Contains the method of each of those mutations
Arguments Parameters we want to receive in an action (mutation or query)
Fields Attributes we want to return

The command also creates another endpoint to test our graphql endpoint in the development environment.

We can test the command actions by running the server and the site:

http://localhost:3000/graphiql

GraphiQL query test_field

Step 2: Create the models

• Create the main model:

rails generate model Posts author title body:text

• Create the associate model:

rails generate model Comments author body:text post:references

• Run the migrations:

rails db:migrate

• Add the association in the post model file:

has_many :comments, :dependent => :delete_all


accepts_nested_attributes_for :comments, allow_destroy: true

Step 3: Create the GraphQL models

• Create the post model:

rails g graphql:object post

This will generate the PostType file inside the `graphql/types` folder,  where we can see the fields that we will return when requested for a post model.

• Create the comment model:

rails g graphql:object comment

• Add the associations inside the type files comment_type file:

field :post, Types::PostType

• The Post_type file we need to add is:

field :comments, [Types::CommentType]

The files should look like this:

post_type:

module Types
 class PostType < Types::BaseObject
   field :id, ID, null: false
   field :author, String
   field :title, String
   field :body, String
   field :comments, [Types::CommentType]
   field :created_at, GraphQL::Types::ISO8601DateTime, null: false
   field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
 end
end

comments_type:

module Types
 class CommentType < Types::BaseObject
   field :id, ID, null: false
   field :author, String
   field :body, String
   field :post_id, Integer, null: false
   field :post, Types::PostType
   field :created_at, GraphQL::Types::ISO8601DateTime, null: false
   field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
 end
end

Step 4: Create the first mutation

• Generate the mutation:

rails g graphql:mutation create_post

This command creates a new file inside of the graphql/mutations folder and adds the new mutation inside of the mutation_type file.

• Generate the argument files of each model:

Post:

rails g graphql:input post

Comment:

rails g graphql:input comment

This will generate the input type files used for the mutation arguments. We can add all the validations that we need here.

• Add the comment input type inside the post input type to create comments at the same time that we create a post:

argument :comments, [Types::CommentInputType], required: false

This can be useful when you want to create models associated with the same mutation all at once.

The files should look similar to this:

post _input_type: 

module Types
 class PostInputType < Types::BaseInputObject
   argument :id, ID, required: false
   argument :author, String, required: true, validates: { length: { maximum: 50, minimum: 4 } }
   argument :title, String, required: true, validates: { length: { maximum: 50, minimum: 10 } }
   argument :body, String, required: true
   argument :comments_attributes, [Types::CommentInputType], required: false
   argument :created_at, GraphQL::Types::ISO8601DateTime, required: false
   argument :updated_at, GraphQL::Types::ISO8601DateTime, required: false
 end
end

comment_input_type:

module Types
 class CommentInputType < Types::BaseInputObject
   argument :id, ID, required: false
   argument :author, String, required: true, validates: { length: { maximum: 50, minimum: 4 } }
   argument :body, String, required: true
   argument :post_id, Integer, required: false
   argument :created_at, GraphQL::Types::ISO8601DateTime, required: false
   argument :updated_at, GraphQL::Types::ISO8601DateTime, required: false
 end
end

• Create the mutation, adding the fields, arguments, and the solve method. The file should be similar to this:

odule Mutations
 class CreatePost < BaseMutation


   field :post, Types::PostType, null: true
   field :errors, [String], null: false


   argument :post_input, Types::PostInputType, required: true


   def resolve(post_input:)
     post = Post.new()
     post.assign_attributes(post_input.to_h)
     if post.save
     {
       post: post,
       errors: [],
     }
     else
     {
       post: nil,
       errors: post.errors.full_messages,
     }
     end
   end
 end
end

• Now we can test it:

mutation CreatePost {
 createPost(
   input: {
     postInput: {
       author: "Juan A.",
       title: "this is a test",
       body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
       commentsAttributes: [
         {author: "Juan L.", body: "This is a comment"}
       ]
     }
   }
 ) {
   post {
     id
     author
     title
     body
     comments {
       id
       body
       author
     }
   }
   errors
 }
}

GraphiQL mutation CreatePost

Step 5: Create the first query

• Create the query in the query_type file. We need to add the field and also the arguments and the method to resolve it, like this:

field :posts, [Types::PostType] do
  argument :author, String, required: false
  argument :title, String, required: false
end


def posts(author:, title:)
  posts = Post.all
  if author
    posts = posts.where('author LIKE :author', author: author)
  end


  if title
    posts = posts.where('title LIKE :title', title: title)
  end
  posts
end

• Now we can test it: 

query Posts {
 posts (
   author: null, title: null
 ){
   id
   author
   title
   body
   comments {
     id
     body
     author
   }
 }
}

GraphiQL Query Posts

Step 6: Create a query by id

• Add query into the query file, like this:

field :post, Types::PostType do
  argument :id, ID, required: true
end


def post(id:)
  Post.find id
end

• Now we can test it:

query Post {
 post (
   id: 1
 ){
   id
   author
   title
   body
   comments {
     id
     body
     author
   }
 }
}

GraphiQL: to create a query by id Add query into the query file

Step 7: Create the updated mutation

• Generate the mutations:

rails g graphql:mutation update_post
rails g graphql:mutation delete_post

• Update post mutation 

module Mutations
 class UpdatePost < BaseMutation
   field :post, Types::PostType, null: true
   field :errors, [String], null: false


   argument :id, String, required: true
   argument :post_input, Types::PostInputType, required: true


   def resolve(id:, post_input:)
     post = Post.find id
     post.assign_attributes(post_input.to_h)
     if post.save
     {
       post: post,
       errors: [],
     }
     else
     {
       post: nil,
       errors: post.errors.full_messages,
     }
     end
   end
 end
end

GraphiQL: create the updated mutation

A closer look: comparing a RESTful API and a GraphQL API

It’s informative to compare the performance of a GraphQL API with a similar RESTful API. In the examples below, we’ve used a method on the post controller to compare the two approaches:

Create post with Rest:

Create post with Rest

Create post with GraphQL:

Create post with GraphQL

Get post with Rest:

Get post with Rest

Get post with GraphQL:

Get post with GraphQL

Learn more about Rails and GraphQL

Now that you’ve seen how simple it is to use GraphQL with Rails—and how efficient it is compared to RESTful APIs–I hope you’ll want to learn more and explore how this method can help you in your Rails projects going forward.

New call-to-action