FastAPI Response 錯誤處理 (Handling Errors)

在 Web API 開發中,正確地處理錯誤並回傳適當的 HTTP 狀態碼給客戶端是非常重要的。例如:

  • 找不到資源 -> 404 Not Found
  • 權限不足 -> 401 Unauthorized / 403 Forbidden
  • 輸入錯誤 -> 400 Bad Request

FastAPI 提供了 HTTPException 來讓你輕鬆處理這些情況。

使用 HTTPException

你需要從 fastapi 導入 HTTPException

from fastapi import FastAPI, HTTPException

app = FastAPI()

items = {"foo": "The Foo Wrestlers"}

@app.get("/items/{item_id}")
async def read_item(item_id: str):
    if item_id not in items:
        # 當找不到 item 時,拋出 HTTPException
        raise HTTPException(status_code=404, detail="Item not found")

    return {"item": items[item_id]}

當你存取 /items/bar (不存在) 時,你會收到:

HTTP Status Code: 404 Not Found

Response Body:

{
  "detail": "Item not found"
}

為什麼是 raise 而不是 return

因為 HTTPException 是一個 Python 的 例外 (Exception)。 當你 raise 它時:

  1. 原本的函數執行會立刻中斷。
  2. FastAPI 的例外處理器會捕捉到它,並將其轉換為 HTTP 回應。
  3. 這也意味著你不必在每個 if-else 分支都加上 return,程式碼會更簡潔。

自訂 Response Header

有時候你需要在回傳錯誤時加上特定的 Header (例如安全性相關 header),可以在 HTTPException 中加入 headers 參數。

raise HTTPException(
    status_code=401,
    detail="Incorrect username or password",
    headers={"WWW-Authenticate": "Bearer"},
)

自訂例外處理器 (Exception Handlers)

如果你希望統一處理某種自定義例外,可以註冊 Exception Handler。

假設你有一個自訂例外 UnicornException

class UnicornException(Exception):
    def __init__(self, name: str):
        self.name = name

你可以使用 @app.exception_handler 裝飾器來定義如何處理它:

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

app = FastAPI()

@app.exception_handler(UnicornException)
async def unicorn_exception_handler(request: Request, exc: UnicornException):
    return JSONResponse(
        status_code=418,
        content={"message": f"Oops! {exc.name} did something. There goes a rainbow..."},
    )

@app.get("/unicorns/{name}")
async def read_unicorn(name: str):
    if name == "yolo":
        raise UnicornException(name=name)
    return {"unicorn_name": name}

當請求 /unicorns/yolo 時,你會得到 418 I'm a teapot 狀態碼與自訂訊息。

覆寫預設的驗證錯誤 (RequestValidationValidationError)

當 Pydantic 驗證失敗時,FastAPI 預設會回傳 422 狀態碼和詳細的 JSON (包含 loc, msg, type)。如果你想改變這個預設行為,可以覆寫 RequestValidationError 的處理器。

from fastapi.exceptions import RequestValidationError
from fastapi.responses import PlainTextResponse

@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
    return PlainTextResponse(str(exc), status_code=400)

這樣驗證錯誤時就不會回傳 JSON,而是純文字,狀態碼也變成 400。

覆寫 HTTPExceptions

你甚至可以覆寫 FastAPI 內建的 HTTPException 處理器(也就是處理你上面 raise HTTPException 的那個)。

例如,你希望所有 API 的錯誤回應格式都是 {"error": "message"} 而不是 {"detail": "message"}

from starlette.exceptions import HTTPException as StarletteHTTPException

@app.exception_handler(StarletteHTTPException)
async def http_exception_handler(request, exc):
    return JSONResponse(
        status_code=exc.status_code,
        content={"error": exc.detail}, # 將 key 從 detail 改為 error
    )
注意:這裡要導入的是 starlette.exceptions.HTTPException,因為 FastAPI 的 HTTPException 繼承自它,而 Exception Handler 實際上是在處理 Starlette 層級的例外。

總結

  • 使用 raise HTTPException(status_code=..., detail=...) 來中斷執行並回傳錯誤。
  • 使用 @app.exception_handler 來定義特定例外的全域處理邏輯。
  • 這讓你能夠統一管理錯誤回應的格式,保持 API 風格一致。