Skip to content

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:

python
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 user

Even 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:

python
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":

python
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):

python
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:

python
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 runs

Common status codes:

CodeMeaningTypical Use
200OKSuccessful GET/PUT/PATCH
201CreatedSuccessful POST (resource created)
204No ContentSuccessful DELETE (no body to return)
400Bad RequestClient error (bad params)
404Not FoundResource not found
422Unprocessable EntityValidation error
4xx/5xxClient/Server errorCustom exceptions

6.5 Chapter Summary ​

  • response_model filters output to only the fields the client should see — a security boundary;
  • Return type annotation is for static checkers; response_model is 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 ​

  1. Define UserIn (with password) and UserOut (without password); create a POST /register endpoint with response_model=UserOut; verify the password never appears in the response.
  2. Set status_code=status.HTTP_201_CREATED on a POST route; test with curl and confirm the status code.
  3. Implement the three-model pattern for a Product API: ProductIn (with secret_field), ProductOut (without secret_field), ProductResponse (wrapper with message).

Proceed to Chapter 7: Forms, File Upload, and Download.