You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: src/pages/learn/pagination.mdx
+61-21Lines changed: 61 additions & 21 deletions
Original file line number
Diff line number
Diff line change
@@ -1,16 +1,16 @@
1
1
# Pagination
2
2
3
-
> Different pagination models enable different client capabilities
3
+
<pclassName="learn-subtitle">Traverse lists of objects with a consistent field pagination model</p>
4
4
5
-
A common use case in GraphQL is traversing the relationship between sets of objects. There are a number of different ways that these relationships can be exposed in GraphQL, giving a varying set of capabilities to the client developer.
5
+
A common use case in GraphQL is traversing the relationship between sets of objects. There are different ways that these relationships can be exposed in GraphQL, giving a varying set of capabilities to the client developer. On this page, we'll explore how fields may be paginated using a cursor-based connection model.
6
6
7
7
## Plurals
8
8
9
-
The simplest way to expose a connection between objects is with a field that returns a plural type. For example, if we wanted to get a list of R2-D2's friends, we could just ask for all of them:
9
+
The simplest way to expose a connection between objects is with a field that returns a plural [List type](/learn/schema/#list). For example, if we wanted to get a list of R2-D2's friends, we could just ask for all of them:
10
10
11
11
```graphql
12
12
# { "graphiql": true }
13
-
{
13
+
query{
14
14
hero {
15
15
name
16
16
friends {
@@ -22,10 +22,10 @@ The simplest way to expose a connection between objects is with a field that ret
22
22
23
23
## Slicing
24
24
25
-
Quickly, though, we realize that there are additional behaviors a client might want. A client might want to be able to specify how many friends they want to fetch; maybe they only want the first two. So we'd want to expose something like:
25
+
Quickly, though, we realize that there are additional behaviors a client might want. A client might want to be able to specify how many friends they want to fetch—maybe they only want the first two. So we'd want to expose something like this:
26
26
27
27
```graphql
28
-
{
28
+
query{
29
29
hero {
30
30
name
31
31
friends(first: 2) {
@@ -37,20 +37,22 @@ Quickly, though, we realize that there are additional behaviors a client might w
37
37
38
38
But if we just fetched the first two, we might want to paginate through the list as well; once the client fetches the first two friends, they might want to send a second request to ask for the next two friends. How can we enable that behavior?
39
39
40
-
## Pagination and Edges
40
+
## Pagination and edges
41
41
42
-
There are a number of ways we could do pagination:
42
+
There are several ways we could do pagination:
43
43
44
44
- We could do something like `friends(first:2 offset:2)` to ask for the next two in the list.
45
45
- We could do something like `friends(first:2 after:$friendId)`, to ask for the next two after the last friend we fetched.
46
46
- We could do something like `friends(first:2 after:$friendCursor)`, where we get a cursor from the last item and use that to paginate.
47
47
48
-
In general, we've found that **cursor-based pagination** is the most powerful of those designed. Especially if the cursors are opaque, either offset or ID-based pagination can be implemented using cursor-based pagination (by making the cursor the offset or the ID), and using cursors gives additional flexibility if the pagination model changes in the future. As a reminder that the cursors are opaque and that their format should not be relied upon, we suggest base64 encoding them.
48
+
The approach described in the first bullet is classic _offset-based pagination_. However, this style of pagination can have performance and security downsides, especially for larger data sets. Additionally, if new records are added to the database after the user has made a request for a page of results, then offset calculations for subsequent pages may become ambiguous.
49
49
50
-
That leads us to a problem; though; how do we get the cursor from the object? We wouldn't want cursor to live on the `User` type; it's a property of the connection, not of the object. So we might want to introduce a new layer of indirection; our `friends` field should give us a list of edges, and an edge has both a cursor and the underlying node:
50
+
In general, we've found that _cursor-based pagination_ is the most powerful of those designed. Especially if the cursors are opaque, either offset or ID-based pagination can be implemented using cursor-based pagination (by making the cursor the offset or the ID), and using cursors gives additional flexibility if the pagination model changes in the future. As a reminder that the cursors are opaque and their format should not be relied upon, we suggest base64 encoding them.
51
+
52
+
But that leads us to a problem—how do we get the cursor from the object? We wouldn't want the cursor to live on the `User` type; it's a property of the connection, not of the object. So we might want to introduce a new layer of indirection; our `friends` field should give us a list of edges, and an edge has both a cursor and the underlying node:
51
53
52
54
```graphql
53
-
{
55
+
query{
54
56
hero {
55
57
name
56
58
friends(first: 2) {
@@ -67,14 +69,14 @@ That leads us to a problem; though; how do we get the cursor from the object? We
67
69
68
70
The concept of an edge also proves useful if there is information that is specific to the edge, rather than to one of the objects. For example, if we wanted to expose "friendship time" in the API, having it live on the edge is a natural place to put it.
69
71
70
-
## End-of-list, counts, and Connections
72
+
## End-of-list, counts, and connections
71
73
72
-
Now we have the ability to paginate through the connection using cursors, but how do we know when we reach the end of the connection? We have to keep querying until we get an empty list back, but we'd really like for the connection to tell us when we've reached the end so we don't need that additional request. Similarly, what if we want to know additional information about the connection itself; for example, how many total friends does R2-D2 have?
74
+
Now we can paginate through the connection using cursors, but how do we know when we reach the end of the connection? We have to keep querying until we get an empty list back, but we'd like for the connection to tell us when we've reached the end so we don't need that additional request. Similarly, what if we want additional information about the connection itself, for example, how many friends does R2-D2 have in total?
73
75
74
-
To solve both of these problems, our `friends` field can return a connection object. The connection object will then have a field for the edges, as well as other information (like total count and information about whether a next page exists). So our final query might look more like:
76
+
To solve both of these problems, our `friends` field can return a connection object. The connection object will be an Object type that has a field for the edges, as well as other information (like total count and information about whether a next page exists). So our final query might look more like this:
75
77
76
78
```graphql
77
-
{
79
+
query{
78
80
hero {
79
81
name
80
82
friends(first: 2) {
@@ -96,20 +98,50 @@ To solve both of these problems, our `friends` field can return a connection obj
96
98
97
99
Note that we also might include `endCursor` and `startCursor` in this `PageInfo` object. This way, if we don't need any of the additional information that the edge contains, we don't need to query for the edges at all, since we got the cursors needed for pagination from `pageInfo`. This leads to a potential usability improvement for connections; instead of just exposing the `edges` list, we could also expose a dedicated list of just the nodes, to avoid a layer of indirection.
98
100
99
-
## Complete Connection Model
101
+
## Complete connection model
100
102
101
-
Clearly, this is more complex than our original design of just having a plural! But by adopting this design, we've unlocked a number of capabilities for the client:
103
+
Clearly, this is more complex than our original design of just having a plural! But by adopting this design, we've unlocked several capabilities for the client:
102
104
103
105
- The ability to paginate through the list.
104
106
- The ability to ask for information about the connection itself, like `totalCount` or `pageInfo`.
105
107
- The ability to ask for information about the edge itself, like `cursor` or `friendshipTime`.
106
108
- The ability to change how our backend does pagination, since the user just uses opaque cursors.
107
109
108
-
To see this in action, there's an additional field in the example schema, called `friendsConnection`, that exposes all of these concepts. You can check it out in the example query. Try removing the `after` parameter to `friendsConnection` to see how the pagination will be affected. Also, try replacing the `edges` field with the helper `friends` field on the connection, which lets you get directly to the list of friends without the additional edge layer of indirection, when that's appropriate for clients.
110
+
To see this in action, there's an additional field in the example schema, called `friendsConnection`, that exposes all of these concepts:
@@ -129,6 +161,14 @@ To see this in action, there's an additional field in the example schema, called
129
161
}
130
162
```
131
163
132
-
## Connection Specification
164
+
## Connection specification
165
+
166
+
To ensure a consistent implementation of this pattern, the Relay project has a formal [specification](https://facebook.github.io/relay/graphql/connections.htm) you can follow for building GraphQL APIs that use a cursor-based connection pattern.
167
+
168
+
## Recap
169
+
170
+
To recap these recommendations for paginating fields in a GraphQL schema:
133
171
134
-
To ensure a consistent implementation of this pattern, the Relay project has a formal [specification](https://facebook.github.io/relay/graphql/connections.htm) you can follow for building GraphQL APIs which use a cursor based connection pattern.
172
+
- List fields that may return a lot of data should be paginated
173
+
- Cursor-based pagination provides a stable pagination model for fields in a GraphQL schema
174
+
- The cursor connection specification from the Relay project provides a consistent pattern for paginating the fields in a GraphQL schema
0 commit comments