FastAPI ─ 請求與回應(Request & Response)
回傳 JSON、HTML、純文字與自訂格式
簡介
在 Web 應用開發中,回傳正確的資料格式是 API 能否被前端、行動裝置或第三方服務成功使用的關鍵。FastAPI 以其高效能與自動產生 OpenAPI 文件的特性,讓開發者可以輕鬆地定義不同的回應類型。
本篇文章將帶你一步一步了解 如何在 FastAPI 中回傳 JSON、HTML、純文字以及自訂格式,並說明背後的原理、常見陷阱與最佳實踐,讓你在實務專案中快速上手、寫出可維護且符合規範的 API。
核心概念
1. 回傳 JSON(預設回應)
FastAPI 預設使用 JSON 作為回應格式,這是因為 JSON 具備跨平台、易於解析的優勢。只要在路由函式中回傳 Python 的 dict、list、pydantic model,FastAPI 會自動將其序列化為 JSON。
範例 1️⃣:回傳純粹的 dict
from fastapi import FastAPI
app = FastAPI()
@app.get("/items")
def read_items():
"""回傳一組商品資訊,以 JSON 格式呈現。"""
return {"items": ["Apple", "Banana", "Cherry"]}
說明:return 的 dict 會在底層經過 jsonable_encoder 處理,確保所有可序列化的資料型別(如 datetime、Enum)都能正確轉換。
範例 2️⃣:使用 Pydantic Model
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List
class Item(BaseModel):
id: int
name: str
price: float
app = FastAPI()
@app.get("/item/{item_id}", response_model=Item)
def get_item(item_id: int):
"""依據 id 回傳單一商品資料,FastAPI 會自動產生 OpenAPI schema。"""
sample = {"id": item_id, "name": "Laptop", "price": 1299.99}
return sample
說明:response_model 不僅限制回傳欄位,還會在 Swagger UI 中顯示模型結構,提升文件可讀性。
範例 3️⃣:回傳 List[Model]
@app.get("/users", response_model=List[Item])
def list_items():
"""回傳多筆商品,使用 List[Model] 讓文件自動展開。"""
return [
{"id": 1, "name": "Keyboard", "price": 49.9},
{"id": 2, "name": "Mouse", "price": 19.9},
]
2. 回傳 HTML(模板渲染)
有時候我們需要直接在瀏覽器顯示 HTML 頁面(例如管理後台、簡易表單)。FastAPI 內建支援 Jinja2 等模板引擎,只要把 Response 類型指定為 HTMLResponse,並使用 templates.TemplateResponse 即可。
範例 4️⃣:設定 Jinja2 模板
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
import pathlib
BASE_DIR = pathlib.Path(__file__).parent
templates = Jinja2Templates(directory=BASE_DIR / "templates")
app = FastAPI()
@app.get("/welcome", response_class=HTMLResponse)
def welcome(request: Request):
"""使用 Jinja2 渲染 welcome.html,傳入變數 name。"""
return templates.TemplateResponse(
"welcome.html",
{"request": request, "name": "FastAPI 學員"}
)
說明:
templates指向templates/目錄。TemplateResponse需要傳入request物件,讓模板內可使用url_for等 FastAPI 內建工具。response_class=HTMLResponse告訴 FastAPI 回傳text/htmlContent-Type。
範例 5️⃣:動態表單
<!-- templates/form.html -->
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<title>簡易表單</title>
</head>
<body>
<h1>Hello, {{ user }}!</h1>
<form action="/submit" method="post">
<input name="msg" placeholder="輸入訊息">
<button type="submit">送出</button>
</form>
</body>
</html>
@app.get("/form", response_class=HTMLResponse)
def get_form(request: Request):
return templates.TemplateResponse("form.html", {"request": request, "user": "Alice"})
3. 回傳純文字(Plain Text)
在某些情境(如 health check、簡易 API)只需要回傳 純文字。FastAPI 提供 PlainTextResponse,只要在路由上設定 response_class 即可。
範例 6️⃣:健康檢查端點
from fastapi.responses import PlainTextResponse
@app.get("/health", response_class=PlainTextResponse)
def health_check():
"""返回簡單文字,讓容器 Orchestrator 能快速判斷服務狀態。"""
return "OK"
說明:此端點回傳 text/plain; charset=utf-8,不會產生 JSON 包裝,適合 Nginx、Kubernetes liveness probe 使用。
4. 自訂格式(Binary、CSV、XML、PDF …)
FastAPI 允許 自訂回應類型,只要繼承 Response 並設定正確的 media_type,即可傳遞二進位資料或特定檔案格式。
範例 7️⃣:回傳 CSV 檔
from fastapi.responses import StreamingResponse
import csv
import io
@app.get("/export/csv")
def export_csv():
"""將資料即時轉成 CSV 並以串流方式回傳。"""
data = [
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"},
]
def iter_csv():
output = io.StringIO()
writer = csv.DictWriter(output, fieldnames=["id", "name"])
writer.writeheader()
for row in data:
writer.writerow(row)
yield output.getvalue()
output.seek(0)
output.truncate(0)
return StreamingResponse(iter_csv(),
media_type="text/csv",
headers={"Content-Disposition": "attachment; filename=users.csv"})
說明:
- 使用
StreamingResponse可以在資料量大時避免一次性載入記憶體。 Content-Disposition讓瀏覽器下載而非直接顯示。
範例 8️⃣:回傳 PDF(二進位)
from fastapi.responses import Response
from pathlib import Path
@app.get("/report/pdf")
def get_pdf():
"""讀取本機 PDF 檔案,回傳二進位資料,設定正確的 MIME。"""
pdf_path = Path("static/report.pdf")
pdf_bytes = pdf_path.read_bytes()
return Response(content=pdf_bytes,
media_type="application/pdf",
headers={"Content-Disposition": "inline; filename=report.pdf"})
說明:media_type="application/pdf" 告訴瀏覽器此回應為 PDF,inline 讓瀏覽器直接開啟,若改為 attachment 則會觸發下載。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
忘記設定 response_class |
預設回傳 application/json,若返回 HTML 或純文字會被自動包成 JSON,導致前端解析失敗。 |
明確在路由裝飾器上加上 response_class=HTMLResponse、PlainTextResponse 等。 |
| 返回非 JSON 可序列化的物件 | 直接回傳 datetime、Decimal、bytes 會拋出 TypeError。 |
使用 jsonable_encoder 或在 response_model 中自行定義 json_encoders。 |
| 大檔案一次讀取導致記憶體暴衝 | 直接 return FileResponse("big.zip") 會一次載入全部檔案。 |
使用 StreamingResponse 搭配生成器或 aiofiles 逐塊讀取。 |
缺少 Content-Type |
自訂格式時忘記設定 media_type,瀏覽器可能以文字方式顯示二進位資料。 |
始終在 Response/StreamingResponse 中指定正確的 MIME,例如 application/xml、image/png。 |
| 模板安全性 | 直接把使用者輸入渲染到 HTML,易受 XSS 攻擊。 | 使用 Jinja2 預設的自動 escaping,或手動過濾危險字元。 |
最佳實踐:
- 統一回應模型:即使回傳純文字,也可以封裝成
{ "message": "OK" },讓前端有一致的解析邏輯。 - 使用依賴注入(Depends):將常用的
Response物件或檔案處理抽成可重用的依賴。 - 善用 HTTP 狀態碼:回傳成功時使用
200,錯誤時使用4xx/5xx,並在JSONResponse中提供detail欄位。 - 設定 Cache-Control:對於不常變動的靜態檔案(如 PDF、圖片)加上
Cache-Control: public, max-age=86400,提升效能。 - 測試回應 Header:使用
TestClient確認Content-Type、Content-Disposition等 header 正確。
實際應用場景
| 場景 | 需要的回應類型 | 為什麼選擇此類型 |
|---|---|---|
| 前端 SPA 與後端 API | JSON | 前端框架(Vue、React)以 fetch 解析 JSON,結構化資料最方便。 |
| 系統健康檢查 | 純文字 (PlainTextResponse) |
輕量、快速,容器 Orchestrator 只要檢查字串 OK 即可。 |
| 報表下載(CSV、Excel) | StreamingResponse + text/csv |
大量資料即時產生,避免記憶體浪費,且瀏覽器會自動觸發下載。 |
| PDF 合同、發票 | Response + application/pdf |
需要保留檔案格式,讓使用者直接在瀏覽器預覽或下載。 |
| 管理後台介面 | HTML (HTMLResponse) + Jinja2 |
需要動態渲染頁面、表單與驗證訊息,HTML 最直觀。 |
| 第三方系統整合(XML) | 自訂 Response + application/xml |
某些舊系統仍使用 XML 作為資料交換格式。 |
總結
在 FastAPI 中,回傳不同資料格式 並不需要額外的套件或複雜的設定,只要掌握以下三點即可:
- 了解預設行為:未指定
response_class時,FastAPI 會自動將 Python 物件序列化為 JSON。 - 正確宣告
response_class或media_type:HTML、純文字、CSV、PDF…等,都有對應的 Response 類別或自訂 MIME。 - 結合依賴注入與 Pydantic Model:讓回應結構化、文件化,同時保持程式碼的可讀性與可測試性。
透過本文提供的 實作範例、常見陷阱與最佳實踐,你可以在任何 FastAPI 專案中快速切換回應格式,滿足前端 UI、第三方系統或內部工具的需求。未來只要再面對更複雜的二進位傳輸(如圖片、音訊)或自訂協議,概念仍然相同:選擇合適的 Response、設定正確的 MIME、確保資料可序列化,即可在高效能的 FastAPI 應用中游刃有餘。祝開發順利!