FastAPI Pydantic Models
A Pydantic model describes the shape of your data. Think of a paper form with labelled boxes: name, email, age. Each box accepts only one kind of answer. Pydantic checks every box, converts values to the right type, and rejects forms that break the rules. FastAPI relies on Pydantic for request bodies, responses, and documentation.
Creating a Model
A model is a class that inherits from BaseModel. Each attribute becomes a field with a type hint.
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str
email: str
age: int | None = NoneThe fields id, name, and email are required. The field age is optional because it carries a default value.
How a Model Checks Data
Incoming JSON Pydantic model Result
───────────── ────────────── ──────
{"id": "5", "name": "Asha", ──▶ id: int "5" becomes 5 ──▶ User object
"email": "a@x.com"} name: str accepted
email: str accepted
{"id": "abc", "name": "Asha"} ──▶ id: int "abc" fails ──▶ Validation error
email missing ──▶ Validation errorPydantic converts safe values, such as the text "5" into the number 5. It refuses values that cannot convert and lists every problem in one error report.
Using a Model in a Route
from fastapi import FastAPI
app = FastAPI()
@app.post("/users")
def create_user(user: User):
return {"message": f"Welcome {user.name}", "age": user.age}FastAPI reads the JSON body, builds a User object, and passes it to your function. Access fields with dot notation, such as user.name.
Default Values and Field Rules
The Field helper adds rules and descriptions to a single field.
from pydantic import BaseModel, Field
class Product(BaseModel):
name: str = Field(min_length=2, max_length=50)
price: float = Field(gt=0, description="Price in dollars")
stock: int = Field(default=0, ge=0)gt=0means greater than zero.ge=0means greater than or equal to zero.min_lengthandmax_lengthlimit text size.descriptionappears in the interactive docs.
Nested Models
A model can contain other models. Real-world data often nests this way.
class Address(BaseModel):
city: str
country: str
class Customer(BaseModel):
name: str
address: Address
orders: list[int] = []
Customer
├── name: "Ravi"
├── address
│ ├── city: "Pune"
│ └── country: "India"
└── orders: [101, 102, 103]The matching JSON body places the address inside its own curly braces. Validation runs on every level of the tree.
Useful Model Methods
user = User(id=1, name="Asha", email="asha@example.com")
user.model_dump() # converts the model to a dictionary
user.model_dump_json() # converts the model to a JSON string
User.model_validate({"id": 2, "name": "Ravi", "email": "r@x.com"})The method model_dump proves handy when you save data to a database or send it to another service. The method model_validate builds a model from an existing dictionary.
Separate Models for Input and Output
A signup form needs a password. A response must never return it. Two models solve this cleanly.
class UserCreate(BaseModel):
name: str
email: str
password: str
class UserPublic(BaseModel):
id: int
name: str
email: str
Client ──▶ UserCreate (has password) ──▶ your code ──▶ UserPublic (no password) ──▶ ClientPair UserPublic with response_model on the route and FastAPI strips every extra field automatically.
Common Mistakes
Using a Mutable Default Carelessly
Pydantic copies list defaults for each new object, so orders: list[int] = [] is safe inside a model. Plain functions behave differently, so avoid the habit outside models.
Mixing Up Required and Optional
A field with no default is required, even when its type includes None. Add = None to make it optional.
Models Across the Whole App
Pydantic models appear in many places across a FastAPI project. Request bodies use them to validate incoming JSON. Response models use them to shape outgoing data. Settings classes use them to read configuration. Database layers use them to turn rows into clean objects.
A single idea ties these uses together: describe the data once, and let the library enforce the description everywhere. Teams that follow this habit write less checking code by hand, and the interactive docs always show up-to-date examples because they read the same models.
Start with small models and grow them. Add a field when a feature needs it, add a rule when bad data appears, and split a model when two routes need different views of the same record.
Key Points
- A Pydantic model is a class with typed fields that inherits from
BaseModel. - Models validate data and convert values to the declared types.
Fieldadds limits and descriptions to individual fields.- Models nest inside other models for complex data.
- Separate input and output models protect private fields such as passwords.
