Chapter 6 · Response Models and Status Codes
Chapter goals: Master the output filtering capability of response_model and its security significance, learn input/output model separation, and correctly set status codes and document metadata.
6.1 Why Response Models Exist
A path function's return value is serialized as-is by default. If your database object carries password_hash, internal_notes, or other sensitive fields, returning it裸 is a security incident. response_model lets you declare what the client can see:
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserIn(BaseModel):
# Input model: includes plaintext password (only for registration)
username: str
email: EmailStr
password: str
class UserOut(BaseModel):
# Output model: never includes password
username: str
email: EmailStr
@app.post("/user/", response_model=UserOut)
def create_user(user: UserIn):
# Assume saved to DB. Returning the full user object here,
# but response_model=UserOut filters out the password field
return userEven if the function returns the full object, the client only receives username and email. Filtering is enforced by the framework, not developer discipline — that is the security value of response models.
Note: response_model is a decorator parameter, not a function parameter — a common beginner mistake.
6.2 Return Type Annotation vs response_model
Modern FastAPI supports two declaration styles:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
# Method 1: Return type annotation (recommended; editors/static checkers understand it)
@app.get("/items/a", response_model=Item)
def get_a() -> Item:
return Item(name="pen", price=3.0)
# Method 2: Return type annotation alone — FastAPI also generates docs and filters
@app.get("/items/b")
def get_b() -> Item:
return Item(name="book", price=42.0)When both are present, response_model takes priority. This design serves a specific scenario: the function actually returns a database ORM object or dict that does not match the declared model type — the return type annotation is for mypy, response_model is for FastAPI's "docs + validation + filtering":
from typing import Any
@app.get("/db-items/{item_id}", response_model=Item)
def get_db_item(item_id: int) -> Any:
# Returns a SQLAlchemy ORM object, type-incompatible with Item,
# use Any to satisfy the static checker; FastAPI still serializes via Item
return fake_db.get(item_id)6.3 The Standard Input/Output Model Separation Pattern
The complete three-model pattern for registration (official tutorial style):
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserIn(BaseModel):
username: str
email: EmailStr
password: str
class UserOut(BaseModel):
username: str
email: EmailStr
class UserCreateResponse(BaseModel):
message: str
user: UserOut
@app.post("/user/", response_model=UserCreateResponse)
def create_user(user: UserIn):
# Hash password, save to DB, return without password
return UserCreateResponse(
message="User created",
user=UserOut(username=user.username, email=user.email),
)Three models serve three purposes: UserIn for input validation, UserOut for output filtering, UserCreateResponse for the API's response shape.
6.4 Status Codes and Response Metadata
Set the HTTP status code explicitly:
from fastapi import FastAPI, status
app = FastAPI()
@app.post("/items/", status_code=status.HTTP_201_CREATED)
def create_item(item: dict):
return {"created": True}
@app.get("/items/{item_id}", status_code=status.HTTP_404_NOT_FOUND)
def read_item(item_id: int):
return {"error": "not found"} # Note: this route would never return 404
# because the path param validation happens before the function runsCommon status codes:
| Code | Meaning | Typical Use |
|---|---|---|
| 200 | OK | Successful GET/PUT/PATCH |
| 201 | Created | Successful POST (resource created) |
| 204 | No Content | Successful DELETE (no body to return) |
| 400 | Bad Request | Client error (bad params) |
| 404 | Not Found | Resource not found |
| 422 | Unprocessable Entity | Validation error |
| 4xx/5xx | Client/Server error | Custom exceptions |
6.5 Chapter Summary
response_modelfilters output to only the fields the client should see — a security boundary;- Return type annotation is for static checkers;
response_modelis for FastAPI's serialization; - Three-model pattern:
In(input) →Out(output) →Response(wrapper); status_code=on the decorator sets the HTTP status; 201 for creation, 204 for deletion.
🧪 随堂测验
点击你认为正确的选项。答错时会展示正确答案与原因解析。
1. What is the primary security purpose of response_model?
2. When both return type annotation and response_model are present, which takes priority?
3. What HTTP status code is conventionally returned for a successful POST that creates a resource?
4. What is the three-model pattern used for?
🛠️ Hands-On Exercises
- Define
UserIn(with password) andUserOut(without password); create a POST/registerendpoint withresponse_model=UserOut; verify the password never appears in the response. - Set
status_code=status.HTTP_201_CREATEDon a POST route; test with curl and confirm the status code. - Implement the three-model pattern for a
ProductAPI:ProductIn(with secret_field),ProductOut(without secret_field),ProductResponse(wrapper with message).
Proceed to Chapter 7: Forms, File Upload, and Download.