Implementing Cursor-based Pagination For Every GraphQL API
Backends often return a massive amount of data, and intaking all data simultaneously causes more overhead and increases the response time. Pagination preserves the application's performance by receiving small pieces of data in subsequent requests until the entire dataset is received.
When Facebook publicly released GraphQL as a client-driven API-based query language, it quickly gained hype because it allowed front-end developers to modify their backends easily. Applications using GraphQL are more efficient and work promptly on slow networks, and therefore, it is quickly replacing the traditional API architectures like REST. Pagination is an essential concept in GraphQL, but there aren't many resources on pagination in GraphQL.
In this post, we'll compare the different ways to handle pagination in GraphQL and learn how to configure a REST directive to perform cursor-based pagination for every REST API using StepZen.
TL;DR: You can find the complete documentation on paginating GraphQL using StepZen here.
Comparing Different Methods of Pagination in GraphQL
Pagination in GraphQL is not different from pagination in REST APIs, although some types of pagination better fit GraphQL. Before discussing the preferred pagination method in GraphQL, let's look at different pagination types. Typically, APIs offer three methods for pagination. These are:
1. Offset Pagination
Offset pagination consists of two primary parameters: limit and offset. The limit indicates the maximum number of results to show. And the offset denotes the position in the list from where the pagination starts.
Suppose you have a list of 100 students. If you set the limit parameter to 10 and offset to 20, the database engine will count from the 20th student and display the following ten students in each iteration. For instance, the first iteration will show the students from 20 to 30, then from 30 to 40, etc.
Although offset pagination is the most straightforward, it has a significant drawback. When some items are added or deleted, offset pagination results in repeated or missing data values.
Pros:
- Most common way to do pagination in general.
- Relatively simple implementation.
- Most SQL-based databases support the limit and the offset variables, so it's easier to map values.
Cons:
- Prone to data inconsistencies (repeated or missing data).
- It doesn't provide information about more pages, total pages, or retrieval of previous pages.
2. Page Number Pagination
As the name specifies, page number pagination returns a single page per request. You all have seen a "next page" option when working with tables; each sheet shows the same number of results or entries. Similarly, page number pagination returns the same number of results per request.
Let's take an example to understand how it works. Page number pagination uses the after parameter to indicate the starting point for pagination. For instance, if you have a list of 30 students and set the after and the limit parameters to 23 and 5, respectively, then the output list will show a list of 5 students from the 23rd to 28th entry.
Page number pagination is more reliable as the upcoming results start from the last fetched values.
Pros:
- Easier to implement.
- It doesn't require complex logical analysis.
Cons:
- Data inconsistencies.
3. Cursor Pagination
The third kind of pagination is cursor-based pagination. However, it is most complicated but is adaptable to dynamic data. Therefore, it is the preferred way to do pagination in GraphQL.
This method includes a specific parameter for the cursor. A cursor is nothing but a reference point that shows the position of an item in the database. The data are represented as nodes and a cursor as an edge in the graphical representation.
A cursor is a base64 encoded number. The query written for cursor-based pagination returns an object representation instead of a list.
Pros:
- The preferred way to do pagination in GraphQL.
- Provides valuable data for UX uses.
- Allows reverse pagination.
- No issues in the case of dynamic data as pagination are done to a specific row.
Cons:
- It doesn't allow random access.
- Needs complicated queries.
How to Implement Pagination in GraphQL using StepZen
Now you know about all the possible pagination methods and the preferred way of doing pagination in GraphQL, which is cursor-based pagination. If your REST API supports offset or page number pagination, you can easily implement cursor-based pagination in GraphQL using StepZen.
The two essential parameters you need to specify for every type of pagination are type
and setters
. The type
parameters define the pagination style you want to implement. It has three possible values: PAGE_NUMBER
, OFFSET
, and NEXT_CURSOR
. Next comes the setters
parameters, which need to be pointed towards a field that indicates how many results or pages the response will output. You can find the list of all parameters with different pagination styles in the documentation.
Let's look at some examples:
1. Implementing Cursor Pagination for Offset
The below snippet illustrates how to implement GraphQL cursor-based pagination using StepZen for REST APIs supporting offset pagination. Note that the parameter first
is set to the number of required results. The second parameter, after
, is set to the starting point of the pagination.
customers(
first: Int! = 20
after: String! = ""
): CustomerConnection
@rest(
endpoint:"https://api.example.com/customers?limit=$first&offset=$after"
pagination: {
type: OFFSET
setters: [{field:"total" path: "meta.total_count"}]
}
)
Since it's the first request, the after
parameter is equal to an empty string. The first
parameter is set to 20
, which means that the first 20 results will be returned. On the second request, you can change the value for after
to be equal to the value of first
from the previous request. Every new request will be a multiple of the first
parameter.
2. Implementing Page Number Pagination
Take a look at the following code to get a better understanding of implementing cursor-based pagination in GraphQL using StepZen, when your REST API is relying on page number pagination:
customers(
first: Int! = 20
after: String! = ""
): CustomerConnection
@rest(
endpoint:"https://api.example.com/customers?page=$after&per_page=$first"
pagination: {
type: NEXT_CURSOR
setters: [{field:"nextCursor" path: "meta.next"}]
}
)
The above example illustrates some important aspects. The after
parameter is set to an empty string, which indicates that it is the first request. In contrast, the first parameter is set to 20
, meaning that 20 results will be returned per page. Remember, the value of the first parameter remains the same for subsequent requests as well. The after
parameter in the upcoming request will be according to the page number you want to be returned. For instance, it is 3
for the third and 4
for the fourth page.
3. Implementing Cursor Pagination
Implementing cursor-based pagination is very similar to the other two pagination methods. The only difference is that you need to specify the type
parameter as NEXT_CURSOR
, and change the setters parameter to point towards the field that indicates the next cursor.
customers(
first: Int! = 20
after: String! = ""
): CustomerConnection
@rest(
endpoint:"https://api.example.com/customers?first=$first&after=$after"
pagination: {
type: NEXT_CURSOR
setters: [{field:"nextCursor" path: "meta.next"}]
}
)
This, of course, only applies if your REST API already supports cursor-based pagination.
How to Query Cursor Pagination in GraphQL
In the previous section, you've learned how to implement cursor-based pagination for any REST API using StepZen. Cursor-based pagination is for example, used by Relay, and is also supported by the StepZen GraphQL API. Cursor-based pagination is the preferred way to do pagination in GraphQL, as it provides valuable data for UX uses. But it comes with the downside of being more complex to query, as we saw in the first section.
Let's look at the Connection
type that is used by cursor-based pagination:
type Customer {
activities: [Activity]
addresses: [Address]
contacts: Contacts
description: String
designation: String
}
type CustomerEdge {
node: Customer
cursor: String
}
type CustomerConnection {
pageInfo: PageInfo!
edges: [CustomerEdge]
}
The CustomerConnection
type is the one that is returned by the customers
query. It contains a pageInfo
field, which is of type PageInfo
. The PageInfo
type contains the following fields:
query MyQuery {
customers {
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
}
}
}
These fields inform you about the current page, and whether there are more pages to be fetched. The endCursor
and startCursor
fields are the cursors that you need to use in the next request. The hasNextPage
and hasPreviousPage
fields indicate whether there are more pages to be fetched.
The edges
field contains a list of CustomerEdge
objects. The CustomerEdge
type contains a node
field of type Customer
. To get the information about the customer, you would need to use the node
field:
query MyQuery {
customers(first: 3, after: "eyJjIjoiTzpRdWVyeTpwYXJrcyIsIm8iOjl9") {
edges {
node {
id
description
}
}
}
}
The concept of "edges and nodes" is a bit confusing but very straightforward. It comes from the concept that everything in GraphQL is a graph. The edges
field contains a list of CustomerEdge
objects, information that is specific for this connection but not shared by all nodes. The CustomerEdge
type contains a node
field of type Customer
. This is the actual customer information.
For Relay, you can find more information on GraphQL connections in the documentation.
Conclusion
GraphQL allows different kinds of pagination. But how does one decide which one will work best? GraphQL recommends using cursor-based pagination. However, it depends on the requirements of your application. If you are using a REST API already supporting cursor-based pagination, then it's a no-brainer. But if you are using a REST API that is not supporting cursor-based pagination, then you can use StepZen to implement it.
The common and the most straightforward kinds of pagination are offset and page number. Although they are relatively simple to implement, they are more suitable for static data. In contrast, cursor-based pagination is complex but best for changing data as it prevents data inconsistencies. With StepZen, it's straightforward to implement cursor-based pagination for any API.
Learn more by visiting StepZen Docs. Try it out with a free account, and we'd love to get your feedback and answer any questions on our Discord.
Top comments (0)