Skip to main content

How to Add Cursor-Based Pagination to a REST API

Replace offset pagination with keyset pagination on a stable, unique sort key for fast, consistent pages. Return opaque base64 cursors and navigation links so clients page reliably.

Difficulty
Intermediate
Duration
40 minutes
Steps
5

What and why

Offset pagination (LIMIT 20 OFFSET 1000) gets slow on large tables and can skip or repeat rows when data changes between pages. Cursor (keyset) pagination uses a stable pointer into the result set, so it stays fast and consistent. It is the right default for large or actively changing lists.

Prerequisites

  • A REST list endpoint.
  • A database with an indexable, ordered key.
  • Basic SQL.

Steps

1. Understand offset vs cursor pagination

Offset must scan and discard all skipped rows, so deep pages are expensive. A cursor encodes the last item seen; the next page asks for rows after that item, which the database finds via an index.

2. Choose a stable sort key

The key must be unique and ordered. A monotonic id works; if you sort by a non-unique column like created_at, add the id as a tiebreaker so the order is deterministic.

3. Implement a keyset query

Page forward by comparing against the last seen tuple:

SELECT id, title, created_at
FROM articles
WHERE (created_at, id) < ($1, $2)
ORDER BY created_at DESC, id DESC
LIMIT 20;

For the first page, omit the WHERE clause.

4. Encode and return cursors

Make the cursor opaque so clients do not depend on its structure:

const cursor = Buffer.from(JSON.stringify({ created_at, id })).toString('base64url');
res.json({ data, nextCursor: hasMore ? cursor : null });

Decode it on the next request to get the WHERE parameters.

5. Add navigation links

Return ready-to-use links so clients need not build URLs:

{ "data": [], "links": { "next": "/v1/articles?cursor=eyJ...&limit=20" } }

A null next means the end of the list.

Verification

  • Paging through the full list returns each row exactly once.
  • Inserting a row mid-pagination does not duplicate or skip results.
  • Deep pages respond as fast as the first page.

Next Steps

Support bidirectional paging with a previous cursor, cap the page size to protect the server, and document that cursors are opaque and version-specific so clients never parse them.

Prerequisites

  • A REST list endpoint
  • A database with an ordered key
  • Basic SQL knowledge

Steps

  • 1
    Understand offset vs cursor pagination
  • 2
    Choose a stable sort key
  • 3
    Implement a keyset query
  • 4
    Encode and return cursors
  • 5
    Add navigation links

Category

API Design