FastAPI 路由(Routing)
主題:路由命名(name 屬性)
簡介
在 FastAPI 中,每一條路由(endpoint)都是一個可被呼叫的函式。除了 HTTP 方法與路徑之外,FastAPI 允許我們為路由 指定 name 屬性,這看似簡單卻能在大型專案中帶來巨大的好處。
- 透過
name,我們可以在 URL 產生、文件生成、測試 與 權限驗證 等環節取得一致且可讀的標識。 - 當路徑或參數變更時,只要
name不變,其他使用該名稱產生 URL 的程式碼仍能正常運作,降低重構成本。
本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,逐步帶你掌握 name 屬性的使用方式,讓你的 FastAPI 應用更具可維護性與可讀性。
核心概念
1. name 是什麼?
在 FastAPI 中,路由的 name 是 一個字串標識,用於在應用內部引用該路由。它會被 APIRouter、FastAPI 以及 OpenAPI 文件所保存。
@app.get("/items/{item_id}", name="read-item")
async def read_item(item_id: int):
return {"item_id": item_id}
重點:
name不會影響 HTTP 回傳內容,只是 內部的別名。如果未提供,FastAPI 會自動使用函式名稱作為name。
2. 為什麼要自訂 name?
| 場景 | 使用 name 的好處 |
|---|---|
產生 URL(url_path_for) |
只要提供 name,即可取得正確路徑,無需硬編碼字串。 |
| OpenAPI 文件 | name 會顯示在 Swagger UI 的「Operation ID」欄位,提升文件可讀性。 |
| 測試 | 測試時可直接以 name 呼叫路由,避免路徑變動導致測試失效。 |
| 權限系統 | 以 name 為鍵存放權限資訊,讓授權邏輯更抽象。 |
3. url_path_for 的使用
FastAPI 提供 request.url_for(name, **path_params) 以及 app.url_path_for(name, **path_params) 兩種方式,根據不同情境選擇:
from fastapi import FastAPI, Request
app = FastAPI()
@app.get("/", name="home")
async def home():
return {"msg": "Welcome"}
@app.get("/items/{item_id}", name="read-item")
async def read_item(item_id: int):
return {"item_id": item_id}
@app.get("/redirect")
async def redirect(request: Request):
# 產生 read-item 的完整 URL
item_url = request.url_for("read-item", item_id=42)
return {"redirect_to": str(item_url)}
4. 在 APIRouter 中使用 name
當專案拆分成多個 router 時,name 仍然保持全局唯一性(建議加上前綴):
from fastapi import APIRouter
router = APIRouter(prefix="/users", tags=["users"])
@router.get("/", name="users:list")
async def list_users():
return [{"id": 1, "name": "Alice"}]
@router.get("/{user_id}", name="users:get")
async def get_user(user_id: int):
return {"id": user_id, "name": "Bob"}
在主程式中掛載:
app.include_router(router)
使用時仍可透過 app.url_path_for("users:get", user_id=5) 取得正確路徑。
5. name 與 OpenAPI Operation ID
FastAPI 會把 name 作為 operationId,這在自動產生前端 SDK 時相當重要。若想自行控制 operationId,可在 FastAPI(..., openapi_tags=...) 中設定 operation_id,但大多數情況只要給好 name 即可。
程式碼範例
以下提供 5 個實用範例,涵蓋從基礎到進階的應用情境。每段程式碼均附有說明註解。
範例 1:最簡單的 name 用法
from fastapi import FastAPI
app = FastAPI()
@app.get("/ping", name="health-check")
async def ping():
"""健康檢查端點,僅回傳 'pong'。"""
return {"msg": "pong"}
在 Swagger UI 中,health-check 會成為 operationId,方便前端產生器辨識。
範例 2:使用 url_path_for 產生動態 URL
from fastapi import FastAPI, Request
app = FastAPI()
@app.get("/users/{username}", name="user-profile")
async def user_profile(username: str):
return {"user": username}
@app.get("/link-to-profile")
async def link(request: Request):
# 產生指向 'user-profile' 的 URL,username 為動態參數
profile_url = request.url_for("user-profile", username="alice")
return {"profile_url": str(profile_url)}
即使未來將 /users/{username} 改為 /members/{username},只要 name 不變,link 端點仍能正確產生新 URL。
範例 3:在 APIRouter 中統一命名規則
from fastapi import APIRouter, FastAPI
router = APIRouter(prefix="/products", tags=["products"])
@router.get("/", name="products:list")
async def list_products():
return [{"id": 1, "name": "Keyboard"}]
@router.get("/{product_id}", name="products:detail")
async def product_detail(product_id: int):
return {"id": product_id, "name": "Keyboard"}
app = FastAPI()
app.include_router(router)
# 產生 detail 路由的 URL
@app.get("/demo-url")
async def demo_url(request):
url = request.url_for("products:detail", product_id=1)
return {"url": str(url)}
使用 : 作為前綴分隔,可避免名稱衝突,且在大型專案中更易辨識。
範例 4:結合權限系統(以 name 為 key)
from fastapi import FastAPI, Depends, HTTPException, status
app = FastAPI()
# 假設的權限字典
PERMISSIONS = {
"admin:dashboard": ["admin"],
"user:profile": ["admin", "user"],
}
def check_permission(name: str, role: str):
allowed = PERMISSIONS.get(name, [])
if role not in allowed:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Permission denied",
)
def get_current_role():
# 這裡簡化為固定返回 'user'
return "user"
@app.get("/admin", name="admin:dashboard")
async def admin_dashboard(role: str = Depends(get_current_role)):
check_permission("admin:dashboard", role)
return {"msg": "Welcome to admin dashboard"}
@app.get("/profile", name="user:profile")
async def user_profile(role: str = Depends(get_current_role)):
check_permission("user:profile", role)
return {"msg": f"Hello, {role}"}
只要 name 保持不變,即可在權限表中直接對應,避免硬編碼路徑。
範例 5:測試中使用 name 取得路由
from fastapi.testclient import TestClient
from main import app # 假設上述程式碼在 main.py
client = TestClient(app)
def test_user_detail_url():
# 透過 name 產生 URL
url = app.url_path_for("users:get", user_id=10)
response = client.get(str(url))
assert response.status_code == 200
assert response.json()["id"] == 10
測試不再依賴硬寫的 /users/10,若路徑改為 /members/10,只要 name 不變,測試仍能通過。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解法 |
|---|---|---|
| 名稱重複 | 多個路由使用相同 name 會導致 url_path_for 找不到正確路徑。 |
為每個 router 加上前綴(如 users:, products:),或在專案初期制定命名規範。 |
忘記更新 name |
改變路徑卻忘記檢查 name 是否仍具意義。 |
重構時,使用 IDE 全域搜尋 name=,確保語意仍符合功能。 |
| 過長或過於抽象 | name 太長或過於抽象,閱讀時不易辨識。 |
建議使用 <domain>:<action>(例如 orders:create)的簡潔格式。 |
| 在路由內部硬編碼 URL | 直接寫 /users/1 而非使用 url_path_for。 |
永遠 以 request.url_for 或 app.url_path_for 產生 URL,保持彈性。 |
| 忽略 OpenAPI 的影響 | 未注意 name 會成為 operationId,導致前端 SDK 產生衝突。 |
若需要自訂 operationId,使用 @router.get(..., operation_id="..."),但仍保持 name 的一致性。 |
最佳實踐
- 統一命名規則:
<module>:<verb>(如items:list、items:create)。 - 保持唯一性:跨 router 時使用前綴或命名空間。
- 使用
url_path_for:任何需要產生 URL 的地方,都以name為基礎。 - 文件同步:在 OpenAPI 文件或 README 中列出
name與對應功能,方便新成員快速了解。 - 測試覆蓋:在單元測試中使用
name產生路徑,確保未來路徑變更不會破壞測試。
實際應用場景
多語系網站
- 前端根據使用者語言切換不同的路由名稱,後端只需要提供一致的
name,前端透過url_path_for產生正確的本地化 URL。
- 前端根據使用者語言切換不同的路由名稱,後端只需要提供一致的
微服務 API Gateway
- API Gateway 只需要知道每條路由的
name,即可根據設定轉發請求,無需硬編碼微服務的具體路徑。
- API Gateway 只需要知道每條路由的
動態文件產生
- 透過
name生成 PDF、CSV 等檔案的下載連結,確保即使路徑改變,檔案連結仍正確。
- 透過
權限與 RBAC 系統
- 權限表以
name為鍵,對應角色與操作,讓授權邏輯與路由定義解耦。
- 權限表以
自動產生前端 SDK
- 使用 OpenAPI Generator 時,
name會變成函式名稱,讓前端開發者直接呼叫client.usersGet()之類的 API。
- 使用 OpenAPI Generator 時,
總結
name屬性 是 FastAPI 中一個低調卻強大的功能,讓路由具備可辨識的別名。- 透過
name,我們可以 安全產生 URL、提升 OpenAPI 文件可讀性、簡化測試與授權,同時降低因路徑變更帶來的維護成本。 - 為了避免衝突與混亂,建議在專案初期 制定統一的命名規則,並在所有需要產生 URL 的地方 使用
url_path_for。
掌握了路由命名後,你的 FastAPI 應用將更具彈性、可讀性與可維護性,無論是小型原型還是企業級微服務,都能從中受益。祝開發順利,快去為你的 API 加上清晰的名字吧!