🔥 Firehook

Blog · api rest

Pagination, filtering, and sorting in REST APIs

Design consistent pagination, filtering, and sorting so clients can query large datasets reliably and predictably.

API gateway with structured flow, representing pagination and filtering

Why query design matters

Large datasets are inevitable. Without pagination and filtering, clients will attempt to fetch everything and your API will slow down or fail.

Clear query rules also protect your infrastructure and make client integrations much easier to maintain.

Offset pagination: simple but fragile

Offset pagination (`page` + `limit`) is easy to implement and easy to explain. It is a good default for small datasets.

The downside is instability: if records are inserted or deleted between requests, results shift and users can see duplicates or misses.

Cursor pagination: stable and scalable

Cursor pagination uses a pointer (cursor) to the last seen item. It remains stable even when data changes.

It is more complex for clients, but it is the better choice for large or fast-changing datasets.

Sorting should be explicit

Provide a `sort` parameter and document allowed fields. A simple convention is `sort=created_at` for ascending and `sort=-created_at` for descending.

Sorting must be deterministic; otherwise pagination breaks.

Filtering rules

Use query parameters for filters: `/orders?status=paid&customer_id=123`. This is flexible and easy to extend.

Avoid encoding filters in paths unless they represent distinct resource types.

Field selection

If clients only need a few fields, provide a `fields` parameter. It reduces bandwidth and improves performance.

Keep field selection controlled to avoid accidental exposure of sensitive data.

Metadata and totals

Clients often want total counts, but they can be expensive to compute. Provide them only when useful.

For cursor pagination, a `next_cursor` and `has_next` are often enough.

Error handling for queries

If a filter or sort is invalid, return a clear 400 with a message describing the issue.

Query rules are part of your API contract, so document them and keep them stable.

FAQ

Is cursor pagination always better than offset?
Cursor pagination is more stable for large datasets, but offset is simpler for small collections.
How should I expose sorting?
Use a `sort` parameter with explicit fields and direction, e.g. `sort=-created_at`.
Should filters be in the path or query?
Use query parameters for filters to keep paths resource-focused.
How do I return total counts?
Return counts when useful, but be careful with expensive queries. Consider separate endpoints for totals.
What about pagination in filtered results?
Pagination should operate after filtering. Always apply filters first, then paginate.