跳到主要内容

2.4 响应模型与状态码

本章目标:学会控制接口返回什么数据、什么结构、什么状态码

1. response_model:声明返回结构

上一章说到,返回用户信息时不能带密码。response_model 就是干这个的:

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


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


class UserOut(BaseModel):
id: int
username: str
email: str


# 模拟数据库
fake_db: dict[int, dict] = {}
next_id = 1


@app.post("/users", response_model=UserOut)
def create_user(user: UserCreate):
global next_id
saved = {"id": next_id, **user.model_dump()} # 包含 password
fake_db[next_id] = saved
next_id += 1
return saved # 虽然返回了含 password 的字典……

关键点:函数返回的字典里明明有 password,但因为声明了 response_model=UserOut,FastAPI 会按 UserOut 过滤输出,实际响应是:

{"id": 1, "username": "xiaoming", "email": "a@b.com"}

response_model 的三大作用:

  1. 过滤字段:模型里没有的字段不会返回(防止泄露敏感数据)
  2. 校验输出:返回的数据不符合模型会直接报错,帮你尽早发现 bug
  3. 生成文档/docs 里能看到这个接口的响应结构

也可以直接用返回值类型注解代替 response_model,效果相同:

@app.post("/users")
def create_user(user: UserCreate) -> UserOut: ...

两种写法选一种即可,本教程统一用 response_model=,因为后面接数据库时它更灵活。

2. 返回列表

@app.get("/users", response_model=list[UserOut])
def list_users():
return list(fake_db.values())

3. 自定义状态码

HTTP 语义上,「创建成功」应该返回 201 而不是 200:

from fastapi import status


@app.post("/users", response_model=UserOut, status_code=status.HTTP_201_CREATED)
def create_user(user: UserCreate):
...

status 模块里是所有状态码的常量(status.HTTP_404_NOT_FOUND 等),比直接写数字 201 可读性好。

常见约定:

操作状态码
查询成功200(默认)
创建成功201
删除成功(无返回内容)204
客户端参数错误400 / 422
未登录401
无权限403
资源不存在404

删除接口的典型写法(204 表示成功但无内容,函数应返回 None):

@app.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_user(user_id: int):
fake_db.pop(user_id, None)
# 不 return 任何东西

4. 完整 CRUD 示例(内存版)

把前几章的知识串起来,写一个完整的「用户管理」——数据先存在字典里,第四部分会无缝换成数据库:

from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel, Field

app = FastAPI(title="用户管理 API(内存版)")


class UserCreate(BaseModel):
username: str = Field(min_length=3, max_length=20)
email: str
password: str = Field(min_length=6)


class UserUpdate(BaseModel):
# 更新时所有字段都可选:只改传了的字段
username: str | None = Field(default=None, min_length=3, max_length=20)
email: str | None = None


class UserOut(BaseModel):
id: int
username: str
email: str


fake_db: dict[int, dict] = {}
next_id = 1


@app.post("/users", response_model=UserOut, status_code=status.HTTP_201_CREATED)
def create_user(user: UserCreate):
global next_id
saved = {"id": next_id, **user.model_dump()}
fake_db[next_id] = saved
next_id += 1
return saved


@app.get("/users", response_model=list[UserOut])
def list_users():
return list(fake_db.values())


@app.get("/users/{user_id}", response_model=UserOut)
def get_user(user_id: int):
if user_id not in fake_db:
raise HTTPException(status_code=404, detail="用户不存在")
return fake_db[user_id]


@app.patch("/users/{user_id}", response_model=UserOut)
def update_user(user_id: int, user: UserUpdate):
if user_id not in fake_db:
raise HTTPException(status_code=404, detail="用户不存在")
# exclude_unset=True:只取调用方真正传了的字段
updates = user.model_dump(exclude_unset=True)
fake_db[user_id].update(updates)
return fake_db[user_id]


@app.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_user(user_id: int):
if user_id not in fake_db:
raise HTTPException(status_code=404, detail="用户不存在")
del fake_db[user_id]

请把这段代码完整跑一遍,在 /docs 里按顺序操作:创建两个用户 → 查列表 → 查单个 → 改名字 → 删除 → 再查(应该 404)。

这里出现的 HTTPException 用于返回错误,第 06 章会详细讲;PATCH + exclude_unset 的部分更新模式是实际项目的标准做法。

5. 补充:直接返回自定义 Response(了解即可)

绝大多数情况返回字典/模型就够了。特殊场景可用:

from fastapi.responses import PlainTextResponse, RedirectResponse, FileResponse

@app.get("/txt", response_class=PlainTextResponse)
def get_txt():
return "纯文本内容"

@app.get("/old-url")
def old():
return RedirectResponse("/new-url") # 重定向

@app.get("/download")
def download():
return FileResponse("report.pdf", filename="报告.pdf") # 文件下载

本章小结

  • response_model 声明返回结构:过滤敏感字段、校验输出、生成文档
  • status_code=status.HTTP_201_CREATED 自定义状态码;删除用 204
  • 更新接口用「全可选模型 + model_dump(exclude_unset=True)」实现部分更新
  • 本章的内存版 CRUD 是后面数据库版的骨架,务必亲手跑通

下一章:2.5 表单和文件上传