FastAPI 大型專案結構與 APIRouter

當你的 FastAPI 應用程式越來越大,將所有的程式碼都塞在一個 main.py 裡是不可行的。你需要將程式碼拆分成多個檔案,並組織成模組。

FastAPI 提供了 APIRouter,讓你能夠像主 app 一樣定義路由,最後再將它們「掛載」到主程式上。

推薦的目錄結構

以下是一個典型的大型 FastAPI 專案結構範例:

.
├── app
│   ├── __init__.py
│   ├── main.py           # 程式入口
│   ├── dependencies.py   # 共用的依賴項
│   └── routers           # 路由模組
│       ├── __init__.py
│       ├── items.py      # Items 相關的路徑操作
│       └── users.py      # Users 相關的路徑操作
├── requirements.txt
└── .env

使用 APIRouter

讓我們來看看 app/routers/users.py 是如何寫的:

from fastapi import APIRouter

# 建立一個 Router
# prefix: 這底下所有路由的前綴路徑
# tags: 在 Swagger UI 中用來分類的標籤
# responses: 設定通用的錯誤回應
router = APIRouter(
    prefix="/users",
    tags=["users"],
    responses={404: {"description": "Not found"}},
)

@router.get("/")
async def read_users():
    return [{"username": "Rick"}, {"username": "Morty"}]

@router.get("/me")
async def read_user_me():
    return {"username": "fakecurrentuser"}

@router.get("/{username}")
async def read_user(username: str):
    return {"username": username}

注意到了嗎?這裡我們使用 @router.get(...) 而不是 @app.get(...)

在主程式中掛載 Router

接下來,在 app/main.py 中,我們需要將這些 Router 匯入並加入到主 app 中。

from fastapi import FastAPI
from .routers import users, items

app = FastAPI()

# 使用 include_router 將定義好的 router 加入
app.include_router(users.router)
app.include_router(items.router)

@app.get("/")
async def root():
    return {"message": "Hello Bigger Applications!"}

include_router 的進階用法

include_router 不只是把路由加進來,它還能幫你做「批次化配置」。這在管理數十個甚至上百個路由時非常強大。

1. 網址前綴堆疊 (Prefix Stacking)

如果你在 APIRouter 中定義了 prefix="/users",而在 main.pyinclude_router 中又定義了 prefix="/api",則最終的網址會自動堆疊:

# app/routers/users.py
router = APIRouter(prefix="/info")

@router.get("/me") # 原始路徑是 /info/me
...

# app/main.py
app.include_router(users.router, prefix="/users")

最終造訪路徑會變成:http://localhost:8000/users/info/me

2. 繼承標籤與錯誤回應

你可以透過 include_router 為特定的路徑分組統一加上 Swagger 標籤,這比在每個路由上個別加 tags 方便得多:

app.include_router(
    items.router,
    prefix="/items",
    tags=["Items 管理系統"],
    responses={418: {"description": "I'm a teapot"}},
)

3. 全域路由驗證 (Dependencies)

這是最具威力的用法:你可以直接為整個 Router 設定依賴項,常見於「特定區塊需要登入」的場景。

# 只有管理員可以存取 admin 路由下的所有路徑
app.include_router(
    admin.router,
    prefix="/admin",
    dependencies=[Depends(verify_admin_access)]
)

為什麼要這樣設計?

  1. 關注點分離 (SoC):路由模組 (routers) 只需專注於業務邏輯的定義,而權限、前綴與分類則由主程式 (main.py) 統一控管。
  2. 減少重複 (DRY):你不需要在 50 個路由中重複寫「驗證 Token」的依賴項,只需在 include_router 設定一次。
  3. 靈活性:如果你想更改整個 /items 模組的 URL 前綴,只需動 main.py 的一行程式碼。

開發小撇步:避免循環引用 (Circular Imports)

在大型專案中,循環引用 是新手的惡夢(例如 A 匯入 B,B 又匯入 A)。

最佳實踐:

  • app = FastAPI() 留在 main.py 中。
  • routers 下的各個檔案永遠不要匯入 appmain.py
  • routers 只匯入 APIRouter 來定義邏輯。
  • 最後由 main.py 統一匯入 routers 並掛載。

這樣就能確保依賴鏈是單向的:main.py -> routers -> models/schemas

總結

  • 使用 APIRouter 將路由邏輯拆分到不同的模組中。
  • 使用 app.include_router() 將子路由掛載到主應用程式。
  • 善用 prefix, tags, dependencies 的堆疊與繼承特性。
  • 保持清晰的目錄結構對於專案的可維護性至關重要。