paginator-rs
Modular Rust pagination library with database and web framework integrations.
Features
- Page-based, offset/limit, and cursor (keyset) pagination
- Builder API with multi-field sorting
- Filtering with 14 operators (eq, ne, gt, lt, gte, lte, like, ilike, in, between, is_null, is_not_null) and multi-field search
- Optional
COUNT(*)skipping via.disable_total_count() - Parameterized queries in all database integrations
- Serde serialization built in
Crates
| Crate | Purpose |
|---|---|
paginator-rs |
Core trait and types |
paginator-utils |
Shared types (params, response, metadata) |
paginator-sqlx |
SQLx (PostgreSQL, MySQL, SQLite) |
paginator-sea-orm |
SeaORM |
paginator-surrealdb |
SurrealDB |
paginator-axum |
Axum extractors and responses |
paginator-rocket |
Rocket guards and responders |
paginator-actix |
Actix-web extractors and responders |
paginator-zod |
zod-rs validation of pagination input and TypeScript response schemas |
Installation
[]
= "0.3.2"
Add the integration crate you need, for example:
= { = "0.3.2", = ["postgres", "runtime-tokio"] }
Usage
Building parameters
use ;
let params = new
.page
.per_page
.filter_eq
.filter_gt
.search
.sort_by
.sort_desc
.build;
Cursor pagination
use ;
let params = new
.per_page
.sort_by
.cursor_after
.disable_total_count // skip COUNT(*)
.build;
Cursors are Base64-encoded and validated on decode; use .cursor_from_encoded(cursor) to resume from an API response.
SQLx
use PaginatorBuilder;
use paginate_query;
let params = new.page.per_page.build;
let result = .await?;
println!;
Axum
use ;
async
SeaORM, SurrealDB, Rocket, and Actix-web work the same way; paginator-examples has a runnable example for every feature and integration:
Validating input with zod-rs
paginator-zod validates raw pagination query JSON against a zod-rs schema before it becomes PaginationParams, with path-aware, localizable errors. It enforces per_page bounds and, optionally, allow-lists for sort and filter fields.
use PaginationSchema;
use json;
let schema = new
.max_per_page
.allowed_sort_fields
.allowed_filter_fields;
// Ok -> PaginationParams, ready to paginate
let params = schema.validate?;
// Err -> "per_page: Too big: expected number to have <= 100"
schema.validate.unwrap_err;
It also emits a Zod schema for the response envelope so frontends get typed, validated responses:
println!;
// export const paginated = <T extends z.ZodTypeAny>(item: T) =>
// z.object({ data: z.array(item), meta: PaginationMetaSchema });
Response format
Cursor pagination adds next_cursor/prev_cursor; with disable_total_count(), total and total_pages are omitted. Web framework integrations also set X-Total-Count, X-Total-Pages, X-Current-Page, and X-Per-Page headers.
Query parameters
GET /api/users?page=1&per_page=10&filter=status:eq:active&filter=age:gt:18&search=developer&search_fields=title,bio&sort_by=created_at&sort_direction=desc
page— 1-indexed, default 1per_page— default 20, max 100sort_by/sort_direction— field andasc/descfilter—field:operator:value, repeatable (AND logic)search/search_fields— query text and comma-separated fields
License
MIT © 2025 Maulana Sodiqin