Skip to content

响应模型与状态码 ​

输入模型和输出模型可以不同 ​

用户注册时会发送密码,但接口响应不应返回密码或密码哈希。分别定义输入、输出模型,可以清楚地表达这件事。

完整示例:

python
from fastapi import FastAPI
from pydantic import BaseModel, Field

app = FastAPI()


class UserCreate(BaseModel):
    username: str = Field(min_length=3)
    password: str = Field(min_length=8)


class UserPublic(BaseModel):
    id: int
    username: str


@app.post("/users", response_model=UserPublic, status_code=201)
def create_user(user: UserCreate):
    return {"id": 1, "username": user.username, "internal_note": "教学示例"}

响应只包含 id 和 username,没有 internal_note。这个示例演示字段控制,没有实现密码存储;密码处理见第 15 章。

response_model 用于描述响应结构、校验返回数据、进行序列化并筛选字段。处理函数返回 ORM 对象时,也可以借助适当配置使用响应模型。官方响应模型说明

输出错误与输入错误不同 ​

客户端提交不合法参数,默认常得到 422;如果你声明输出必须有 id,却返回了缺少 id 的数据,这是服务器代码不符合接口约定,通常表现为服务器错误,应由开发者修复。

不要为了让错误消失而随意去掉响应模型。它往往正在帮助你发现接口实现与约定不一致。

常见状态码 ​

状态码常见含义场景
200成功查询、修改
201已创建创建资源
202已接受,处理尚未完成接收后台处理请求
204成功且没有响应正文删除资源
400请求不符合业务要求不能将订单转到该状态
401缺少或无效的身份凭据没登录、Token 过期
403无权限普通用户执行管理员操作
404未找到资源或路径任务不存在
409冲突唯一用户名已存在
422请求数据校验失败字段缺失、类型或范围不对
500服务器内部错误代码异常、输出不合法

这里给出常见使用习惯,具体业务仍要制定一致的接口约定。官方响应状态码说明

204 不应返回 JSON 正文 ​

追加到本章完整示例:

python
from fastapi import Response, status


@app.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_user(user_id: int):
    # 教学示例:实际删除逻辑应在这里执行
    return Response(status_code=status.HTTP_204_NO_CONTENT)

如果你想返回 {"message": "删除成功"},可以选用 200 并声明相应响应结构。

响应类型详解 ​

FastAPI 的处理函数不只能返回字典。根据内容形式,可以返回不同类型的响应对象,这些类都从 fastapi.responses 导入。先看总览:

响应写法用途典型场景
直接返回 dict/listJSON(默认)大多数 API
JSONResponse定制 JSON自定义状态码/头
FileResponse下载文件附件下载、图片
StreamingResponse分块流式传输大文件、实时数据
HTMLResponseHTML 页面服务端渲染页面
PlainTextResponse纯文本robots.txt 等
RedirectResponse重定向旧地址跳新地址
Response自定义原始内容XML、YAML、二进制

一个重要区别:直接返回 dict 等普通数据时,FastAPI 会走完整的响应模型处理流程(校验、序列化、字段筛选);而直接返回一个 Response 对象时,框架把它当成“已经封装好的成品”,原样发送,不再替你序列化。官方自定义响应说明

1. JSON 响应 ​

返回字典或列表,FastAPI 会自动转成 JSON,并设好 Content-Type: application/json:

python
@app.get("/data")
def get_data():
    return {"name": "手机", "price": 2999}

需要定制状态码、响应头时,手动构造 JSONResponse:

python
from fastapi.responses import JSONResponse


@app.get("/data/custom")
def get_data_custom():
    return JSONResponse(
        content={"name": "手机", "price": 2999},
        status_code=200,
        headers={"X-Custom": "abc", "X-Total-Count": "1"},
    )

JSONResponse 适合需要额外头(比如分页总数 X-Total-Count)或自定义状态码的场景;普通场景直接 return 字典即可。

2. 文件响应 FileResponse ​

下载文件用 FileResponse。它像快递员直接搬走一整箱文件,不拆开、不整箱读进内存,而是从磁盘流式读取后发送,所以再大的文件也不会撑爆内存:

python
from fastapi.responses import FileResponse


