第 2 章 · 第一个应用与自动文档
本章目标:掌握路径操作装饰器与 HTTP 动词的对应关系,理解路由匹配顺序,学会使用 /docs、/redoc 与 openapi.json 三种自动产物。
2.1 路径操作装饰器
"路径操作"(path operation)指的是"以哪个 HTTP 动词访问哪条路径"。FastAPI 用装饰器把它们声明出来:
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_model、status_code、tags、summary 等参数(第 6 章开始陆续登场),它们只影响文档和响应行为,不影响路由匹配本身。
2.2 路由匹配顺序是"从上到下"
FastAPI 按代码声明顺序逐条尝试匹配。这带来一个经典陷阱:
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"}修正方法很简单——固定路径必须写在含路径参数的路由之前:
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 返回值的自动序列化
路径函数可以直接返回 dict、list、标量值、Pydantic 模型,甚至数据库 ORM 对象,FastAPI 会自动转换成 JSON:
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
启动应用后你会免费得到两套文档:
- Swagger UI:http://127.0.0.1:8000/docs —— 可交互,能直接在页面上填参数发请求;
- ReDoc:http://127.0.0.1:8000/redoc —— 只读排版,适合给第三方阅读。
它们的共同数据源是 OpenAPI 规范生成的 JSON,直接访问 http://127.0.0.1:8000/openapi.json 就能看到原始 schema:
{
"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() 时可以传入元数据,它们会出现在文档页面顶部:
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 中,正确做法是?
🛠️ 动手实践
- 为一个"待办事项"资源实现完整的五个动词路由(GET 列表 / POST 创建 / GET 单个 / PUT 替换 / DELETE),并用 /docs 页面逐一试通。
- 故意把
/todos/today放到/todos/{todo_id}后面复现本章的路由陷阱,观察现象后再修复。 - 访问
/openapi.json,找到你 POST 接口对应的片段,指出它的operationId和responses结构。
玩转自动文档后,进入第 3 章学习参数声明。