Available Now: Explore our latest release with enhanced accessibility and powerful IDP features
By Liam Ross | 2021 Aug 16
5 min
Tags
tutorial
api
Working for Apryse, I’ve had the pleasure of building various APIs using GraphQL. And one aspect that persists across our projects is the need to retrieve data one page at a time to avoid massive amounts of data being transferred.
In this blog, we discuss two patterns you can use for your API -- offset-based and cursor-based pagination. We also look at the pros and cons of each paging method, and in our conclusion, summarize where each method is preferable.
Pagination or paging is an important concept for retrieving list data from a back end. One example of pagination would be showing the ten-most recent users who signed up in a table, and then enabling click "next page" to display ten older signups (and so on).
There are multiple ways to achieve pagination, but most APIs use one of two patterns:
GraphQL Offset-based pagination
GraphQL Cursor-based pagination
Let’s take a closer look at each pattern in the following GraphQL pagination examples.
Offset-based pagination (also known as ‘skip-based’) generally accepts two parameters: 'limit' (or 'first') to set the max number of results, and 'offset' (or 'skip') to set how many results to skip past.
As defined in GraphQL, offset-based pagination is quite simple:
type User {
id: ID!
}
type Query {
signedUpUsers(limit: Int, offset: Int): [User!]!
}
As you can see, to add pagination, all you have to do is add the arguments 'limit' and 'offset' to the field 'signedUpUsers'. To get the third page of results in a ten-row table, you would do this:
signedUpUsers(limit: 10, offset: 20) {
id
}
First, with offset, you will notice repeated data in certain situations. For example: if you add a new row while paging through data. This is because you aren’t paging relative to any specific row; instead, you are just offsetting ‘n’ number of rows and getting a limit of ‘m’ number of rows.
Therefore, if a single new row is added while you look at a three-item page 1 ('offset=0', 'limit=3'), the first item on page 2 will be the same as the last item on page 1. This is because all items are shifted back by one to accommodate the new row.
Other cons of offset include:
Cursor-based pagination comes in many forms, but I will implement Relay's GraphQL Cursor Connections Specification. Cursor-based is more verbose than offset, but it provides richer functionality and is used by most major GraphQL APIs (ex: GitHub).
Cursor-based accepts two parameters that serve for forward pagination, and two for reverse pagination. For forward pagination, there is 'first' which defines the limit of how many items are returned, and 'after' which provides the offset cursor. For reverse, there is 'last' (defines limit) and 'before' (defines offset cursor). We provide some examples later on how to use these.
The key concepts of cursor-based are:
Here is the same example from our Offset-based sample, but implemented with Cursor-based pagination:
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String!
endCursor: String!
}
type User {
id: ID!
}
type UserEdge {
node: User!
cursor: String!
}
type UserConnection {
totalCount: Int!
edges: [UserEdge!]!
pageInfo: PageInfo!
}
type Query {
signedUpUsers(first: Int, after: String, last: Int, before: String): UserConnection!
}
This pattern more closely aligns to the idea of pagination. You will initially call something like:
signedUpUsers(first: 10) {
totalCount
edges {
cursor # can omit this to save bandwidth
node {
id # node contains the User values
}
}
pageInfo {
hasNextPage # if false, can disable next page button
hasPreviousPage # if false, can disable prev page button
startCursor # used for getting prev page
endCursor # used for getting next page
}
}
Then, use 'endCursor' (ex '"abc123def456"') to get the next page:
signedUpUsers(first: 10, after: "abc123def456") {
# ...
}
Or, use 'startCursor' (ex '"123abc456def"') to get the previous page:
signedUpUsers(last: 10, before: "123abc456def") {
# ...
}
The final solution will then look something like this:
In summary, both types of GraphQL pagination, cursor-based and offset-based, have their uses.
Offset-based is preferable where, for example:
In contrast, cursor-based is preferred where:
That’s all! We hope this guide was helpful. If you have any questions, about this article or otherwise, don’t hesitate to contact us.
Tags
tutorial
api
Liam Ross
Share this post
PRODUCTS
Enterprise
Small Business
Popular Content