KAHIBARO
Discord Login Register

7.17 Sorting

Why Sorting Matters in REST APIs

Sorting lets API clients control the order of returned data. Instead of always getting data in the order stored in the database, clients can ask for:

Good sorting support makes your API more flexible and reduces the need for clients to download data and sort it themselves.

In this chapter we focus on how to design and implement sorting in REST APIs, not on database-specific details or performance topics, which are covered elsewhere.


Common Sorting Requirements

Different APIs and clients often need similar sorting behaviors.

Examples:

A typical requirement is:

Passing Sorting in Query Parameters

Sorting is part of how you want results, not what you want. So it belongs in query parameters, not in the URL path or request body for simple GET list endpoints.

Example endpoint:

http
GET /products?sort=price
GET /products?sort=-price
GET /products?sort=price,-rating

You will see several common conventions for the sort query parameter.


Sorting Conventions

1. Single field, separate direction parameter

You provide one parameter for field, one for direction.

http
GET /users?sort_by=name&order=asc
GET /users?sort_by=created_at&order=desc

This is easy to understand but can be less compact when you add multiple fields.

Pros

Cons

2. Single `sort` parameter, plus-minus convention

You use one parameter, and a minus sign indicates descending order.

http
GET /users?sort=name        # name ascending
GET /users?sort=-name       # name descending
GET /users?sort=age,-name   # age ascending, then name descending

This is used by many APIs, for example JSON:API.

Pros

Cons

3. Verbose direction syntax

You include both field and direction explicitly in one parameter.

Examples:

http
GET /users?sort=name:asc
GET /users?sort=name:desc
GET /users?sort=age:asc,name:desc

or even:

http
GET /users?sort=+name,-created_at

Pros

Cons

Supporting Multiple Sort Fields

Sometimes you need to sort by more than one field to get predictable ordering.

Example: You want users ordered by age, and if two users have the same age, order them by name.

In SQL you might write:

sql
ORDER BY age ASC, name ASC;

In your API you might express it as:

http
GET /users?sort=age,name

Or with directions:

http
GET /users?sort=age,-name

In this example:

The backend should apply these in order.

Important rule: Sorting by multiple fields should always be deterministic.
If you allow sorting by one field only, consider adding a hidden tiebreaker like the primary key:
ORDER BY given_field, id

This avoids unstable result order when data changes between requests.


Safe and Allowed Sort Fields

Never allow clients to sort by any arbitrary string they pass in. This is dangerous:

Instead, always define a whitelist of allowed sort fields.

Example strategy in pseudocode:

python
ALLOWED_SORT_FIELDS = {
    "name": "users.name",
    "age": "users.age",
    "created_at": "users.created_at",
}

Workflow:

  1. Parse the sort query parameter.
  2. For each requested sort field:
    • Remove any leading -.
    • Check if the field is in ALLOWED_SORT_FIELDS.
    • If not, return a 400 Bad Request with a helpful error message.
  3. Build your ORDER BY clause only from allowed, known fields.

Rule: Never put user input directly into an ORDER BY clause.
Always map client-visible sort names to internal, hardcoded column names or expressions.


Handling Invalid Sorting Requests

When clients send invalid sorting options, your API should respond clearly.

Common invalid cases:

A good approach is to return 400 Bad Request with a structured error response.

Example response:

json
{
  "detail": {
    "error": "invalid_sort",
    "message": "Cannot sort by 'height'. Allowed fields: name, age, created_at.",
    "field": "sort"
  }
}

This is easier to debug than silently ignoring bad sort parameters.


Sorting and Default Order

You should define a default sort order for each list endpoint. This default is used when the client does not specify sorting.

Examples:

You have two main choices when the client sends sort:

For example, if the client asks:

http
GET /products?sort=price

You can:

Appending a tiebreaker like primary key is usually helpful, but you should not override the client’s intent about which field is primary.


Sorting and Pagination

Sorting and pagination are tightly connected. If you change sort order, page 1 will show different items.

Key points:

Example workflow for a typical page-number pagination:

  1. Parse sort parameter, decide sort fields and directions.
  2. Apply ORDER BY to the query.
  3. Apply LIMIT and OFFSET (or equivalent).

In SQL terms:

sql
SELECT *
FROM products
ORDER BY price ASC, id ASC
LIMIT 20 OFFSET 40;  -- page 3 of size 20

If a client changes sort from price to name, the same page and page size will show a completely different subset of products.

You should document:

Examples of Sorting in REST APIs

Example 1: Simple user listing

Endpoint:

http
GET /users

Supported sort fields:

Client examples:

http
GET /users

Meaning: default sort, for example created_at descending.

http
GET /users?sort=name

Meaning: order by name ascending.

http
GET /users?sort=-last_login

Meaning: most recently logged in first.

Response (sorted by name):

json
{
  "items": [
    {"id": 3, "name": "Alice", "created_at": "2024-01-10T10:00:00Z"},
    {"id": 5, "name": "Bob", "created_at": "2024-01-12T12:30:00Z"},
    {"id": 1, "name": "Charlie", "created_at": "2023-12-20T09:15:00Z"}
  ]
}

Example 2: Products with combined sort and pagination

Endpoint:

http
GET /products

Query:

http
GET /products?sort=price,-rating&page=1&page_size=3

Assume the data:

idnamepricerating
1A104.5
2B103.0
3C55.0
4D204.0
5E54.0

Sort: price ascending, then rating descending.

Sorted order:

  1. C (price 5, rating 5.0)
  2. E (price 5, rating 4.0)
  3. A (price 10, rating 4.5)
  4. B (price 10, rating 3.0)
  5. D (price 20, rating 4.0)

For page=1&page_size=3, the API returns the first 3 in that order.

Response:

json
{
  "items": [
    {"id": 3, "name": "C", "price": 5, "rating": 5.0},
    {"id": 5, "name": "E", "price": 5, "rating": 4.0},
    {"id": 1, "name": "A", "price": 10, "rating": 4.5}
  ],
  "page": 1,
  "page_size": 3,
  "total": 5
}

Sorting and Filtering Together

Sorting is often used together with filtering (covered in another chapter). The typical order in implementation:

  1. Apply filters (where conditions).
  2. Apply sorting (order by).
  3. Apply pagination.

Example request:

http
GET /products?category=books&min_price=10&sort=-created_at

Meaning:

The API might translate that to something like:

sql
SELECT *
FROM products
WHERE category = 'books'
  AND price >= 10
ORDER BY created_at DESC, id DESC
LIMIT 20;

Note how sorting comes after filtering in the SQL query.


Sorting Best Practices

To make your API sorting support robust and user friendly, follow these guidelines.

Sorting best practice checklist

  • Use a single query parameter for sorting, such as sort.
  • Support both ascending and descending order.
  • Use a consistent convention, for example:
    • sort=field for ascending, sort=-field for descending.
  • Always whitelist allowed sort fields.
  • Handle invalid sort values with clear 400 Bad Request responses.
  • Document:
    • List of sortable fields.
    • Default sort order.
    • How to sort descending.
    • How to sort by multiple fields.
  • Apply sorting before pagination and after filtering.
  • Add a stable tiebreaker such as primary key in your actual query, so the order is deterministic.

If you follow these rules, clients can predictably control data order and your API remains safe and maintainable.


Summary

In REST APIs, sorting is controlled through query parameters and usually uses a dedicated parameter such as sort. You should:

This makes your API flexible to client needs without sacrificing security or clarity.

Views: 6

Comments

Please login to add a comment.

Don't have an account? Register now!