本文 AI 產出,尚未審核

FastAPI 教學:include_router() 的使用與實務應用


簡介

在開發大型 API 時,路由的組織與維護往往是最容易產生混亂的地方。若所有端點都寫在同一個 main.py,隨著功能增多,檔案會變得又長又難以閱讀,測試與重構的成本也會急速上升。

FastAPI 為了解決這個問題,提供了 include_router() 這個強大的工具,讓我們可以把路由切割成多個 router 模組,然後在主應用程式中「注入」它們。這不僅提升了程式碼的可讀性,也讓 單元測試權限驗證版本管理 等需求更容易落實。

本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,一路帶你掌握 include_router() 的使用技巧,並提供實務上的應用情境,讓你在專案中快速建立乾淨、可擴充的 API 結構。


核心概念

1️⃣ 什麼是 APIRouter

APIRouter 是 FastAPI 提供的路由容器,它本身就像是一個小型的 FastAPI 應用。你可以在裡面定義路徑、請求方法、依賴項、回應模型等,最後再把它 「包含」 到主應用 (FastAPI()) 中。

from fastapi import APIRouter

router = APIRouter()

2️⃣ 為什麼要使用 include_router()

  • 模組化:每個功能區塊(如使用者、商品、訂單)可以各自擁有自己的 router 檔案。
  • 重用性:同一個 router 可以在不同的 FastAPI 應用或測試環境中重複使用。
  • 前綴與標籤統一:透過 include_router() 可以一次性設定路徑前綴 (prefix) 與 OpenAPI 標籤 (tags)。
  • 依賴注入:在 include_router() 時可以為整個子路由套用共用的依賴(例如驗證、DB 連線)。

3️⃣ 基本使用流程

  1. 建立子路由檔案(如 users.py)並定義 APIRouter
  2. 在子路由中寫入端點
  3. 在主程式 (main.py) 中匯入子路由,使用 app.include_router() 注入。

以下示範完整流程,從檔案結構到程式碼說明。


程式碼範例

範例一:最簡單的 include_router()

目標:把「使用者」相關的 API 拆分到 routers/users.py,在 main.py 中引入。

project/
│
├─ main.py
└─ routers/
   └─ users.py

routers/users.py

# routers/users.py
from fastapi import APIRouter

router = APIRouter()

@router.get("/", tags=["users"])
async def list_users():
    """
    取得所有使用者列表
    """
    return [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]

@router.get("/{user_id}", tags=["users"])
async def get_user(user_id: int):
    """
    依 ID 取得單一使用者
    """
    return {"id": user_id, "name": f"User-{user_id}"}

main.py

# main.py
from fastapi import FastAPI
from routers import users  # 匯入子路由模組

app = FastAPI(title="Demo API")

# 使用 include_router 注入子路由,並設定路徑前綴
app.include_router(users.router, prefix="/users", tags=["Users"])

重點prefix="/users" 讓子路由內的 "/" 變成 "/users/",而 tags 會在 OpenAPI 文件中自動分組。


範例二:共用依賴與中間件

目標:在 include_router() 時為整個子路由套用驗證依賴,避免在每個端點重複寫 Depends()

# routers/items.py
from fastapi import APIRouter, Depends, HTTPException, status

def verify_token(token: str = "dummy-token"):
    # 假設的驗證函式
    if token != "valid-token":
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
                            detail="Invalid token")
    return token

router = APIRouter(dependencies=[Depends(verify_token)])

@router.get("/", tags=["items"])
async def list_items():
    return [{"id": 1, "name": "Item A"}, {"id": 2, "name": "Item B"}]
# main.py
from fastapi import FastAPI
from routers import items

app = FastAPI()

# 直接把子路由注入,依賴已在子路由內設定
app.include_router(items.router, prefix="/items")

技巧:使用 APIRouter(dependencies=[...]) 可以讓所有路由自動套用同一個依賴,讓程式碼更乾淨。


範例三:版本管理與多層路由

目標:同時支援 API v1 與 v2,且 v2 繼承 v1 的部分路由,示範如何使用多層 include_router()

# routers/v1/users.py
from fastapi import APIRouter

router = APIRouter()

@router.get("/", tags=["v1-users"])
async def list_users_v1():
    return [{"id": 1, "name": "Alice (v1)"}]
# routers/v2/users.py
from fastapi import APIRouter
from routers.v1 import users as v1_users

router = APIRouter()

# 包含 v1 的路由,保持相容
router.include_router(v1_users.router, tags=["v2-users"])

