Request and Response Handling
Explore how FastAPI handles requests and responses, including data validation and serialization.
Content
Response Models
Versions:
Watch & Learn
AI-discovered learning video
Sign in to watch the learning video for this topic.
Response Models in FastAPI — Make your API return the stuff you actually want (and document it too)
Ever shipped an endpoint that returns everything including the kitchen sink and your DB password? Yeah, me neither. (Lies.) Let’s fix that. You already know how to define routes and endpoints from Routing and Endpoints and how to validate incoming data with Pydantic models from Data Validation with Pydantic. Now we’ll take that Pydantic swagger energy and use it to shape outgoing responses so your API is tidy, documented, and not accidentally leaking internals.
Big picture: A response model tells FastAPI what shape the output should have. FastAPI will use that for serializing, validating, and documenting responses. It’s the gatekeeper between your internal data and what your users actually see.
Why response models matter (aside from being neat)
- Security / privacy: Prevent leaking internal fields (passwords, internal IDs, flags).
- Docs: OpenAPI docs show the exact response schema; excellent for frontends and clients.
- Consistency: Clients can rely on a stable output shape.
- Validation: FastAPI validates responses, so you catch accidental weird returns early.
Imagine your endpoint returns a SQLAlchemy object with 37 attributes. With a response model you only expose what matters — like ordering fries and getting only the fries, not the whole grill.
How to use response models (the 30-second version)
- Define a Pydantic model for the output (e.g., UserOut).
- Use the response_model parameter on your path operation decorator or the return type annotation.
Example:
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List
app = FastAPI()
class UserOut(BaseModel):
id: int
username: str
email: str
@app.get('/users', response_model=List[UserOut])
def list_users():
# Could be rows from DB, dicts, Pydantic model instances
db_rows = [
{'id': 1, 'username': 'alice', 'email': 'a@example.com', 'password': 'nope'},
{'id': 2, 'username': 'bob', 'email': 'b@example.com', 'password': 'nope'}
]
return db_rows
FastAPI will: convert each dict/ORM object into UserOut, drop unlisted fields (like password), and produce the OpenAPI schema for GET /users.
A little more nuance (because software is cruel)
Returning ORM objects
If you return SQLAlchemy models or other ORM objects, add Config.orm_mode = True to your Pydantic model so Pydantic can read attributes instead of keys.
class UserOut(BaseModel):
id: int
username: str
class Config:
orm_mode = True
Lists, nested models, optional fields
You can do response_model=List[UserOut], nested models are fine, and optional fields behave as you'd expect.
Response model vs returning a Response
If you return a fastapi.Response, JSONResponse, StreamingResponse, FileResponse, etc., FastAPI will not run the response_model serialization/validation for you. Use response_model when you return Python data structures (dicts, Pydantic models, lists).
Tip: If you need to stream bytes or send file streams, you take control and FastAPI defers to you.
Controlling which fields are exposed
Sometimes you want dynamic control: expose different fields for different users or endpoints.
response_model_includeandresponse_model_excludelet you include/exclude certain fields at runtime.response_model_exclude_none=Trueremoves nulls from responses.response_model_by_alias=Trueuses field aliases defined in Pydantic models.
Example:
@app.get('/users/{user_id}', response_model=UserOut, response_model_exclude_none=True)
def get_user(user_id: int):
user = get_user_from_db(user_id)
return user
You can also pass sets to include/exclude when calling the decorator, or compute them dynamically in the handler.
Performance considerations
- Response validation is useful but not free. If your endpoint returns large datasets and you trust the output (e.g., you already sanitized and serialized manually), you can skip
response_modelto avoid the extra validation step. - Use
ORJSONResponseif you want faster JSON serialization than the built-in one.
Table: quick compare
| Concern | With response_model | Without response_model |
|---|---|---|
| Validation of output | ✅ | ❌ |
| OpenAPI schema generation | ✅ | ❌ |
| Potential CPU overhead | Slightly higher | Lower |
Fancy control: partial responses, aliases, and excluding defaults
response_model_exclude_defaults=Truehides default values if you prefer less noise.- Combine
includeandexcludeto shape fields precisely. response_model_by_alias=Trueis handy when your Pydantic model uses aliases for JSON keys (e.g., camelCase for frontend).
Example mixing options:
@app.get('/me', response_model=UserOut, response_model_exclude_none=True, response_model_by_alias=True)
def read_me():
return current_user # Pydantic or ORM object
Practical patterns and gotchas
- If you return Pydantic models directly, FastAPI is happy. If you return ORM objects, use orm_mode.
- Returning binary streams or custom Response objects bypasses response_model — intentional.
- For large lists, consider pagination and keep response_model to page items, not the entire list if you're doing streaming.
- If you need different shapes for the same endpoint depending on context, consider separate endpoints or dynamic include/exclude to keep OpenAPI sane.
Expert take: Response models are less about stopping bugs and more about making your contract with clients explicit. Contracts reduce surprise.
Example with APIRouter (building on Routing and Endpoints)
from fastapi import APIRouter
router = APIRouter(prefix='/users')
@router.get('/', response_model=List[UserOut])
def list_users():
return db_list_users()
@router.get('/{id}', response_model=UserOut)
def get_user(id: int):
return db_get_user(id)
Plug the router into your app like you already learned, and the docs will show those responses automatically.
Closing — TL;DR (and a rallying cry)
- Use response models to control what your API returns, improve docs, and catch output errors early.
- Set
orm_mode=Truefor ORM objects, useexclude_noneorinclude/excludefor dynamic shapes, and know that returning a Response bypasses response_model.
Key takeaways:
- Response model = output contract. Keep it strict and intentional.
- Documentation and client coordination become effortless when you use models consistently.
- Balance validation cost vs safety: don’t validate giant blobs if unnecessary.
Go forth and shape your API outputs like the responsible dev you are. Your clients will thank you. Your future self will thank you. Your logs will be less cursed.
Comments (0)
Please sign in to leave a comment.
No comments yet. Be the first to comment!