本文 AI 產出,尚未審核

FastAPI 路由 – responses 屬性(自訂回應狀態碼)


簡介

在 Web API 開發中,回傳的 HTTP 狀態碼不只是一種訊號,更是與前端、第三方服務或測試工具溝通的橋樑。FastAPI 內建的 responses 參數讓我們可以在路由函式上一次性宣告多種可能的回應,不僅提升文件自動產生(OpenAPI/Swagger)的完整度,也讓程式碼的可讀性與維護性大幅提升。

本篇文章將帶你從概念說明走到實作範例,最後分享常見陷阱與最佳實踐,讓你在 FastAPI 中自如地使用 responses自訂回應狀態碼,打造更符合業務需求的 API。


核心概念

1️⃣ responses 參數的定位

  • responsesFastAPI 路由裝飾器(@app.get@app.post…) 的可選參數。
  • 它接受 字典,鍵為 HTTP 狀態碼(int 或 str),值為 描述、模型或範例,最常見的結構如下:
{
    200: {"description": "成功回傳"},
    404: {"description": "找不到資源", "model": ErrorModel},
    422: {"description": "驗證失敗"},
}
  • 當路由回傳對應的狀態碼時,FastAPI 會自動套用 responses 中的描述與模型,Swagger UI 會顯示完整的回應格式。

2️⃣ 為什麼要使用 responses

場景 沒有 responses responses
API 文件生成 只能看到預設 200/422 每個自訂狀態碼都有說明與範例
前端開發者 必須自行查閱程式碼 直接在 Swagger 看到錯誤結構
單元測試 需要自行記錄錯誤格式 可直接從 app.openapi() 取得模型

結論:使用 responses 能讓 API 自說自話,減少溝通成本。

3️⃣ responses 與 Pydantic 模型的結合

FastAPI 允許在 responses 中指定 Pydantic 模型,讓錯誤回傳的結構固定且可驗證。例如:

class ErrorResponse(BaseModel):
    detail: str
    code: int

responses 中使用:

@app.get("/items/{item_id}", responses={404: {"model": ErrorResponse, "description": "找不到項目"}})
async def read_item(item_id: int):
    ...

此時,當回傳 404 時,Swagger 會顯示 ErrorResponse 的 JSON 範例,且 FastAPI 會自動把 ErrorResponse 物件序列化成回應。


程式碼範例

以下示範 5 個常見的 responses 用法,從最簡單到進階應用,皆附上說明註解。

範例 1️⃣ 基本自訂 404 回應

from fastapi import FastAPI, HTTPException

app = FastAPI()

@app.get(
    "/users/{user_id}",
    responses={404: {"description": "使用者不存在"}}
)
async def get_user(user_id: int):
    """
    取得單一使用者資料。若使用者不存在,回傳 404。
    """
    if user_id != 1:  # 假設只有 ID=1 的使用者
        raise HTTPException(status_code=404, detail="User not found")
    return {"user_id": user_id, "name": "Alice"}

重點responses 只負責文件說明,實際回傳仍需使用 HTTPExceptionResponse


範例 2️⃣ 使用 Pydantic 模型描述錯誤結構

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

class NotFoundError(BaseModel):
    detail: str
    code: int = 404

app = FastAPI()

@app.get(
    "/products/{product_id}",
    responses={404: {"model": NotFoundError, "description": "找不到商品"}}
)
async def read_product(product_id: int):
    if product_id != 42:
        raise HTTPException(status_code=404, detail="Product not found")
    return {"product_id": product_id, "name": "Magic Wand"}

說明:當 404 被觸發時,FastAPI 會把 NotFoundError 轉成 JSON,Swagger 會顯示 detailcode 欄位。


範例 3️⃣ 多種狀態碼(200、400、401、403)

from fastapi import FastAPI, Header, HTTPException, Depends
from pydantic import BaseModel

class TokenError(BaseModel):
    detail: str
    code: int

app = FastAPI()

def verify_token(x_token: str = Header(...)):
    if x_token != "secret-token":
        raise HTTPException(status_code=401, detail="Invalid token")

@app.post(
    "/orders/",
    dependencies=[Depends(verify_token)],
    responses={
        200: {"description": "訂單建立成功"},
        400: {"description": "請求資料不合法"},
        401: {"model": TokenError, "description": "授權失敗"},
        403: {"description": "權限不足"},
    },
)
async def create_order(order: dict):
    """
    建立新訂單。若資料格式錯誤回傳 400,若 token 不正確回傳 401。
    """
    if "item_id" not in order:
        raise HTTPException(status_code=400, detail="Missing item_id")
    # 假設權限檢查失敗
    # raise HTTPException(status_code=403, detail="Forbidden")
    return {"order_id": 123, "status": "created"}

重點dependencies 可以與 responses 同時使用,讓授權失敗的說明更完整。


