Pagination patterns in FastAPI

Difficulty: Intermediate

Question

What are the common pagination patterns in FastAPI and how do you implement them?

Answer

1. Offset-based pagination - Uses `skip` (offset) and `limit` query parameters. Simple but slow for large datasets (DB scans rows to skip).

2. Cursor-based pagination - Uses a pointer (cursor) to the last item. Much more efficient for large datasets because the DB can seek directly. Common in APIs with real-time data (timelines, feeds).

3. Page-number pagination - Uses `page` and `page_size` parameters. User-friendly but internally converts to offset.

Best practice: Encapsulate pagination logic in a reusable dependency so all list endpoints share the same interface. Return metadata (`total`, `page`, `has_next`) alongside the data.

Code examples

Reusable pagination dependency

from fastapi import FastAPI, Depends, Query
from pydantic import BaseModel
from typing import Annotated, Generic, TypeVar, List

app = FastAPI()
T = TypeVar("T")

class PaginationParams:
    def __init__(
        self,
        page: Annotated[int, Query(ge=1)] = 1,
        size: Annotated[int, Query(ge=1, le=100)] = 20,
    ):
        self.page = page
        self.size = size
        self.offset = (page - 1) * size

class PaginatedResponse(BaseModel, Generic[T]):
    items: List[T]
    total: int
    page: int
    size: int
    pages: int

@app.get("/users")
def list_users(pagination: PaginationParams = Depends()):
    total = db.query(User).count()
    users = db.query(User).offset(pagination.offset).limit(pagination.size).all()
    return PaginatedResponse(
        items=users,
        total=total,
        page=pagination.page,
        size=pagination.size,
        pages=(total + pagination.size - 1) // pagination.size,
    )

Key points

Concepts covered

pagination, offset, cursor, dependency, reusable