上一篇我们用 ItemCreate 接收 JSON 请求体,解决了“前端传什么进来”的问题。

这一篇看另一边:接口要返回什么给前端。

做完后,我们会让 POST /items 在函数里生成一个内部字段 internal_note,但浏览器最终拿到的 JSON 里看不到它。这个效果就是响应模型带来的。

我会继续将代码放到 fastapi-beginner-lab 仓库中,有需要的朋友自取。

这次要改哪几行

前几篇已经写过的启动代码和 GET 接口不再重复贴。这次只看新增和改动的部分。

这段代码重点看 ItemPublicresponse_model=ItemPublic。前者说明响应允许有哪些字段,后者把这个说明挂到 POST /items 上。

class ItemPublic(BaseModel):
    id: int
    name: str
    price: float
    is_offer: bool = False


@app.post("/items", response_model=ItemPublic)
def create_item(item: ItemCreate):
    item_data = item.model_dump()
    return {
        "id": 1,
        **item_data,
        "internal_note": "Only visible inside the server.",
    }

这里函数返回的字典里有 internal_note,但 ItemPublic 里没有这个字段。

我们先跑起来看结果。

/docs 里调用一次

启动服务:

.\.venv\Scripts\Activate.ps1
$env:PYTHONIOENCODING = "utf-8"
$env:PYTHONUTF8 = "1"
fastapi dev app/main.py

打开:

http://127.0.0.1:8000/docs

点开 POST /items,请求体填入:

{
  "name": "Notebook",
  "price": 12.5,
  "is_offer": true
}

Execute 后,应该能看到:

{
  "id": 1,
  "name": "Notebook",
  "price": 12.5,
  "is_offer": true
}

注意这里没有 internal_note

函数明明返回了这个字段,响应里却没有它。FastAPI 按 ItemPublic 把返回值过滤了一遍。

为什么请求模型和响应模型要分开

第 3 篇里我们写了 ItemCreate

class ItemCreate(BaseModel):
    name: str
    price: float
    is_offer: bool = False

它描述的是创建商品时,客户端需要提交什么。

这一篇加的 ItemPublic 描述的是创建成功后,接口准备返回什么。

class ItemPublic(BaseModel):
    id: int
    name: str
    price: float
    is_offer: bool = False

这两个模型看起来很像,但用途不同。

ItemCreate 不需要 id,因为创建时 ID 通常由服务端生成。

ItemPublic 需要 id,因为创建成功后前端通常要知道新商品的 ID。

真实项目里还会有更多差异。比如服务端内部可能保存 internal_notecost_pricecreated_by 这类字段,但它们不一定适合返回给前端。

response_model 做了什么

response_model 写在接口装饰器上:

@app.post("/items", response_model=ItemPublic)

它告诉 FastAPI:这个接口返回给客户端的数据,要按 ItemPublic 来处理。

这会带来三个直接效果:

  • 响应字段会被过滤,只保留 ItemPublic 里声明过的字段。
  • /docs 里会显示这个接口的响应结构。
  • 如果函数返回的数据缺少 ItemPublic 要求的字段,服务端会报错,提醒我们代码返回了不符合约定的数据。

现在最值得记住的是第一点:响应模型能挡住不该出现在响应里的字段。

看一下内部字段怎么被过滤

我们返回的是这个字典:

return {
    "id": 1,
    **item_data,
    "internal_note": "Only visible inside the server.",
}

如果没有 response_model=ItemPublic,这个接口会把 internal_note 一起返回。

加上响应模型后,FastAPI 会按 ItemPublic 重新整理响应:

函数返回 dict -> 按 ItemPublic 过滤字段 -> 转成 JSON 响应

所以浏览器看到的是:

{
  "id": 1,
  "name": "Notebook",
  "price": 12.5,
  "is_offer": true
}

这一步很适合新手早点养成习惯。接口返回值不是临时拼一个 dict 就结束了,它也是前端会依赖的约定。

/docs 里看响应结构

回到 /docs,点开 POST /items

除了请求体,你还能看到响应区域里多了 ItemPublic 对应的结构。

这说明 FastAPI 不只是运行时过滤字段,还会把响应格式写进接口文档。前端看文档时,就知道这个接口会返回 idnamepriceis_offer

现在我们还在单文件示例里写代码,感觉不明显。等接口多起来后,这种明确的响应结构会减少很多沟通成本。

动手改一下

现在给函数内部返回值加一个成本价字段:

@app.post("/items", response_model=ItemPublic)
def create_item(item: ItemCreate):
    item_data = item.model_dump()
    return {
        "id": 1,
        **item_data,
        "internal_note": "Only visible inside the server.",
        "cost_price": 8.0,
    }

保存后回到 /docs,再次调用 POST /items

如果响应里看不到 cost_price,说明 response_model=ItemPublic 仍然在工作。

到这里,这篇的目标已经完成:

  • 我们用 ItemPublic 描述了接口返回给前端的数据。
  • 我们让 POST /items 使用了 response_model=ItemPublic
  • 我们确认了函数内部返回的 internal_note 不会出现在最终响应里。

下一篇继续处理接口失败的情况:资源不存在时,应该返回 404,而不是随手返回一段字符串。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