Pagination, Filtering, and Sorting

RESTAPI DesignBackendPerformance
Share on LinkedIn Share on X Share on Reddit Share on HN Share on Bluesky

Support exported "all open tickets" and the request timed out at GET /tickets?status=open&page=847&limit=100. Offset pagination on a million-row table is an accidental denial of service. List endpoints are where APIs meet database physics: indexes, stable ordering, and bounded result sets. Getting pagination, filtering, and sorting right upfront saves you from bolting cursor tokens onto a breaking v2 later.

Cursor pagination mechanics

Encode the last seen sort key in an opaque cursor:

GET /v1/events?limit=50&cursor=eyJ0IjoiMjAyNS0wNC0xVDEyOjAwOjAwWiIsImlkIjoiZXZfOTkifQ

Server decodes, applies WHERE (created_at, id) > (:t, :id) ORDER BY created_at, id LIMIT 50. Tie-break with primary key for stable ordering when timestamps collide. Response includes:

{
  "data": [...],
  "pagination": {
    "next_cursor": "eyJ...",
    "has_more": true
  }
}

Sign cursors with HMAC if clients must not tamper with internal sort state.

Filtering with an allowlist

Document filters explicitly in OpenAPI:

GET /v1/products?category=electronics&price_gte=100&price_lte=500&in_stock=true

Implementation pattern:

ALLOWED_FILTERS = {
    "category": (Product.category, "eq"),
    "price_gte": (Product.price, "gte"),
    "price_lte": (Product.price, "lte"),
    "in_stock": (Product.stock, "gt", 0),
}

def apply_filters(query, params):
    for key, value in params.items():
        if key not in ALLOWED_FILTERS:
            raise ValidationError(f"Unknown filter: {key}")
        column, op, *args = ALLOWED_FILTERS[key]
        query = operators[op](query, column, value, *args)
    return query

Composite indexes must match common filter plus sort combinations—(status, created_at DESC) if every query filters by status.

Sort syntax

Support comma-separated keys with - prefix for descending:

GET /v1/orders?sort=-created_at,id

Validate each token against SORTABLE_FIELDS. Return 400 for -discount if discount is computed and unindexed. Default sort should match the cursor key columns.

Offset escape hatch

If you must support page and offset for legacy clients, cap maximum offset (e.g., 10,000) and log callers exceeding it. Provide CSV export or async search for deep scans.

Total counts are expensive

total and totalPages require COUNT(*) which doubles query cost. Offer has_more instead, or make totals opt-in via ?include=total_count on admin routes only.

Rate limiting list endpoints

Broad filters on unindexed JSON columns are abuse vectors. Combine authentication, per-key rate limits, and query timeouts (statement_timeout in Postgres) so one integrator cannot scan your table.

Load tests should include worst-case filter combinations your docs allow. A missing composite index on tenant_id, status, created_at stays invisible until a large customer enables multi-tenant filtering at scale. Explain default sort explicitly; undefined order frustrates integrators building reconciliation jobs.

Publish worked examples: curl for first page, sample error when filter enum is wrong. Support closes tickets faster when docs show exact problem JSON for invalid queries. Cap include or expand depth for embedded resources—clients otherwise simulate GraphQL over REST one GET at a time.

Offset escape hatches need hard caps and logging. Provide async export for deep scans instead of unbounded OFFSET that doubles query cost on million-row tables.

Run the change through your standard PR checklist: tests, observability, and a two-minute rollback drill in staging. Small operational habits accumulate into systems that survive on-call nights without heroics.

Share a short write-up in your engineering channel after rollout: what shipped, what metric you watch, and who owns follow-up. That closes the loop for teammates who were not in the PR and surfaces gaps in docs before the next person repeats the same investigation.

Resources

Trade-offs I keep revisiting for rest api pagination filtering sorting

Operating rest api pagination filtering sorting well means tying design choices to measurable outcomes and explicit owners. Ambiguous ownership is how pages rot.

For rest api pagination filtering sorting:

Revisit the design when the metric that justified rest api pagination filtering sorting stops moving — sunsetting is a feature.

Signal Target Alarm
Coverage % Team-defined SLO Page on burn rate
Mean time to detect Baseline − noise Ticket if sustained
Escapes to prod Budget cap Weekly review

Load and chaos experiments for rest api pagination filtering sorting

