22title : Implementing Cursor-based Pagination
33---
44
5+ import { Callout } from " nextra/components" ;
6+
57When a GraphQL API returns a list of data, pagination helps avoid
68fetching too much data at once. Cursor-based pagination fetches items
79relative to a specific point in the list, rather than using numeric offsets.
@@ -18,15 +20,15 @@ that works well with clients.
1820
1921Cursor-based pagination typically uses a structured format that separates
2022pagination metadata from the actual data. The most widely adopted pattern follows the
21- [ Relay Cursor Connections Specification] ( https://relay.dev/graphql/connections.htm ) . While
23+ [ GraphQL Cursor Connections Specification] ( https://relay.dev/graphql/connections.htm ) . While
2224this format originated in Relay, many GraphQL APIs use it independently because of its
2325clarity and flexibility.
2426
2527This pattern wraps your list of items in a connection type, which includes the following fields:
2628
27- - ` edges ` : A list of edge objects, each representing an item in the list.
28- - ` node ` : The actual object you want to retrieve, such as user, post, or comment.
29- - ` cursor ` : An opaque string that identifies the position of the item in the list.
29+ - ` edges ` : A list of edge objects, representing for each item in the list:
30+ - ` node ` : The actual object you want to retrieve, such as user, post, or comment.
31+ - ` cursor ` : An opaque string that identifies the position of the item in the list.
3032- ` pageInfo ` : Metadata about the list, such as whether more items are available.
3133
3234The following query and response show how this structure works:
@@ -192,7 +194,7 @@ const usersField = {
192194 let start = 0 ;
193195 if (args .after ) {
194196 const index = decodeCursor (args .after );
195- if (index != null ) {
197+ if (Number . isFinite ( index) ) {
196198 start = index + 1 ;
197199 }
198200 }
@@ -243,7 +245,7 @@ async function resolveUsers(_, args) {
243245
244246 if (args .after ) {
245247 const index = decodeCursor (args .after );
246- if (index != null ) {
248+ if (Number . isFinite ( index) ) {
247249 offset = index + 1 ;
248250 }
249251 }
@@ -279,6 +281,25 @@ an `OFFSET`. To paginate backward, you can reverse the sort order and slice the
279281results accordingly, or use keyset pagination for improved performance on large
280282datasets.
281283
284+ <Callout type='info'>
285+
286+ The above is just an example to aid understanding; in a production application,
287+ for most databases it is better to use ` WHERE ` clauses to implement cursor
288+ pagination rather than using ` OFFSET ` . Using ` WHERE ` can leverage indices
289+ (indexes) to jump directly to the relevant records, whereas ` OFFSET ` typically
290+ must scan over and discard that number of records. When paginating very large
291+ datasets, ` OFFSET ` can become more expensive as the value grows, whereas using
292+ ` WHERE ` tends to have fixed cost. Using ` WHERE ` can also typically handle the
293+ addition or removal of data more gracefully.
294+
295+ For example, if you were ordering a collection of users by username, you could
296+ use the username itself as the ` cursor` , thus GraphQL's ` allUsers (first: 10 ,
297+ after: $cursor)` could become SQL's ` WHERE username > $1 LIMIT 10 ` . Even if
298+ that user was deleted, you could still continue to paginate from that position
299+ onwards.
300+
301+ </Callout>
302+
282303## Handling edge cases
283304
284305When implementing pagination, consider how your resolver should handle the following scenarios:
@@ -297,7 +318,7 @@ errors.
297318
298319To learn more about cursor-based pagination patterns and best practices, see:
299320
300- - [Relay Cursor Connections Specification](https://relay.dev/graphql/connections.htm)
321+ - [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm)
301322- [Pagination](https://graphql.org/learn/pagination/) guide on graphql.org
302323- [` graphql- relay- js` ](https://github.com/graphql/graphql-relay-js): Utility library for
303324building Relay-compatible GraphQL servers using GraphQL.js
0 commit comments