Implementing Pagination

1. Using Offset-Based Pagination

ParamExample
offset?offset=40&limit=20
limitPage size, capped (e.g. 100)
ProsCons
Random page access, simple SQLSlow for large offsets, inconsistent if data changes

2. Using Cursor-Based Pagination

ParamExample
cursorOpaque token (base64-encoded)
limitPage size
ResponsenextCursor, prevCursor

Example: Cursor Response

{
  "data": [...],
  "pagination": {
    "nextCursor": "eyJpZCI6MTIzLCJ0cyI6MTcwfQ==",
    "hasMore": true
  }
}

3. Using Page-Based Pagination

ParamExample
page?page=3&perPage=20
perPage / sizeItems per page

4. Including Pagination Metadata

FieldRequired
totalRecommended (expensive for large datasets)
page / perPageYes
totalPagesComputed: ceil(total/perPage)
hasNext / hasPrevOptional convenience

RFC 5988 / 8288 Link header carries navigation links.

Link: </users?page=3&per_page=20>; rel="next",
      </users?page=8&per_page=20>; rel="last",
      </users?page=1&per_page=20>; rel="first",
      </users?page=1&per_page=20>; rel="prev"
relMeaning
firstPage 1
prevPrevious page (omit on first)
nextNext page (omit on last)
lastFinal page (requires total count)

7. Setting Default and Maximum Page Sizes

SettingTypical Value
Default page size20-25
Maximum page size100 (clamp; do not error)
Large datasetDocument hard limit, suggest streaming/export

8. Implementing Keyset Pagination

Uses WHERE (sortKey, id) > (lastSortKey, lastId) instead of OFFSET — O(log n) regardless of page.

Example: Keyset Query

SELECT id, name, created_at FROM users
WHERE (created_at, id) < (?, ?)
ORDER BY created_at DESC, id DESC
LIMIT 21;  -- 21 to detect hasMore

9. Handling Empty Result Sets

ScenarioResponse
No results200 OK + {"data": [], "total": 0}
Page beyond range200 OK + empty data (NOT 404)
Invalid page param400 Bad Request

10. Using Consistent Pagination Parameters

ConventionRecommendation
Single stylePick one (page/cursor/offset) per API
Param namingpage+perPage OR limit+offset
Across endpointsSame style for all collections