在 FastAPI 中编写接口时,响应模型(Response Model)状态码(Status Code) 是控制接口返回内容的两个核心机制。合理使用它们可以让 API 返回的数据既安全又规范。

一、response_model 基本用法

response_model 是整个 FastAPI 响应体系的核心参数。它告诉 FastAPI 使用指定的 Pydantic 模型来对返回数据进行序列化、校验和过滤。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()

class UserCreate(BaseModel):
username: str
password: str
email: EmailStr

class UserResponse(BaseModel):
id: int
username: str
email: EmailStr

# 创建时接收 password,但返回时通过 response_model 剔除 password
@app.post("/users", response_model=UserResponse)
async def create_user(user: UserCreate):
# 模拟创建用户,返回包含 password 的数据
saved_user = {
"id": 1,
"username": user.username,
"password": user.password, # 有值但不会被返回
"email": user.email,
}
return saved_user

关键点:即使函数内部返回了 password 字段,response_model=UserResponse 也会将其过滤掉,保证敏感数据不泄露。

二、字段筛选:include 与 exclude

除了定义专门的响应模型,FastAPI 还支持动态控制返回字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class UserOut(BaseModel):
id: int
username: str
email: str
phone: str
is_active: bool
created_at: str

# 只包含指定字段
@app.get("/users/public/{user_id}", response_model=UserOut,
response_model_include={"id", "username"})
async def get_public_user(user_id: int):
return {
"id": user_id, "username": "zhangsan",
"email": "zhangsan@example.com",
"phone": "13800000000",
"is_active": True, "created_at": "2026-07-01"
}
# 返回: {"id": 1, "username": "zhangsan"}

# 排除敏感字段
@app.get("/users/internal/{user_id}", response_model=UserOut,
response_model_exclude={"phone"})
async def get_internal_user(user_id: int):
return {
"id": user_id, "username": "zhangsan",
"email": "zhangsan@example.com",
"phone": "13800000000",
"is_active": True, "created_at": "2026-07-01"
}
# 返回: 除 phone 外的所有字段

还有一个实用参数 response_model_exclude_unset=True,它只返回实际被赋值的字段,未被显式赋值的字段会被忽略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ItemUpdate(BaseModel):
name: str | None = None
price: float | None = None
desc: str | None = None

@app.patch("/items/{item_id}", response_model=ItemUpdate,
response_model_exclude_unset=True)
async def update_item(item_id: int, item: ItemUpdate):
# 假设只传了 price,返回中也只会包含 price
update_data = item.model_dump(exclude_unset=True)
# 模拟更新后返回
return update_data
# 请求 body: {"price": 19.9}
# 响应: {"price": 19.9}

三、状态码控制

FastAPI 通过 status_code 参数控制响应状态码,推荐使用 starlette.status 提供的内置枚举,代码更可读。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from fastapi import FastAPI
from starlette import status

app = FastAPI()

@app.post("/items", status_code=status.HTTP_201_CREATED)
async def create_item(name: str):
return {"id": 1, "name": name}
# 返回 201 状态码

@app.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_item(item_id: int):
# 204 通常不返回 body
return None

# 常用状态码速查
# HTTP_200_OK = 200 # GET、PUT 成功
# HTTP_201_CREATED = 201 # POST 创建成功
# HTTP_204_NO_CONTENT = 204 # DELETE 成功,无内容
# HTTP_400_BAD_REQUEST = 400 # 请求参数错误
# HTTP_401_UNAUTHORIZED = 401 # 未认证
# HTTP_403_FORBIDDEN = 403 # 无权限
# HTTP_404_NOT_FOUND = 404 # 资源不存在
# HTTP_422_UNPROCESSABLE_ENTITY = 422 # 请求体验证失败
# HTTP_500_INTERNAL_SERVER_ERROR = 500 # 服务器内部错误

也可以在路由函数内部动态返回状态码,使用 Response 对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
from fastapi import FastAPI, Response
from starlette import status

@app.post("/login")
async def login(username: str, password: str):
if username == "admin" and password == "123456":
return {"token": "xxx"}
# 动态设置状态码
return Response(
content='{"detail": "Invalid credentials"}',
status_code=status.HTTP_401_UNAUTHORIZED,
media_type="application/json"
)

四、响应模型继承与组合

通过模型继承可以避免重复定义,同时保持类型安全。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class UserBase(BaseModel):
username: str
email: str

class UserPublic(UserBase):
"""公开信息"""
id: int

class UserPrivate(UserBase):
"""内部信息(含敏感字段)"""
id: int
phone: str
is_active: bool

@app.get("/users/{user_id}", response_model=UserPublic)
async def get_user(user_id: int):
user = {"id": 1, "username": "zhangsan",
"email": "z@example.com", "phone": "13800000000",
"is_active": True}
return user # 只会返回 id, username, email

五、返回不同类型的响应

一个接口可能需要返回不同结构的响应,使用 Union 配合 response_model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from typing import Union
from fastapi import FastAPI, HTTPException

class ItemResponse(BaseModel):
id: int
name: str
price: float

class ErrorResponse(BaseModel):
detail: str

@app.get("/items/{item_id}",
response_model=Union[ItemResponse, ErrorResponse],
responses={
404: {"model": ErrorResponse, "description": "Item not found"},
200: {"model": ItemResponse, "description": "Success"},
})
async def get_item(item_id: int):
if item_id == 0:
return ErrorResponse(detail="Item not found")
return ItemResponse(id=item_id, name="Widget", price=9.99)

六、实战:统一响应格式封装

实际项目中通常需要统一的响应格式,通过响应模型封装很容易实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from typing import Generic, TypeVar
from pydantic import BaseModel

T = TypeVar("T")

class APIResponse(BaseModel, Generic[T]):
"""统一响应格式"""
code: int = 200
message: str = "success"
data: T | None = None

# 使用
@app.get("/users/{user_id}", response_model=APIResponse[UserPublic])
async def get_user(user_id: int):
user = query_user_by_id(user_id) # 伪代码
return APIResponse(data=user)

@app.get("/users/{user_id}", response_model=APIResponse[None])
async def get_user_not_found(user_id: int):
return APIResponse(code=404, message="User not found", data=None)

总结

  • response_model 是安全返回数据的核心,自动过滤未声明的字段,建议每个接口都显式指定
  • response_model_include/exclude 提供动态字段控制,但优先使用专门的响应模型类,更清晰
  • status_code 使用 starlette.status 枚举,避免魔法数字
  • 通过模型继承和泛型封装可以构建统一规范的响应体系,大幅提升 API 的可维护性