@router.get("/profile/{user_id}", tags=["v2-users"])
async def user_profile(user_id: int):
    return {"id": user_id, "profile": f"Profile of user {user_id} (v2)"}
# main.py
from fastapi import FastAPI
from routers.v1 import users as v1_users
from routers.v2 import users as v2_users

app = FastAPI()

# V1 路由
app.include_router(v1_users.router, prefix="/api/v1/users", tags=["V1 Users"])

# V2 路由(包含 V1 的相容路由)
app.include_router(v2_users.router, prefix="/api/v2/users", tags=["V2 Users"])

實務意義:當 API 必須升版卻又要保留舊版相容性時,使用 多層 include_router() 能讓新舊路由共存,且維護成本低。


範例四:測試子路由的獨立性

目標:在單元測試時,只載入需要測試的 router,避免啟動整個應用。

# test_items.py
from fastapi.testclient import TestClient
from routers.items import router as items_router
from fastapi import FastAPI

app = FastAPI()
app.include_router(items_router, prefix="/items")
client = TestClient(app)

def test_list_items():
    response = client.get("/items/")
    assert response.status_code == 200
    assert isinstance(response.json(), list)

好處:測試只聚焦於 items 模組,速度更快且不會因其他路由的變更而失效。


常見陷阱與最佳實踐

陷阱 說明 解決方式
忘記設定 prefix 子路由的路徑會直接掛在根目錄,導致 URL 重複或混亂。 include_router() 時明確指定 prefix,或在子路由內使用相對路徑。
重複的 tags 同一個端點在 OpenAPI 中會顯示多次,影響文件可讀性。 只在 include_router() 或路由裝飾器中設定一次 tags,避免重複。
依賴衝突 子路由內部與 include_router() 同時設定 dependencies,可能導致兩次驗證。 決定哪一層負責依賴,盡量只在一個地方設定。
循環匯入 多層 router 互相 include_router() 時,若檔案相互 import,會出現循環引用錯誤。 使用 延遲匯入(在函式內部 import)或將共用 router 放在獨立模組。
測試環境與實務環境不一致 測試時直接使用 TestClient(app),但忘了在 app 中包含所有 router。 為測試建立專屬的 FastAPI 實例,只載入需要的 router,確保行為一致。

最佳實踐

  1. 檔案結構
    app/
    ├─ main.py
    ├─ routers/
    │  ├─ __init__.py
    │  ├─ users.py
    │  ├─ items.py
    │  └─ v2/
    │     └─ users.py
    └─ dependencies/
       └─ auth.py
    
  2. 統一命名:子路由變數統一命名為 router,方便 include_router() 時直接使用 module.router
  3. 前綴與版本:使用 prefix="/api/v1"prefix="/api/v2" 讓版本管理更直觀。
  4. 文件說明:在每個 router 檔案最上方加入模組說明與路由概覽,提升可維護性。
  5. 自動載入:若專案較大,可寫一段程式在 main.py 中自動遍歷 routers 目錄並載入所有 router,減少手動 include_router() 的工作量。

實際應用場景

  1. 電商平台

    • routers/products.py 處理商品 CRUD。
    • routers/orders.py 處理訂單流程,並在 include_router() 時加上 prefix="/orders" 與驗證依賴。
    • routers/users/ 內分別有 auth.py(登入註冊)與 profile.py(個人資料),透過子路由分層管理。
  2. 微服務架構

    • 各微服務本身都是一個 FastAPI 應用,但在 API Gateway 中可利用 include_router() 把多個服務的路由匯聚成單一入口,方便統一文件與授權。
  3. 內部管理系統

    • 需要 多語系權限分層,可在 include_router() 時使用 dependencies=[Depends(get_current_user)],讓所有子路由自動套用權限檢查。
  4. 測試驅動開發 (TDD)

    • 每個功能模組都有自己的 router,測試時只載入該 router,確保測試速度快且不受其他功能影響。

總結

include_router() 是 FastAPI 提供的 路由模組化 機制,透過 APIRouter 我們可以:

  • 把功能切分成獨立檔案,提升可讀性與維護性。
  • 一次性設定路徑前綴、標籤與共用依賴,讓 OpenAPI 文件更清晰。
  • 支援版本管理與多層路由,在升版或微服務整合時不會產生混亂。
  • 簡化測試,只載入需要的 router 即可完成單元測試。

在實務開發中,建議從專案開始就採用 router 結構,配合上述最佳實踐與常見陷阱的避免方式,能讓你的 FastAPI 專案在規模擴大時仍保持乾淨、易於擴充。快把 include_router() 實踐在你的下一個 API 專案中,體驗更高效的開發流程吧!