本文 AI 產出,尚未審核

FastAPI - API 文件(OpenAPI / Swagger / ReDoc)

主題:自訂 OpenAPI Schema


簡介

在使用 FastAPI 建立 RESTful 服務時,框架會自動產生符合 OpenAPI(舊稱 Swagger)規範的文件。這份文件不僅讓前端、測試或第三方開發者能即時瀏覽與測試 API,還能直接匯出給 API 管理平台、API 閘道或自動產生 SDK。

然而,預設產生的 Schema 常常只能滿足「基本」的需求;在實務專案裡,我們常需要 補充說明、調整欄位型別、加入自訂的驗證規則或安全機制,才能讓文件真正反映業務邏輯。
本篇文章將從 概念、實作、常見陷阱與最佳實踐,一步步帶你完成 OpenAPI Schema 的自訂,讓你的 API 文件既完整易於維護


核心概念

1. OpenAPI 與 FastAPI 的關係

  • OpenAPI:一套描述 HTTP API 的規範,定義路徑、請求/回應結構、認證方式等。
  • FastAPI:在啟動時會自動根據路由、Pydantic 模型與型別提示產生符合 OpenAPI 3.0 的 JSON(或 YAML)文件,並提供 Swagger UI 以及 ReDoc 兩種前端介面。

重點:FastAPI 只會根據 程式碼 產生文件,若程式碼中缺少說明或型別資訊,文件也會缺少相應內容。

2. 為什麼需要自訂 Schema

場景 預設行為 需要自訂的原因
多語系錯誤訊息 只顯示英文 detail 必須提供中文說明,提升使用者體驗
客製化欄位說明 只顯示欄位名稱 需要加入業務規則、格式範例
隱藏內部端點 全部端點皆曝光 某些測試或管理端點不應出現在公開文件
自訂安全機制 只支援 HTTP Basic 需要 OAuth2、API Key、JWT 等多種方式

3. FastAPI 提供的自訂入口

  1. app.openapi():覆寫此方法即可回傳自訂的 OpenAPI dict。
  2. app.openapi_schema:直接賦值一個已修改好的 dict,FastAPI 會直接使用它。
  3. APIRouterinclude_in_schema 參數:控制單一路由是否出現在文件中。
  4. Field(..., description="...")Schema(... ):在 Pydantic 模型層面提供欄位說明、範例、驗證。

程式碼範例

以下範例均以 Python 3.9+FastAPI 0.109 為基礎,使用 uvicorn 執行。

範例 1:最簡單的自訂 OpenAPI 標題與描述

from fastapi import FastAPI

app = FastAPI(
    title="我的購物車 API",
    version="1.0.0",
    description="提供商品 CRUD、下單與付款功能的 RESTful 服務。",
)

@app.get("/items/{item_id}")
async def read_item(item_id: int):
    """
    取得單一商品資訊
    """
    return {"item_id": item_id, "name": "範例商品"}

說明:直接在 FastAPI() 建構子裡設定 titleversiondescription,即可讓 Swagger UI 與 ReDoc 顯示自訂的文件資訊。

範例 2:在 Pydantic 模型中加入欄位說明與範例

from pydantic import BaseModel, Field

class OrderItem(BaseModel):
    product_id: int = Field(..., description="商品的唯一識別碼")
    quantity: int = Field(
        ...,
        gt=0,
        description="購買數量,必須大於 0",
        example=2,
    )
    note: str | None = Field(
        None,
        description="給商家的備註(可選)",
        max_length=200,
        example="請以禮物包裝",
    )

重點Fielddescription 會出現在 Swagger UI 的欄位說明,example 則會成為 範例值,讓測試者更容易填寫正確的資料。

範例 3:覆寫 app.openapi() 以加入全域安全機制

from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import APIKeyHeader

API_KEY_NAME = "X-My-Api-Key"
api_key_header = APIKeyHeader(name=API_KEY_NAME, auto_error=False)

def get_api_key(key: str = Depends(api_key_header)):
    if key != "secret-key":
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Invalid API Key",
        )
    return key

app = FastAPI()

# ---------- 自訂 OpenAPI ----------
def custom_openapi():
    if app.openapi_schema:
        return app.openapi_schema
    openapi_schema = app.original_openapi()
    # 加入全域安全定義
    security_scheme = {
        "ApiKeyAuth": {
            "type": "apiKey",
            "in": "header",
            "name": API_KEY_NAME,
        }
    }
    openapi_schema["components"]["securitySchemes"] = security_scheme
    # 讓所有路由預設使用此安全機制
    for path in openapi_schema["paths"].values():
        for operation in path.values():
            operation.setdefault("security", [{"ApiKeyAuth": []}])
    app.openapi_schema = openapi_schema
    return app.openapi_schema

app.original_openapi = app.openapi
app.openapi = custom_openapi
# -----------------------------------

@app.get("/secure-data", dependencies=[Depends(get_api_key)])
async def secure_data():
    return {"msg": "這是受保護的資源"}

說明

  1. 先保留原始 app.openapiapp.original_openapi),以免遺失 FastAPI 自動產生的基礎結構。
  2. components.securitySchemes 中加入自訂的 API Key 定義。
  3. 逐一把每個 operation(GET、POST…)的 security 欄位設為 ApiKeyAuth,讓 Swagger UI 自動顯示授權欄位。

範例 4:使用 include_in_schema=False 隱藏測試端點

