本文 AI 產出,尚未審核

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

主題:自訂回應類型(response_class


簡介

在使用 FastAPI 建立 API 時,我們常會直接回傳 Python 的資料結構(如 dictlist)或是使用預設的 JSONResponse。雖然這樣足以滿足大多數情境,但在實務開發中,我們經常需要 自訂回應的 Content‑Type、狀態碼、標頭或是回傳格式(例如回傳 HTML、純文字、CSV、檔案流等)。

response_class 參數正是為了這類需求而設計。它讓開發者在 路由宣告階段就指定回應類型,從而避免在函式內手動建立 Response 物件,提升程式的可讀性與維護性。掌握 response_class,不僅能讓 API 更符合前端或第三方系統的期望,也能減少錯誤與重複程式碼。

本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,一步步帶你深入了解如何在 FastAPI 中 自訂回應類型,並提供實務上常見的應用情境,讓你在日常開發中立即上手。


核心概念

1. 為什麼需要 response_class

  • 明確的 Content‑Type:預設的 JSONResponse 會把回傳資料序列化為 application/json。如果要回傳 HTML純文字XMLCSV 等格式,必須改變 Content-Type
  • 自訂狀態碼:有時候回傳的資料本身不代表成功或失敗,需要自行設定 HTTP 狀態碼(如 201、202、204)。
  • 額外標頭:例如 CORS、Cache‑Control、Content‑Disposition(下載檔案)等,都可以在回應類型裡一次性設定。
  • 效能考量:使用適合的回應類型(例如 StreamingResponse)可以避免一次載入過大資料,減少記憶體佔用。

重點response_class 只是一個 類別,它會在路由被呼叫時被 FastAPI 自動實例化,並接受路由函式的回傳值作為建構參數。

2. 常見的內建回應類別

類別 目的 預設 Content‑Type
JSONResponse 回傳 JSON application/json
HTMLResponse 回傳 HTML text/html
PlainTextResponse 回傳純文字 text/plain
RedirectResponse 302 重新導向 text/plain
StreamingResponse 串流傳輸(大檔案、即時資料) media_type 而定
FileResponse 直接回傳檔案(支援 Range) 依檔案類型自動偵測
Response 完全自訂(無預設 header) application/octet-stream

提示:如果內建類別無法滿足需求,你也可以自行繼承 Response 來實作自訂回應類別。

3. 基本語法

from fastapi import FastAPI, Response
from fastapi.responses import HTMLResponse, PlainTextResponse

app = FastAPI()

@app.get("/html", response_class=HTMLResponse)
async def get_html():
    return "<h1>Hello, FastAPI!</h1>"
  • response_class 直接寫在 @app.get()@app.post() 等裝飾器上。
  • 函式回傳的資料會被 傳入 指定的回應類別建構子中,FastAPI 會自動完成序列化或編碼的工作。

4. 進階使用 – 搭配 status_codeheaders

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

app = FastAPI()

@app.post("/create", response_class=JSONResponse, status_code=status.HTTP_201_CREATED)
async def create_item(item: dict):
    # 假裝寫入資料庫...
    return {"msg": "item created", "item": item}
  • status_code 可在裝飾器參數裡直接指定。
  • 需要額外標頭時,可在回傳的字典裡加入 headers(或在函式內自行建立 Response)。

5. 何時使用 StreamingResponseFileResponse

  • 下載檔案:使用 FileResponse 能自動支援 Content‑Disposition(下載檔名)與 Range 請求,適合大檔案。
  • 即時串流(例如 CSV 產生、影片串流):StreamingResponse 允許你以 generator 或 async generator 逐塊傳送資料,降低記憶體壓力。

程式碼範例

下面提供 5 個實用範例,從最簡單的 HTML 回傳到進階的檔案下載與串流,涵蓋常見需求。

範例 1:回傳純文字(PlainTextResponse

from fastapi import FastAPI
from fastapi.responses import PlainTextResponse

app = FastAPI()

@app.get("/ping", response_class=PlainTextResponse)
async def ping():
    """
    用於健康檢查的最簡單回應。
    客戶端只會收到純文字 "pong"。
    """
    return "pong"

說明

  • PlainTextResponse 會自動設定 Content-Type: text/plain; charset=utf-8
  • 適合 監控心跳簡易測試

範例 2:回傳 HTML(HTMLResponse)並設定自訂標頭

from fastapi import FastAPI, Header
from fastapi.responses import HTMLResponse

app = FastAPI()

@app.get("/welcome", response_class=HTMLResponse)
async def welcome():
    """
    回傳一段簡易的 HTML 頁面,並在回應標頭加入自訂資訊。
    """
    html_content = """
    <html>
        <head><title>Welcome</title></head>
        <body>
            <h1>歡迎使用 FastAPI!</h1>
            <p>這是一個自訂 HTML 回應的範例。</p>
        </body>
    </html>
    """
    # 直接在回傳字串外加標頭
    return HTMLResponse(content=html_content, headers={"X-Developer": "YourName"})

說明

  • 雖然在裝飾器已指定 HTMLResponse,我們仍可在回傳時手動建立 HTMLResponse 以加入額外標頭。
  • 若不需要額外標頭,只回傳字串即可,FastAPI 會自動使用 HTMLResponse 包裝。

範例 3:回傳 JSON 並自訂狀態碼(JSONResponse

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

app = FastAPI()

@app.post("/login", response_class=JSONResponse, status_code=status.HTTP_202_ACCEPTED)
async def login(username: str, password: str):
    """
    假設驗證成功,回傳 token,並使用 202 Accepted 表示已接受請求。
    """
    # 這裡僅示範,實務請使用安全的驗證機制
    token = f"{username}-token"
    return {"access_token": token, "token_type": "bearer"}

說明

  • status_code 直接在裝飾器裡設定,讓回應自動帶入 202。
  • JSONResponse 會把回傳的 dict 序列化為 JSON。

範例 4:檔案下載(FileResponse

from fastapi import FastAPI
from fastapi.responses import FileResponse
import pathlib

app = FastAPI()
BASE_DIR = pathlib.Path(__file__).parent

@app.get("/download/report", response_class=FileResponse)
async def download_report():
    """
    下載一份 CSV 報表。FileResponse 會自動偵測 MIME 類型,
    並支援瀏覽器的「另存新檔」對話框。
    """
    file_path = BASE_DIR / "reports" / "monthly_report.csv"
    # `filename` 參數會設定 Content-Disposition 為 attachment,讓瀏覽器下載
    return FileResponse(path=file_path, filename="monthly_report.csv", media_type="text/csv")

說明

  • FileResponse 只需要提供檔案路徑、下載檔名與 media_type
  • 若檔案過大,FastAPI 會自動使用 Range 標頭支援斷點續傳。

範例 5:即時串流 CSV(StreamingResponse

from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import csv
import io

app = FastAPI()

def generate_csv():
    """
    產生 CSV 的 generator,逐行寫入資料。
    這裡以 10,000 筆模擬資料說明。
    """
    output = io.StringIO()
    writer = csv.writer(output)
    # 寫入表頭
    writer.writerow(["id", "name", "value"])
    yield output.getvalue()
    output.seek(0)
    output.truncate(0)

    for i in range(1, 10001):
        writer.writerow([i, f"name_{i}", i * 10])
        # 每 1000 筆 flush 一次
        if i % 1000 == 0:
            yield output.getvalue()
            output.seek(0)
            output.truncate(0)

    # 最後一次 flush
    yield output.getvalue()

@app.get("/stream/csv", response_class=StreamingResponse)
async def stream_csv():
    """
    直接把 CSV 內容串流給客戶端,適合大量資料下載而不佔用記憶體。
    """
    return StreamingResponse(
        generate_csv(),
        media_type="text/csv",
        headers={"Content-Disposition": "attachment; filename=big_data.csv"}
    )

說明

  • generate_csvgenerator,每次產生一小段 CSV 文字,避免一次性載入全部 10,000 筆資料到記憶體。
  • StreamingResponse 會把產生的字串逐塊寫入 HTTP 回應。
  • Content-Disposition 設為 attachment,瀏覽器會自動彈出下載視窗。

常見陷阱與最佳實踐

陷阱 說明 解決方式 / 最佳實踐
忘記設定 media_type 使用 Response 或自訂類別時,若未指定 media_type,預設會是 application/octet-stream,前端可能無法正確解析。 始終在回應類別中明確設定 media_type(如 text/htmltext/csv)。
回傳字串與 Response 混用 在已指定 response_class=HTMLResponse 的路由中,直接回傳 Response 物件會導致 FastAPI 再次包裝,產生雙層回應。 不要同時使用 response_class 與手動建立同類型的 Response,若需要自訂標頭,直接回傳該類別的實例即可。
大檔案一次載入 使用 FileResponse 時若先把檔案讀入記憶體(open().read()),會失去 Range 支援,且佔用過多記憶體。 直接把檔案路徑交給 FileResponse,讓 FastAPI 處理檔案流與 Range。
錯誤的編碼 回傳中文文字或 CSV 時未指定編碼,導致瀏覽器顯示亂碼。 media_type 加上 charsettext/plain; charset=utf-8)或在 CSV 產生時使用 utf-8-sig
產生過多的 Response 物件 在迴圈內每次都建立 Response 會影響效能。 一次建立並回傳,或使用 StreamingResponse 逐塊輸出。
忽略 CORS 若前端跨域請求回傳 HTML 或檔案,未設定 CORS 會被瀏覽器阻擋。 使用 fastapi.middleware.cors.CORSMiddleware,或在回應標頭加入 Access-Control-Allow-Origin

最佳實踐總結

  1. 先思考回傳類型:是 JSON、HTML、檔案或串流?根據需求直接挑選對應的 response_class
  2. 明確設定 media_typecharset,避免前端解析錯誤。
  3. 盡量使用內建類別FileResponseStreamingResponse),它們已優化了 I/O 與 Header 處理。
  4. 針對大型資料,使用 StreamingResponseFileResponse,絕不一次把全部資料讀入記憶體。
  5. 保持回應簡潔:若只需要額外標頭,直接在 Response 建構子裡加 headers,不要在路由內多次呼叫 Response

實際應用場景

場景 使用的 response_class 為何適合
健康檢查 API PlainTextResponse 回傳簡短文字,最小化開銷,易於監控工具解析。
渲染單頁應用(SPA)的入口 HTML HTMLResponse 直接回傳完整的 HTML,讓前端框架接管路由。
OAuth2 登入成功後的跳轉 RedirectResponse 302 重新導向到前端授權頁面。
產生 PDF 報表 FileResponse 大檔案下載、支援 Range,讓瀏覽器顯示下載對話框。
即時匯出大量資料(CSV、Excel) StreamingResponse 串流傳輸,降低記憶體使用,適合上百萬筆資料。
自訂錯誤頁面 HTMLResponse + status_code=404 直接回傳自訂的錯誤 HTML,提升使用者體驗。
WebSocket 之外的 SSE(Server‑Sent Events) Responsetext/event-stream 使用 Response 並設定 media_type="text/event-stream",即可推送即時事件。

案例說明
假設你正在開發一個 財務系統,使用者可以點擊「匯出交易紀錄」按鈕,系統會即時產生 CSV 檔案。若直接在端點裡一次性把所有資料讀入記憶體,當資料量達到數十萬筆時,伺服器會發生 MemoryError。此時,你可以改用 StreamingResponse 搭配生成器,讓 CSV 資料逐行寫入回應,既不佔用過多記憶體,又能讓使用者即時下載檔案。


總結

  • response_class 是 FastAPI 為回應自訂化提供的核心機制,讓開發者能在路由層面直接指定回傳的 Content‑Type、狀態碼與標頭。
  • 內建的回應類別(JSONResponseHTMLResponseFileResponseStreamingResponse…)已涵蓋大多數實務需求,若仍不足,可自行繼承 Response 來實作。
  • 正確使用 response_class 能提升 API 的可讀性、效能與相容性,尤其在 大檔案下載、即時串流、跨域前端互動 等情境下效果顯著。
  • 避免常見陷阱(未設定 media_type、一次載入大檔案、錯誤的編碼等),並遵循最佳實踐:先思考回傳類型 → 明確設定 media_type → 使用內建類別 → 針對大型資料採用串流

掌握了 response_class,你的 FastAPI 專案將能更靈活地服務前端與第三方系統,從簡單的健康檢查到複雜的檔案串流,都能以最適合的方式回應請求。快把這些技巧運用到你的下個專案,讓 API 更貼近實務需求吧!