agentbench / data /tech_docs /fastapi_response_model.md
Nomearod's picture
feat: Day 4 — corpus, ingest script, first 10 golden questions
a152b95

Response Model in FastAPI

The response_model parameter on route decorators lets you declare the shape of the data your endpoint returns. FastAPI uses it to validate, serialize, and document the response -- filtering out any fields not defined in the model and generating accurate OpenAPI schemas.

Basic Response Model

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class UserIn(BaseModel):
    username: str
    email: str
    password: str

class UserOut(BaseModel):
    username: str
    email: str

@app.post("/users/", response_model=UserOut, status_code=201)
async def create_user(user: UserIn):
    # In a real app, hash the password and save to DB
    return user  # password is automatically filtered out

Even though the handler returns the full UserIn object (which includes password), the response_model=UserOut declaration ensures that only username and email appear in the response. This is a critical security pattern -- it prevents accidental leakage of sensitive fields like passwords, tokens, or internal IDs.

Status Codes

FastAPI provides the status_code parameter to set the HTTP response status code. Common codes include:

Code Constant Usage
200 status.HTTP_200_OK Successful GET
201 status.HTTP_201_CREATED Successful creation
204 status.HTTP_204_NO_CONTENT Successful deletion
400 status.HTTP_400_BAD_REQUEST Client error
404 status.HTTP_404_NOT_FOUND Resource not found
422 status.HTTP_422_UNPROCESSABLE_ENTITY Validation error
from fastapi import status

@app.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_item(item_id: int):
    # delete logic
    return None

The default status_code for all route decorators is 200.

Filtering Fields with response_model_include and response_model_exclude

You can dynamically control which fields appear in the response without creating a separate model:

class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float = 0.0
    internal_code: str = "N/A"

@app.get(
    "/items/{item_id}",
    response_model=Item,
    response_model_exclude={"internal_code"},
)
async def read_item(item_id: int):
    return {
        "name": "Widget",
        "description": "A useful widget",
        "price": 35.99,
        "tax": 3.60,
        "internal_code": "WDG-001",
    }

The response_model_exclude parameter accepts a set of field names to strip from the output. Similarly, response_model_include accepts a set of field names to keep -- all others are excluded. If both are provided, response_model_include is applied first, then response_model_exclude removes fields from that subset.

Excluding Unset and Default Values

Two additional parameters control whether default or unset values appear in the response:

@app.get(
    "/items/{item_id}",
    response_model=Item,
    response_model_exclude_unset=True,
)
async def read_item(item_id: int):
    return Item(name="Widget", price=35.99)
    # Response: {"name": "Widget", "price": 35.99}
    # Fields with defaults (description, tax) are omitted
  • response_model_exclude_unset=True -- omits fields the user did not explicitly set (default: False)
  • response_model_exclude_defaults=True -- omits fields whose value matches the default (default: False)
  • response_model_exclude_none=True -- omits fields with None values (default: False)

Multiple Response Models

Use Union types or the responses parameter to document endpoints that may return different shapes:

from typing import Union

class ItemPublic(BaseModel):
    name: str
    price: float

class ItemAdmin(BaseModel):
    name: str
    price: float
    internal_code: str
    profit_margin: float

@app.get("/items/{item_id}", response_model=Union[ItemAdmin, ItemPublic])
async def read_item(item_id: int, is_admin: bool = False):
    item_data = get_item(item_id)
    if is_admin:
        return ItemAdmin(**item_data)
    return ItemPublic(**item_data)

When using Union, Pydantic validates the response against each model in order and uses the first match. Place the more specific model first (the one with more fields) to avoid premature matching.