Blog

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 fileContains the structure of our GraphQL instance and some configuration methods
Query type fileContains our queries—as many as we want to create
Mutation type fileContains all the mutations we want 
Mutation folder fileContains the method of each of those mutations
ArgumentsParameters we want to receive in an action (mutation or query)
FieldsAttributes 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

image6.png

 

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:

module 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
 }
}
image4.png

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
   }
 }
}
image5.png

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
   }
 }
}
image2.png

Step 7: Create the updated mutation

• Generate the mutations:

rails g graphql:mutation update_postrails 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
image3.png

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:

image1.png

Create post with GraphQL:

 

image7.png

Get post with Rest:

image8.png

Get post with GraphQL:

image9.png

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.

Ready to be Unstoppable? Partner with Gorilla Logic, and you can be.

TALK TO OUR SALES TEAM