大型应用结构
本教程共 50 篇 · 第 45 篇 · 更新于 2026-08-12 · 约 7 分钟阅读
本节目标:当接口越来越多时,学会用 APIRouter 把路由拆到多个文件,再用 include_router 组装起来,并掌握包、模块与相对导入的组织方式。
小项目可以把所有接口写在一个 main.py。但真实应用接口一多,单文件会又长又乱,改起来容易出错。FastAPI 提供 APIRouter 帮你把路由分组到不同文件,再用统一的方式拼回主应用。它相当于 Flask 里的 Blueprint。
45-1 一个典型的多文件结构
假设我们要做一个应用,有「用户」和「物品」两组接口,再附带一些内部管理员接口。可以这样组织:
.
├── app
│ ├── __init__.py
│ ├── main.py
│ ├── dependencies.py
│ ├── routers
│ │ ├── __init__.py
│ │ ├── items.py
│ │ └── users.py
│ └── internal
│ ├── __init__.py
│ └── admin.py
每个目录里的 __init__.py 是空文件,它的存在让这个目录变成 Python 的常规「包」,里面的 .py 文件变成「模块」,互相之间才能用相对导入。(Python 3.3 以后没有 __init__.py 也能作为命名空间包导入,但入门项目保留它更清晰,也能避免工具识别问题。)
app是顶层包app/main.py是主模块,里面创建FastAPI()实例app/routers/是子包,存放各路由模块app/routers/users.py是子模块,放用户相关接口
45-2 用 APIRouter 写一组路由
APIRouter 用法和 FastAPI 几乎一模一样,可以把它看成「迷你 FastAPI」。在 users.py 里这样写:
# app/routers/users.py
from fastapi import APIRouter
router = APIRouter()
@router.get("/users/")
async def read_users():
return [{"username": "张三"}, {"username": "李四"}]
@router.get("/users/{user_id}")
async def read_user(user_id: str):
return {"user_id": user_id}
注意这里没用 app = FastAPI(),而是用 router = APIRouter(),装饰器也换成 @router.get(...)。其余路径操作的写法完全不变。
45-3 给整组路由统一加前缀和标签
物品相关的接口路径都带 /items 前缀,且想统一打上 items 标签。与其在每个接口写一遍,不如在创建 APIRouter 时一次性指定:
# app/routers/items.py
from fastapi import APIRouter
router = APIRouter(
prefix="/items",
tags=["items"],
responses={404: {"description": "找不到"}},
)
@router.get("/")
async def read_items():
return [{"item": "苹果"}, {"item": "香蕉"}]
@router.get("/{item_id}")
async def read_item(item_id: str):
return {"item_id": item_id}
加 prefix="/items" 后,上面两个接口的实际路径变成 /items/ 和 /items/{item_id}。前缀结尾不要加斜杠。tags 会出现在自动文档里分组显示,responses 给这组接口补充统一的响应说明。
Tip把
prefix、tags、responses放在APIRouter上,能避免在每个接口重复写,是 FastAPI 帮你少写重复代码的小技巧。
45-4 模块之间用相对导入
items.py 里需要用到 dependencies.py 定义的依赖。两个文件不在同一层目录,要用相对导入:.. 表示「上一层包」。
# app/routers/items.py
from fastapi import APIRouter, Depends
from ..dependencies import get_token_header
router = APIRouter(
prefix="/items",
tags=["items"],
dependencies=[Depends(get_token_header)],
)
一个点 . 表示当前包,两个点 .. 表示父包。这里 ..dependencies 意思是:从父包 app 里找 dependencies 模块。三个点 ... 会继续往上层找,但这里 app 已经是顶层,用三个点会报错。
45-5 公共依赖放进独立模块
把多个地方都要用的依赖集中到 app/dependencies.py:
# app/dependencies.py
from fastapi import Header, HTTPException
async def get_token_header(x_token: str = Header(...)):
if x_token != "fake-token":
raise HTTPException(status_code=400, detail="X-Token 头无效")
return x_token
这样 items.py 和 users.py 都能引用它,改一处即全局生效。
再补上 app/internal/admin.py,它是一个带路由的模块(45-6 会被 include_router 挂载):
# app/internal/admin.py
from fastapi import APIRouter
router = APIRouter()
@router.post("/")
async def update_admin():
return {"message": "管理员更新成功"}
45-6 主应用里 include_router
关键一步在主文件 main.py:导入各路由模块,用 app.include_router() 把它们挂到主应用。
# app/main.py
from fastapi import Depends, FastAPI
from .dependencies import get_token_header
from .internal import admin
from .routers import items, users
app = FastAPI()
@app.get("/")
async def root():
return {"msg": "主应用根路径"}
app.include_router(users.router)
app.include_router(items.router)
app.include_router(
admin.router,
prefix="/admin",
tags=["admin"],
dependencies=[Depends(get_token_header)],
responses={418: {"description": "我是茶壶"}},
)
导入时用 from .routers import items, users 这种相对导入,导入的是整个子模块(不是只导入 router 变量),避免两个模块里都有同名 router 变量互相覆盖。
app.include_router(users.router) 把 users 模块里所有接口加进主应用。include_router 还能在挂载时补充 prefix、tags、dependencies,即使被挂载的 APIRouter 本身没写这些(比如 admin.py 是别人共享给你的,不能改)。
45-7 在 pyproject.toml 配置入口
应用拆成包后,入口文件是 app/main.py。建议在 pyproject.toml 里声明入口,省得每次敲命令都写路径:
[tool.fastapi]
entrypoint = "app.main:app"
这等价于 from app.main import app。之后直接运行:
fastapi dev
它会自动找到 app.main:app。VS Code 插件和 FastAPI Cloud 等工具也能据此定位你的应用。
Tip不配置入口也能跑:
fastapi dev app/main.py。但每次都要记路径。配了入口更省心,也利于其它工具识别你的应用。
45-8 运行并查看文档
启动后打开 http://127.0.0.1:8000/docs,你会看到所有子模块的路径都出现了,而且带正确的前缀和标签。include_router 几乎零性能开销,不会拖慢每个请求。
45-9 进阶:router 套 router、多次挂载
一个 APIRouter 还能被包含进另一个 APIRouter:
router.include_router(other_router)
同一个 router 也能用不同前缀挂载多次,比如同时暴露 /api/v1 和 /api/latest 两套路径。这是进阶用法,多数项目用不到,知道有这个能力即可。
NoteAPIRouter 不是「挂载隔离」的,它会被并入主应用,所以接口会出现在 OpenAPI 文档和交互界面里。FastAPI 会保留原 router,并合并前缀、依赖、标签等元数据。
45-10 测试时怎么组织
结构拆好之后,测试文件也跟着分模块更清晰。比如 app/routers/test_users.py 专测用户接口,app/test_main.py 测主应用。它们都用相对导入拿 app,再用 TestClient(app) 发请求(测试章节会细讲)。文件结构清晰,测试也好定位。
45-11 小结
接口多了就用 APIRouter 分组到不同文件,用 prefix、tags 统一配置,再用 app.include_router() 拼回主应用。用 __init__.py 组织成包,用 . 和 .. 做相对导入。入口写在 pyproject.toml 里,运行更省事。
45-12 不必过早拆分
提醒一句:不是项目一开始就要拆成多文件。接口只有三五个时,单文件 main.py 反而最直观。等接口明显变多、一个文件超过两三百行、或多人要同时改不同模块时,再按「用户」「物品」「订单」等维度拆成 routers/ 下的子模块。拆分是为了可读和可协作,别为了架构而架构。
真要拆的时候,按「业务领域」切比按「技术分层」切更好用。也就是说,优先分成用户、订单、商品这样的模块,而不是把所有模型塞进一个 models.py、所有路由塞进一个 routers.py。前者改一个功能只动一个目录,后者改一个功能要在好几个大文件之间来回跳。判断标准很朴素:改动某个需求时,你需要打开的文件越少,结构就越合理。