Reusing Pagination with FastAPI’s Depends: A Practical, Reusable Pattern
Centralize FastAPI pagination logic with a single Depends‑based dependency. Validate page/size, expose OpenAPI docs, and keep list handlers lean. Includes a concrete example, validation checks, and trade‑offs like offset vs cursor pagination.
17 Jan 2026, 05:25 UTC

Concrete Problem: Repeating Pagination Logic in Every Endpoint
When building a REST API, most “list” endpoints need to accept page and size query parameters, validate them, and compute an offset. In a small project you might copy‑paste the same code into every handler, but in real‑world codebases that leads to duplication, inconsistent defaults, and hard‑to‑maintain validation rules.
FastAPI’s dependency injection system (the Depends helper) lets you centralize that logic. The result is a single, tested, documented component that all list routes can reuse.
Thesis: A Shared Pagination Dependency Keeps Validation, Defaults, and OpenAPI in One Place
By defining a dependency that returns a tiny, typed object (e.g. a dataclass), you:
- encapsulate validation rules in one place,
- expose the same OpenAPI schema to every consumer,
- keep handler functions short and focused on business logic,
- allow composition with other dependencies (auth, DB sessions, etc.).
Section 1 – Building the Pagination Dependency
Below is a minimal, fully typed example for FastAPI 0.110+ with Pydantic v2. The dependency uses fastapi.Query for automatic validation and OpenAPI generation.
from fastapi import Depends, Query
from dataclasses import dataclass
@dataclass
class Pagination:
page: int
size: int
# Dependency function
async def pagination(
page: int = Query(1, ge=1, description="Page number, starting at 1"),
size: int = Query(
20,
ge=1,
le=100,
description="Number of items per page (max 100)"
)
) -> Pagination:
return Pagination(page=page, size=size)
Key points:
Queryarguments automatically validate and produce a 422 error if the client suppliespage=0orsize=200.- The default values (1 and 20) appear in the generated Swagger UI.
- Because the function is async, it plays nicely with async DB sessions.
Section 2 – Using the Dependency in a List Endpoint
Now declare the dependency in your route. Notice the handler receives a single Pagination object, not separate query parameters.
from fastapi import FastAPI, Depends
from sqlalchemy.ext.asyncio import AsyncSession
app = FastAPI()
# Dummy DB session dependency – replace with your real implementation
async def get_db() -> AsyncSession:
...
@app.get("/items", response_model=list[ItemOut])
async def list_items(
pagination: Pagination = Depends(pagination),
db: AsyncSession = Depends(get_db)
):
offset = (pagination.page - 1) * pagination.size
result = await db.execute(
select(Item).offset(offset).limit(pagination.size)
)
items = result.scalars().all()
return items
What happens under the hood?
- FastAPI first resolves
paginationby calling thepaginationdependency. - It validates the query string against the
Queryconstraints. - If validation passes, the
Paginationobject is passed tolist_items. - Any other dependencies (e.g.,
db) are resolved afterwards.
Section 3 – Observing Validation and OpenAPI Integration
To verify the dependency works as intended, run the app and navigate to /docs. You should see:
- Two query parameters,
pageandsize, with defaults 1 and 20. - Minimum and maximum constraints (ge=1, le=100) shown in the UI.
- A 422 response schema automatically generated for invalid input.
Testing the validation is simple:
curl -i http://localhost:8000/items?page=0
Expected output: HTTP 422 with a JSON body describing the validation errors. No handler logic runs because validation fails early.
Section 4 – Trade‑Offs and Limitations
- Offset Pagination: The pattern shown uses offset-based pagination. On very large tables, high page numbers cause the database to scan many rows, leading to performance degradation. In those scenarios, consider a cursor or keyset strategy.
- Version Compatibility: Validation syntax (e.g.,
Query(..., ge=1)) differs between Pydantic v1 and v2. Verify your FastAPI and Pydantic versions before adopting the code verbatim. - Dependency Side‑Effects: Keep the pagination dependency lightweight. It should only parse and validate; heavy logic (e.g., database queries) belongs in the handler or a separate service layer.
Actionable Takeaway
Adopt a shared pagination dependency in any FastAPI project that exposes list endpoints. It:
- Reduces code duplication.
- Ensures consistent validation and defaults.
- Keeps your OpenAPI docs clean and accurate.
Next steps: replace offset logic with cursor pagination if you anticipate deep paging, and consider adding a sort_by dependency for consistent ordering across endpoints.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.