Why implement pagination?
It is very common for applications to fetch a large number of items from the server.
Imagine you’re building Instagram, and you need to fetch the latest posts from your friends who has 4829 posts in their account.
Fetching all the posts at once will slow down the performance of your application.
What Instagram and many other applications do is to use pagination, fetching 20 posts at a time for example.
Types of pagination
There are two types of pagination that we can use in GraphQL:
- Offset-based pagination
- Cursor-based pagination
Offset based pagination allows the users to jump to a specific page directly.
The problem with offset based pagination is the implementation tends to be slow for large datasets.
This is because the server needs to fetch all the items before the offset to skip.
For cursor based pagination, the client passes a cursor to the server, which indicates where to start fetching the next set of data.
Cursor based pagination is preferred over offset based pagination because of its performance and will be the focus of this post.
Do not reinvent the wheel
The creators of GraphQL at Facebook (now Meta) has provided us with a standard way to implement cursor based pagination, or what many people call Relay style pagination.
It is developed based on their need to do paginations on their feeds.
Is it mandatory to use this standard?
No, but this standard is good enough for most use cases and we don’t need to reinvent the wheel.
Let’s take a look at how it works.
Cursor-based pagination overview
This is how the cursor based pagination looks like in a product search query.
Looks like there’s a lot going on, so let’s break it down.
PageInfo
When we use cursor based pagination, we rely on pageInfo to determine if there are more pages to fetch.
This is what the page info response looks like when there are more pages to fetch.
The endCursor will be used to fetch the next set of data.
Use the endCursor to fetch the next set of data until there are no more pages to fetch.
What should I use as the cursor?
The cursor can be anything.
It can be the id of the item, or the cursor from your database.
Use the cursor that is most convenient for you.
Edges and nodes
The edges is an array of objects that contains the cursor and the node.
Here’s an example of edges response for the product search query.
As you can see, the node contains the actual data that we need.
As for the cursor, it’s the same cursor type that is used in the PageInfo.
You can the edge cursor as the pagination identifier for more advanced use cases.
Forward backward pagination and search parameters
Cursor based pagination supports forward and backward pagination.
Any query that supports pagination should have the following parameters:
- first: The number of items to fetch
- after: The cursor to start fetching the next set of items
- last: The number of items to fetch
- before: The cursor to start fetching the previous set of items
Forward pagination
Forward pagination is the most common type of pagination and the one that is used in the example above.
In order to paginate forward, the query should support two types of parameters:
- first: the number of items to fetch
- after: the cursor to start fetching the next set of items
Or another example is when we want to fetch the next 20 posts from a post.
Backward pagination
There are some cases where we would want to paginate backwards.
For example, in a chat application, we would want to paginate backwards to fetch the previous messages.
In order to paginate backward, the query should support two types of parameters:
- last: the number of items to fetch
- before: the cursor to start fetching the previous set of items
Best practices
- When you know a query will return a large number of items, use pagination by default.
- When doing forward pagination, only use
first
andafter
. - When doing forward pagination, only use
hasNextPage
andendCursor
to fetch the next set of data. - When doing backward pagination, only use
last
andbefore
. - When doing backward pagination, only use
hasPreviousPage
andstartCursor
to fetch the previous set of data.