Output Models and Response Shaping

Output Models and Response Shaping

Control what you show the world. Learn how to use response_model to filter sensitive data, version responses, and improve API performance.

Output Models and Response Shaping

Security in an API isn't just about who can get in—it's about what you let out. If you query your database for a User, the resulting object likely contains a hashed_password or an internal_admin_note. You must ensure these never reach the client.

In FastAPI, we use Response Models to "shape" the data before it leaves the server.


1. The response_model Parameter

You define your output schema in the path operation decorator. FastAPI then uses this model to filter the data you return.

from pydantic import BaseModel, EmailStr

class UserOut(BaseModel):
    username: str
    email: EmailStr  # Pydantic validates this as a real email format!

@app.get("/users/{user_id}", response_model=UserOut)
async def get_user(user_id: int):
    # This might have 'password', 'secret_key', etc.
    user_data = db.fetch_user(user_id) 
    return user_data

What happens here?

Even if user_data contains 50 fields, FastAPI will only include the fields defined in UserOut in the final JSON response. This is a massive security win.


2. Default Values and exclude_unset

Sometimes your model has fields that are optional. If you don't want to send null values back to the client, you can use response_model_exclude_unset.

@app.get(
    "/items/{id}", 
    response_model=Item, 
    response_model_exclude_unset=True
)
def read_item(id: int):
    return {"id": id, "name": "Tool"} # 'description' is excluded because it wasn't set.

3. Creating Multiple Versions of a Resource

A common pattern in production APIs is to have three versions of the same model:

  1. Base: Common fields like name and email.
  2. Create: Fields needed for signup (includes password).
  3. Out: Fields safe for the public (includes id and created_at).
class UserBase(BaseModel):
    email: EmailStr

class UserCreate(UserBase):
    password: str

class UserOut(UserBase):
    id: int
    is_active: bool

4. Performance: Response Models vs. Dicts

FastAPI performs serialization automatically when you return a Pydantic model.

  • In Pydantic v2: This is extremely fast (Rust-based).
  • Pro Tip: If you are returning a massive list of 10,000 items, returning a raw dictionary can be faster, but you lose the security of the response model filter. We usually recommend sticking with the response model for 99% of use cases.

Visualizing the Filtering Process

graph TD
    A["Database Object"] --> B["FastAPI Response Handler"]
    B --> C["Check response_model definition"]
    C --> D["Filter out extra fields"]
    D --> E["Validate remaining fields"]
    E --> F["JSON Response (Client)"]

Summary

  • response_model: The decorator parameter that controls output.
  • Data Filtering: Never return database objects directly; always filter through a schema.
  • Security: This is your last line of defense against data leaks.
  • Clarity: Response models automatically document the "Success" response in Swagger.

In the next lesson, we wrap up Module 5 with Exercises to test your Pydantic mastery.


Exercise: The Sensitive Filter

You have a Product model. It has a name, price, and wholesale_cost. Design a ProductPublic model that hides the wholesale_cost from regular users. How would you apply it to a GET /products endpoint?

Subscribe to our newsletter

Get the latest posts delivered right to your inbox.

Subscribe on LinkedIn