首页
/ FastAPI 子应用挂载实战:用 Mount 构建多套独立 OpenAPI 与文档 UI

FastAPI 子应用挂载实战:用 Mount 构建多套独立 OpenAPI 与文档 UI

2026-09-06 15:07:16作者:鲍丁臣Ursa

本篇指南基于 FastAPI 官方文档《Sub Applications - Mounts》,讲解如何通过 app.mount() 把一个独立的 FastAPI 子应用挂载到主应用下的某个路径前缀(如 /subapi),使两套应用各自拥有独立的 OpenAPI Schema 与独立的 Swagger UI 文档界面。读完后,你将掌握:多应用共存的路由组织方式、挂载后各应用文档 URL 的正确形态,以及底层 ASGI 规范中 root_path 机制如何自动为子应用注入挂载前缀,使其文档 UI、OpenAPI servers 字段均无需手动配置即可正确工作。

为什么要挂载子应用(Sub Applications)

当项目中确实需要两个相互独立的 FastAPI 应用——它们各自拥有独立的 OpenAPI Schema、独立的 Swagger UI / ReDoc 文档页面——而不是仅仅把一些路由 include_router 到同一个应用里时,可以使用一个"主应用 + 挂载一个或多个子应用"的结构。

"挂载"(Mount)的含义是:把一个完全独立的应用添加到一个特定的路径前缀下,从此该路径下的所有请求都由这个子应用内部声明的 path operations 来处理。与 APIRouter 不同,被挂载的对象是一个完整的 FastAPI 实例:它有自己的标题、元数据、中间件、异常处理、启动/关闭事件和文档路由,而不仅仅是路由表。

完整可运行示例

下面的代码取自仓库示例文件 tutorial001_py310.py,可直接复制到本地运行(要求 Python 3.10+ 与已安装的 FastAPI、Uvicorn):

from fastapi import FastAPI

app = FastAPI()


@app.get("/app")
def read_main():
    return {"message": "Hello World from main app"}


subapi = FastAPI()


@subapi.get("/sub")
def read_sub():
    return {"message": "Hello World from sub API"}


app.mount("/subapi", subapi)

整个示例分成三步,下面逐段拆解。

第一步:创建顶层主应用及其路由

先创建主应用 app,并为其声明属于主应用自己的 path operations

app = FastAPI()


@app.get("/app")
def read_main():
    return {"message": "Hello World from main app"}

这里主应用只声明了 /app 这一个路径操作。挂载子应用后,主应用的自动文档里只会出现 /app,不会混入子应用的路由。

第二步:创建子应用及其路由

子应用 subapi 就是另一个标准的 FastAPI 实例——你可以像创建任何应用一样创建它,只是稍后它会成为被"挂载"的那个:

subapi = FastAPI()


@subapi.get("/sub")
def read_sub():
    return {"message": "Hello World from sub API"}

注意 subapi 内部的路由声明为 /sub,而不是 /subapi/sub——你不需要在子应用里重复书写挂载前缀,前缀由挂载动作自动施加。

第三步:把子应用挂载到主应用

在顶层应用 app 中调用 app.mount("/subapi", subapi),将子应用挂载到路径 /subapi

app.mount("/subapi", subapi)

调用之后:

  • 主应用继续处理不属于 /subapi 前缀的请求(如 GET /app);
  • 所有以 /subapi 开头的请求都会被转发给 subapi 实例,由它内部的路由(如 /subapi/sub)处理;
  • 主应用和子应用各自的 /docs/redoc/openapi.json 路由互不干扰。

运行并查看两套自动 API 文档

启动服务(FastAPI CLI 会由 uv 管理虚拟环境并运行 Uvicorn):

$ uv run fastapi dev

INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

然后打开主应用文档 http://127.0.0.1:8000/docs,你会看到主应用的自动 API 文档,其中只包含它自己的 path operation/app):

主应用 /docs 只展示 /app 路由

再打开子应用的文档 http://127.0.0.1:8000/subapi/docs,会看到子应用自己的自动文档,其 path operation 全部带上了正确的子路径前缀 /subapi

子应用 /subapi/docs 展示带 /subapi 前缀的路由

分别在两个 UI 中尝试调用接口,二者都能正常工作,因为浏览器会分别与各自具体的应用/子应用通信,请求路径天然携带了对应前缀。

技术细节:ASGI 的 root_path 机制

上面文档 URL 自动带上 /subapi 前缀并不是巧合,其底层依赖 ASGI 规范定义的 root_path 机制。当你按上述方式挂载子应用时,FastAPI(经由 Starlette 的 Mount 层)会在转发请求时把挂载路径写入请求的 ASGI scope 中,即 scope["root_path"]。子应用读取到这个值后,就知道应为自己的文档 UI 使用该路径前缀。

从源码中可以印证这一点:

  • fastapi/applications.pysetup() 方法中,Swagger UI 路由处理器在每次请求时都会执行 root_path = req.scope.get("root_path", "").rstrip("/"),然后拼出 openapi_url = root_path + self.openapi_url 再生成 HTML。这就是子应用的 /subapi/docs 能正确指向 /subapi/openapi.json 的原因。
  • 同文件中(fastapi/applications.py),/openapi.json 的响应处理也会读取 root_path,并在 root_path_in_servers=True(默认值)时把它注入 OpenAPI Schema 的 servers 字段列表头部,使文档中的 "Try it out" 请求指向正确的前缀。
  • fastapi/routing.py 中,请求处理也会读取 request.scope.get("root_path", "") 作为挂载路径前缀参与端点上下文(如错误信息中的完整路径展示),说明 root_path 在路由层同样被感知。
  • 另外,FastAPI(root_path=...) 构造参数允许你显式设置根路径:在 fastapi/applications.py__call__ 中,若实例配置了 root_path,会直接写入 scope["root_path"]。这个参数面向"应用位于反向代理之后、代理吞掉了前缀"的场景,详见官方文档 Behind a Proxy。对于 mount 出来的子应用,root_path 由挂载层自动处理,无需手动设置。

由于 root_path 是在 ASGI scope 层逐层传递的,子应用自己也可以继续 mount 更深层的子应用,前缀会逐级叠加,一切照常工作——FastAPI 自动处理了所有这些层级的 root_path

使用建议与边界

  • 何时用 mount:当两个应用需要独立的 OpenAPI 输出、独立的文档页、独立的中间件/事件/标题(例如按业务域或按版本拆分的微服务进程内组合)时;如果只是想在同一应用里组织路由,优先使用 include_router
  • 路径声明不要重复前缀:子应用内部始终声明不带挂载前缀的路由(/sub 而非 /subapi/sub),前缀由 mountroot_path 机制自动完成。
  • 文档 URL 形态:主应用文档位于 http://host:port/docs,挂载在 /subapi 的子应用文档位于 http://host:port/subapi/docs,OpenAPI JSON 同理分别为 /openapi.json/subapi/openapi.json
  • 与显式 root_path 参数的区别:mount 时前缀来自 ASGI scope,自动且无需配置;FastAPI(root_path=...) 是为代理环境手动声明前缀的手段。两者可共存但场景不同,更多用法见 Behind a Proxy

相关仓库路径

登录后查看全文
热门项目推荐
相关项目推荐