Pagination and Relay Connections — Complete Guide
In this tutorial, you will learn about Pagination and Relay Connections. We cover key concepts, practical examples, and best practices to help you master this topic.
The Relay Connection specification provides a standardized pattern for cursor-based pagination in Graphql. Connections wrap lists with edges containing cursors and a node, plus pageInfo for navigation metadata.
What You'll Learn
- Connection, Edge, and PageInfo type definitions
- Cursor-based pagination with forward and backward navigation
- first/after and last/before pagination arguments
- Implementing connection resolvers
- Total count and other metadata
Why It Matters
Cursor-based pagination provides consistent results when data changes between pages. The Relay pattern is the industry standard for GraphQL pagination, supported by Apollo Client and Relay.
Real-World Use
GitHub uses Relay connections for all list fields: RepositoryConnection, IssueConnection, PullRequestConnection. Shopify's Storefront API uses connections for product listings with cursor navigation.
flowchart LR
Query[Query] --> Connection[BookConnection]
Connection --> Edges[Edges]
Connection --> PageInfo[PageInfo]
Edges --> Edge1[Edge 1]
Edges --> Edge2[Edge 2]
Edge1 --> Cursor[Cursor: base64]
Edge1 --> Node[Node: Book]
PageInfo --> HasNext[hasNextPage]
PageInfo --> HasPrev[hasPreviousPage]
PageInfo --> Start[startCursor]
PageInfo --> End[endCursor]
Teacher Mindset
Think of connections as paginated lists. Each item is wrapped in an edge with a cursor (opaque pointer). Clients use cursors to request the next or previous page. The pageInfo object tells clients about navigation possibilities.
Code Examples
# Example 1: Connection types
type BookConnection {
edges: [BookEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
type BookEdge {
cursor: String!
node: Book!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
# Example 2: Paginated query with arguments
type Query {
books(
first: Int
after: String
last: Int
before: String
): BookConnection!
}
// Example 3: Connection resolver implementation
const resolvers = {
Query: {
books: async (_, { first = 10, after, last, before }) => {
const limit = first || last || 10;
const cursor = after || before;
let query = db.books.orderBy('id');
if (cursor) {
const decodedId = Buffer.from(cursor, 'base64').toString();
const op = after ? '>' : '<';
query = query.where('id', op, decodedId);
}
const items = await query.limit(limit + 1).fetch();
const hasMore = items.length > limit;
if (hasMore) items.pop();
const edges = items.map(item => ({
cursor: Buffer.from(String(item.id)).toString('base64'),
node: item
}));
return {
edges,
pageInfo: {
hasNextPage: hasMore && !before,
hasPreviousPage: !!before || (!!after && false),
startCursor: edges[0]?.cursor,
endCursor: edges[edges.length - 1]?.cursor
},
totalCount: await db.books.count()
};
}
}
};
Common Mistakes
- Using offset-based pagination (skip/limit) instead of cursor-based
- Making cursors human-readable instead of opaque strings
- Not returning totalCount for UI display
- Mixing forward (first/after) and backward (last/before) in the same query
- Off-by-one errors in the hasNextPage calculation
Practice
- Define BookConnection, BookEdge, and PageInfo types.
- Implement forward pagination with first and after arguments.
- Add backward pagination with last and before arguments.
- Include totalCount in the connection response.
- Challenge: Implement a complex connection with sorting and filtering.
FAQ
Mini Project
Convert your library books list query to use Relay connections. Implement forward pagination with cursor encoding. Add totalCount to the connection. Test pagination with a set of 25 books.
What's Next
Next, you will learn about Apollo Federation for building a distributed GraphQL architecture.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro