Skip to content

第 2 章 · 第一个应用与自动文档

本章目标:掌握路径操作装饰器与 HTTP 动词的对应关系,理解路由匹配顺序,学会使用 /docs、/redoc 与 openapi.json 三种自动产物。

2.1 路径操作装饰器

"路径操作"(path operation)指的是"以哪个 HTTP 动词访问哪条路径"。FastAPI 用装饰器把它们声明出来:

python
from fastapi import FastAPI

app = FastAPI()


@app.get("/items")       # GET    读取资源
def list_items():
    return [{"id": 1, "name": "apple"}]


@app.post("/items")      # POST   创建资源
def create_item():
    return {"created": True}


@app.put("/items/{item_id}")     # PUT    整体替换
def replace_item(item_id: int):
    return {"replaced": item_id}


@app.patch("/items/{item_id}")   # PATCH  局部更新
def update_item(item_id: int):
    return {"patched": item_id}


@app.delete("/items/{item_id}")  # DELETE 删除
def remove_item(item_id: int):
    return {"deleted": item_id}

@app.get("/items") 做的事情是:告诉 FastAPI"当收到 GET /items 请求时,调用下面这个函数,并把返回值转成 JSON 发回去"。可用的动词还有 head/options/trace,但 REST API 里最常用的就是上面五个。

装饰器参数

每个装饰器都支持 response_modelstatus_codetagssummary 等参数(第 6 章开始陆续登场),它们只影响文档和响应行为,不影响路由匹配本身。

2.2 路由匹配顺序是"从上到下"

FastAPI 按代码声明顺序逐条尝试匹配。这带来一个经典陷阱:

python
from fastapi import FastAPI

app = FastAPI()


# ❌ 错误示范:/users/me 写在 /users/{user_id} 之后
@app.get("/users/{user_id}")
def read_user(user_id: str):
    return {"user_id": user_id}


@app.get("/users/me")
def read_current_user():
    # 永远不会被命中!请求 GET /users/me 时
    # 会先被上面的 /users/{user_id} 匹配走,user_id="me"
    return {"user": "the authenticated user"}

修正方法很简单——固定路径必须写在含路径参数的路由之前

python
from fastapi import FastAPI

app = FastAPI()


# ✅ 正确:固定路径优先声明
@app.get("/users/me")
def read_current_user():
    return {"user": "the authenticated user"}


@app.get("/users/{user_id}")
def read_user(user_id: str):
    return {"user_id": user_id}

这不是 FastAPI 的 bug,而是所有按序匹配的路由器(Express、Flask 的部分场景同理)的共同规则。

2.3 返回值的自动序列化

路径函数可以直接返回 dictlist、标量值、Pydantic 模型,甚至数据库 ORM 对象,FastAPI 会自动转换成 JSON:

python
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    price: float


@app.get("/dict-demo")
def as_dict():
    return {"ok": True, "code": 0}          # dict → JSON 对象


@app.get("/list-demo")
def as_list():
    return [1, 2, 3]                        # list → JSON 数组


@app.get("/model-demo", response_model=Item)
def as_model():
    # Pydantic 模型 → JSON(经 Pydantic 序列化引擎,Rust 实现,速度极快)
    return Item(name="pen", price=3.5)

需要精确控制状态码或响应头时,可以返回 Response 子类;但大多数业务代码返回普通对象即可,把序列化交给框架。

2.4 自动交互文档:/docs 与 /redoc

启动应用后你会免费得到两套文档:

它们的共同数据源是 OpenAPI 规范生成的 JSON,直接访问 http://127.0.0.1:8000/openapi.json 就能看到原始 schema:

json
{
  "openapi": "3.1.0",
  "info": { "title": "FastAPI", "version": "0.1.0" },
  "paths": {
    "/items": {
      "get": {
        "summary": "List Items",
        "operationId": "list_items_items_get",
        "responses": { "200": { "...": "..." } }
      }
    }
  }
}

理解这个结构很有价值:

  • OpenAPI 描述 API 有哪些路径、接受什么参数、返回什么结构;
  • 其中数据结构部分用的是 JSON Schema 标准;
  • Swagger UI 和 ReDoc 只是这份 JSON 的两种可视化前端。

为什么自动文档如此重要

因为文档是从代码类型注解实时生成的,它永远不会过期——改了函数签名,刷新页面文档就变了。这也是很多团队选择 FastAPI 的第一理由。基于 openapi.json 还可以用 openapi-generator 等工具自动生成各语言客户端 SDK。

2.5 给应用加元信息

创建 FastAPI() 时可以传入元数据,它们会出现在文档页面顶部:

python
from fastapi import FastAPI

app = FastAPI(
    title="商品服务 API",
    description="用于教程演示的商品管理接口。\n\n支持增删改查。",
    version="1.2.0",
    docs_url="/docs",        # 自定义 Swagger UI 地址
    redoc_url="/redoc",      # 自定义 ReDoc 地址
    openapi_url="/openapi.json",
)


@app.get("/health", tags=["系统"], summary="健康检查")
def health():
    """返回服务运行状态。

    供负载均衡器和运维监控探活使用。
    """
    return {"status": "ok"}

注意两个细节:

  • 函数 docstring 会成为该接口在文档中的详细描述;
  • tags 参数会把接口分组归档,接口多的时候必备(配合第 19 章 APIRouter 使用更佳)。

2.6 本章小结

  • 路径操作 = HTTP 动词 + 路径,用 @app.get/post/put/patch/delete 声明;
  • 路由按声明顺序匹配,固定路径要写在路径参数路由前面;
  • 返回 dict/list/模型都会自动 JSON 序列化;
  • /docs(Swagger UI) 与 /redoc(ReDoc) 都由 /openapi.json 驱动,随代码实时更新;
  • FastAPI(title=..., version=...) 与 docstring、tags 能显著提升文档质量。

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. 以下两个路由同时存在时,GET /users/me 会命中哪一个? @app.get("/users/{user_id}") 在前,@app.get("/users/me") 在后

2. Swagger UI(/docs 页面)的数据来源是什么?

3. 路径函数直接 return 一个 Python dict 会发生什么?

4. 想让某个接口的说明文字出现在 /docs 中,正确做法是?

🛠️ 动手实践

  1. 为一个"待办事项"资源实现完整的五个动词路由(GET 列表 / POST 创建 / GET 单个 / PUT 替换 / DELETE),并用 /docs 页面逐一试通。
  2. 故意把 /todos/today 放到 /todos/{todo_id} 后面复现本章的路由陷阱,观察现象后再修复。
  3. 访问 /openapi.json,找到你 POST 接口对应的片段,指出它的 operationIdresponses 结构。

玩转自动文档后,进入第 3 章学习参数声明。