Pagination With GraphQL in Rails
We generally do not prefer to retrieve all the required data from the database in one go.
Instead, we want it to be loaded page by page for performance and user experience.
With REST API in Rails,
we pass page and per_page arguments
in the URL and get the paginated data
when using pagination library like mislav/will_paginate.
But for GraphQL,
we need to configure the backend
for paginating the data.
In this blog, we will discuss how can we implement pagination with GraphQL in a Rails project.
Suppose we have a Rails application
with GraphQL APIs implemented
using graphql gem.
In the application,
we have a Types::QueryType class
where items query is implemented
to fetch the items list like below.
module Types
class QueryType < Types::BaseObject
# Add `node(id: ID!) and `nodes(ids: [ID!]!)``
include GraphQL::Types::Relay::HasNodeField
include GraphQL::Types::Relay::HasNodesField
# Add root-level fields here.
# They will be entry points for queries on your schema.
field :items, [Types::ItemType],null: false
def items
Item.all
end
end
end
Whenever we make a GraphQL query for fetching items like this.
{
items {
title
description
}
}
It returns all the items
present in the database like this.
{
"data": {
"items": [
{
"title": "Martian Chronicles",
"description": "Cult book by Ray Bradbury"
},
{
"title": "The Martian",
"description": "Novel by Andy Weir about an astronaut stranded on Mars trying to survive"
},
{
"title": "Doom",
"description": "A group of Marines is sent to the red planet via an ancient Martian portal called the Ark to deal with an outbreak of a mutagenic virus"
},
{
"title": "Mars Attacks!",
"description": "Earth is invaded by Martians with unbeatable weapons and a cruel sense of humor"
},
{
"title": "Reem",
"description": "Test A group of Marines is sent to the red planet via an ancient Martian portal called the Ark to deal with an outbreak of a mutagenic virus"
},
{
"title": "Possible Mars Attacks!",
"description": "Earth is invaded by Martians with unbeatable weapons and a cruel sense of humor"
}
]
}
}
Now let’s see how can we implement
the changes to fetch the items
in a pagination manner
using GraphQL query.
First of all,
let’s add the gem graphql-pagination
in our application
using the following command.
$ bundle add graphql-pagination
This command will add the gem
graphql-pagination to
the application Gemfile
as well as it will run
bundle install to install the gem.
Once the gem is installed,
we are ready to update
the above mentioned
Types::QueryType class
for implementing pagination
for items.
Let’s update the field :items
to use Types::ItemType.collection_type
instead of [Types::ItemType] and
expect the arguments page
and limit in the query.
Here page means
the page number in the list
and limit means
the no. of records per page.
We can set a default limit
to some value, here
we will set it as 2 for a test.
With these changes
the field :items implementation
will look like this.
field :items, Types::ItemType.collection_type, null: false do
argument :page, Integer, required: false
argument :limit, Integer, required: false, default_value: 2
end
In addition to the above changes,
we will also need to
update the items method
for accepting the page and limit
arguments and paginating the records.
We are already having mislav/will_paginate gem in the application so we will use it for the pagination.
Now the items method
will look like this.
def items(limit:, page: nil)
Item.paginate(page: page, per_page: limit)
end
After all these changes the Types::QueryType class will look like this.
module Types
class QueryType < Types::BaseObject
# Add `node(id: ID!) and `nodes(ids: [ID!]!)`
include GraphQL::Types::Relay::HasNodeField
include GraphQL::Types::Relay::HasNodesField
# Add root-level fields here.
# They will be entry points for queries on your schema.
field :items, Types::ItemType.collection_type, null: false do
argument :page, Integer, required: false
argument :limit, Integer, required: false, default_value: 2
end
def items(limit:, page: nil)
Item.paginate(page: page, per_page: limit)
end
end
end
Now, we are implementation ready
for fetching the items
in the pagination manner
We need to remember that,
the items fields data
will now always be fetched
inside the collection object
and we will need to mention
the items related fields
inside the collection.
We can modify the previous GraphQL query to fetch the items in the collection like this.
{
items {
collection {
title
description
}
}
}
It will return the items collection with the default limit of 2.
{
"data": {
"items": {
"collection": [
{
"title": "Martian Chronicles",
"description": "Cult book by Ray Bradbury"
},
{
"title": "The Martian",
"description": "Novel by Andy Weir about an astronaut stranded on Mars trying to survive"
}
]
}
}
}
Let’s try by passing
the page and limit values.
{
items(limit: 3, page: 2) {
collection {
title
description
}
}
}
The data returned is.
{
"data": {
"items": {
"collection": [
{
"title": "Mars Attacks!",
"description": "Earth is invaded by Martians with unbeatable weapons and a cruel sense of humor"
},
{
"title": "Reem",
"description": "Test A group of Marines is sent to the red planet via an ancient Martian portal called the Ark to deal with an outbreak of a mutagenic virus"
},
{
"title": "Possible Mars Attacks!",
"description": "Earth is invaded by Martians with unbeatable weapons and a cruel sense of humor"
}
]
}
}
}
With graphql-pagination gem,
we can also request
the following pagination metadata
in the query alongside collection.
metadata {
totalPages
totalCount
currentPage
limitValue
}
Note: The graphql-pagination gem
doesn’t support totalCount
with will_paginate gem
because will_paginate gem
has method total_entries
instead of total_count for
retreiving the total no. of entries.
In this gist,
we can see how to override
Graphql::Pagination::MetadataType class
for supporting total_count
with will_paginate.
Let’s request the metadata for items.
{
items(limit: 3, page: 2) {
collection {
title
description
}
metadata {
totalPages
totalCount
currentPage
limitValue
}
}
}
The result would be like this
{
"data": {
"items": {
"collection": [
{
"title": "Mars Attacks!",
"description": "Earth is invaded by Martians with unbeatable weapons and a cruel sense of humor"
},
{
"title": "Reem",
"description": "Test A group of Marines is sent to the red planet via an ancient Martian portal called the Ark to deal with an outbreak of a mutagenic virus"
},
{
"title": "Possible Mars Attacks!",
"description": "Earth is invaded by Martians with unbeatable weapons and a cruel sense of humor"
}
],
"metadata": {
"totalPages": 2,
"totalCount": 6,
"currentPage": 2,
"limitValue": 3
}
}
}
}
In this way,
we can implement pagination
for other query types.
But remember that
the queries now need to be
modified to use collection
for fetching the list
once modified for the pagination.
The graphql-pagination gem works
with kaminari pagination library
without overriding metadata unlike
will_paginate gem.