ZCore LogoZCore
How to

How to implement Cursor Pagination

High-performance keyset pagination for large datasets and real-time feeds without offset drift.

For high-throughput datasets or infinite-scroll feeds, offset pagination causes query slowdowns and duplicate items (offset drift). ZCore provides CursorPagination based on encoded keysets.

1. Enable Cursor Pagination on the Router

Open your router file and set pagination_class = CursorPagination. This automatically changes the GET / endpoint query signature to accept cursor and size:

# routers.py
from zcore import BaseRouter
from zcore.db.pagination import CursorPagination
from .models import Task
from .schemas import TaskCreate, TaskUpdate, TaskResponse
from .services import TaskService

class TaskRouter(BaseRouter[TaskCreate, TaskUpdate]):
    model = Task
    create_schema = TaskCreate
    update_schema = TaskUpdate
    schema_out = TaskResponse
    service = TaskService
    
    # Switch from default Page-Number to Keyset Cursor pagination
    pagination_class = CursorPagination
    prefix = "/tasks"
    tags = ["Tasks"]

router_instance = TaskRouter()

2. Client Requests

The client can fetch the first page by simply requesting the desired size, then passing the returned cursor for subsequent pages:

# Initial request (First page)
curl -X GET "http://localhost:8000/tasks/?size=20"

# Subsequent request using the received next_cursor
curl -X GET "http://localhost:8000/tasks/?size=20&cursor=eyJ2YWx1ZSI6ICI...=="

3. Response Structure

The response is wrapped in ZCore's ResponseWrapper. The meta block contains the pagination state:

{
  "success": true,
  "message": "Success",
  "data": [
    {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "title": "Complete documentation",
      "is_completed": false
    }
  ],
  "meta": {
    "next_cursor": "eyJ2YWx1ZSI6ICIyMDI2LTA4LTE1VDA4OjAwOjAwIiwgInBrIjogImExYjJjM2Q0Li4uIn0",
    "has_more": true,
    "size": 20
  }
}

Dynamic Primary Key Inspection: As of rc.2, CursorPagination() automatically inspects your model's primary key column (supporting both uuid.UUID and int primary keys). The cursor token encapsulates {"value": ..., "pk": ...} into a URL-safe Base64 token to execute fast indexed lookups (WHERE col < val) without scanning rows via OFFSET.

On this page