@app.get("/health", include_in_schema=False)
async def health_check():
    """
    用於容器或負載平衡器的健康檢查,不應出現在公開文件中
    """
    return {"status": "ok"}

技巧:將 include_in_schema=False 加在路由裝飾器上,該端點仍可被呼叫,但不會出現在 Swagger 或 ReDoc。

範例 5:為回應加入自訂的描述與範例(使用 ResponseJSONResponse

from fastapi.responses import JSONResponse
from typing import Literal

class OrderResponse(BaseModel):
    order_id: int = Field(..., description="系統產生的訂單編號")
    status: Literal["created", "failed"] = Field(..., description="訂單狀態")
    message: str | None = Field(None, description="若失敗時的錯誤說明")

@app.post(
    "/orders",
    response_model=OrderResponse,
    responses={
        201: {
            "description": "訂單建立成功",
            "content": {
                "application/json": {
                    "example": {
                        "order_id": 12345,
                        "status": "created",
                        "message": None,
                    }
                }
            },
        },
        400: {
            "description": "參數驗證失敗",
            "content": {
                "application/json": {
                    "example": {"detail": "商品不存在"}
                }
            },
        },
    },
)
async def create_order(item: OrderItem):
    # 假設執行業務邏輯
    return JSONResponse(status_code=201, content={"order_id": 12345, "status": "created"})

重點responses 參數允許為每個 HTTP 狀態碼自訂說明與範例,極大提升文件的可讀性與測試便利性。


常見陷阱與最佳實踐

陷阱 可能的後果 解決方案或最佳實踐
忘記保留 app.original_openapi 自訂後失去 FastAPI 自動產生的路徑、模型資訊,文件變得不完整 在覆寫 app.openapi 前先 app.original_openapi = app.openapi,並在自訂函式中呼叫它
Field 中使用不支援的屬性(例如 example 只對 list 無效) Swagger UI 顯示空白或錯誤 依照 OpenAPI 規範確認屬性支援情況,必要時使用 schema_extra
安全機制只在 UI 中顯示,實際路由未加 Depends 前端測試時可通過 UI 授權,但真實呼叫仍會 403 同時在路由或全域 dependencies 中加入驗證函式
過度自訂導致文件難以維護 每次模型變更都需要手動同步文件 盡量利用 Pydantic 的自動說明與 example,僅在必要時才手動編寫 responses
隱藏端點忘記同步測試腳本 CI 測試仍會呼叫已隱藏的端點,產生 404 在測試套件中使用 include_in_schema=False 的端點時,直接呼叫其路徑,避免依賴自動生成的文件

最佳實踐小結

  1. 先以模型說明為主:所有欄位說明、範例、驗證規則盡量寫在 Pydantic 模型內。
  2. 集中管理全域安全:使用 app.openapi 統一注入 securitySchemes,避免每個路由重複寫。
  3. 分層自訂
    • 全域(title、version、security) → FastAPI() / app.openapi
    • 路由層級(include_in_schema、dependencies) → 裝飾器參數
    • 回應層級(responses、example) → @app.<method>(..., response_model=..., responses={...})
  4. 自動化測試:使用 TestClient 讀取 app.openapi(),比對關鍵欄位是否正確,確保文件與程式碼同步。

實際應用場景

  1. 企業內部微服務平台

    • 每個服務都有 OAuth2 授權、API Key 兩套安全機制。透過自訂 app.openapi,一次性在文件中加入兩種 securitySchemes,讓前端團隊只要在 Swagger UI 直接切換即可測試。
  2. 對外公開的 SaaS API

    • 為了降低使用門檻,需提供 多語系的錯誤說明。利用 responses 中的 exampledescription,在不同語系的 UI(Swagger/Redoc)中顯示對應文字。
  3. 金融或醫療系統

    • 某些端點僅限特定客戶端呼叫,必須 隱藏 在公開文件裡。使用 include_in_schema=False 隱藏測試或內部管理端點,同時在 CI 中仍保有測試。
  4. 自動產生 SDK

    • 多數 SDK 產生工具(如 OpenAPI Generator)會依據 OpenAPI JSON 生成程式碼。若文件缺少欄位說明或安全定義,生成的 SDK 會缺少必要的註解或驗證。自訂 Schema 後,SDK 可自動帶入完整的型別與驗證邏輯。

總結

FastAPI 讓我們只寫少量程式碼就能得到完整的 OpenAPI 文件,但實務上往往需要 自訂,才能滿足業務、法規或使用者體驗的需求。
本文從 概念實作(五個具體範例)以及 常見陷阱與最佳實踐,說明了如何:

  1. FastAPI() 建構子中設定全域資訊。
  2. 利用 PydanticField 為模型欄位加入說明與範例。
  3. 透過覆寫 app.openapi(),一次性注入全域 安全機制
  4. 使用 include_in_schema=False 隱藏不需要公開的端點。
  5. 為每個回應狀態碼自訂說明與範例,提升文件可讀性。

掌握這些技巧後,你的 API 文件將不再是「自動產生的草稿」,而是 完整、精準、符合業務需求 的開發與合作工具。未來在擴充微服務、發布 SDK,甚至遵循資安合規時,都能從這套自訂的 OpenAPI Schema 中受益。

祝你在 FastAPI 的開發旅程中,寫出 高品質、易維護 的 API 文件! 🚀