Cursor Pagination
Cursor pagination (keyset pagination) provides better performance and consistency for large datasets compared to offset-based pagination.
Why Cursor Pagination?
Section titled “Why Cursor Pagination?”| Feature | Offset-based | Cursor-based |
|---|---|---|
| Performance on large datasets | Degrades with higher pages | Consistent |
| Concurrent modifications | May skip/duplicate rows | Consistent results |
| Random page access | Supported | Not supported |
| Implementation complexity | Simple | Moderate |
Using the Fluent Builder
Section titled “Using the Fluent Builder”use paginator_rs::{Paginator, CursorValue};
// First page (no cursor needed)let params = Paginator::new() .per_page(20) .sort().asc("id") .build();
// Next page using cursorlet params = Paginator::new() .per_page(20) .cursor() .after("id", CursorValue::Int(42)) .apply() .build();
// Previous pagelet params = Paginator::new() .per_page(20) .cursor() .before("id", CursorValue::Int(42)) .apply() .build();Using the Legacy Builder
Section titled “Using the Legacy Builder”use paginator_rs::{PaginatorBuilder, CursorValue};
// Next pagelet params = PaginatorBuilder::new() .per_page(20) .sort_by("id") .cursor_after("id", CursorValue::Int(42)) .build();
// Previous pagelet params = PaginatorBuilder::new() .per_page(20) .sort_by("id") .cursor_before("id", CursorValue::Int(42)) .build();Cursor Values
Section titled “Cursor Values”The CursorValue enum supports multiple types:
use paginator_rs::CursorValue;
CursorValue::String("2024-01-01T00:00:00Z".to_string())CursorValue::Int(42)CursorValue::Float(3.14)CursorValue::Uuid("550e8400-e29b-41d4-a716-446655440000".to_string())Encoded Cursors
Section titled “Encoded Cursors”Cursors returned in API responses are Base64-encoded JSON. You can decode them:
// From API response meta.next_cursorlet params = PaginatorBuilder::new() .per_page(20) .cursor_from_encoded("eyJmaWVsZCI6ImlkIiwidmFsdWUiOjQyLCJkaXJlY3Rpb24iOiJhZnRlciJ9") .unwrap() .build();
// Or with fluent builderlet params = Paginator::new() .per_page(20) .cursor() .from_encoded("eyJmaWVsZCI6ImlkIiwidmFsdWUiOjQyLCJkaXJlY3Rpb24iOiJhZnRlciJ9") .unwrap() .apply() .build();Disabling Total Count
Section titled “Disabling Total Count”For optimal performance with cursor pagination, skip the expensive COUNT(*) query:
let params = Paginator::new() .per_page(20) .sort().asc("id") .disable_total_count() .build();When total count is disabled, meta.total and meta.total_pages will be None, but meta.has_next and meta.has_prev will still work correctly.
Response with Cursors
Section titled “Response with Cursors”{ "data": [...], "meta": { "page": 1, "per_page": 20, "has_next": true, "has_prev": false, "next_cursor": "eyJmaWVsZCI6ImlkIiwidmFsdWUiOjQ0LCJkaXJlY3Rpb24iOiJhZnRlciJ9", "prev_cursor": "eyJmaWVsZCI6ImlkIiwidmFsdWUiOjQzLCJkaXJlY3Rpb24iOiJiZWZvcmUifQ==" }}