Skip to content

Commit cd7730c

Browse files
authored
Merge pull request graphql#74 from graphql/pageinfo
[bestpractices] Add friends help and cursors to page info
2 parents 906a376 + e7b61aa commit cd7730c

File tree

2 files changed

+38
-14
lines changed

2 files changed

+38
-14
lines changed

site/_core/swapiSchema.js

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@ type FriendsConnection {
135135
# The edges for each of the character's friends.
136136
edges: [FriendsEdge]
137137
138+
# A list of the friends, as a convenience when edges are not needed.
139+
friends: [Character]
140+
138141
# Information for paginating this connection
139142
pageInfo: PageInfo!
140143
}
@@ -150,6 +153,8 @@ type FriendsEdge {
150153
151154
# Information for paginating this connection
152155
type PageInfo {
156+
startCursor: ID
157+
endCursor: ID
153158
hasNextPage: Boolean!
154159
}
155160
@@ -394,13 +399,20 @@ const resolvers = {
394399
friends: ({ friends }) => friends.map(getCharacter),
395400
friendsConnection: ({ friends }, { first, after }) => {
396401
first = first || friends.length;
397-
after = parseInt(fromCursor(after), 10) || 0;
402+
after = after ? parseInt(fromCursor(after), 10) : 0;
403+
const edges = friends.map((friend, i) => ({
404+
cursor: toCursor(i+1),
405+
node: getCharacter(friend)
406+
})).slice(after, first + after);
407+
const slicedFriends = edges.map(({ node }) => node);
398408
return {
399-
edges: friends.map((friend, i) => ({
400-
cursor: toCursor(i+1),
401-
node: getCharacter(friend)
402-
})).slice(after, first + after),
403-
pageInfo: { hasNextPage: first + after < friends.length },
409+
edges,
410+
friends: slicedFriends,
411+
pageInfo: {
412+
startCursor: edges.length > 0 ? edges[0].cursor : null,
413+
hasNextPage: first + after < friends.length,
414+
endCursor: edges.length > 0 ? edges[edges.length - 1].cursor : null
415+
},
404416
totalCount: friends.length
405417
};
406418
},
@@ -411,20 +423,28 @@ const resolvers = {
411423
friends: ({ friends }) => friends.map(getCharacter),
412424
friendsConnection: ({ friends }, { first, after }) => {
413425
first = first || friends.length;
414-
after = parseInt(fromCursor(after), 10) || 0;
426+
after = after ? parseInt(fromCursor(after), 10) : 0;
427+
const edges = friends.map((friend, i) => ({
428+
cursor: toCursor(i+1),
429+
node: getCharacter(friend)
430+
})).slice(after, first + after);
431+
const slicedFriends = edges.map(({ node }) => node);
415432
return {
416-
edges: friends.map((friend, i) => ({
417-
cursor: toCursor(i+1),
418-
node: getCharacter(friend)
419-
})).slice(after, first + after),
420-
pageInfo: { hasNextPage: first + after < friends.length },
433+
edges,
434+
friends: slicedFriends,
435+
pageInfo: {
436+
startCursor: edges.length > 0 ? edges[0].cursor : null,
437+
hasNextPage: first + after < friends.length,
438+
endCursor: edges.length > 0 ? edges[edges.length - 1].cursor : null
439+
},
421440
totalCount: friends.length
422441
};
423442
},
424443
appearsIn: ({ appearsIn }) => appearsIn,
425444
},
426445
FriendsConnection: {
427446
edges: ({ edges }) => edges,
447+
friends: ({ friends }) => friends,
428448
pageInfo: ({ pageInfo }) => pageInfo,
429449
totalCount: ({ totalCount }) => totalCount,
430450
},

site/learn/BestPractice-Pagination.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ There are a number of ways we could do pagination:
5454

5555
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.
5656

57-
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 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:
57+
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:
5858

5959
```graphql
6060
{
@@ -92,13 +92,16 @@ To solve both of these problems, our `friends` field can return a connection obj
9292
cursor
9393
}
9494
pageInfo {
95+
endCursor
9596
hasNextPage
9697
}
9798
}
9899
}
99100
}
100101
```
101102

103+
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.
104+
102105
## Complete Connection Model
103106

104107
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:
@@ -108,7 +111,7 @@ Clearly, this is more complex than our original design of just having a plural!
108111
- The ability to ask for information about the edge itself, like `cursor` or `friendshipTime`.
109112
- The ability to change how our backend does pagination, since the user just uses opaque cursors.
110113

111-
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.
114+
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.
112115

113116
```graphql
114117
# { "graphiql": true }
@@ -124,6 +127,7 @@ To see this in action, there's an additional field in the example schema, called
124127
cursor
125128
}
126129
pageInfo {
130+
endCursor
127131
hasNextPage
128132
}
129133
}

0 commit comments

Comments
 (0)