本文 AI 產出,尚未審核

FastAPI 路由(Routing)

主題:路由命名(name 屬性)


簡介

FastAPI 中,每一條路由(endpoint)都是一個可被呼叫的函式。除了 HTTP 方法與路徑之外,FastAPI 允許我們為路由 指定 name 屬性,這看似簡單卻能在大型專案中帶來巨大的好處。

  • 透過 name,我們可以在 URL 產生文件生成測試權限驗證 等環節取得一致且可讀的標識。
  • 當路徑或參數變更時,只要 name 不變,其他使用該名稱產生 URL 的程式碼仍能正常運作,降低重構成本。

本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,逐步帶你掌握 name 屬性的使用方式,讓你的 FastAPI 應用更具可維護性與可讀性。


核心概念

1. name 是什麼?

在 FastAPI 中,路由的 name一個字串標識,用於在應用內部引用該路由。它會被 APIRouterFastAPI 以及 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 的好處
產生 URLurl_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_forapp.url_path_for 產生 URL,保持彈性。
忽略 OpenAPI 的影響 未注意 name 會成為 operationId,導致前端 SDK 產生衝突。 若需要自訂 operationId,使用 @router.get(..., operation_id="..."),但仍保持 name 的一致性。

最佳實踐

  1. 統一命名規則<module>:<verb>(如 items:listitems:create)。
  2. 保持唯一性:跨 router 時使用前綴或命名空間。
  3. 使用 url_path_for:任何需要產生 URL 的地方,都以 name 為基礎。
  4. 文件同步:在 OpenAPI 文件或 README 中列出 name 與對應功能,方便新成員快速了解。
  5. 測試覆蓋:在單元測試中使用 name 產生路徑,確保未來路徑變更不會破壞測試。

實際應用場景

  1. 多語系網站

    • 前端根據使用者語言切換不同的路由名稱,後端只需要提供一致的 name,前端透過 url_path_for 產生正確的本地化 URL。
  2. 微服務 API Gateway

    • API Gateway 只需要知道每條路由的 name,即可根據設定轉發請求,無需硬編碼微服務的具體路徑。
  3. 動態文件產生

    • 透過 name 生成 PDF、CSV 等檔案的下載連結,確保即使路徑改變,檔案連結仍正確。
  4. 權限與 RBAC 系統

    • 權限表以 name 為鍵,對應角色與操作,讓授權邏輯與路由定義解耦。
  5. 自動產生前端 SDK

    • 使用 OpenAPI Generator 時,name 會變成函式名稱,讓前端開發者直接呼叫 client.usersGet() 之類的 API。

總結

  • name 屬性 是 FastAPI 中一個低調卻強大的功能,讓路由具備可辨識的別名。
  • 透過 name,我們可以 安全產生 URL提升 OpenAPI 文件可讀性簡化測試與授權,同時降低因路徑變更帶來的維護成本。
  • 為了避免衝突與混亂,建議在專案初期 制定統一的命名規則,並在所有需要產生 URL 的地方 使用 url_path_for

掌握了路由命名後,你的 FastAPI 應用將更具彈性、可讀性與可維護性,無論是小型原型還是企業級微服務,都能從中受益。祝開發順利,快去為你的 API 加上清晰的名字吧!