FastAPI 教學 – 請求與回應(Request & Response)
主題:狀態碼設定(status_code)
簡介
在 Web API 的開發過程中,HTTP 狀態碼是與前端或其他服務溝通的關鍵訊號。它不僅告訴呼叫者「請求成功」或「失敗」的基本事實,更能傳遞更細緻的語意(如資源已建立、驗證失敗、資源不存在等)。
FastAPI 作為一個以 型別提示 為核心的高效框架,對於 回應狀態碼 的設定提供了直觀且彈性的方式。正確使用 status_code 可以讓 API 的行為更符合 RESTful 原則、提升除錯效率,也能讓自動產生的 OpenAPI 文件更完整、易於使用。
本篇文章將深入說明在 FastAPI 中如何設定與自訂 HTTP 狀態碼,並透過多個實用範例展示常見情境、最佳實踐與可能的陷阱,幫助你從 初學者 逐步晉升為 中級開發者。
核心概念
1. 為什麼要明確指定 status_code
- 語意清晰:使用正確的狀態碼(如
201 Created、204 No Content)讓前端開發者一眼就能判斷 API 的結果。 - 自動文件化:FastAPI 會把路由的
status_code寫入 OpenAPI (Swagger) 文件,前端或第三方服務在產生程式碼時會自動帶入正確的型別與錯誤處理。 - 錯誤排查:當 API 回傳不符合預期的狀態碼時,測試與除錯會更快速定位問題。
小技巧:若不指定
status_code,FastAPI 會預設回傳200 OK(成功)或422 Unprocessable Entity(驗證失敗)。在大多數情況下,我們仍建議手動設定,以免產生歧義。
2. 基本語法
在 FastAPI 中,status_code 參數可直接寫在路由裝飾器 (@app.get, @app.post …) 裡:
from fastapi import FastAPI, status
app = FastAPI()
@app.post("/items/", status_code=status.HTTP_201_CREATED)
async def create_item(item: dict):
# 實作新增邏輯
return {"msg": "Item created", "item": item}
status模組提供了 可讀性高的常數(如status.HTTP_201_CREATED),建議優先使用,以免寫錯數值。- 也可以直接寫數字(
status_code=201),但可讀性較差。
3. 回傳自訂 Response 物件
有時候我們需要在同一個端點根據不同情況回傳不同的狀態碼,這時可以使用 fastapi.Response(或子類別 JSONResponse、PlainTextResponse):
from fastapi import FastAPI, Response, status
app = FastAPI()
@app.delete("/items/{item_id}")
async def delete_item(item_id: int):
if item_id == 0:
# 假設 0 為保護的資源,拒絕刪除
return Response(status_code=status.HTTP_403_FORBIDDEN)
# 正常刪除
return Response(status_code=status.HTTP_204_NO_CONTENT)
204 No Content表示請求成功,但回傳內容為空,常用於 刪除 或 更新 操作。
4. 與 Pydantic Model 結合
若路由回傳的是 Pydantic 模型,FastAPI 仍會依照 status_code 設定產生正確的 OpenAPI schema:
from fastapi import FastAPI, status
from pydantic import BaseModel
class Item(BaseModel):
id: int
name: str
price: float
app = FastAPI()
@app.get("/items/{item_id}", response_model=Item, status_code=status.HTTP_200_OK)
async def read_item(item_id: int):
# 假資料
return Item(id=item_id, name="Apple", price=1.99)
- 即使
status_code為200,回傳的 JSON 結構仍由Item模型定義。
5. 用於例外處理(Exception Handling)
FastAPI 允許自訂例外類別,並在捕捉時指定回傳的狀態碼:
from fastapi import FastAPI, HTTPException, status
app = FastAPI()
@app.get("/protected")
async def protected_route(token: str | None = None):
if token != "secret-token":
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials",
headers={"WWW-Authenticate": "Bearer"},
)
return {"msg": "Access granted"}
HTTPException會自動產生對應的回應,包含 狀態碼、detail 以及 headers。
程式碼範例
以下提供 5 個實務上常見 的範例,示範不同情境下如何設定 status_code。
範例 1:建立資源 – 201 Created
from fastapi import FastAPI, status
from pydantic import BaseModel
app = FastAPI()
class TodoCreate(BaseModel):
title: str
description: str | None = None
@app.post("/todos/", status_code=status.HTTP_201_CREATED)
async def create_todo(todo: TodoCreate):
# 假設把資料寫入資料庫,回傳新建的 ID
new_id = 42
return {"id": new_id, **todo.dict()}
說明:使用
201代表「已成功建立新資源」,前端可依此決定是否要立即跳轉至新資源的 URL(如/todos/42)。
範例 2:更新資源 – 200 OK 或 204 No Content
from fastapi import FastAPI, Response, status
from pydantic import BaseModel
app = FastAPI()
class UserUpdate(BaseModel):
name: str | None = None
email: str | None = None
@app.put("/users/{user_id}", response_model=UserUpdate)
async def update_user(user_id: int, payload: UserUpdate):
# 實作更新邏輯,這裡僅回傳更新後的資料
updated_user = {"id": user_id, **payload.dict()}
# 若不想回傳內容,可改用 Response(status_code=204)
return updated_user # 預設回傳 200 OK
變體:若 API 設計上不需要回傳任何內容,可改寫為:
@app.put("/users/{user_id}")
async def update_user(user_id: int, payload: UserUpdate):
# 執行更新
return Response(status_code=status.HTTP_204_NO_CONTENT)
範例 3:刪除資源 – 204 No Content 與錯誤處理
from fastapi import FastAPI, Response, HTTPException, status
app = FastAPI()
fake_db = {1: "item1", 2: "item2"}
@app.delete("/items/{item_id}")
async def delete_item(item_id: int):
if item_id not in fake_db:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Item not found"
)
del fake_db[item_id]
# 成功刪除後回傳 204
return Response(status_code=status.HTTP_204_NO_CONTENT)
說明:若資源不存在,回傳
404 Not Found;成功刪除則回傳204 No Content,符合 RESTful 的慣例。
範例 4:授權失敗 – 401 Unauthorized 並加上 WWW-Authenticate Header
from fastapi import FastAPI, HTTPException, Header, status
app = FastAPI()
@app.get("/secure-data")
async def secure_data(authorization: str | None = Header(default=None)):
if authorization != "Bearer mysecrettoken":
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid token",
headers={"WWW-Authenticate": "Bearer"}
)
return {"secret": "這是機密資料"}
重點:
WWW-AuthenticateHeader 讓瀏覽器或 API 客戶端知道應該使用哪種驗證方式。
範例 5:自訂錯誤回應 – 422 Unprocessable Entity(驗證失敗)
FastAPI 內建的驗證失敗會自動回傳 422,但我們可以自行拋出相同狀態碼,加入更具體的錯誤資訊:
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel, validator
app = FastAPI()
class RegisterForm(BaseModel):
username: str
password: str
@validator("password")
def password_strength(cls, v):
if len(v) < 8:
raise ValueError("密碼長度至少 8 個字元")
return v
@app.post("/register", status_code=status.HTTP_201_CREATED)
async def register(form: RegisterForm):
# 假設已檢查過密碼強度,直接回傳成功訊息
return {"msg": "註冊成功"}
說明:若密碼不符合規則,Pydantic 會拋出
ValueError,FastAPI 會捕捉並回傳422,錯誤訊息會顯示在回應的detail欄位。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
忘記設定 status_code |
預設回傳 200 OK,會讓前端誤以為是「建立成功」或「更新成功」等。 |
明確寫出 status_code=,尤其在 POST、DELETE、PUT 等非 GET 操作。 |
| 使用錯誤的數值 | 直接寫 status_code=201 容易打錯(如 202、301)。 |
使用 from fastapi import status,改寫為 status.HTTP_201_CREATED。 |
在同一端點混用 return 與 Response |
可能導致返回的內容與狀態碼不一致(如回傳 JSON 卻設定 204)。 |
統一回傳方式:若要回傳內容,使用 JSONResponse 或直接回傳模型;若不回傳,使用 Response 並設定正確的 status_code。 |
忽略 headers |
某些狀態碼(如 401、403)需要特定 Header,缺少會影響客戶端的行為。 |
在拋出 HTTPException 時,加入 headers= 參數。 |
錯誤使用 status.HTTP_200_OK 於錯誤情境 |
讓 API 文檔看起來不正確,測試也會失敗。 | 依照 RESTful 原則 為每個錯誤情境選擇對應的狀態碼(400、404、409、422…)。 |
最佳實踐
- 一律使用
status常數:可讀性佳,避免硬編碼。 - 根據 HTTP 方法選擇慣例狀態碼:
GET→200 OK(或304 Not Modified)POST→201 Created(或202 Accepted)PUT/PATCH→200 OK或204 No ContentDELETE→204 No Content
- 在 OpenAPI 文件中加入說明:使用
responses參數描述可能的錯誤回應,讓前端開發者一目了然。 - 統一錯誤格式:自訂一個錯誤回應模型(如
ErrorResponse),在所有HTTPException中使用,保持 API 的一致性。
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel
class ErrorResponse(BaseModel):
code: int
message: str
app = FastAPI()
@app.get("/items/{item_id}", responses={
404: {"model": ErrorResponse, "description": "Item not found"}
})
async def get_item(item_id: int):
if item_id != 1:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Item not found"
)
return {"id": 1, "name": "Sample"}
實際應用場景
1. 電子商務平台 – 訂單建立
- 流程:使用者提交訂單 → 後端驗證庫存 → 成功時建立訂單 → 回傳
201 Created並在LocationHeader 提供訂單查詢 URL。 - 實作:
@app.post("/orders/", status_code=status.HTTP_201_CREATED)
async def create_order(order: OrderCreate):
order_id = save_to_db(order)
return Response(
status_code=status.HTTP_201_CREATED,
headers={"Location": f"/orders/{order_id}"},
content=json.dumps({"order_id": order_id}),
media_type="application/json"
)
2. 社交媒體 – 按讚與取消按讚
- 流程:
POST /posts/{id}/like→ 成功回傳200 OK(已按讚)或204 No Content(已取消按讚)。 - 實作:
@app.post("/posts/{post_id}/like")
async def toggle_like(post_id: int, user_id: int):
liked = await like_service.toggle(user_id, post_id)
if liked:
return {"msg": "已按讚"}
return Response(status_code=status.HTTP_204_NO_CONTENT)
3. 金融系統 – 交易失敗的細節回報
- 流程:交易失敗可能因餘額不足、風控審核等原因,需回傳
400 Bad Request、402 Payment Required或409 Conflict。 - 實作:
@app.post("/transactions/")
async def create_transaction(tx: TransactionCreate):
try:
result = await transaction_service.process(tx)
except InsufficientBalanceError as e:
raise HTTPException(
status_code=status.HTTP_402_PAYMENT_REQUIRED,
detail=str(e)
)
except ConflictError as e:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail=str(e)
)
return {"status": "success", "tx_id": result.id}
總結
在 FastAPI 中,正確設定 status_code 不僅是遵循 HTTP 標準的好習慣,更是提升 API 可讀性、文件品質與除錯效率的關鍵。透過 status 常數、Response 物件和 HTTPException 的靈活運用,我們可以:
- 清楚傳達成功與失敗的語意(如
201 Created、204 No Content、404 Not Found)。 - 自動產生完整的 OpenAPI 文件,讓前端與合作夥伴能快速了解每個端點的行為。
- 在實務專案中保持一致的錯誤回應格式,提升系統的可維護性。
記得在設計每個 API 時,先思考「這個請求的結果應該對應哪一個 HTTP 狀態碼?」再把它寫在程式碼裡。透過本文提供的範例與最佳實踐,你現在已具備在 FastAPI 專案中 自信、正確 使用 status_code 的能力。祝開發順利,寫出既 好用 又 好懂 的 API!