FastAPI Data Validation
Pydantic is the library that powers all data checking in FastAPI. It reads the type hints you write in Python and enforces them on every piece of incoming data. Bad data never reaches your business logic — Pydantic stops it at the door.
Think of Pydantic as a Customs Inspector
Incoming Data (from client)
│
▼
[ Pydantic Inspector ]
✓ Is every required field present?
✓ Is each value the right type?
✓ Does each value pass field rules?
│
Pass → clean Python object → your function
Fail → 422 error with exact problem description
A Basic Pydantic Model
from pydantic import BaseModel
class Order(BaseModel):
item_name: str
quantity: int
price_per_unit: float
gift_wrap: bool = False
Each line declares a field. The type after the colon is enforced. The = False makes gift_wrap optional with a default value.
Field Validators — Adding Rules Beyond Type
Import Field from Pydantic to add rules like minimum value, maximum length, or a regex pattern:
from pydantic import BaseModel, Field
class Product(BaseModel):
name: str = Field(min_length=2, max_length=100)
price: float = Field(gt=0, description="Price must be positive")
stock: int = Field(ge=0, le=10000)
Field constraint shortcuts: gt = greater than ge = greater than or equal to lt = less than le = less than or equal to min_length / max_length → for strings pattern → regex match
Email and URL Validation
Pydantic includes special types for common formats. Install the email validator first:
pip install "pydantic[email]"
from pydantic import BaseModel, EmailStr, HttpUrl
class ContactForm(BaseModel):
email: EmailStr
website: HttpUrl
Valid: {"email": "user@example.com", "website": "https://mysite.com"}
Invalid: {"email": "not-an-email", "website": "not-a-url"}
→ 422 error on both fields
Nested Models
Pydantic models can contain other Pydantic models. This mirrors real-world structures like an order that contains an address:
class Address(BaseModel):
street: str
city: str
pincode: str
class Order(BaseModel):
item: str
quantity: int
shipping_address: Address ← nested model
Valid body:
{
"item": "Laptop",
"quantity": 1,
"shipping_address": {
"street": "12 MG Road",
"city": "Bengaluru",
"pincode": "560001"
}
}
Pydantic validates both the outer model and every nested model.
Lists Inside Models
from typing import List
class Cart(BaseModel):
user_id: int
items: List[str]
coupon_codes: List[str] = []
{
"user_id": 7,
"items": ["Shoes", "Belt", "Watch"],
"coupon_codes": ["SAVE10"]
}
Custom Validators with @validator
For rules that go beyond field constraints, write a custom validator method:
from pydantic import BaseModel, validator
class Registration(BaseModel):
username: str
password: str
confirm_password: str
@validator("confirm_password")
def passwords_must_match(cls, v, values):
if "password" in values and v != values["password"]:
raise ValueError("Passwords do not match")
return v
Bad input: password = "abc123" confirm_password = "xyz999" → 422: Passwords do not match Good input: password = "abc123" confirm_password = "abc123" → Passes validation
Viewing Validation Errors
When Pydantic rejects data, FastAPI returns a 422 response with a structured list of problems:
{
"detail": [
{
"loc": ["body", "price"],
"msg": "ensure this value is greater than 0",
"type": "value_error.number.not_gt"
},
{
"loc": ["body", "name"],
"msg": "ensure this value has at least 2 characters",
"type": "value_error.any_str.min_length"
}
]
}
Each error shows exactly which field failed and why. Clients can display these messages to end users without any extra work on your part.
Key Points
- Pydantic enforces types and rules on every field of incoming data.
- Use
Field()to add constraints likegt,min_length, andpattern. - Use
EmailStrandHttpUrlfor format validation on common types. - Nested models validate inner objects automatically.
- Custom
@validatormethods handle business rules that simple type hints cannot express.
