首页 / FastAPI 入门教程 / 大型应用结构

FastAPI 入门教程

大型应用结构

本教程共 50 篇 · 第 45 篇 · 更新于 2026-08-12 · 约 7 分钟阅读

FastAPIFastAPI 入门教程APIRouter应用结构模块化include_router

本节目标:当接口越来越多时,学会用 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

prefixtagsresponses 放在 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.pyusers.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 还能在挂载时补充 prefixtagsdependencies,即使被挂载的 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 两套路径。这是进阶用法,多数项目用不到,知道有这个能力即可。

Note

APIRouter 不是「挂载隔离」的,它会被并入主应用,所以接口会出现在 OpenAPI 文档和交互界面里。FastAPI 会保留原 router,并合并前缀、依赖、标签等元数据。

45-10 测试时怎么组织

结构拆好之后,测试文件也跟着分模块更清晰。比如 app/routers/test_users.py 专测用户接口,app/test_main.py 测主应用。它们都用相对导入拿 app,再用 TestClient(app) 发请求(测试章节会细讲)。文件结构清晰,测试也好定位。

45-11 小结

接口多了就用 APIRouter 分组到不同文件,用 prefixtags 统一配置,再用 app.include_router() 拼回主应用。用 __init__.py 组织成包,用 ... 做相对导入。入口写在 pyproject.toml 里,运行更省事。

45-12 不必过早拆分

提醒一句:不是项目一开始就要拆成多文件。接口只有三五个时,单文件 main.py 反而最直观。等接口明显变多、一个文件超过两三百行、或多人要同时改不同模块时,再按「用户」「物品」「订单」等维度拆成 routers/ 下的子模块。拆分是为了可读和可协作,别为了架构而架构。

真要拆的时候,按「业务领域」切比按「技术分层」切更好用。也就是说,优先分成用户、订单、商品这样的模块,而不是把所有模型塞进一个 models.py、所有路由塞进一个 routers.py。前者改一个功能只动一个目录,后者改一个功能要在好几个大文件之间来回跳。判断标准很朴素:改动某个需求时,你需要打开的文件越少,结构就越合理。