Build the mental model
Offset pagination (`skip`, `limit`) is simple but can skip or repeat items when the underlying data changes between page loads. Cursor-based pagination uses an opaque cursor — a string encoding a stable sort key — as a "continue from here" pointer, making it more stable. The Relay-style connection pattern standardizes this shape as `edges` (each with a `cursor` and `node`) plus `pageInfo` (`hasNextPage`, `endCursor`).
Connect it to a real scenario
Expose the Tutorial Platform's catalog list as `tutorials(first: 20, after: $cursor): TutorialConnection!`. When the frontend's "Load more" button is clicked, it passes `pageInfo.endCursor` as the next request's `after` argument, continuing until `pageInfo.hasNextPage` is `false`.
Try the working example
type TutorialEdge {
cursor: String!
node: Tutorial!
}
type PageInfo {
hasNextPage: Boolean!
endCursor: String
}
type TutorialConnection {
edges: [TutorialEdge!]!
pageInfo: PageInfo!
}
type Query {
tutorials(first: Int!, after: String): TutorialConnection!
}You can write a Relay-style connection schema and explain a cursor-based "load more" flow.5-minute try-it
Write a `commentsConnection(first: Int!, after: String): CommentConnection!` schema and describe how a frontend loop would use `pageInfo.hasNextPage`.
One important caution
Exposing the cursor as a plain, readable row ID invites clients to do cursor arithmetic (id+1); keep cursors as opaque, encoded strings.
GraphQL — Pagination — GraphQL