最常用方式是在路由装饰器中设置status_code参数,如@app.post("/users", status_code=201),它固定返回指定状态码、影响OpenAPI文档和响应头,但不干扰返回值序列化;若需动态控制,则通过函数参数response: Response修改,或直接return JSONResponse等子类实现完全自定义响应。

直接在路由装饰器里设 status_code 参数
这是最常用、最轻量的方式,适合状态码固定且不依赖运行时逻辑的场景。比如创建资源默认返回 201,而不是默认的 200。
它只影响 OpenAPI 文档生成和响应头中的状态码,不干扰返回值序列化或 response_model 的处理。
-
status_code是@app.get()这类装饰器的参数,不是函数参数 - 支持数字(如
201)或枚举(如status.HTTP_201_CREATED) - 如果函数体内又通过
Response修改了status_code,以函数体内为准(后者覆盖前者) - 注意:某些状态码(如
204、304)会自动清空响应体,即使你 return 了 dict,FastAPI 也会忽略它
示例:
@app.post("/users", status_code=201)
def create_user(name: str):
return {"id": 123, "name": name}
用 Response 参数动态改状态码和 Header
当你需要根据业务逻辑决定状态码(比如“查不到就创建并返回 201”),或者要同时设置多个 Header,就得在函数签名里加一个 response: Response 参数。
这个 Response 是 FastAPI 注入的临时对象,你对它的修改(status_code、headers、set_cookie)会被合并进最终响应,不影响 response_model 的字段过滤。
- 必须显式声明类型为
Response,否则 FastAPI 不会注入 -
response.headers["X-Custom"] = "value"可设任意 header,但前端能读到需配合 CORS 的expose_headers - 多个中间件或依赖项都改
response.status_code时,最后执行的那个生效 - 别在依赖项里设
status_code后,又在路径函数里覆盖——容易漏掉逻辑分支
示例:
@app.put("/tasks/{task_id}")
def get_or_create_task(task_id: str, response: Response):
if task_id not in tasks:
tasks[task_id] = "new"
response.status_code = status.HTTP_201_CREATED
response.headers["X-Resource"] = "created"
return {"task": tasks[task_id]}
直接 return Response 子类控制更底层行为
当你要完全绕过 FastAPI 默认的 JSON 序列化流程(比如返回纯文本、XML、流式响应、重定向),就得用 return JSONResponse(...) 或 return RedirectResponse(...) 这类明确构造的响应实例。
这种方式把状态码、header、content-type 全部收归一手控制,但代价是放弃 response_model 自动校验和文档生成能力。
-
JSONResponse、PlainTextResponse、StreamingResponse都来自 Starlette,FastAPI 只做了 re-export - header 必须作为构造参数传入,不能事后赋值(不像注入的
Response参数) - 如果你只改状态码和一两个 header,用注入方式更简洁;真要换 content-type 或流式传输,才值得切到这里
- 别混用:不要既声明
response: Response参数,又return JSONResponse(...),前者会被忽略
示例:
@app.get("/health")
def health_check():
return JSONResponse(
content={"status": "ok"},
status_code=200,
headers={"Cache-Control": "no-cache"}
)
Header 和状态码的优先级与调试要点
实际部署中,最容易出问题的是「你以为设了,其实没生效」。核心在于理解 FastAPI 响应组装顺序:路径函数返回值 → response_model 转换 → 注入的 Response 对象补全 → 最终响应。
- 状态码冲突时:
return Response(..., status_code=N)> 函数体内response.status_code = N> 装饰器status_code=N - Header 冲突时:函数体内
response.headers["X"] = ...和return JSONResponse(..., headers={...})不叠加,后者完全覆盖前者 - 用
curl -v或浏览器 Network 面板看真实响应头,别只信日志或文档 - CORS 场景下,自定义 header 如
X-Request-ID必须显式加到expose_headers列表,否则前端 JS 拿不到
复杂逻辑里状态码和 header 往往耦合,建议把这类控制逻辑抽成依赖项,便于复用和测试,但记得检查执行顺序是否符合预期。


