Reviewers should challenge assumptions encoded in rest api pagination filtering sorting: defaults copied from tutorials, timeouts that exceed upstream SLAs, and authz checks applied only on the primary UI path. Require a short threat or failure note in the PR when the change touches a trust boundary.

Concrete probes:

  1. Scenario A for rest api pagination filtering sorting: partial dependency outage — prove clients degrade gracefully and retries do not amplify load.
  2. Scenario B for rest api pagination filtering sorting: bad config shipped — prove rollback within the declared RTO without data corruption.
  3. Scenario C for rest api pagination filtering sorting: traffic 3× baseline — prove autoscaling or shedding keeps the golden journey healthy.

Capacity planning with rest api pagination filtering sorting in mind

Roll out rest api pagination filtering sorting behind a flag or weighted route when possible. Start with internal users or a low-risk geography. Watch the signals in the table for at least one full business cycle before calling the migration done. Keep the previous path warm until error budgets stabilize.

Document the owner, the dashboard, and the single command that reverts the change. If that sentence is hard to write, the design is not ready for production traffic.

Multi-tenant concerns in rest api pagination filtering sorting

Detail 1 (80): for rest api pagination filtering sorting, define the contract between producers and consumers explicitly — payload shape, timeout, and idempotency key. When multi-tenant concerns in rest api pagination filtering sorting becomes painful, it is usually because that contract was implicit.

I keep a short matrix: who can break rest api pagination filtering sorting, how we detect it within five minutes, and who is paged. Update the matrix when ownership moves. Add one synthetic check that exercises the failure path, not only the happy path. Prefer checks that run continuously over quarterly manual reviews that everyone skips under deadline pressure.

If you only remember one thing about rest api pagination filtering sorting: optimize for reversible decisions. Reversibility beats cleverness when the incident channel is busy and the blast radius is unclear.

Compliance evidence for rest api pagination filtering sorting

Detail 2 (404): for rest api pagination filtering sorting, define the contract between producers and consumers explicitly — payload shape, timeout, and idempotency key. When compliance evidence for rest api pagination filtering sorting becomes painful, it is usually because that contract was implicit.

I keep a short matrix: who can break rest api pagination filtering sorting, how we detect it within five minutes, and who is paged. Update the matrix when ownership moves. Add one synthetic check that exercises the failure path, not only the happy path. Prefer checks that run continuously over quarterly manual reviews that everyone skips under deadline pressure.

If you only remember one thing about rest api pagination filtering sorting: optimize for reversible decisions. Reversibility beats cleverness when the incident channel is busy and the blast radius is unclear.

Developer experience when changing rest api pagination filtering sorting

Detail 3 (791): for rest api pagination filtering sorting, define the contract between producers and consumers explicitly — payload shape, timeout, and idempotency key. When developer experience when changing rest api pagination filtering sorting becomes painful, it is usually because that contract was implicit.

I keep a short matrix: who can break rest api pagination filtering sorting, how we detect it within five minutes, and who is paged. Update the matrix when ownership moves. Add one synthetic check that exercises the failure path, not only the happy path. Prefer checks that run continuously over quarterly manual reviews that everyone skips under deadline pressure.

If you only remember one thing about rest api pagination filtering sorting: optimize for reversible decisions. Reversibility beats cleverness when the incident channel is busy and the blast radius is unclear.

Frequently asked questions

When is offset pagination acceptable?

Offset works for admin tables under a few thousand rows where users jump to arbitrary pages and concurrent writes are rare. Public APIs with high insert rates should use cursor pagination because OFFSET scans degrade linearly and pages skip or duplicate rows when data shifts between requests.

How do I prevent filter parameters from becoming SQL injection?

Never interpolate user input into SQL or ORM fragments. Map allowed filter fields to typed column references via an allowlist. Parse operators from a fixed enum (=, gt, lt, in) and bind values as parameters. Reject unknown fields with 400 rather than silently ignoring them, which confuses clients.

Should sort field names be exposed directly?

Expose logical sort keys (createdAt, -amount) mapped internally to indexed columns. Reject sorts on unindexed columns in production APIs to avoid full table scans. Document default sort and maximum page size; cap limit at 100 unless the consumer has a contracted bulk export path.

Hiring a senior Android / Flutter engineer?

I architect and ship production mobile software — Kotlin, Jetpack Compose, Flutter — for robotics, EV infrastructure, fintech, and real-time systems. Open to remote roles in Europe and the US.

Get in touch →