paginator-rs
A comprehensive, modular Rust pagination library with support for multiple databases and web frameworks. Built for production use with a focus on ergonomics, performance, and maintainability.
โจ Features
Core Features
- ๐ฏ Flexible Pagination: Page-based and offset/limit pagination
- ๐ง Builder Pattern: Fluent API for constructing pagination parameters
- ๐ Rich Metadata: Automatic calculation of total pages, has_next, has_prev
- ๐จ Sorting Support: Multi-field sorting with ascending/descending order
- โ ๏ธ Error Handling: Comprehensive error types with helpful messages
- ๐ JSON Serialization: Built-in serde support
Advanced Features
- ๐ Cursor Pagination: Keyset-based pagination for large datasets with consistent results
- โก Optional COUNT(): Skip expensive COUNT queries with
.disable_total_count() - ๐ SQL Injection Prevention: Parameterized queries in all database integrations
- ๐๏ธ CTE Support: Common Table Expressions (WITH clauses) work seamlessly
- ๐ Advanced Filtering: 14 filter operators (eq, ne, gt, lt, like, in, between, etc.)
- ๐ Full-text Search: Multi-field fuzzy search with case-sensitive options
Database Integrations
- SQLx (
paginator-sqlx): PostgreSQL, MySQL, SQLite support - SeaORM (
paginator-sea-orm): Type-safe ORM pagination with entity support - SurrealDB (
paginator-surrealdb): Multi-model database with SQL-like queries
Web Framework Integrations
- Axum (
paginator-axum): Query extractors and JSON responses with headers - Rocket (
paginator-rocket): Request guards and responders - Actix-web (
paginator-actix): Extractors, responders, and middleware
๐งฑ Workspace Structure
paginator-rs/
โโโ paginator-rs/ # Core trait and types
โโโ paginator-utils/ # Shared types (params, response, metadata)
โโโ paginator-sqlx/ # SQLx database integration
โโโ paginator-sea-orm/ # SeaORM integration
โโโ paginator-surrealdb/ # SurrealDB integration
โโโ paginator-axum/ # Axum web framework integration
โโโ paginator-rocket/ # Rocket web framework integration
โโโ paginator-actix/ # Actix-web integration
โโโ paginator-examples/ # Usage examples
๐ฆ Installation
Core Library
[]
= "0.2.1"
= "0.2.1"
= { = "1", = ["derive"] }
With SQLx (PostgreSQL)
[]
= { = "0.2.1", = ["postgres", "runtime-tokio"] }
= { = "0.8", = ["postgres", "runtime-tokio"] }
With SeaORM
[]
= { = "0.2.1", = ["sqlx-postgres", "runtime-tokio"] }
= { = "1.1", = ["sqlx-postgres", "runtime-tokio"] }
With SurrealDB
[]
= { = "0.2.1", = ["protocol-ws", "kv-mem"] }
= { = "2.1", = ["protocol-ws", "kv-mem"] }
With Axum
[]
= "0.2.1"
= "0.7"
With Rocket
[]
= "0.2.1"
= { = "0.5", = ["json"] }
With Actix-web
[]
= "0.2.1"
= "4"
๐ Usage Examples
Basic Pagination
use ;
use ;
// Using builder pattern
let params = new
.page
.per_page
.sort_by
.sort_asc
.build;
// Or create directly
let params = new;
Filtering & Search
use ;
// Example 1: Simple filtering
let params = new
.page
.per_page
.filter_eq
.filter_gt
.build;
// Example 2: Advanced filtering with multiple operators
let params = new
.filter_in
.filter_between
.build;
// Example 3: Full-text search
let params = new
.search
.build;
// Example 4: Combined filters and search
let params = new
.page
.per_page
.filter_eq
.filter_gt
.search
.sort_by
.sort_desc
.build;
// Get generated SQL WHERE clause
if let Some = params.to_sql_where
Cursor-Based Pagination
Cursor pagination (keyset pagination) provides better performance and consistency for large datasets compared to offset-based pagination.
use ;
// Example 1: First page with cursor support
let params = new
.per_page
.sort_by
.sort_asc
.build;
// Example 2: Next page using cursor (better than offset!)
let params = new
.per_page
.sort_by
.cursor_after
.build;
// Example 3: Previous page
let params = new
.per_page
.sort_by
.cursor_before
.build;
// Example 4: Decode from encoded cursor (from API response)
let params = new
.per_page
.cursor_from_encoded
.unwrap
.build;
// Example 5: Skip COUNT query for better performance
let params = new
.per_page
.sort_by
.cursor_after
.disable_total_count // Skip expensive COUNT(*)
.build;
Cursor Pagination Benefits:
- โ Better performance on large datasets (no OFFSET overhead)
- โ Consistent results even with concurrent data modifications
- โ No skipped or duplicate rows
- โ Works with filters and search
- โ Secure Base64-encoded cursor strings
Available Filter Operators:
filter_eq(field, value)- Equal (=)filter_ne(field, value)- Not equal (!=)filter_gt(field, value)- Greater than (>)filter_lt(field, value)- Less than (<)filter_gte(field, value)- Greater than or equal (>=)filter_lte(field, value)- Less than or equal (<=)filter_like(field, pattern)- SQL LIKE pattern matchingfilter_ilike(field, pattern)- Case-insensitive LIKEfilter_in(field, values)- IN arrayfilter_between(field, min, max)- BETWEEN min AND maxfilter_is_null(field)- IS NULLfilter_is_not_null(field)- IS NOT NULL
Search Options:
search(query, fields)- Case-insensitive fuzzy searchsearch_exact(query, fields)- Exact match searchsearch_case_sensitive(query, fields)- Case-sensitive search
With Axum
use ;
use ;
use Serialize;
async
let app = new.route;
With SQLx (PostgreSQL)
use paginate_query;
use PaginatorBuilder;
use PgPool;
async
With SeaORM
use PaginateSeaOrm;
use PaginationParams;
use ;
async
With SurrealDB
use Surreal;
use Ws;
use ;
use PaginatorBuilder;
async
With Rocket
use ;
use ;
async
With Actix-web
use ;
use ;
async
๐งช Response Format
Standard Pagination Response
Cursor Pagination Response
When using cursor pagination with .disable_total_count():
Note: When disable_total_count() is used, total and total_pages fields are omitted from the response for better performance.
HTTP Headers (Web Framework Integrations)
X-Total-Count: 100
X-Total-Pages: 5
X-Current-Page: 1
X-Per-Page: 20
Note: X-Total-Count and X-Total-Pages headers are only included when total is available (not using disable_total_count()).
๐ฏ Query Parameters
Basic Pagination & Sorting
GET /api/users?page=2&per_page=20&sort_by=name&sort_direction=asc
page: Page number (1-indexed, default: 1)per_page: Items per page (default: 20, max: 100)sort_by: Field to sort by (optional)sort_direction:ascordesc(optional)
With Filters
GET /api/users?page=1&filter=status:eq:active&filter=age:gt:18&filter=role:in:admin,moderator
filter: Filter in formatfield:operator:value- Multiple filters can be combined (AND logic)
Filter Format Examples:
status:eq:active- Equalage:gt:18- Greater thanage:between:18,65- Betweenrole:in:admin,moderator,user- In arrayname:like:%john%- LIKE patterndeleted_at:is_null- IS NULL
With Search
GET /api/users?search=john&search_fields=name,email,bio
search: Search query textsearch_fields: Comma-separated list of fields to search in
Combined Example
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
๐ง Builder Pattern
use PaginatorBuilder;
let params = new
.page
.per_page
.sort_by
.sort_desc
.build;
โ ๏ธ Error Handling
use ;
// Errors are comprehensive and helpful
match result
๐๏ธ Architecture
- Easy to Use: Builder pattern and sensible defaults
- Easy to Debug: Comprehensive error messages and type safety
- Easy to Maintain: Modular crate structure with clear separation of concerns
๐ Security
SQL Injection Prevention
All database integrations use parameterized queries with bound parameters to prevent SQL injection attacks:
// โ
SAFE: All filter values are bound parameters
let params = new
.filter_eq
.build;
// The malicious input is safely escaped as a parameter value
// SQL: WHERE status = $1 (with parameter: "'; DROP TABLE users; --")
Implementation Details:
paginator-sqlx: Uses SQLx'sQueryBuilderwith.push_bind()for all valuespaginator-sea-orm: Uses SeaORM's type-safe query builderpaginator-surrealdb: Uses SurrealDB's parameterized query API- Filter values, search terms, and sort fields are never concatenated into SQL strings
Secure Cursor Encoding
Cursors are Base64-encoded JSON objects to prevent tampering:
// Cursor structure: { "field": "id", "value": 42, "direction": "after" }
// Encoded: "eyJmaWVsZCI6ImlkIiwidmFsdWUiOjQyLCJkaXJlY3Rpb24iOiJhZnRlciJ9"
// โ
Type-safe decoding with validation
let cursor = decode?;
// Returns error if cursor is tampered or invalid
Best Practices
- โ Always validate user input before building pagination parameters
- โ
Use type-safe filter values (
FilterValue::String,FilterValue::Int, etc.) - โ Cursors are automatically validated during decoding
- โ All database queries use parameterized statements
- โ No raw SQL concatenation in any integration
๐ Examples
Run the examples:
๐ค Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
๐ License
MIT ยฉ 2025 Maulana Sodiqin
๐ Links
- Repository
- Documentation (coming soon)
- crates.io (coming soon)