Query Parameters, Pagination, Filtering and Sorting
Design scalable collection endpoints that can handle large datasets.
Filtering and Searching
Collection endpoints should use query parameters for filtering.
Examples:
/products?category=books/users?status=active/orders?createdAfter=2026-10-01
For multiple filters, define a predictable syntax. Avoid allowing arbitrary database expressions directly from clients.
Search is conceptually different from exact filtering. /products?q=react may perform full-text or relevance-based search, while /products?category=books is an exact filter.
Always validate query parameters and translate them into safe database operations rather than concatenating raw input into database queries.
const { status = 'active', limit = '20' } = req.query;
const parsedLimit = Math.min(Number(limit), 100);
const users = await userService.find({
status,
limit: parsedLimit
});Offset Pagination
Offset pagination uses parameters such as page and limit or offset and limit.
Example: GET /products?page=3&limit=20.
It is easy to implement and works well for many administrative interfaces. However, large offsets can become expensive because the database may need to scan or skip many records. Results can also shift between requests when records are inserted or deleted.
Use stable ordering and enforce maximum page sizes. Never allow clients to request unlimited records.
120100Cursor Pagination
Cursor pagination returns an opaque cursor representing a position in an ordered dataset.
Example:
GET /events?limit=50&after=eyJpZCI6MTAwMH0
The server uses the cursor to continue from a known position rather than skipping a large number of records. Cursor pagination is particularly useful for feeds, event streams, large datasets, and frequently changing collections.
The cursor should normally be opaque. Clients should not need to understand database-specific values encoded inside it.
Sorting, Projection and Expansion
Sorting can be expressed with parameters such as sort=-createdAt,name, where a convention defines descending and ascending order.
Field selection can reduce response size: GET /users?fields=id,name,email.
Relationship expansion can be expressed through parameters such as ?include=orders, but uncontrolled expansion can create expensive queries and accidental data exposure.
Every dynamic field, sort key, and expansion option should be validated against an allowlist.
Never pass arbitrary client-supplied field names, sort expressions, or database operators directly into database queries.