FastAPI 系列

  1. FastAPI 系列:从第一个接口到生产部署

  2. FastAPI 为什么适合做 JSON API

  3. 第一个接口怎么跑起来

  4. 路径、查询和请求体为什么要分开声明(当前篇)

  5. Pydantic 模型怎么当契约

  6. 路由怎么拆

  7. 依赖注入到底省了什么

  8. 数据库会话怎么进接口

  9. 状态码和异常为什么不要裸 raise

  10. 鉴权怎么接到 Depends 上

  11. 中间件、CORS 和后台任务分别解决什么

  12. 测试怎么写才不连真实库

  13. 生产怎么部署

一个 HTTP 请求里至少有三类输入:路径上的资源标识、问号后面的过滤条件、以及 JSON 体。FastAPI 把它们映射到不同的函数参数。混在一起写,OpenAPI 会乱,校验也会在错误的位置失败。

路径参数、查询参数与 JSON 请求体分别进入函数参数

路径参数:资源是谁

路径参数写在 URL 模板里,通常表示「哪一条资源」:

from typing import Annotated

from fastapi import FastAPI, Path

app = FastAPI()


@app.get("/items/{item_id}")
async def read_item(
    item_id: Annotated[int, Path(ge=1, description="商品 ID")],
):
    return {"item_id": item_id}

/items/3 会得到整数 3;/items/foo 返回 422。路径参数默认必填——它本来就是地址的一部分。

查询参数:过滤和开关

查询参数跟在 ? 后面,适合分页、搜索、可选开关:

from typing import Annotated

from fastapi import Query


@app.get("/items/")
async def list_items(
    q: Annotated[str | None, Query(max_length=50)] = None,
    skip: Annotated[int, Query(ge=0)] = 0,
    limit: Annotated[int, Query(ge=1, le=100)] = 20,
):
    return {"q": q, "skip": skip, "limit": limit}

有默认值就是可选。需要必填查询参数时,去掉默认值即可。列表查询可以用 list[str],对应重复的 ?tag=a&tag=b

请求体:要写入的数据

POST / PUT / PATCH 的 JSON 体用 Pydantic 模型,不要把整段 JSON 收成 dict

from pydantic import BaseModel


class Item(BaseModel):
    name: str
    price: float
    tags: list[str] = []


@app.post("/items/")
async def create_item(item: Item):
    return item

FastAPI 看到参数是 Pydantic 模型,就按 request body 解析。GET 通常没有 body,不要把模型塞进 GET。

混写时 FastAPI 怎么判断

参数形态

默认去哪里读

简单类型且名字在路径里

Path

简单类型且不在路径里

Query

Pydantic 模型

JSON Body

Request / Response

Starlette 对象,不是用户输入字段

拿不准就显式标注 Path()Query()Body()。现代写法是 Annotated[int, Path(ge=1)],比默认参数里塞 Path() 更清晰。

它们也是输入,但不是路径或查询:

from fastapi import Header, Cookie


@app.get("/context")
async def read_context(
    user_agent: Annotated[str | None, Header()] = None,
    session_id: Annotated[str | None, Cookie()] = None,
):
    return {"user_agent": user_agent, "session_id": session_id}

HTTP 头名字里的连字符会转成下划线:User-Agentuser_agent。鉴权头后面会交给 OAuth2PasswordBearer,不要自己手拆 Authorization 除非有特殊协议。

常见误区

把过滤条件写进路径

/items/search/red/cheap 很难演进。搜索用查询参数:/items?color=red&max_price=20

GET 带 JSON 体

很多客户端、缓存和代理不按规范处理 GET body。过滤条件放 Query。

所有输入都收成一个 dict

校验、文档和编辑器补全都没了。下一篇会把 body 再拆成入参模型和出参模型。

小结

路径回答「哪一个」,查询回答「怎么筛」,请求体回答「要写入什么」。分开声明后,422 错误会告诉你是哪一类字段错了。下一篇用 Pydantic 把契约写完整。

参考资料