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️⃣ 基本使用流程
- 建立子路由檔案(如
users.py)並定義APIRouter。 - 在子路由中寫入端點。
- 在主程式 (
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,確保行為一致。 |
最佳實踐:
- 檔案結構:
app/ ├─ main.py ├─ routers/ │ ├─ __init__.py │ ├─ users.py │ ├─ items.py │ └─ v2/ │ └─ users.py └─ dependencies/ └─ auth.py - 統一命名:子路由變數統一命名為
router,方便include_router()時直接使用module.router。 - 前綴與版本:使用
prefix="/api/v1"、prefix="/api/v2"讓版本管理更直觀。 - 文件說明:在每個 router 檔案最上方加入模組說明與路由概覽,提升可維護性。
- 自動載入:若專案較大,可寫一段程式在
main.py中自動遍歷routers目錄並載入所有 router,減少手動include_router()的工作量。
實際應用場景
電商平台:
routers/products.py處理商品 CRUD。routers/orders.py處理訂單流程,並在include_router()時加上prefix="/orders"與驗證依賴。routers/users/內分別有auth.py(登入註冊)與profile.py(個人資料),透過子路由分層管理。
微服務架構:
- 各微服務本身都是一個 FastAPI 應用,但在 API Gateway 中可利用
include_router()把多個服務的路由匯聚成單一入口,方便統一文件與授權。
- 各微服務本身都是一個 FastAPI 應用,但在 API Gateway 中可利用
內部管理系統:
- 需要 多語系、權限分層,可在
include_router()時使用dependencies=[Depends(get_current_user)],讓所有子路由自動套用權限檢查。
- 需要 多語系、權限分層,可在
測試驅動開發 (TDD):
- 每個功能模組都有自己的 router,測試時只載入該 router,確保測試速度快且不受其他功能影響。
總結
include_router() 是 FastAPI 提供的 路由模組化 機制,透過 APIRouter 我們可以:
- 把功能切分成獨立檔案,提升可讀性與維護性。
- 一次性設定路徑前綴、標籤與共用依賴,讓 OpenAPI 文件更清晰。
- 支援版本管理與多層路由,在升版或微服務整合時不會產生混亂。
- 簡化測試,只載入需要的 router 即可完成單元測試。
在實務開發中,建議從專案開始就採用 router 結構,配合上述最佳實踐與常見陷阱的避免方式,能讓你的 FastAPI 專案在規模擴大時仍保持乾淨、易於擴充。快把 include_router() 實踐在你的下一個 API 專案中,體驗更高效的開發流程吧!