本文 AI 產出,尚未審核

FastAPI 課程 – 請求與回應(Request & Response)

主題:使用 JSONResponseHTMLResponsePlainTextResponse


簡介

在 Web API 開發中,回應(Response) 的格式直接影響前端或其他服務端的使用體驗。FastAPI 預設會根據路由函式的回傳型別自動產生 JSON,然而實際專案往往需要傳回 HTML 頁面純文字 或是 自訂的 JSON 結構(例如加入額外的 HTTP 標頭)。

本單元將聚焦於 FastAPI 內建的三種常用回應類別:JSONResponseHTMLResponsePlainTextResponse。透過這些工具,你可以:

  • 精確控制回應的 Content‑Type
  • 自訂 HTTP 狀態碼與標頭
  • 在同一個應用中同時支援 API 與網頁渲染

掌握這些技巧,能讓你的服務更彈性、易於維護,同時提升使用者的使用體驗。


核心概念

1. 為什麼不直接回傳字典或字串?

FastAPI 允許直接回傳 dictliststr 等 Python 原生物件,框架會自動轉換成 JSON 或純文字。但這樣的自動化有以下限制:

  1. 無法自行設定 HTTP 標頭(例如 Cache-ControlX-Custom-Header)。
  2. 回傳的 MIME type 只能是預設值(JSON 為 application/json、字串為 text/plain)。
  3. 無法一次回傳多種資料類型(例如同時回傳 JSON 與檔案)。

使用 JSONResponseHTMLResponsePlainTextResponse,可以突破上述限制,讓回應更具可控性。


2. JSONResponse

JSONResponse 繼承自 Starlette 的 Response,專門用來回傳 JSON 資料。它的特點包括:

  • 自訂 status_code:可以回傳 200、201、400、404 等任意狀態碼。
  • 可加入自訂標頭:例如 X-Request-IDCache-Control
  • 支援 json_dumps 參數,讓你自訂序列化方式(例如使用 orjson)。

範例 1:基本的 JSONResponse

from fastapi import FastAPI
from fastapi.responses import JSONResponse

app = FastAPI()

@app.get("/api/user/{user_id}")
async def get_user(user_id: int):
    # 假設從資料庫取得使用者資料
    user_data = {"id": user_id, "name": "Alice", "role": "admin"}
    # 直接回傳 JSONResponse,狀態碼為 200
    return JSONResponse(content=user_data)

說明

  • content 參數接受任何可 JSON 序列化的 Python 物件。
  • 若未指定 status_code,預設為 200。

範例 2:自訂標頭與狀態碼

@app.post("/api/items")
async def create_item(item: dict):
    # 假設成功寫入資料庫
    response_data = {"message": "Item created", "item": item}
    headers = {"X-Request-ID": "12345abcde", "Cache-Control": "no-store"}
    return JSONResponse(
        content=response_data,
        status_code=201,
        headers=headers
    )

重點headers 必須是 dict[str, str],FastAPI 會自動合併到回應中。

範例 3:使用 orjson 提升序列化效能

import orjson
from fastapi.responses import JSONResponse

class ORJSONResponse(JSONResponse):
    """自訂 JSONResponse,使用 orjson 作為序列化工具"""
    def render(self, content: any) -> bytes:
        return orjson.dumps(content)

@app.get("/fast")
async def fast_endpoint():
    data = {"msg": "使用 orjson 序列化更快"}
    return ORJSONResponse(content=data)

說明

  • 只要覆寫 render 方法,即可替換底層的 JSON 序列化函式。
  • orjson 在大資料量時效能顯著提升。

3. HTMLResponse

當你的 FastAPI 同時扮演 APIWeb Server 時,HTMLResponse 能直接回傳 HTML 內容,避免額外使用模板引擎(如 Jinja2)時的繁瑣設定。

範例 4:回傳簡易 HTML 頁面

from fastapi.responses import HTMLResponse

@app.get("/welcome", response_class=HTMLResponse)
async def welcome_page():
    html_content = """
    <html>
        <head><title>歡迎使用 FastAPI</title></head>
        <body>
            <h1>🚀 歡迎來到 FastAPI 世界!</h1>
            <p>這是一個簡易的 HTML 回應示範。</p>
        </body>
    </html>
    """
    return HTMLResponse(content=html_content, status_code=200)

技巧:在路由裝飾器裡使用 response_class=HTMLResponse,可以讓 IDE 更清楚回傳類型。

範例 5:結合 Jinja2 與 HTMLResponse

from fastapi import Request
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse

templates = Jinja2Templates(directory="templates")

