Filtering & Sorting
Filtering narrows a collection to the rows a client cares about; sorting decides their order. Both are query parameters on the collection, and a little convention keeps them readable.
Filtering
Section titled “Filtering”The simplest, most discoverable form is one query parameter per field:
GET /articles?status=published&author=42For ranges and operators, a common convention is a bracketed or suffixed operator:
GET /articles?createdAt[gte]=2026-01-01&views[gt]=1000Keep the operator vocabulary small and documented (gte, lte, gt, lt, ne, in). Whatever you pick, apply it the same way on every collection.
Sorting
Section titled “Sorting”Accept a sort parameter listing fields, with a leading - for descending:
GET /articles?sort=-createdAt,titleThat reads as “newest first, then by title ascending”. Define which fields are sortable (sorting on an unindexed column can be expensive) and reject the rest with 400.
Applying both in code
Section titled “Applying both in code”| Advantages (Filtering & Sorting) | Trade-offs |
|---|---|
| Client fetches only what it needs — smaller payload | Many query parameters make URLs long and hard to read |
| Server-side filtering beats filtering all the data client-side | Complex filters need a query language (OData, custom) |
| Server-side sort uses a database index — faster than sorting in memory | Filter/sort parameters must be validated to prevent SQL injection |
| Filter and sort combine in a single request | Multi-field sort makes the URL more complex |
Common mistakes
Section titled “Common mistakes”Not validating filter parameters Symptom:
GET /users?sort=; DROP TABLE users--- SQL injected through the sort parameter
- Allowlist the fields that can be filtered/sorted:
if (!['name','email','createdAt'].includes(sort)) throw
Expensive filters with no index Symptom:
GET /orders?status=pending— runsWHERE status = 'pending'on a table with millions of rows- No index on the
statuscolumn — a full table scan on every request - Add an index on frequently filtered fields, or restrict which filters are supported
💡 Real-world examples
GitHub API:
GET /repos/\{owner\}/\{repo\}/issues?state=open&labels=bug&sort=created&direction=desc- Multi-dimensional filter and sort in a single request
Stripe API:
GET /charges?created[gte]=1609459200&created[lte]=1640995199&limit=100- Range filter via bracket notation — filters between two dates
Sources: GitHub REST API — issues, Stripe API — pagination and filtering