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 = None

The 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 error

Pydantic 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=0 means greater than zero.
  • ge=0 means greater than or equal to zero.
  • min_length and max_length limit text size.
  • description appears 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) ──▶ Client

Pair 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.
  • Field adds 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.