@app.get("/profile/{username}", response_class=HTMLResponse)
async def profile(request: Request, username: str):
    # 假設從資料庫取得使用者資訊
    user = {"username": username, "age": 28, "city": "Taipei"}
    # 使用 Jinja2 渲染模板,最後交給 HTMLResponse
    return templates.TemplateResponse("profile.html", {"request": request, "user": user})

說明

  • TemplateResponse 本身已經是 HTMLResponse 的子類別,直接回傳即可。
  • 若需要額外設定標頭,只要在 TemplateResponse 後再包一層 HTMLResponse

4. PlainTextResponse

雖然字串回傳會自動轉為 text/plain,但使用 PlainTextResponse 可以 明確宣告 回應類型,並且方便加入自訂標頭或狀態碼。

範例 6:回傳純文字訊息

from fastapi.responses import PlainTextResponse

@app.get("/ping")
async def ping():
    # 常見的健康檢查端點
    return PlainTextResponse(content="pong", status_code=200)

範例 7:回傳錯誤訊息與自訂標頭

@app.get("/error")
async def error_demo():
    error_msg = "發生未預期的錯誤,請稍後再試"
    headers = {"Retry-After": "30"}  # 建議客戶端 30 秒後重試
    return PlainTextResponse(content=error_msg, status_code=503, headers=headers)

常見陷阱與最佳實踐

陷阱 說明 解決方案
忘記設定 media_type 若回傳的內容與 Content-Type 不符,瀏覽器或客戶端可能無法正確解析。 使用 JSONResponse(media_type="application/vnd.api+json") 或自行指定 media_type
直接回傳 dict 但需要自訂標頭 直接回傳 dict 時無法加入自訂標頭。 改用 JSONResponse,把標頭放在 headers 參數。
HTMLResponse 中的字元編碼問題 若 HTML 內容包含非 ASCII 字元,未指定編碼會導致亂碼。 確保在 <meta charset="UTF-8"> 中宣告,或在 HTMLResponse 加上 charset="utf-8"
大量資料直接用 JSONResponse 大量資料序列化會佔用大量記憶體,導致效能瓶頸。 使用 StreamingResponse 搭配生成器,或採用 orjsonujson
忘記在路由裝飾器中宣告 response_class IDE 可能無法正確推斷回傳類型,導致自動完成失效。 明確寫 @app.get(..., response_class=HTMLResponse)

最佳實踐

  1. 統一回應結構:即使使用 JSONResponse,也建議包一層標準格式(如 { "code": 0, "data": ..., "msg": "成功" }),方便前端統一處理。
  2. 使用 status_code 代表業務狀態:例如 201 代表資源建立成功,404 代表找不到資源,而非僅用 200 搭配訊息。
  3. 適度自訂 media_type:對於特定 API(如 GraphQL)或自訂協議,記得改變 media_type
  4. 將共用標頭抽離成 Middleware:如果每個回應都需要 X-Request-ID,可以寫一個 Middleware 自動加入,減少重複程式碼。

實際應用場景

場景 使用的 Response 類別 為什麼適合
健康檢查 (Health Check) PlainTextResponse 簡單、低開銷,客戶端只需判斷文字是否為 pong
前端單頁應用 (SPA) 的入口頁 HTMLResponse 直接回傳已渲染好的 HTML,讓瀏覽器立即載入 Vue/React。
RESTful API 回傳資料 JSONResponse 標準化的 JSON 結構,易於前端解析與文件生成。
下載檔案時的狀態訊息 PlainTextResponse (或 JSONResponse 搭配 Content-Disposition) 客戶端可直接顯示文字或在錯誤時提供 JSON 錯誤碼。
多語系網站的 SEO 頁面 HTMLResponse + Jinja2 透過模板渲染動態 meta 標籤,提高搜尋引擎可見度。
即時資料串流 StreamingResponse (非本文主題,但可與 JSONResponse 結合) 大量資料不適合一次性序列化。

總結

本單元帶你從 基礎的自動回傳,深入到 手動控制回應類型 的三大工具:JSONResponseHTMLResponsePlainTextResponse。掌握這些回應類別,你將能:

  • 精準控制 Content‑Type、狀態碼與標頭,提升 API 的可讀性與可維護性。
  • 在同一個 FastAPI 應用中同時提供 API 與網頁,減少部署與維護成本。
  • 依據不同業務需求選擇最適合的回應方式,從健康檢查到 SEO 頁面皆能輕鬆因應。

記得在實務開發中,統一回應結構適當使用 Middleware,以及根據資料量選擇序列化工具,都是提升效能與可讀性的關鍵。祝你在 FastAPI 的旅程中,寫出更乾淨、彈性十足的服務! 🚀