Script Valley
REST API Development: Complete Course from Beginner to Production
Advanced Concepts: Pagination, Filtering, Versioning, and Rate LimitingLesson 5.1

Pagination Strategies: Page-Based and Cursor-Based

pagination, page-based pagination, cursor-based pagination, offset pagination, limit offset, pagination metadata, performance

Pagination Strategies: Page-Based and Cursor-Based

No REST API should return unbounded lists of data. Returning 100,000 records in a single response will crash clients, exhaust server memory, and saturate network bandwidth. Pagination divides large result sets into manageable pages, and choosing the right strategy has significant performance implications.

DiagramPage-Based vs Cursor-Based Pagination

IMAGE PROMPT (replace this block with your generated image):

Flat side-by-side comparison diagram on white background. Title: Pagination Strategies Compared. Two columns separated by a vertical divider. Left column header: Page-Based (Offset) (light amber background #fffbeb). Right column header: Cursor-Based (light #3A5EFF background #e8ecff). Left column shows: a sequence of page buttons (1 2 3 4 5) with current page highlighted, query example ?page=3&limit=20, DB query SKIP 40 LIMIT 20. Below: three bullet points — Simple to implement (green check), Jump to any page (green check), Slow on large tables (red warning). Right column shows: a scrolling list with cursor arrow pointing at a record, query example ?after=507f1f77&limit=20, DB query WHERE _id > 507f1f77 LIMIT 21. Below: three bullet points — Consistent results (green check), Fast on any size (green check), Cannot jump to page 47 (amber warning). At the bottom center: Use case recommendation box — Use Page-Based for: admin panels, small datasets. Use Cursor-Based for: feeds, large datasets, infinite scroll. Brand color #3A5EFF for cursor column header. White background.

Page-Based (Offset) Pagination

Page-based pagination uses a page number and limit: GET /api/posts?page=3&limit=20. This skips (page-1)*limit records and returns the next limit records.

const page = parseInt(req.query.page) || 1;
const limit = Math.min(parseInt(req.query.limit) || 20, 100);
const skip = (page - 1) * limit;

const [data, total] = await Promise.all([
  Post.find(filter).skip(skip).limit(limit).sort(sort),
  Post.countDocuments(filter)
]);

res.json({
  success: true,
  data,
  meta: {
    total,
    page,
    limit,
    totalPages: Math.ceil(total / limit),
    hasNextPage: page * limit < total,
    hasPrevPage: page > 1
  }
});

Limitation: OFFSET-based pagination is slow on large datasets because the database must scan all preceding rows even if it does not return them. Records can also appear twice or be skipped if data is inserted or deleted between requests.

Cursor-Based Pagination

Cursor-based pagination uses the ID or timestamp of the last seen record as a cursor, fetching the next N records after it. It is more efficient and consistent for large, frequently changing datasets.

const { after, limit = 20 } = req.query;
const query = after ? { _id: { $gt: after } } : {};

const data = await Post.find({ ...filter, ...query })
  .limit(parseInt(limit) + 1)
  .sort({ _id: 1 });

const hasNextPage = data.length > limit;
if (hasNextPage) data.pop();

res.json({
  data,
  meta: {
    hasNextPage,
    nextCursor: hasNextPage ? data[data.length - 1]._id : null
  }
});

Up next

Filtering, Sorting, and Searching

Sign in to track progress