FastAPI & Pydantic Models
FastAPI is built on Pydantic: you describe request bodies, responses, and configuration as Python classes with type hints, and Pydantic parses and validates incoming data, serializes outgoing data, and generates the JSON Schema that becomes your OpenAPI documentation. One set of models gives you runtime validation, editor autocompletion, and API docs, the Python equivalent of what Zod does for TypeScript.
Pydantic v2, rewritten with a Rust core, is much faster than v1 and has a cleaner API (model_validate, field_validator, model_config). Designing models well (separate create, update, and read schemas, precise constraints, and explicit response models) is key to APIs that are safe, well documented, and easy to evolve.
TL;DR
- Request bodies are
BaseModelsubclasses; FastAPI validates JSON and returns 422 with details on failure. - Constrain fields with
Field(...)(lengths, ranges, patterns) and specialized types (EmailStr,HttpUrl,UUID,datetime). - Custom rules go in
@field_validatorand@model_validator. response_model(or a return type annotation) validates and filters output, so internal fields never leak.- Use separate schemas for create, update (all optional), and read; share fields through base classes.
from_attributes=Truereads ORM objects; pydantic-settings loads typed configuration from the environment.
Quick Example
Invalid input produces a structured 422 response pointing to the exact field (body.items.0.quantity), with no hand-written validation code.
Core Concepts
Request Models
Declaring a parameter typed as a BaseModel tells FastAPI to read it from the JSON body. Nested models, lists, dicts, unions, enums, and optional fields all work. Pydantic coerces compatible input by default (a string "42" to an int in lax mode). Use strict mode or strict types (StrictInt) where coercion is undesirable. Query and path parameters use the same type system via Query() and Path(), and FastAPI also supports query parameter models.
Field Constraints and Types
Constraints appear in the OpenAPI schema, so clients and generated SDKs see them too.
Validators
@field_validator("name"): validate or transform a single field (modebeforeruns on raw input,afteron parsed values).@model_validator(mode="after"): cross-field rules ("end date after start date", "unique SKUs").- Raise
ValueErrorwith a clear message, and FastAPI returns it in the 422 response. @computed_fieldadds derived values to serialized output.
Response Models
Setting response_model=OrderRead, or annotating the return type, makes FastAPI:
- Validate the returned data against the model (catching bugs where you return the wrong shape).
- Filter output to the model's fields, so extra attributes, including sensitive ones, are dropped.
- Document the response schema in OpenAPI.
Options like response_model_exclude_none=True control serialization. Returning ORM objects works when the model has from_attributes=True.
Separate Schemas per Use Case
A common pattern:
For PATCH, use payload.model_dump(exclude_unset=True) to apply only the fields the client actually sent.
Discriminated Unions
For polymorphic payloads, use a Literal tag field and Field(discriminator="type"): Pydantic picks the right model directly, validation errors are clearer, and OpenAPI documents it with oneOf plus a discriminator.
Settings Management
pydantic-settings loads configuration from environment variables and .env files into typed, validated objects:
Best Practices
Never Return Raw ORM Objects Without a Response Model
Always declare a response model or return type, so FastAPI filters output. It prevents accidental exposure of password hashes, internal flags, or new columns added later.
Validate at the Boundary, Trust Internally
Put input constraints in request models so handlers receive clean data. Keep business rules that need database access (uniqueness, stock availability) in service code, returning clear errors.
Keep Models Separate From ORM Entities
API schemas and database models change for different reasons. Separate classes (or SQLModel used carefully) let you evolve the database without breaking the API contract. See API design.
Use Precise Types for Money and Time
Use Decimal for currency (never float), timezone-aware datetime, and UUID types. Document formats in field descriptions and examples.
Common Mistakes
One Model for Create, Update, and Read
Reusing a single model means clients can set server-managed fields (id, status, created_at), or every field is optional everywhere. Define separate schemas.
Mutable Defaults Without Field
In Pydantic, tags: list[str] = [] is safe, since defaults are copied, but be explicit with Field(default_factory=list) for clarity, and remember that the plain-Python mutable default pitfall still applies to dataclasses and regular classes.
Using Pydantic v1 Patterns on v2
orm_mode, .dict(), .parse_obj(), and @validator are v1 APIs. Their v2 equivalents are from_attributes, .model_dump(), .model_validate(), and @field_validator. Mixing them causes deprecation warnings or errors after upgrades.
FAQ
How does FastAPI validate request bodies?
It inspects endpoint parameters. A parameter typed as a Pydantic model is read from the JSON body and validated with model_validate. If validation fails, FastAPI returns a 422 response listing each error's location, message, and type, without calling your handler.
What does response_model do?
It validates your handler's return value against a schema, filters out fields not in that schema, serializes the result, and documents the response in OpenAPI. Annotating the return type (-> OrderRead) achieves the same thing in modern FastAPI.
How do I read Pydantic models from SQLAlchemy objects?
Set model_config = ConfigDict(from_attributes=True) on the response model. FastAPI (or OrderRead.model_validate(orm_obj)) then reads attributes from the ORM object. Load needed relationships eagerly to avoid lazy-loading issues in async code.
Should I use SQLModel?
SQLModel combines SQLAlchemy models and Pydantic schemas in one class, which reduces duplication for simple CRUD apps. For larger APIs, separate SQLAlchemy models and Pydantic schemas give more flexibility to evolve the database and the API independently.
Related Topics
- FastAPI — The framework overview
- Python Type Hints — The typing foundation of Pydantic
- FastAPI Dependencies — Parameter models in dependencies
- API Design — Designing request and response contracts
- Zod — The TypeScript counterpart for schema validation
- SQLAlchemy — ORM models behind your schemas