FastAPI 路由 – responses 屬性(自訂回應狀態碼)
簡介
在 Web API 開發中,回傳的 HTTP 狀態碼不只是一種訊號,更是與前端、第三方服務或測試工具溝通的橋樑。FastAPI 內建的 responses 參數讓我們可以在路由函式上一次性宣告多種可能的回應,不僅提升文件自動產生(OpenAPI/Swagger)的完整度,也讓程式碼的可讀性與維護性大幅提升。
本篇文章將帶你從概念說明走到實作範例,最後分享常見陷阱與最佳實踐,讓你在 FastAPI 中自如地使用 responses 來 自訂回應狀態碼,打造更符合業務需求的 API。
核心概念
1️⃣ responses 參數的定位
responses是FastAPI路由裝飾器(@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只負責文件說明,實際回傳仍需使用HTTPException或Response。
範例 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 會顯示detail與code欄位。
範例 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。 |
最佳實踐
- 先設計錯誤模型:在實作路由前先定義
ErrorResponse、ValidationError等統一模型。 - 文件即程式:
responses應該與raise HTTPException同步更新,避免「文件過時」。 - 分層管理:把共用的
responses抽成常量或函式,放在utils/response.py中,方便維護。 - 測試驗證:使用
TestClient驗證每個狀態碼的回傳結構,確保模型與實際相符。 - Swagger 說明:在
description中加入 中文說明、錯誤代碼說明,提升前端同仁的使用體驗。
實際應用場景
| 場景 | 為什麼需要 responses |
|---|---|
| 金融交易 API | 必須明確告知 交易失敗(如 402 Payment Required、403 Forbidden)以及錯誤原因,前端才能正確顯示提示。 |
| 多租戶 SaaS | 各租戶的權限不同,403、404 的回傳說明需要依租戶客製化,文件化後有助於 API 使用者快速定位問題。 |
| 批次上傳 | 當上傳 CSV 時,可能出現 422(驗證失敗)或 400(檔案格式錯誤),透過 responses 給出範例 JSON,讓測試腳本自動生成錯誤測試資料。 |
| 第三方整合 | 供應商要求 標準化的錯誤碼(如 409 Conflict),responses 能在 Swagger 中直接呈現,減少溝通成本。 |
| 微服務間通訊 | 每個微服務的錯誤回傳需保持一致,使用共用 responses 模組可以保證所有服務的錯誤文件一致性。 |
總結
responses是 FastAPI 中用來宣告 自訂回應狀態碼與模型 的強大工具,能同步提升 API 文件品質、前端開發效率 以及 測試可讀性。- 透過 Pydantic 模型 搭配
responses,可以把錯誤訊息結構化,讓整個系統在錯誤處理上更具一致性。 - 實作時務必 同時 更新路由的例外拋出與
responses,避免文件與實際行為不符。 - 把共用的錯誤描述抽成 函式或常量,維護成本大幅降低;配合單元測試,確保每個狀態碼的回傳結構正確。
掌握了 responses 的使用,你的 FastAPI 專案將能 清楚、完整且一致 地向使用者傳達每一次 API 呼叫的結果,從而建立更可靠、易於維護的服務。祝開發順利 🎉!