FastAPI 快速上手
FastAPI 是一个基于 Python 类型标注的现代 Web 框架,底层是 Starlette(ASGI)和 Pydantic(数据校验)。它的核心思想很纯粹:你写在函数签名上的类型标注,就是一切契约的来源——路由参数解析、请求体校验、响应序列化、自动生成的 OpenAPI 文档,全部从同一份标注推导出来。写一次标注,得到校验、文档和编辑器补全,这是它对「动态语言写后端」这件事的最大重构。
安装与第一个应用
FastAPI 本身只是个 ASGI 应用,需要搭配一个服务器才能跑起来,最常用的是 uvicorn:
1 | |
最小应用只需一个文件(main.py):
1 | |
启动开发服务器,--reload 开启热重载:
1 | |
main:app 的含义是「main.py 文件里的 app 对象」。启动后访问 http://127.0.0.1:8000/ 即可看到 JSON 响应。FastAPI 最让人省心的地方是开箱即用的交互式文档:
http://127.0.0.1:8000/docs—— Swagger UI,可直接发请求调试http://127.0.0.1:8000/redoc—— ReDoc,适合阅读http://127.0.0.1:8000/openapi.json—— 原始 OpenAPI 规范
这些文档不是手写的,而是从你的路由签名和 Pydantic 模型自动生成的。
路径参数与查询参数
把类型标注加到路径参数上,FastAPI 会自动做类型转换与校验:
1 | |
访问 /items/3 返回 {"item_id": 3}(注意是数字而非字符串);访问 /items/abc 则直接返回 422 错误,并附上详细的校验失败原因——你不需要手写任何校验代码。
查询参数就是「出现在函数签名里、但不在路径字符串里的参数」,默认值让它变为可选:
1 | |
需要更细的约束时,用 Query:
1 | |
ge/gt/le/lt 分别是 ≥、>、≤、<。这些约束同样会写进 OpenAPI 文档,前端能直接看到。
注意一个约定:
Query(None, ...)表示「可选,默认 None」;Query(..., ...)中的...(Ellipsis)表示「必填」。两者别混淆。
请求体:Pydantic 模型
GET 参数适合零散字段,POST/PUT 的复杂数据用 Pydantic 模型描述。定义一个继承 BaseModel 的类,把它作为参数类型即可:
1 | |
发一个 JSON 请求体,FastAPI 自动反序列化成 Item 实例;字段缺失或类型不符,照样返回 422 + 错误详情。模型可以嵌套,描述树形结构毫无压力:
1 | |
数据校验:Field 与约束
Field 给字段附加约束和元信息,是 Query 的模型字段版:
1 | |
description 会进入文档页面,比手写注释更贴近接口契约。校验失败时,响应体里的 detail 数组会精确指出哪个字段、因为什么原因失败,省去了大量联调扯皮。
响应模型
默认情况下 FastAPI 会原样返回你 return 的内容。但当返回值里混着敏感字段(比如密码哈希、内部 ID)时,用一个独立的输出模型来「过滤」是更安全的做法:
1 | |
response_model 不仅过滤字段,还负责把 ORM 对象、字典等序列化成响应。返回 SQLAlchemy 模型时,需要在输出模型上开启 from_attributes=True(Pydantic v2 语法,v1 是 orm_mode = True),让它能从对象的属性读取数据。
异步与同步
FastAPI 同时支持 async def 和普通 def 路由,这是它性能的关键之一:
1 | |
两种写法的分工很重要:
async def:适合调用异步库(httpx、asyncpg、aioredis)。函数在事件循环里协程式运行,期间不阻塞其他请求。- 普通
def:适合调用同步阻塞库(requests、pymysql)。FastAPI 会把它丢到一个外部线程池里执行,不会卡住事件循环。
最常见的性能陷阱就是把同步阻塞调用塞进 async def——此时它运行在事件循环线程上,一次阻塞会拖慢所有并发请求。如果拿不准,调用同步库就用普通 def,让框架帮你兜底。
依赖注入
依赖注入(DI)是 FastAPI 的灵魂特性。任何「多个路由都要重复做的事」——查询参数解析、数据库会话、鉴权——都能抽成一个依赖函数,用 Depends 注入:
1 | |
依赖可以嵌套(依赖里再依赖),形成一棵依赖树。带 yield 的依赖则用于资源管理,类似上下文管理器:
1 | |
鉴权也建在 DI 之上:写一个依赖校验 token,把它的返回值作为「当前用户」注入到需要保护的路由里。
路由分组:APIRouter
项目变大后,把所有路由堆在 main.py 里不可维护。APIRouter 是一个「子应用」,可以按模块拆分:
1 | |
1 | |
prefix 给整组路由加前缀,tags 让它们在文档里归到一组。这是组织中大型 FastAPI 项目的标准做法。
中间件与 CORS
中间件包裹在整个应用外侧,每个请求进来、每个响应出去都会经过。前后端分离场景下最常用的是 CORS:
1 | |
自定义中间件用 @app.middleware("http"),能在请求前后插入逻辑(计时、日志、统一错误处理等)。
常见陷阱
在 async def 里调用同步阻塞库。比如 async def 里用 requests.get(...) 或同步数据库驱动,会阻塞整个事件循环。改用异步库,或把函数降级为普通 def 让线程池兜底。
路由声明顺序错误。/users/me 必须声明在 /users/{user_id} 之前,否则 me 会被当成 user_id 去做 int 转换,直接 422。FastAPI 按声明顺序匹配路由,顺序就是优先级。
Pydantic v1 与 v2 的 API 差异。v2 里 class Config 换成了 model_config = ConfigDict(...),.dict() / .parse_obj() 换成了 .model_dump() / .model_validate(),orm_mode 换成了 from_attributes。混用两套写法会踩坑,新项目直接锁定 v2 语法。
可变默认值。def f(tags: list = []) 是 Python 的经典坑,FastAPI 也不会替你免疫。用 Query(default=[]) 或 None + 函数内初始化代替。
response_model 与返回 ORM 对象。返回 SQLAlchemy 模型却没在输出模型上设 from_attributes=True,会得到「无法序列化」的错误。Pydantic 默认只接受 dict / 自身实例,要让它读对象属性必须显式开启。
把 ...(必填)和 None(可选)搞混。Query(...) 是必填,Query(None) 是可选默认空,Query(None, ...) 这种写法自相矛盾、行为不可预期。
总结
FastAPI 的设计哲学是「以类型标注为单一数据源」。掌握这一点,大部分特性都可以举一反三地推导出来。
| 特性 | 关键点 | 示例 |
|---|---|---|
| 路由参数 | 签名上的类型标注自动转换/校验 | def read(item_id: int) |
| 查询参数 | 非路径参数 + 默认值,Query 加约束 | q: str | None = Query(None) |
| 请求体 | Pydantic BaseModel 作参数类型 | def create(item: Item) |
| 校验 | Field 给字段加约束与描述 | Field(..., gt=0) |
| 响应 | response_model 过滤/序列化输出 | @app.post(..., response_model=UserOut) |
| 异步 | async def 用异步库,def 走线程池 | async def slow(): await ... |
| 依赖注入 | Depends 复用逻辑,yield 管资源 | db = Depends(get_db) |
| 路由分组 | APIRouter + include_router | router = APIRouter(prefix="/items") |
| 文档 | 自动生成 /docs、/redoc | 无需手写 |
上手路径建议:先用路径参数和 Pydantic 模型把 CRUD 跑通,再引入 Depends 做鉴权和数据库会话,最后用 APIRouter 拆分模块。一旦习惯了「写标注即写契约」的节奏,回到传统框架反而会觉得啰嗦。