範例 4️⃣ 回傳自訂狀態碼與 Header

from fastapi import FastAPI, Response, status

app = FastAPI()

@app.delete(
    "/files/{file_id}",
    responses={
        204: {"description": "檔案已成功刪除"},
        404: {"description": "找不到檔案"},
    },
)
async def delete_file(file_id: str):
    """
    刪除指定檔案。成功時回傳 204 No Content,且不帶任何 body。
    """
    if file_id != "abc123":
        raise HTTPException(status_code=404, detail="File not found")
    # 刪除動作...
    return Response(status_code=status.HTTP_204_NO_CONTENT)

說明204 常用於「成功但無內容」的情境,responses 仍需要宣告,讓文件完整。


範例 5️⃣ 動態產生 responses(共用函式)

有時多個路由會使用相同的錯誤模型,建議抽成工具函式:

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import Dict, Any

class APIError(BaseModel):
    detail: str
    code: int

def common_errors() -> Dict[int, Dict[str, Any]]:
    """返回所有路由共用的錯誤描述與模型"""
    return {
        400: {"description": "請求格式錯誤"},
        401: {"model": APIError, "description": "未授權"},
        404: {"model": APIError, "description": "資源不存在"},
    }

app = FastAPI()

@app.get(
    "/books/{isbn}",
    responses=common_errors()
)
async def get_book(isbn: str):
    if isbn != "978-1234567890":
        raise HTTPException(status_code=404, detail="Book not found")
    return {"isbn": isbn, "title": "FastAPI 入門"}

好處:維護統一的錯誤說明,只要改一次 common_errors,所有路由即同步更新。


常見陷阱與最佳實踐

陷阱 說明 解決方式
忘記實際拋出對應的 HTTPException responses 只影響文件,若程式碼仍回傳 200,文件與實際行為不符。 確保在條件判斷時使用 raise HTTPException(status_code=…)Response(status_code=…)
模型與實際回傳不一致 responses 中宣告了 model,但路由回傳的是字典或其他類型。 回傳 Pydantic 物件或使用 JSONResponse(content=model.dict())
重複宣告相同狀態碼 多個路由分別寫相同的錯誤描述,容易遺漏更新。 把共用的錯誤字典抽成函式或常量(如上例 common_errors())。
使用非標準狀態碼 自訂 299、450 等非 RFC 標準碼,Swagger 可能不顯示。 盡量使用 RFC 7231 定義的標準碼,或在 description 中說明特殊含義。
忘記在 OpenAPI 設定 response_model_exclude_unset 回傳的模型包含預設值,導致文件過於冗長。 使用 response_model_exclude_unset=True 或在模型中設定 Config

最佳實踐

  1. 先設計錯誤模型:在實作路由前先定義 ErrorResponseValidationError 等統一模型。
  2. 文件即程式responses 應該與 raise HTTPException 同步更新,避免「文件過時」。
  3. 分層管理:把共用的 responses 抽成常量或函式,放在 utils/response.py 中,方便維護。
  4. 測試驗證:使用 TestClient 驗證每個狀態碼的回傳結構,確保模型與實際相符。
  5. Swagger 說明:在 description 中加入 中文說明錯誤代碼說明,提升前端同仁的使用體驗。

實際應用場景

場景 為什麼需要 responses
金融交易 API 必須明確告知 交易失敗(如 402 Payment Required、403 Forbidden)以及錯誤原因,前端才能正確顯示提示。
多租戶 SaaS 各租戶的權限不同,403404 的回傳說明需要依租戶客製化,文件化後有助於 API 使用者快速定位問題。
批次上傳 當上傳 CSV 時,可能出現 422(驗證失敗)或 400(檔案格式錯誤),透過 responses 給出範例 JSON,讓測試腳本自動生成錯誤測試資料。
第三方整合 供應商要求 標準化的錯誤碼(如 409 Conflict),responses 能在 Swagger 中直接呈現,減少溝通成本。
微服務間通訊 每個微服務的錯誤回傳需保持一致,使用共用 responses 模組可以保證所有服務的錯誤文件一致性。

總結

  • responsesFastAPI 中用來宣告 自訂回應狀態碼與模型 的強大工具,能同步提升 API 文件品質前端開發效率 以及 測試可讀性
  • 透過 Pydantic 模型 搭配 responses,可以把錯誤訊息結構化,讓整個系統在錯誤處理上更具一致性。
  • 實作時務必 同時 更新路由的例外拋出與 responses,避免文件與實際行為不符。
  • 把共用的錯誤描述抽成 函式或常量,維護成本大幅降低;配合單元測試,確保每個狀態碼的回傳結構正確。

掌握了 responses 的使用,你的 FastAPI 專案將能 清楚、完整且一致 地向使用者傳達每一次 API 呼叫的結果,從而建立更可靠、易於維護的服務。祝開發順利 🎉!