Filtering and Sorting
Pagination usually becomes useful when combined with filtering and sorting.
A GraphQL API might define:
input ProductFilterInput {
category: ProductCategory
minPrice: Float
maxPrice: Float
search: String
}
enum ProductSortField {
PRICE
CREATED_AT
NAME
}
enum SortDirection {
ASC
DESC
}
input ProductSortInput {
field: ProductSortField!
direction: SortDirection!
}
Then:
products(
first: Int!
after: String
filter: ProductFilterInput
sort: ProductSortInput
): ProductConnection!
The server should translate these inputs into controlled database operations rather than accepting arbitrary database syntax.
Sorting also needs deterministic tie-breaking.
For example, sorting only by createdAt can produce ambiguous ordering if multiple documents share the same timestamp.
A better database order might be:
createdAt DESC, _id DESC
The unique ID acts as a tie-breaker.
This matters especially for cursor pagination. If ordering is not deterministic, the same cursor can produce inconsistent page boundaries.
Typed Filtering and Sorting Input
input ProductFilterInput {
category: ProductCategory
minPrice: Float
maxPrice: Float
search: String
}
enum ProductSortField {
PRICE
CREATED_AT
NAME
}
enum SortDirection {
ASC
DESC
}
input ProductSortInput {
field: ProductSortField!
direction: SortDirection!
}💡 The API exposes a controlled set of filtering and sorting capabilities. Clients cannot directly provide arbitrary MongoDB or SQL expressions. The repository translates these typed values into database-specific queries.
Key Takeaways
- Filters should be represented with explicit GraphQL input types.
- Sort fields should come from a controlled enum where possible.
- Deterministic ordering is important for reliable pagination.
- Tie-breaker fields can make ordering stable.
Knowledge Check
Why is a unique tie-breaker useful when sorting records for cursor pagination?