python 46 lines · 8 steps

Reusable query params with Pydantic in FastAPI

Model list-endpoint query parameters once as a validated Pydantic class, then share it across multiple routes.

Explained by highlit
1from datetime import date
2from enum import Enum
3from typing import Annotated
4 
5from fastapi import APIRouter, Depends, Query
6from pydantic import BaseModel, Field, model_validator
7 
8router = APIRouter()
9 
10 
11class SortOrder(str, Enum):
12 asc = "asc"
13 desc = "desc"
14 
15 
16class ListingParams(BaseModel):
17 q: str | None = Field(default=None, max_length=120)
18 page: int = Field(default=1, ge=1)
19 per_page: int = Field(default=25, ge=1, le=100)
20 sort_by: str = Field(default="created_at", pattern=r"^[a-z_]+$")
21 order: SortOrder = SortOrder.desc
22 created_after: date | None = None
23 created_before: date | None = None
24 
25 @property
26 def offset(self) -> int:
27 return (self.page - 1) * self.per_page
28 
29 @model_validator(mode="after")
30 def check_date_window(self) -> "ListingParams":
31 if self.created_after and self.created_before and self.created_after > self.created_before:
32 raise ValueError("created_after must be on or before created_before")
33 return self
34 
35 
36ListingQuery = Annotated[ListingParams, Query()]
37 
38 
39@router.get("/orders")
40async def list_orders(params: ListingQuery, service: OrderService = Depends()):
41 return await service.paginate(params)
42 
43 
44@router.get("/invoices")
45async def list_invoices(params: ListingQuery, service: InvoiceService = Depends()):
46 return await service.paginate(params)
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Grouping related query parameters into a single Pydantic model keeps endpoint signatures clean and validation centralized.
  2. 2A model validator lets you enforce cross-field rules that no single field constraint can express.
  3. 3Aliasing an Annotated type turns a validated parameter set into a reusable building block for many routes.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Reusable query params with Pydantic in FastAPI — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code