How it works
The explanation, with examples you can run.
The shape
type Query { posts(first: Int = 20, after: String, last: Int, before: String): PostConnection!} type PostConnection { edges: [PostEdge!]! pageInfo: PageInfo! totalCount: Int # optional, and can be expensive} type PostEdge { cursor: String! node: Post!} type PageInfo { hasNextPage: Boolean! hasPreviousPage: Boolean! startCursor: String endCursor: String}query Feed($after: String) { posts(first: 20, after: $after) { edges { cursor node { id title } } pageInfo { hasNextPage endCursor } }}first/after page forwards and last/before page backwards. The spec for this pattern grew out of Relay, but it is used well beyond Relay. Fields that describe the relationship rather than either side belong on the edge: TeamMemberEdge.role or joinedAt.
A toy connection
Press Run to see what your code prints, in order.
Runs in this browser, in an isolated frame with no access to this page. console.log, info, warn and error are shown; code runs as a classic script, so use an async function for await.
The toy finds the cursor's item by scanning an array; a real server decodes the cursor into the sort key and asks the database for rows after it, using an index. It should also reject a cursor it can't decode rather than starting from the beginning.
Choices to make
- Maximum page size: document it and enforce it; it is also an input to query cost limits.
totalCount: aCOUNT(*)over a large filtered set can cost more than the page itself. Offer it only where it is cheap, or approximate it.- Stable order: cursors need a total order, so add a unique tie-breaker (usually the id) to any sort key.
- Normalised caches on the client merge pages by the connection's field and arguments, so keep argument names consistent across the schema.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.