Home Python FastAPI Request Body and Pydantic Models

FastAPI Request Body and Pydantic Models

⚡ Quick Answer
In FastAPI, you define a request body by creating a class that inherits from Pydantic's BaseModel. When you use this class as a type hint in your endpoint function, FastAPI automatically: 1. Reads the incoming JSON. 2. Validates it against your schema. 3. Converts types (coercion). 4. Injects a populated Python object into your function. If validation fails, the client receives a 422 error with specific details—no manual if/else validation required.

Defining a Pydantic Model with Type Coercion

A Pydantic model is not just a validator; it's a data transformer. If a client sends a numeric string like "99.99" for a field typed as float, Pydantic will 'coerce' it into a proper Python float automatically.

io/thecodeforge/models/items.py · PYTHON
1234567891011121314151617181920
from fastapi import FastAPI
from pydantic import BaseModel, Field
from typing import Optional

app = FastAPI()

class ForgeItem(BaseModel):
    # Required field: No default provided
    name: str
    # Field with constraints and metadata
    price: float = Field(gt=0, description="Unit price in USD")
    # Optional with default value
    is_active: bool = True
    # Explicitly optional field
    description: Optional[str] = None

@app.post('/forge/items')
async def create_artifact(item: ForgeItem):
    # item is now a fully validated ForgeItem object
    return {"msg": "Success", "data": item.model_dump()}
▶ Output
{"msg": "Success", "data": {"name": "Circuit Board", "price": 45.5}}

Handling Nested Models and Collections

APIs are rarely flat. Pydantic handles recursive validation of nested objects and lists with ease. If even one deeply nested field fails validation, the entire request is rejected, maintaining data integrity.

io/thecodeforge/models/users.py · PYTHON
1234567891011121314151617181920
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()

class Address(BaseModel):
    street: str
    city: str
    zip_code: str

class ForgeUser(BaseModel):
    username: str
    # EmailStr provides built-in regex validation for emails
    email: EmailStr
    address: Address
    tags: list[str] = []

@app.post('/forge/users')
async def register_user(user: ForgeUser):
    return {"status": "registered", "user": user}
▶ Output
Recursive validation ensures 'address' matches the Address schema before execution.

Advanced Logic with Field Validators

Sometimes basic constraints like min_length aren't enough. Use @field_validator for complex cross-field validation or custom business rules that require Python logic.

io/thecodeforge/models/products.py · PYTHON
1234567891011121314151617181920
from fastapi import FastAPI
from pydantic import BaseModel, Field, field_validator

app = FastAPI()

class ForgeProduct(BaseModel):
    sku: str = Field(min_length=8, max_length=12)
    inventory_count: int = Field(ge=0)
    category: str

    @field_validator('sku')
    @classmethod
    def sku_must_be_uppercase(cls, v: str) -> str:
        if not v.isupper():
            raise ValueError('SKU must be all uppercase for tracking purposes')
        return v

@app.post('/forge/inventory')
async def update_stock(product: ForgeProduct):
    return product
▶ Output
422 Unprocessable Entity if SKU is lowercase.

🎯 Key Takeaways

  • Pydantic models act as the 'Data Contract' between your client and your server.
  • Type Coercion: Pydantic intelligently converts compatible data types (e.g., string '1' to int 1) during parsing.
  • Recursive Validation: Nested models allow for complex, structured data validation in a single declaration.
  • Field Metadata: Use Field() to add description, example, and numeric/string constraints for both validation and Swagger UI.
  • Model Dumping: Use .model_dump() to convert a Pydantic object back into a dictionary for database operations.

Interview Questions on This Topic

  • QHow does FastAPI distinguish between a Pydantic model intended for the request body and one intended for query parameters?
  • QExplain the 'Data Coercion' lifecycle: What happens internally when a client sends an ISO-formatted string to a field typed as `datetime.datetime`?
  • QScenario: You have a high-traffic endpoint receiving 10MB JSON payloads. How would you optimize Pydantic's validation performance?
  • QHow would you implement a validator that checks the 'price' field against a 'minimum_discount' field to ensure business logic consistency?
  • QWhat is the difference between Pydantic's `BaseModel` and Python's native `@dataclass`, and why does FastAPI prefer the former?

Frequently Asked Questions

What is the difference between Optional[str] and str with a default of None?

In Pydantic v2, Optional[str] (or str | None) indicates that the value itself can be null (JSON null). However, if you don't provide a default value (e.g., field: Optional[str]), the field remains required in the request body—the client must send the key, even if its value is null. To make the key itself optional, you must provide a default: field: Optional[str] = None.

Can I use Pydantic models for response data as well?

Absolutely. Use the response_model parameter in your decorator: @app.get('/', response_model=UserOut). This is a core TheCodeForge best practice as it automatically filters out sensitive data (like passwords) before the JSON is sent back to the client.

How do I handle partial updates (PATCH) with Pydantic?

To allow partial updates, you can create a model where all fields are optional. When processing the request, use model.model_dump(exclude_unset=True). This ensures you only update the fields the user actually sent, rather than overwriting existing data with default values.

🔥
Naren Founder & Author

Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.

← PreviousFastAPI Path Parameters and Query ParametersNext →FastAPI Response Models and Status Codes
Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged