FastAPI路由实战:从入门到精通
FastAPI是一个基于Python类型提示的现代API框架,其核心优势在于将路由定义、参数提取、数据校验和文档生成统一整合。文章详细介绍了FastAPI的路由系统,包括路径参数、查询参数、请求体处理,以及如何使用Query、Path、Body等工具精确控制参数来源。同时阐述了response_model对输出数据的过滤作用,依赖注入(Depends)机制,以及通过APIRouter组织大型项目的
目录
4. 什么叫“路由”以及 FastAPI 里的“path operation”
6. 路由函数参数的真正魔法:FastAPI 会按“位置来源”自动识别参数
7. 更精细地声明参数来源:Query()、Path()、Body()、Header()、Cookie()、Form()、File()
8. FastAPI 路由函数的返回值与 response_model
11. 依赖注入 Depends():FastAPI 路由的第二根脊梁
13. APIRouter:项目一变大,别再把所有路由塞进一个文件
FastAPI 及其路由使用详解
1. FastAPI 到底是什么
你可以把 FastAPI 理解成一句话:
用 Python 的类型标注,直接声明 HTTP API 的输入、输出、校验和文档。
这玩意儿最妙的地方不只是“快”,而是它把这些原本分散的事情揉成了一套统一机制:
-
路由定义
-
参数提取
-
数据校验
-
响应序列化
-
OpenAPI 文档生成
-
Swagger UI / ReDoc 交互文档
官方文档明确说明,FastAPI 是一个基于标准 Python type hints 的 API 框架,并且会自动生成 OpenAPI schema,以及默认提供 /docs 和 /redoc 两套文档界面。(FastAPI)
2. 为什么它在 Python 后端里这么常见
因为它特别适合“接口层”和“数据边界层”。
传统 Web 框架里,经常要自己做这些事:
-
手动从 URL 里取参数
-
手动从 query string 里取值
-
手动解析 JSON
-
手动判断缺字段、类型不对
-
手动写接口文档
FastAPI 的思路是:
你只管在函数签名里声明你要什么,FastAPI 负责把请求拆开、校验、转换并注入给你。 这也是它和 Flask 那种“先拿 request 再自己掏参数”的风格最大的差别。官方文档把这种模式贯穿到了路径参数、查询参数、请求体、Header、Cookie、Form、File 这些输入来源上。(FastAPI)
3. 先看一个最小 FastAPI 程序
官方教程当前推荐先安装:
pip install "fastapi[standard]"
然后可以用开发命令运行应用,比如:
fastapi dev main.py
这来自官方的安装和 First Steps 教程。(FastAPI)
最小示例:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
这段代码里有四个核心角色:
-
FastAPI():创建应用对象 -
@app.get("/"):注册一个 GET 路由 -
root():这个路由对应的处理函数 -
return {...}:返回响应内容
官方 First Steps 文档把这套流程叫作 path operation:路径(path)加操作(operation,比如 GET / POST)一起构成一个接口定义。(FastAPI)
4. 什么叫“路由”以及 FastAPI 里的“path operation”
在 FastAPI 里,路由通常就指你写的这一类接口:
@app.get("/items")
@app.post("/users")
@app.put("/orders/{order_id}")
@app.delete("/files/{file_id}")
它本质上由两部分组成:
-
Path:比如
/items/{item_id} -
HTTP 方法:GET、POST、PUT、DELETE 等
官方文档把它叫作 path operation,这是 OpenAPI 术语里也很常见的概念。你可以把它简单理解成:一个 URL + 一个 HTTP 动作 = 一个接口路由。(FastAPI)
5. 常见路由装饰器怎么用
最常用的有这些:
@app.get("/items")
@app.post("/items")
@app.put("/items/{item_id}")
@app.delete("/items/{item_id}")
@app.patch("/items/{item_id}")
这些装饰器就是在告诉 FastAPI:
-
访问什么路径
-
用什么 HTTP 方法
-
调哪个 Python 函数处理
response_model、status_code、tags、summary、description、dependencies 等也都可以直接写在这些装饰器参数里。官方文档明确说明,response_model 可以用于任意 path operation 装饰器,而 path operation configuration 里也支持状态码、标签、摘要等配置。(FastAPI)
例如:
from fastapi import FastAPI, status
app = FastAPI()
@app.post(
"/items",
status_code=status.HTTP_201_CREATED,
tags=["items"],
summary="创建商品",
description="创建一个新的商品记录"
)
async def create_item():
return {"ok": True}
6. 路由函数参数的真正魔法:FastAPI 会按“位置来源”自动识别参数
FastAPI 最强的地方之一,就是直接从函数签名判断参数来自哪里。
6.1 路径参数 Path Parameter
看这个:
@app.get("/items/{item_id}")
async def read_item(item_id: int):
return {"item_id": item_id}
因为路径里写了 {item_id},函数里也有 item_id,FastAPI 就知道这是路径参数。而且你写了 int,它就会自动转换和校验。官方路径参数文档明确说明:路径参数可以直接通过标准类型注解声明,并进行数据转换与校验。(FastAPI)
如果传入不是整数,比如 /items/abc,它会自动报验证错误,而不是傻乎乎继续跑。这就是类型驱动接口的威力。
6.2 查询参数 Query Parameter
看这个:
@app.get("/items/")
async def read_items(skip: int = 0, limit: int = 10):
return {"skip": skip, "limit": limit}
这里 skip 和 limit 没有出现在路径里,所以 FastAPI 会把它们识别成查询参数,也就是:
/items/?skip=0&limit=10
官方查询参数文档明确说明:路径参数和查询参数可以同时声明,FastAPI 会根据名字和路径模板自动区分它们;而对于非路径参数,有默认值就不是必填,没有默认值就是必填。(FastAPI)
例如:
@app.get("/search/")
async def search(q: str):
return {"q": q}
这里 q 没有默认值,所以它是必填 query 参数。
6.3 请求体 Request Body
当你把一个 Pydantic 模型 写进函数参数里,FastAPI 通常就会把它识别成请求体。
from pydantic import BaseModel
class Item(BaseModel):
name: str
price: float
description: str | None = None
@app.post("/items/")
async def create_item(item: Item):
return item
这里客户端需要发 JSON body,FastAPI 会:
-
解析请求体
-
按
Item模型校验 -
自动生成文档 schema
官方请求体文档明确把 request body 定义为客户端发送给 API 的数据体,并演示了把 Pydantic 模型作为 body 输入的标准写法。(FastAPI)
7. 更精细地声明参数来源:Query()、Path()、Body()、Header()、Cookie()、Form()、File()
虽然 FastAPI 能自动猜很多情况,但工程里你常常需要更明确的控制。这时就该用这些参数工具。官方参数参考文档明确列出了它们:Query()、Path()、Body()、Cookie()、Header()、Form()、File()。(FastAPI)
7.1 Query():给查询参数加约束
from typing import Annotated
from fastapi import Query
@app.get("/items/")
async def read_items(
q: Annotated[str | None, Query(min_length=2, max_length=20)] = None
):
return {"q": q}
这比只写 q: str | None = None 更强,因为你还能顺手加:
-
最小长度
-
最大长度
-
正则
-
描述
-
示例
-
alias
官方字符串查询参数验证文档说明,FastAPI 允许你给参数增加额外验证和元信息。(FastAPI)
7.2 Path():给路径参数加校验
from typing import Annotated
from fastapi import Path
@app.get("/items/{item_id}")
async def read_item(
item_id: Annotated[int, Path(gt=0, description="商品 ID,必须大于 0")]
):
return {"item_id": item_id}
官方文档说明,Path() 和 Query() 一样,也支持数值校验和元数据。(FastAPI)
7.3 Body():显式声明请求体
from typing import Annotated
from fastapi import Body
@app.post("/echo/")
async def echo(
message: Annotated[str, Body(embed=True)]
):
return {"message": message}
这在你不想用完整 Pydantic 模型,只想收一个简单 body 值时很好用。
7.4 Header():读取请求头
from typing import Annotated
from fastapi import Header
@app.get("/headers/")
async def read_headers(
user_agent: Annotated[str | None, Header()] = None
):
return {"user_agent": user_agent}
官方 Header 参数文档明确说明,Header 参数的声明方式和 Query、Path、Cookie 类似。(FastAPI)
7.5 Form() 和 File():处理表单和文件上传
from typing import Annotated
from fastapi import File, Form, UploadFile
@app.post("/upload/")
async def upload_file(
username: Annotated[str, Form()],
file: UploadFile = File(...)
):
return {"username": username, "filename": file.filename}
官方文档说明,表单与文件可以一起声明;要接收上传文件或表单数据,需要安装 python-multipart。另外,Form 必须显式写出来,否则 FastAPI 会把对应参数当成 query 参数或 JSON body 参数。(FastAPI)
8. FastAPI 路由函数的返回值与 response_model
很多新手只关注“怎么收参数”,但路由真正稳不稳,输出模型同样关键。
官方文档明确说明,FastAPI 可以通过函数返回类型注解,或通过装饰器上的 response_model,来:
-
验证返回数据
-
生成响应 JSON Schema
-
在自动文档里展示响应结构
-
按声明类型过滤输出字段(FastAPI)
最推荐的工程写法之一是:
from pydantic import BaseModel
class ItemOut(BaseModel):
id: int
name: str
price: float
@app.get("/items/{item_id}", response_model=ItemOut)
async def read_item(item_id: int):
data = {
"id": item_id,
"name": "Keyboard",
"price": 199.0,
"internal_cost": 120.0,
}
return data
这里即使你返回了 internal_cost,FastAPI 也会按 response_model=ItemOut 过滤掉它。这一点特别重要,像守门员一样把不该暴露的字段踢出去。官方文档明确写到:response_model 会用于文档、验证,以及把输出转换和过滤成声明的类型;如果同时写了返回类型和 response_model,后者优先。(FastAPI)
9. 路由配置:status_code、tags、summary、description
一个成熟接口不只是“能跑”,还要有好文档。
FastAPI 的 path operation 支持很多 OpenAPI 元信息配置,包括:
-
status_code -
tags -
summary -
description -
response_description -
dependencies
这些信息会进入 OpenAPI schema,并显示在 /docs 里。官方文档对 path operation configuration 和 advanced configuration 都有明确说明。(FastAPI)
示例:
@app.post(
"/users/",
status_code=201,
tags=["users"],
summary="创建用户",
description="创建一个新的用户账号,用户名必须唯一"
)
async def create_user():
return {"ok": True}
这样 Swagger UI 里接口就不会像没梳头一样乱糟糟。
10. async def 和 def 该怎么选
这是 FastAPI 里一个很常见的实际问题。官方 async 文档给出的建议很清楚:
-
如果你调用的是需要
await的异步库,就用async def -
如果你使用的是不支持
await的阻塞库,可以用普通def -
你可以在同一个应用里混用
def和async def -
FastAPI 会正确处理它们(FastAPI)
例如:
@app.get("/async-items/")
async def read_async_items():
# await 异步数据库/HTTP客户端
return {"mode": "async"}
@app.get("/sync-items/")
def read_sync_items():
# 调同步库
return {"mode": "sync"}
官方还说明:普通 def 的 path operation 会被放到外部线程池里执行,以避免阻塞服务器。(FastAPI)
工程上你可以这么记:
-
异步库 →
async def -
同步阻塞库 →
def -
不确定时,先看你调用的库是否要求
await
11. 依赖注入 Depends():FastAPI 路由的第二根脊梁
如果说“参数自动提取”是 FastAPI 的第一根脊梁,那 依赖注入 就是第二根。
官方文档把 dependency injection 定义为:路由函数声明自己需要什么,FastAPI 负责准备并注入这些依赖。(FastAPI)
例如:
from typing import Annotated
from fastapi import Depends, FastAPI
app = FastAPI()
def get_current_user():
return {"id": 1, "name": "Alice"}
@app.get("/me")
async def read_me(
user: Annotated[dict, Depends(get_current_user)]
):
return user
这意味着:
-
路由本身不负责“怎么拿当前用户”
-
这个逻辑被抽到了依赖函数里
-
路由只声明“我需要它”
这非常适合做:
-
登录态校验
-
数据库会话注入
-
配置对象注入
-
权限检查
-
公共分页参数
-
限流或审计逻辑
12. 如果依赖只想执行,不关心返回值怎么办
这也是路由里很常见的情况。比如你只想在访问某个接口前检查 token,但不需要把 token 值传进函数。
官方文档提供了两种方式:
方式 1:写成函数参数依赖
from typing import Annotated
from fastapi import Depends
def verify_token():
# 校验逻辑
return True
@app.get("/secure")
async def secure_api(
_: Annotated[bool, Depends(verify_token)]
):
return {"ok": True}
方式 2:写到装饰器 dependencies 里
from fastapi import Depends
@app.get("/secure", dependencies=[Depends(verify_token)])
async def secure_api():
return {"ok": True}
官方文档明确说明:当你不需要依赖返回值,只需要它被执行时,可以把它放在 path operation decorator 的 dependencies 列表里。(FastAPI)
13. APIRouter:项目一变大,别再把所有路由塞进一个文件
这几乎是 FastAPI 工程化里最重要的路由组织工具。
官方 APIRouter 文档明确说明:APIRouter 用于把 path operations 分组,例如为了把应用拆成多个文件;它最终会被包含进 FastAPI 应用或另一个 APIRouter。同时它还支持 prefix、tags、dependencies 等统一配置。(FastAPI)
13.1 基本示例
from fastapi import APIRouter
router = APIRouter(prefix="/users", tags=["users"])
@router.get("/")
async def list_users():
return [{"id": 1, "name": "Alice"}]
@router.get("/{user_id}")
async def get_user(user_id: int):
return {"id": user_id, "name": "Alice"}
然后在主应用里注册:
from fastapi import FastAPI
from .routers import users
app = FastAPI()
app.include_router(users.router)
这会得到:
-
GET /users/ -
GET /users/{user_id}
14. APIRouter 为什么特别适合大项目
因为它能把“模块边界”切清楚。
例如一个稍微正常点的项目,通常会长这样:
app/
├── main.py
├── routers/
│ ├── users.py
│ ├── items.py
│ └── auth.py
├── schemas/
│ ├── user.py
│ └── item.py
├── dependencies.py
└── core/
└── config.py
对应示意代码:
routers/users.py
from fastapi import APIRouter
router = APIRouter(prefix="/users", tags=["users"])
@router.get("/")
async def list_users():
return [{"id": 1, "name": "Alice"}]
routers/items.py
from fastapi import APIRouter
router = APIRouter(prefix="/items", tags=["items"])
@router.get("/")
async def list_items():
return [{"id": 101, "name": "Keyboard"}]
main.py
from fastapi import FastAPI
from app.routers import users, items
app = FastAPI(title="Demo API")
app.include_router(users.router)
app.include_router(items.router)
官方 Bigger Applications 文档就是围绕这种多文件组织方式展开的,并且建议把多个地方复用的依赖提取到单独模块里。(FastAPI)
15. 路由级、Router 级、应用级依赖怎么选
这是工程里非常实用的一层思维。
路由级依赖
只对单个接口生效。
@app.get("/private", dependencies=[Depends(verify_token)])
async def private_api():
return {"ok": True}
Router 级依赖
对某一组接口统一生效。
router = APIRouter(
prefix="/admin",
tags=["admin"],
dependencies=[Depends(verify_token)]
)
应用级依赖
全局生效。
APIRouter 官方参考中明确列出了 dependencies 参数既可以用于 router,也可用于 path operation;它们都会被应用到对应范围内的路由上。(FastAPI)
工程上可以这么选:
-
单接口特殊校验 → 路由级
-
同一模块共享认证 → Router 级
-
全局统一审计/追踪 → 应用级
16. 路由里如何注入配置对象
这和你前面在学的 Pydantic Settings 很搭。
FastAPI 官方 settings 文档推荐:把配置读取封装成依赖,并用 @lru_cache 缓存,这样不会每次请求都重新读取 .env。(FastAPI)
示意:
from functools import lru_cache
from typing import Annotated
from fastapi import Depends, FastAPI
app = FastAPI()
class Settings:
app_name = "demo"
@lru_cache
def get_settings():
return Settings()
@app.get("/info")
async def read_info(settings: Annotated[Settings, Depends(get_settings)]):
return {"app_name": settings.app_name}
这个模式很稳,测试时也好替换。
17. 自动文档为什么是 FastAPI 路由体系的一部分
很多人以为 /docs 只是附赠玩具,其实它和路由设计是同一套系统长出来的。
FastAPI 会根据你定义的:
-
路由路径
-
HTTP 方法
-
参数来源和类型
-
请求体模型
-
响应模型
-
tags / summary / description
-
status_code / responses
自动生成 OpenAPI schema,并提供 Swagger UI 和 ReDoc。官方文档明确说明:OpenAPI schema 是自动生成的,Swagger UI 默认在 /docs,ReDoc 默认在 /redoc。(FastAPI)
所以你每写一个路由,其实不是只写了一段处理逻辑,而是在同时写:
-
运行时行为
-
输入契约
-
输出契约
-
文档契约
这就是 FastAPI 让人上瘾的地方,挺像买一送三,但不是商场打折,是类型系统在干活。
18. 一个更完整的路由示例
下面给你一个比较像真实项目的例子,把你前面学的 Pydantic 和 FastAPI 路由串起来:
from functools import lru_cache
from typing import Annotated
from fastapi import APIRouter, Depends, FastAPI, Header, HTTPException, Path, Query, status
from pydantic import BaseModel, Field
app = FastAPI(title="Agent Backend Demo")
class Settings:
app_name: str = "agent-backend"
@lru_cache
def get_settings():
return Settings()
async def verify_token(x_token: Annotated[str | None, Header()] = None):
if x_token != "secret-token":
raise HTTPException(status_code=401, detail="invalid token")
class ItemCreate(BaseModel):
name: str = Field(min_length=2, max_length=50)
price: float = Field(gt=0)
class ItemOut(BaseModel):
id: int
name: str
price: float
router = APIRouter(
prefix="/items",
tags=["items"],
dependencies=[Depends(verify_token)]
)
@router.get("/", response_model=list[ItemOut], summary="获取商品列表")
async def list_items(
skip: Annotated[int, Query(ge=0)] = 0,
limit: Annotated[int, Query(ge=1, le=100)] = 10,
):
data = [
{"id": 1, "name": "Keyboard", "price": 199.0},
{"id": 2, "name": "Mouse", "price": 99.0},
]
return data[skip: skip + limit]
@router.get("/{item_id}", response_model=ItemOut, summary="获取单个商品")
async def get_item(
item_id: Annotated[int, Path(gt=0)],
settings: Annotated[Settings, Depends(get_settings)],
):
return {"id": item_id, "name": settings.app_name, "price": 88.0}
@router.post(
"/",
response_model=ItemOut,
status_code=status.HTTP_201_CREATED,
summary="创建商品"
)
async def create_item(item: ItemCreate):
return {"id": 101, "name": item.name, "price": item.price}
app.include_router(router)
这个例子里你能看到几乎所有主干能力:
-
FastAPI()创建应用 -
APIRouter()分组路由 -
prefix和tags -
路径参数
Path() -
查询参数
Query() -
Header 依赖校验
-
Pydantic 请求体模型
-
response_model -
status_code -
配置对象依赖注入
这些能力都来自官方教程和参考文档的主线组合。(FastAPI)
19. 初学 FastAPI 路由最容易踩的坑
坑 1:以为所有参数都是 body
不是。
FastAPI 会根据函数签名和类型判断参数来自哪里。路径里同名的是 path;不在路径里、又不是 Pydantic body 模型的,多半会被当成 query;显式用 Header()、Form()、File()、Body() 则按对应来源处理。(FastAPI)
坑 2:返回什么都行,不写 response_model
能跑,但不够稳。
不写输出模型,文档和响应过滤能力就少一大块,后续很容易把内部字段漏出去。(FastAPI)
坑 3:项目一大还把所有路由写在 main.py
这会把项目养成意大利面条怪。
路由多了就应该拆 APIRouter,再用 include_router() 组合。(FastAPI)
坑 4:明明是公共逻辑,却每个接口都手写一遍
这就是 Depends() 该出场的时候。认证、数据库 session、配置对象、分页规则、权限检查,都应该尽量抽成依赖。(FastAPI)
20. 你可以怎么记住 FastAPI 路由的设计哲学
最后给你一个非常实用的脑图式总结:
一条 FastAPI 路由,本质上是四层东西叠在一起
第一层:HTTP 映射
路径是什么,方法是什么。(FastAPI)
第二层:输入契约
参数从哪里来,类型是什么,怎么校验。(FastAPI)
第三层:业务执行
真正的 Python 函数逻辑。这个部分是你自己的代码。
第四层:输出契约和文档
返回值长什么样,状态码是多少,文档怎么展示。(FastAPI)
把这四层看懂,你再看 FastAPI 路由,就不是“几个装饰器和几个参数”,而是一套非常完整的 API 声明系统。
更多推荐



所有评论(0)