@app.get("/download")
def download():
    return FileResponse(
        path="report.pdf",        # 服务器上的文件路径
        filename="年报.pdf",      # 下载到客户端时显示的名字
        media_type="application/pdf",
    )
  • path:文件在服务器上的实际路径。
  • filename:设置后会自动加上 Content-Disposition 头,浏览器按这个名字下载,而不是直接打开。
  • media_type:不写时 FastAPI 会按文件后缀猜测;猜不准或想强制指定时手动写。

图片等静态资源也可以直接返回,前端 <img src="/download"> 就能显示。

3. 流式响应 StreamingResponse ​

StreamingResponse 边生成边发送,不必一次性把所有数据攒齐。它像水管放水或看直播:水(数据)流到哪就送到哪,而不是先装满一桶再倒。

python
from fastapi.responses import StreamingResponse


def generate_lines():
    for i in range(100):
        yield f"第 {i} 行\n"


@app.get("/stream")
def stream():
    return StreamingResponse(generate_lines(), media_type="text/plain")

适用场景:

场景说明
大文件/大数据导出逐块读取,内存占用恒定
实时数据日志、进度、监控数据持续推送
AI 逐字回复像聊天机器人那样一个字一个字往外蹦

传给 StreamingResponse 的可以是普通生成器,也可以是 async 生成器(用 async def + yield)。media_type 明确告诉客户端这是什么内容。

4. HTML、纯文本与重定向 ​

python
from fastapi.responses import HTMLResponse, PlainTextResponse, RedirectResponse


@app.get("/", response_class=HTMLResponse)
def home():
    return "<h1>你好,欢迎</h1>"


@app.get("/robots.txt", response_class=PlainTextResponse)
def robots():
    return "User-agent: *\nDisallow: /admin"


@app.get("/old")
def old():
    return RedirectResponse(url="/new")
  • HTMLResponse:返回网页,可配合模板引擎做服务端渲染。
  • PlainTextResponse:返回不带任何格式的纯文本。
  • RedirectResponse:让浏览器跳到新地址,默认状态码 307;也可用 status_code=301 表示永久跳转。

前两个用了 response_class 参数,含义是“把返回的普通数据按这个响应类来包装”;而 RedirectResponse 直接返回了响应对象。

5. 自定义响应数据 ​

当需要返回 JSON 之外的格式(XML、YAML、纯字节等),用最基础的 Response 手动指定内容和类型:

python
from fastapi import Response


@app.get("/export.xml")
def export_xml():
    xml = """<?xml version="1.0" encoding="UTF-8"?>
<item>
    <name>手机</name>
    <price>2999</price>
</item>"""
    return Response(
        content=xml,
        media_type="application/xml",
        headers={"X-From": "FastAPI"},
    )

Response 的 content 接收字符串或字节;media_type 决定 Content-Type 头;headers 添加自定义响应头。注意:直接返回 Response 时,框架不会帮你做 JSON 序列化,内容是什么就原样发什么。

如果只是想给普通 JSON 响应加几个头,还有一个更轻量的办法——在函数里注入 Response 对象改它的 headers:

python
@app.get("/data")
def get_data(response: Response):
    response.headers["X-Custom"] = "abc"
    return {"name": "手机", "price": 2999}

这样既保留了返回字典的便利(自动 JSON 序列化),又能自定义响应头。

如何选择响应类型 ​

需求推荐写法
返回 JSON 数据直接 return dict
JSON 但需自定义头/状态码JSONResponse
下载文件FileResponse
大文件/实时流StreamingResponse
返回网页HTMLResponse
返回纯文本PlainTextResponse
跳转RedirectResponse
自定义格式/字节Response

本章练习 ​

为商品系统实现以下接口:

  1. GET /products 返回商品 JSON 列表,并加上自定义头 X-Total-Count;
  2. GET /products/{id}/receipt 下载一份收据文件;
  3. GET /products/export 以流式方式逐条导出商品数据;
  4. GET /products/{id}.xml 返回 XML 格式的商品详情。

过关标准:能解释返回普通数据与返回 Response 对象的区别;能为“下载文件”“流式导出”“自定义格式”三种需求分别选出正确的响应类。