Query Parameters, Filtering, Sorting and Pagination

intermediate
12 min

Query Parameters, Filtering, Sorting and Pagination

A collection endpoint that returns every row in the table works fine in a demo and falls over in production. Real APIs let clients ask for exactly the slice of data they need. In this lesson we design the query-parameter conventions for filtering, sorting, and pagination that power every serious list endpoint.

Query Parameters: The Basics

Everything after the ? in a URL is the query string: key-value pairs that refine a request without changing which resource it addresses.

text
GET /api/products?category=audio&sort=-price&page=2&limit=20

Path segments identify the resource (the products collection); query parameters describe how to view it. Options, filters, and view settings belong in the query string, never in the path.

Filtering

The simplest convention maps field names directly to equality filters: ?status=active&category=audio. For richer comparisons, common patterns include suffixed keys like ?price_gte=100&price_lte=500 (greater/less than or equal), or bracket syntax like ?price[gte]=100. Search across text fields usually gets its own parameter, conventionally ?q=wireless.

Whichever style you choose, validate every filter against an allow-list of filterable fields. Passing arbitrary parameters straight into database queries is both a performance trap and an injection risk.

Sorting

A single sort parameter with a direction prefix has become the de facto standard: ?sort=price ascending, ?sort=-price descending, and comma-separated for tie-breaking: ?sort=-createdAt,name. Always define a default sort (often newest first) so results are deterministic, and reject unknown sort fields with 400 rather than silently ignoring them.

Pagination: Offset Style

Offset pagination uses page and limit (or offset and limit):

text
GET /api/orders?page=3&limit=25

The server skips (page - 1) * limit rows and returns the next batch, plus metadata:

json
{ "data": ["..."], "meta": { "page": 3, "limit": 25, "total": 1240, "totalPages": 50 } }

Offset pagination is easy to implement and lets users jump to page 40 directly. Its weaknesses: deep pages get slow (the database still scans skipped rows), and results shift if rows are inserted or deleted between requests, causing duplicates or gaps.

Pagination: Cursor Style

Cursor (keyset) pagination fixes both problems. Instead of a page number, the server returns an opaque cursor pointing at the last item delivered; the client sends it back to continue.

text
GET /api/orders?limit=25&cursor=eyJpZCI6OTgxfQ

Under the hood the cursor encodes a sort key like the last seen ID, and the query becomes WHERE id < 981 ORDER BY id DESC LIMIT 25, which stays fast at any depth and is immune to insertions shifting the window. The trade-off: no jumping to an arbitrary page. Twitter, Stripe, and Slack all paginate this way. Rule of thumb: cursor pagination for infinite feeds and large datasets, offset pagination for admin tables where page jumping matters.

Caps and Defaults

Always enforce a default limit (say 20) and a hard maximum (say 100). Without a cap, one careless ?limit=1000000 request can take down your database. Return 400 for negative or non-numeric values rather than guessing.

Key Takeaways

  • Path identifies the collection; query parameters filter, sort, and page it.
  • Use allow-lists for filterable and sortable fields, with -field for descending sort.
  • Offset pagination is simple and jumpable; cursor pagination is fast and consistent at scale.
  • Enforce default and maximum page sizes, and always return pagination metadata.

Next up: Designing a Complete CRUD API, where all these pieces click together.

Query Parameters, Filtering, Sorting and Pagination - REST APIs | CodeYourCraft | CodeYourCraft