patterntypescriptexpressModerate
Pagination with Cursor-Based Links and Headers
Viewed 0 times
paginationcursor paginationLink headerRFC 5988next pageoffset pagination
Problem
Offset-based pagination (page=2&limit=20) breaks when items are inserted or deleted between requests. Clients also lack a standard way to discover total counts and next/prev links.
Solution
Use cursor-based pagination with Link headers for stateless, consistent pagination.
app.get('/users', async (req, res) => {
const limit = Math.min(Number(req.query.limit) || 20, 100);
const cursor = req.query.cursor as string | undefined;
const users = await db.getUsers({ cursor, limit: limit + 1 });
const hasMore = users.length > limit;
const items = users.slice(0, limit);
const nextCursor = hasMore
? Buffer.from(items[items.length - 1].id).toString('base64')
: null;
// RFC 5988 Link header
const links = [];
if (nextCursor) {
links.push(`<${req.baseUrl}/users?cursor=${nextCursor}&limit=${limit}>; rel="next"`);
}
if (links.length) res.setHeader('Link', links.join(', '));
res.setHeader('X-Total-Count', await db.countUsers());
res.json(items);
});Why
Cursor pagination is stable — inserting/deleting items doesn't shift pages. The Link header is the standard (RFC 5988) mechanism for pagination discovery.
Gotchas
- Cursors must be opaque to clients — encode them (base64, encrypted) so clients don't parse or manipulate them.
- X-Total-Count is non-standard but widely used — consider it optional as counting large tables is expensive.
- Cursor pagination is not reversible by default — implementing 'prev' requires bidirectional cursors.
Revisions (0)
No revisions yet.