FastAPIでデータを返す方法:Responseオブジェクト徹底解説


FastAPIでデータを返す方法:Responseオブジェクト徹底解説

FastAPIは、PythonでAPIを迅速に構築するための、モダンで高性能なWebフレームワークです。その大きな魅力の一つは、開発者がAPIエンドポイントからPythonの標準的なデータ型(辞書、リスト、Pydanticモデルなど)を返すだけで、自動的にJSONレスポンスを生成してくれる手軽さにあります。しかし、Web APIの世界はJSONだけではありません。HTMLページを返したり、プレーンテキストを返したり、ファイルをダウンロードさせたり、ストリーミングデータを提供したり、カスタムヘッダーやクッキーを設定したり、特定のHTTPステータスコードを返したりする必要も出てきます。

このような、より高度なレスポンス制御を実現するために、FastAPIはResponseオブジェクトとその派生クラスを提供しています。本記事では、FastAPIにおけるResponseオブジェクトの役割、使い方、そして様々な派生クラス(JSONResponse, PlainTextResponse, HTMLResponse, RedirectResponse, StreamingResponse, FileResponseなど)を徹底的に解説します。約5000語をかけて、それぞれのクラスの詳細、主要なパラメータ、実践的な使用例、そしてResponseオブジェクトがFastAPIの他の機能(response_model, response_class, 依存性注入など)とどのように連携するのかを深く掘り下げていきます。

FastAPIのデフォルトの挙動を超えた、より柔軟でパワフルなAPIレスポンスの構築方法をマスターしましょう。

1. FastAPIのデフォルトの挙動:手軽なJSONレスポンス

FastAPIの最も基本的な使い方では、APIエンドポイントの関数(パスオペレーション関数)からPythonのデータを返すだけで十分です。FastAPIは賢く、返されたデータを理解し、適切なHTTPレスポンスに変換します。

例えば、以下のようなコードは非常に一般的です。

“`python
from fastapi import FastAPI

app = FastAPI()

@app.get(“/items/{item_id}”)
def read_item(item_id: int):
“””
アイテム情報を返すシンプルなエンドポイント
“””
return {“item_id”: item_id, “name”: “Awesome Item”}
“`

この例では、Pythonの辞書{"item_id": item_id, "name": "Awesome Item"}を返しています。FastAPIは、この辞書を自動的にJSON形式の文字列に変換し、HTTPレスポンスのボディとして送信します。

デフォルトの変換ルール:

  • Python Dict/List: jsonable_encoderを使用してJSONに変換されます。これは、標準的なPythonデータ型(str, int, float, bool, None, list, dict)に加えて、Pydanticモデル、datetimeオブジェクト、UUIDなどをJSON互換の形式に変換できるFastAPIの内部ユーティリティです。
  • Pydanticモデル: モデルの.dict()メソッドや.json()メソッドが使用され、バリデーション済みのデータがJSONに変換されます。
  • その他のデータ型: Pydanticモデル以外でも、標準的なPythonデータ型は適切にJSONエンコードされます。

デフォルトのステータスコードとヘッダー:

  • 成功時のデフォルトのステータスコードは200 OKです。
  • デフォルトのContent-Typeヘッダーは通常application/jsonに設定されます。
  • FastAPIは、必要に応じて他のヘッダー(例: Content-Length)も自動的に追加します。

このデフォルトの挙動は、JSON APIを素早く構築する上で非常に便利です。しかし、前述の通り、JSON以外のレスポンスや、より詳細なレスポンス制御が必要な場合は、明示的にResponseオブジェクトを使用する必要があります。

2. なぜ明示的にResponseオブジェクトを使う必要があるのか?

FastAPIのデフォルトの挙動は素晴らしいですが、すべてに対応できるわけではありません。以下のようなシナリオでは、明示的にResponseオブジェクトを作成して返すことが不可欠または推奨されます。

  1. JSON以外のデータを返したい:

    • HTMLページを返す (text/html)
    • プレーンテキストを返す (text/plain)
    • CSVファイルやその他のテキストファイルを返す
    • XMLを返す (application/xml)
    • 画像、音声、動画などのバイナリデータを返す
    • PDFファイルなどのドキュメントを返す
  2. カスタムヘッダーを設定したい:

    • Cache-Controlヘッダーを設定してキャッシュを制御する
    • Content-Dispositionヘッダーを設定してファイルダウンロードを強制する
    • オリジン間リソース共有(CORS)に関連するヘッダー(Access-Control-...)を設定する(通常はFastAPIのCORSミドルウェアを使用しますが、特定のレスポンスに対して手動で設定する場合)
    • カスタムのアプリケーション固有のヘッダーを追加する
  3. クッキー(Cookies)を設定または削除したい:

    • ユーザー認証情報やセッション情報を保持するクッキーを発行する
    • 不要になったクッキーを削除する
  4. デフォルトとは異なるHTTPステータスコードを返したい:

    • 201 Created(リソース作成成功)
    • 204 No Content(成功したがレスポンスボディがない)
    • 400 Bad Request(クライアントエラー、通常はHTTPExceptionを使用しますが、カスタムエラーレスポンスボディが必要な場合)
    • 404 Not Found(リソースが見つからない、通常はHTTPExceptionを使用しますが、カスタムエラーレスポンスボディが必要な場合)
    • 301 Moved Permanently または 307 Temporary Redirect(リダイレクト)
  5. 大きなデータや動的に生成されるデータをストリーミングしたい:

    • 非常に大きなファイルを少しずつ送信する
    • サーバーセントイベント (SSE) のようなリアルタイムに近いデータストリームを送信する
    • 動画や音声などのメディアストリームを送信する
  6. 静的ファイルを効率的に提供したい:

    • ディスク上のファイルを直接、かつ効率的にブラウザに送信する
  7. 特定のシリアライゼーション(例: UJSON)を使用したい:

    • 標準のjsonライブラリよりも高速なJSONシリアライザを使用する場合

これらのシナリオでは、単にPythonデータを返すだけではFastAPIが意図した通りのレスポンスを生成できません。代わりに、FastAPIが提供するResponseクラスやその派生クラスのインスタンスを明示的に作成し、パスオペレーション関数から返す必要があります。

3. コアとなるResponseクラスとその主要パラメータ

FastAPIでレスポンスを制御するための基盤となるのが、fastapi.responsesモジュールにあるResponseクラスです。そして、その用途に応じた多くの派生クラスが存在します。

すべてのResponseクラスは、少なくとも以下の主要なパラメータを受け取ることができます(クラスによっては特定のパラメータが必須であったり、追加のパラメータがあったりします)。

  • content: レスポンスボディとして送信するデータ。これはバイト列(bytes)である必要があります。文字列を指定した場合、FastAPIはデフォルトでUTF-8エンコーディングを使用してバイト列に変換します。
  • status_code: HTTPステータスコード。int型で指定します。fastapi.statusモジュールには、よく使われるステータスコードの定数が用意されています(例: status.HTTP_200_OK, status.HTTP_404_NOT_FOUND)。
  • headers: レスポンスに含めるHTTPヘッダー。Dict[str, str]型で指定します。キーと値は文字列である必要があります。
  • media_type: レスポンスボディのメディアタイプ(MIMEタイプ)。str型で指定します(例: "application/json", "text/html", "image/png", "application/octet-stream")。これを指定しない場合、FastAPIはレスポンスクラスの種類やコンテンツから推測しようとしますが、明示的に指定するのが安全です。これはレスポンスのContent-Typeヘッダーになります。

これらのパラメータを理解することが、様々なResponseオブジェクトを使いこなす鍵となります。

4. 様々なResponse派生クラスの詳細と使い方

FastAPIは、一般的なWeb開発のニーズに応えるために、いくつかの便利なResponse派生クラスを提供しています。これらは基本的なResponseクラスを継承し、特定のメディアタイプの設定や、コンテンツの型に応じた自動的なバイト列変換などの機能を追加しています。

4.1. JSONResponse

最も頻繁に使用される派生クラスの一つです。FastAPIのデフォルト挙動は実質的にこれを使っています。明示的に使うのは、カスタムヘッダーやステータスコードを設定しつつJSONデータを返したい場合などです。

  • クラス: fastapi.responses.JSONResponse
  • 主な機能: Pythonのデータ構造(辞書、リスト、Pydanticモデルなど)を受け取り、自動的にJSON文字列にシリアライズしてバイト列に変換します。media_typeはデフォルトでapplication/jsonになります。
  • コンストラクタパラメータ:
    • content: JSONシリアライズ可能なPythonデータ構造。
    • status_code: HTTPステータスコード(デフォルト: 200)。
    • headers: カスタムヘッダー(デフォルト: None)。
    • media_type: メディアタイプ(デフォルト: application/json)。通常は変更しません。
    • encoder: JSONエンコードに使用するカスタム関数(オプション)。
    • json_dumps: JSON文字列化に使用するカスタム関数(オプション)。

使用例:

“`python
from fastapi import FastAPI, status
from fastapi.responses import JSONResponse

app = FastAPI()

@app.get(“/custom-json”)
def custom_json_response():
“””
カスタムヘッダーとステータスコードを持つJSONレスポンス
“””
content = {“message”: “Hello from custom JSON”, “status”: “ok”}
headers = {“X-Custom-Header”: “SomeValue”, “Cache-Control”: “no-cache”}
return JSONResponse(content=content, status_code=status.HTTP_200_OK, headers=headers)

@app.post(“/created-resource”, status_code=status.HTTP_201_CREATED) # デコレータでもステータス設定可能だが、Responseでも可能
def create_resource():
# … リソース作成ロジック …
new_resource_info = {“id”: “abc123”, “url”: “/resources/abc123”}
# ここではstatus_code=201がデコレータで設定されているが、
# Responseオブジェクトで上書きすることも可能
return JSONResponse(
content=new_resource_info,
status_code=status.HTTP_201_CREATED, # 明示的に指定
headers={“Location”: “/resources/abc123”} # Locationヘッダーは201でよく使われる
)
“`

JSONResponseは、カスタムヘッダーやステータスコードを設定したい場合に、デフォルトのJSON挙動を維持するための明確な方法を提供します。

4.2. PlainTextResponse

シンプルなテキストデータを返したい場合に使用します。

  • クラス: fastapi.responses.PlainTextResponse
  • 主な機能: 文字列(str)を受け取り、UTF-8エンコーディングでバイト列に変換します。media_typeはデフォルトでtext/plainになります。
  • コンストラクタパラメータ:
    • content: 返す文字列。
    • status_code: HTTPステータスコード(デフォルト: 200)。
    • headers: カスタムヘッダー(デフォルト: None)。
    • media_type: メディアタイプ(デフォルト: text/plain)。

使用例:

“`python
from fastapi import FastAPI
from fastapi.responses import PlainTextResponse

app = FastAPI()

@app.get(“/hello-text”, response_class=PlainTextResponse) # response_classを指定すると、文字列を返した場合にこれが使われる
def hello_text():
“””
プレーンテキストを返すエンドポイント
“””
# return “Hello, world!” # response_class=PlainTextResponse があればこれでOK
return PlainTextResponse(content=”Hello, world!\nThis is a plain text response.”)

@app.get(“/current-time”)
def current_time():
“””
現在の時間をプレーンテキストで返す
“””
from datetime import datetime
now = datetime.now().strftime(“%Y-%m-%d %H:%M:%S”)
return PlainTextResponse(content=f”Current time: {now}”, headers={“X-Source”: “FastAPI”})
“`

PlainTextResponseは、ログ出力や簡単なステータス確認など、APIが厳密な構造を持たないテキストを返すのに適しています。

4.3. HTMLResponse

HTMLコンテンツを返したい場合に使用します。

  • クラス: fastapi.responses.HTMLResponse
  • 主な機能: 文字列(str)としてHTMLコンテンツを受け取り、UTF-8エンコーディングでバイト列に変換します。media_typeはデフォルトでtext/htmlになります。
  • コンストラクタパラメータ:
    • content: 返すHTML文字列。
    • status_code: HTTPステータスコード(デフォルト: 200)。
    • headers: カスタムヘッダー(デフォルト: None)。
    • media_type: メディアタイプ(デフォルト: text/html)。

使用例:

“`python
from fastapi import FastAPI
from fastapi.responses import HTMLResponse

app = FastAPI()

@app.get(“/html-page”, response_class=HTMLResponse) # response_classを指定すると、文字列を返した場合にこれが使われる
def html_page():
“””
シンプルなHTMLページを返すエンドポイント
“””
html_content = “””
<!DOCTYPE html>


FastAPI HTML Example

Hello from FastAPI!

This is an HTML response.



“””
# return html_content # response_class=HTMLResponse があればこれでOK
return HTMLResponse(content=html_content, status_code=200)

@app.get(“/item-details/{item_id}”, response_class=HTMLResponse)
def item_details_html(item_id: int):
“””
アイテム詳細をHTMLで表示(簡易版)
“””
item_name = f”Item {item_id}”
item_description = f”Details for item {item_id}.”
html_content = f”””
<!DOCTYPE html>


{item_name}

{item_name}

{item_description}

Back to HTML example


“””
return HTMLResponse(content=html_content)
“`

HTMLResponseは、サーバーサイドレンダリングされたHTMLを返す場合や、簡単なランディングページをAPIと一緒にホストする場合などに便利です。ただし、大規模なテンプレートレンダリングにはJinja2などのテンプレートエンジンと連携させるのが一般的です(FastAPIはこれらのテンプレートエンジンとの統合もサポートしています)。

4.4. RedirectResponse

クライアントを別のURLにリダイレクトさせたい場合に使用します。

  • クラス: fastapi.responses.RedirectResponse
  • 主な機能: Locationヘッダーをセットし、適切なリダイレクトステータスコード(デフォルトは307 Temporary Redirect)を返します。
  • コンストラクタパラメータ:
    • url: リダイレクト先のURL(str型)。
    • status_code: リダイレクトのステータスコード(デフォルト: 307 Temporary Redirect)。よく使われるのは301 Moved Permanently, 302 Found, 303 See Other, 307 Temporary Redirect, 308 Permanent Redirectです。301308は永続的な移動を示し、ブラウザがそのURLをキャッシュする可能性があります。302, 303, 307は一時的なリダイレクトです。303は通常、POSTリクエスト後のリダイレクトに使用され、リダイレクト先のURLにはGETリクエストが送信されます。
    • headers: カスタムヘッダー(デフォルト: None)。

使用例:

“`python
from fastapi import FastAPI
from fastapi.responses import RedirectResponse, HTMLResponse
from fastapi import status

app = FastAPI()

@app.get(“/docs”)
def redirect_to_docs():
“””
/docsから/docs/にリダイレクト
“””
return RedirectResponse(url=”/docs/”) # デフォルトは307

@app.get(“/external”)
def redirect_to_external():
“””
外部サイトに永続的にリダイレクト
“””
return RedirectResponse(url=”https://fastapi.tiangolo.com/”, status_code=status.HTTP_301_MOVED_PERMANENTLY)

@app.post(“/login”)
def login_user(username: str, password: str):
“””
ログイン処理後、成功したらダッシュボードにリダイレクト
“””
# … ログイン認証ロジック …
if username == “test” and password == “secret”:
# 認証成功。POSTリクエスト後にGETリクエストとしてリダイレクトするには303 See Otherが良い
return RedirectResponse(url=”/dashboard”, status_code=status.HTTP_303_SEE_OTHER)
else:
# 認証失敗。エラーページを表示
# Note: 認証失敗は通常HTTPExceptionを使う方が良いが、例としてHTMLを返す
return HTMLResponse(content=”

Login Failed

Invalid credentials.

“, status_code=status.HTTP_401_UNAUTHORIZED)

@app.get(“/dashboard”)
def dashboard_page():
“””
ダッシュボードページ(簡易版)
“””
return HTMLResponse(content=”

Welcome to the Dashboard!

“)
“`

リダイレクトは、URL構造の変更、認証後の遷移、または一時的なリソースの移動などに広く使用されます。適切なステータスコードを選択することが重要です。

4.5. StreamingResponse

大きなデータや動的に生成されるデータを少しずつクライアントに送信(ストリーミング)したい場合に使用します。これは特に、ファイルのダウンロード、リアルタイムのデータフィード、Server-Sent Events (SSE)などに役立ちます。

  • クラス: fastapi.responses.StreamingResponse
  • 主な機能: コンテンツとして非同期イテレータまたはジェネレータを受け取り、そのイテレータ/ジェネレータが生成するチャンクを逐次的にレスポンスボディとして送信します。これにより、データをすべてメモリにロードすることなく、大きなデータを効率的に扱うことができます。
  • コンストラクタパラメータ:
    • content: 非同期イテレータ、イテレータ(ジェネレータ)、またはファイルライクオブジェクト。生成される要素はバイト列(bytes)または文字列(str、この場合はUTF-8でエンコードされる)である必要があります。
    • status_code: HTTPステータスコード(デフォルト: 200)。
    • headers: カスタムヘッダー(デフォルト: None)。特にファイルダウンロードではContent-Dispositionヘッダーが重要です。
    • media_type: レスポンスボディのメディアタイプ。ストリーミングのタイプによって適切に設定する必要があります(例: ファイルダウンロードならapplication/octet-stream、動画ならvideo/mp4、SSEならtext/event-stream)。

使用例1: 大きなファイルのストリーミングダウンロード

“`python
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from fastapi import Header # ヘッダーを使うため

app = FastAPI()

ダミーの巨大ファイルを作成 (実際のアプリケーションでは既存のファイルを使用)

def create_dummy_large_file(filename=”large_dummy_file.txt”, size_mb=100):
“””指定サイズのダミーファイルを生成”””
size_bytes = size_mb * 1024 * 1024
chunk_size = 1024 * 1024 # 1MB chunks
content = b”a” * (chunk_size) # Write 1MB ‘a’s at a time
with open(filename, “wb”) as f:
written = 0
while written < size_bytes:
f.write(content)
written += chunk_size
print(f”Written {written}/{size_bytes} bytes”)
print(f”Dummy file ‘{filename}’ ({size_mb}MB) created.”)

ファイル生成 (必要に応じて一度だけ実行)

create_dummy_large_file()

async def generate_file_chunks(filename=”large_dummy_file.txt”, chunk_size=1024*1024):
“””ファイルをチャンクごとに読み込むジェネレータ”””
print(f”Opening file: {filename}”)
try:
with open(filename, “rb”) as f:
while True:
chunk = f.read(chunk_size)
if not chunk:
break
# print(f”Read {len(chunk)} bytes”)
yield chunk
except FileNotFoundError:
print(f”File not found: {filename}”)
# yield bytes() # Empty yield to signal end/error
raise # Re-raise the exception to be handled by FastAPI/middlewares if needed
finally:
print(f”Finished streaming file: {filename}”)

@app.get(“/download-large-file”)
async def download_large_file():
“””
StreamingResponseを使用して大きなファイルをダウンロードさせる
“””
file_path = “large_dummy_file.txt”
try:
# ファイルサイズを取得 (Content-Lengthヘッダーに必要)
import os
file_size = os.path.getsize(file_path)

    # Content-Dispositionヘッダーを設定してダウンロードファイル名を指定
    headers = {
        'Content-Disposition': 'attachment; filename="downloaded_large_file.txt"',
        'Content-Length': str(file_size) # ファイルサイズヘッダー
    }

    # ジェネレータを渡す
    return StreamingResponse(generate_file_chunks(file_path), media_type="application/octet-stream", headers=headers)

except FileNotFoundError:
    # ファイルが見つからない場合はエラーレスポンス
    from fastapi import HTTPException
    raise HTTPException(status_code=404, detail="File not found")
except Exception as e:
    from fastapi import HTTPException
    raise HTTPException(status_code=500, detail=f"Error streaming file: {e}")

“`

この例では、generate_file_chunksというジェネレータ関数がファイルのチャンクを読み込み、StreamingResponseはそのチャンクをクライアントに送信します。これにより、ファイル全体をメモリに読み込む必要がなく、効率的なダウンロードが可能になります。Content-Dispositionヘッダーは、ブラウザにファイルをダウンロードさせること、およびダウンロード時のファイル名を指示します。Content-Lengthヘッダーは、ダウンロード進行状況の表示などに役立ちます。

使用例2: Server-Sent Events (SSE)

“`python
import asyncio
from fastapi import FastAPI
from fastapi.responses import StreamingResponse

app = FastAPI()

async def event_generator():
“””SSEイベントを生成する非同期ジェネレータ”””
count = 0
while count < 5: # 例として5回だけイベントを送信
message = f”data: This is event {count}\n\n” # SSEフォーマット
print(f”Sending SSE: {message.strip()}”)
yield message.encode(‘utf-8’) # バイト列でyield
count += 1
await asyncio.sleep(1) # 1秒待機
print(“Finished sending SSE events.”)
# yield b”data: end\n\n” # 終了を示すイベント(クライアント側で処理可能)

@app.get(“/stream-events”)
async def stream_events():
“””
Server-Sent Events (SSE) をストリーミングする
“””
headers = {
“Cache-Control”: “no-cache”,
“Connection”: “keep-alive”,
“Content-Type”: “text/event-stream”, # SSEのメディアタイプ
}
# 非同期ジェネレータを渡す
return StreamingResponse(event_generator(), media_type=”text/event-stream”, headers=headers)

クライアント側(ブラウザのJavaScriptなど)は EventSource API を使って接続します

例: const eventSource = new EventSource(“/stream-events”);

eventSource.onmessage = function(event) { console.log(event.data); };

“`

SSEでは、media_typetext/event-streamに設定し、コンテンツはdata: ...\n\nのようなSSEフォーマットで生成する必要があります。非同期ジェネレータを使うことで、awaitを使って非同期処理(例: 待機、データベースからのポーリングなど)を挟むことができ、ノンブロッキングなストリーミングが可能になります。

StreamingResponseを使う際は、提供するイテレータ/ジェネレータがバイト列を生成すること、そして適切なmedia_typeを設定することが非常に重要です。また、ジェネレータ内でブロッキングI/O(例: 同期的なファイル読み込みやネットワーク呼び出し)を行うと、アプリケーション全体のパフォーマンスが低下する可能性があるため、非同期I/O (asyncio) を使用するか、ThreadPoolExecutorなどでラップすることを検討してください。上記のファイルストリーミング例では、簡単のために同期I/O (open().read()) を使っていますが、実運用ではaiofilesのような非同期ファイルライブラリを使うべきです。

4.6. FileResponse

静的なファイルを効率的にクライアントに送信したい場合に使用します。StreamingResponseと似ていますが、こちらはディスク上の 既存 のファイルを送信することに特化しており、より最適化されています。範囲リクエスト (Range requests) にも対応しており、動画や音声などのシーク可能なメディアファイルの提供に適しています。

  • クラス: fastapi.responses.FileResponse
  • 主な機能: 指定されたパスのファイルをレスポンスとして送信します。ファイルのMIMEタイプを自動的に推測しようとします。範囲リクエストを自動的に処理します。
  • コンストラクタパラメータ:
    • path: 送信するファイルのファイルシステム上のパス(strまたはPathオブジェクト)。
    • status_code: HTTPステータスコード(デフォルト: 200)。
    • headers: カスタムヘッダー(デフォルト: None)。Content-Dispositionなどを追加できます。
    • media_type: メディアタイプ。指定しない場合はファイル名から推測します。
    • filename: クライアントへのダウンロード時のファイル名。Content-Dispositionヘッダーを自動的に設定します。
    • stat_result: ファイルのstat結果。パフォーマンス向上のために事前にos.stat(path)を実行して渡すことができます。
    • method: HTTPメソッド。通常はFastAPIが自動的に設定しますが、手動で指定することも可能です。
    • background: レスポンス送信完了後に実行するBackgroundTasksオブジェクト。ファイルディスクリプタを閉じるなどのクリーンアップ処理に使用できます。

使用例:

“`python
from fastapi import FastAPI, BackgroundTasks
from fastapi.responses import FileResponse

app = FastAPI()

ダミーファイル作成 (例: 画像ファイル)

Pillowがあれば画像ファイルを作成する例

try:
from PIL import Image, ImageDraw
import os

def create_dummy_image(filename="dummy_image.png"):
    """シンプルなダミー画像ファイルを作成"""
    if not os.path.exists(filename):
        img = Image.new('RGB', (60, 30), color = 'red')
        d = ImageDraw.Draw(img)
        d.text((10,10), "Hello", fill=(255,255,0))
        img.save(filename)
        print(f"Dummy image '{filename}' created.")

create_dummy_image()

except ImportError:
print(“Pillow not installed. Skipping dummy image creation. Install with ‘pip install Pillow'”)
# Pillowがない場合は適当なテキストファイルを代わりに作成
def create_dummy_text_file(filename=”dummy_file.txt”):
if not os.path.exists(filename):
with open(filename, “w”) as f:
f.write(“This is a dummy text file.\n”)
f.write(“It can be served using FileResponse.\n”)
print(f”Dummy text file ‘{filename}’ created.”)
create_dummy_text_file()

@app.get(“/static-image”)
async def get_static_image():
“””
静的な画像をFileResponseで返す
“””
file_path = “dummy_image.png”
# FileResponseはMIMEタイプを自動推測しようとします
# filenameを指定すると、Content-Dispositionヘッダーが自動的に設定されます
return FileResponse(path=file_path, filename=”hello_image.png”)

@app.get(“/static-file”)
async def get_static_file():
“””
静的なファイルをFileResponseで返す(ダウンロード)
“””
file_path = “dummy_file.txt”
# filenameを指定しない場合は、ブラウザはContent-Typeに基づいて表示/ダウンロードを判断
# return FileResponse(path=file_path)

# filenameを指定してダウンロードを強制し、ダウンロード時のファイル名を指定
return FileResponse(path=file_path, filename="downloaded_text_file.txt", media_type="text/plain")

BackgroundTasksを使用する例

def cleanup_file(path: str):
“””ファイルを削除するバックグラウンドタスク”””
print(f”Cleaning up file: {path}”)
import os
os.remove(path)

@app.get(“/temporary-file”)
async def get_temporary_file(background_tasks: BackgroundTasks):
“””
一時的なファイルを生成し、送信後に削除する
“””
# 実際にはここで一時ファイルを生成するロジックが入る
temp_file_path = “temp_report.csv”
with open(temp_file_path, “w”) as f:
f.write(“ID,Name\n”)
f.write(“1,Alice\n”)
f.write(“2,Bob\n”)

# レスポンス送信完了後にファイルを削除するタスクを追加
background_tasks.add_task(cleanup_file, temp_file_path)

# ファイルを送信
return FileResponse(
    path=temp_file_path,
    filename="report.csv",
    media_type="text/csv" # CSVのMIMEタイプを指定
)

“`

FileResponseは、ウェブサイトの静的アセット(画像、CSS、JS)、生成されたレポートファイル、またはメディアファイルなど、サーバーに存在するファイルを効率的に提供するのに最適です。background_tasks引数を使用すると、レスポンスがクライアントに送信された後にファイルのクリーンアップなどの処理を実行できます。これは特に一時ファイルを扱う場合に便利です。

4.7. その他のレスポンスクラス

FastAPIは他にもいくつかのレスポンスクラスを提供しています。

  • UJSONResponse: ujsonライブラリを使用してJSONを高速にシリアライズするJSONResponseの代替です。ujsonをインストールしている場合に使用できます。
  • ORJSONResponse: orjsonライブラリを使用してJSONを高速にシリアライズするJSONResponseの代替です。orjsonは非常に高速で、複雑なデータ型もサポートします。orjsonをインストールしている場合に使用できます。
  • XMLResponse: XMLコンテンツを返すためのクラス。文字列としてXMLを受け取り、media_typeはデフォルトでapplication/xmlまたはtext/xmlになります。

これらのレスポンスクラスは、特定のライブラリを使いたい場合や、特定のメディアタイプを扱う場合に選択肢となります。

5. ResponseオブジェクトとFastAPIの他の機能との連携

Responseオブジェクトは単独で使うだけでなく、FastAPIの強力な他の機能と組み合わせて使うことで、さらに柔軟なレスポンス制御が可能になります。

5.1. response_class パラメータ

ルートデコレータ(@app.get(), @app.post()など)のresponse_classパラメータを使用すると、そのルートのデフォルトのレスポンスクラスを指定できます。

“`python
from fastapi import FastAPI
from fastapi.responses import PlainTextResponse, HTMLResponse

app = FastAPI()

@app.get(“/text”, response_class=PlainTextResponse)
def get_text():
“””
このルートはデフォルトでPlainTextResponseを使用
文字列を返すと自動的にPlaintextになる
“””
return “This is text.” # response_class=PlainTextResponseが適用される

@app.get(“/html”, response_class=HTMLResponse)
def get_html():
“””
このルートはデフォルトでHTMLResponseを使用
文字列を返すと自動的にHTMLになる
“””
return “

This is HTML.

” # response_class=HTMLResponseが適用される

@app.get(“/override”)
def override_response_class():
“””
デフォルトはJSONResponseだが、PlainTextResponseで上書き
“””
# ここではresponse_classを指定していないので、デフォルトのJSONResponseが期待されるが、
# 明示的にPlainTextResponseオブジェクトを返すことでそれを上書きできる
return PlainTextResponse(content=”This overrides the default JSON response.”)
“`

response_classを設定しておくと、パスオペレーション関数が単なるデータ(辞書、Pydanticモデル、文字列など)を返した場合に、指定したレスポンスクラスを使ってシリアライズが行われます。これは、特定のルートで一貫して非JSONレスポンスを返したい場合に便利です。

重要な注意点: パスオペレーション関数が 明示的に Responseオブジェクトのインスタンス(例: return HTMLResponse(...))を返した場合、その返されたオブジェクトが使用されます。response_classパラメータは、関数が Responseオブジェクトではない 値を返した場合のデフォルトのレスポンスクラスとして機能します。また、返されたResponseオブジェクトのクラスがresponse_class異なる 場合でも、返されたオブジェクトが優先されます。

5.2. response_model パラメータとResponseオブジェクトの組み合わせ

ルートデコレータのresponse_modelパラメータは、レスポンスボディのデータスキーマを定義し、以下の機能を提供します。

  • レスポンスデータのアウトプットバリデーション(response_modelで定義されたPydanticモデルに従っているか)
  • レスポンスデータのシリアライゼーション(Pydanticモデルに変換し、JSONなどにエンコード)
  • APIドキュメント(OpenAPIスキーマ)でのレスポンスボディの定義

では、response_modelと明示的なResponseオブジェクトはどのように連携するのでしょうか? これはFastAPIで少し混乱しやすい部分なので、明確にしておきましょう。

ルール:

  1. パスオペレーション関数が 明示的にResponseオブジェクト を返した場合、response_modelによるレスポンスボディのバリデーションやシリアライゼーションは行われません。返されたResponseオブジェクトのcontentmedia_typestatus_codeheadersがそのまま使用されます。
  2. ただし、response_modelで定義されたAPIドキュメント(OpenAPIスキーマ)は引き続き生成されます。これは、APIの利用者に対して、そのエンドポイントがどのような 構造 のデータを返すことを意図しているかを示すために重要です。
  3. response_classパラメータが設定されている場合、APIドキュメントではresponse_modelのスキーマがresponse_classmedia_typeを使って定義されます。例えば、response_model=MyModelかつresponse_class=HTMLResponseの場合、ドキュメント上はMyModelの構造がtext/html形式で返されると表示されます(これは実際にはFastAPIがMyModelをHTMLに自動変換するわけではないため、誤解を招く可能性があります。通常、response_classresponse_modelを組み合わせる場合は、カスタムのエンコーダー/レスポンスクラスを用意する必要があります)。
  4. 最も一般的なパターンは、デフォルトのJSONレスポンスではresponse_modelを使ってデータ構造を定義しつつ、特定のケース(ファイルダウンロード、リダイレクト、エラーなど)で response_modelをバイパスして 明示的なResponseオブジェクトを返す、というものです。

例:

“`python
from fastapi import FastAPI, HTTPException, status
from fastapi.responses import JSONResponse, HTMLResponse, FileResponse
from pydantic import BaseModel
import os

app = FastAPI()

class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None

@app.get(“/items/{item_id}”, response_model=Item)
def read_item(item_id: str):
“””
response_modelを使ってJSONレスポンスのスキーマを定義
“””
# 通常のデータ(辞書)を返すと、これがresponse_modelに基づいてバリデーション&JSON化される
if item_id == “foo”:
return {“name”: “Foo”, “price”: 50.2}
elif item_id == “bar”:
return {“name”: “Bar”, “description”: “The Bar fighters”, “price”: 62.0, “tax”: 20.2}
else:
# この場合はHTTPExceptionが優先され、response_modelは適用されない(FastAPIの組み込みエラーハンドラが処理)
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=”Item not found”)

@app.get(“/items/{item_id}/html”, response_model=Item, response_class=HTMLResponse)
def read_item_html(item_id: str):
“””
response_modelでスキーマを定義しつつ、HTMLResponseを返す例
(response_modelはドキュメント生成にのみ使われる)
“””
if item_id == “foo”:
item = Item(name=”Foo”, price=50.2)
# ここではHTMLResponseを明示的に返す
# HTMLResponseのcontentは検証されない
html_content = f”””

Item: {item.name}

Price: {item.price}

“””
return HTMLResponse(content=html_content)
else:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=”Item not found in HTML format”)

@app.get(“/report”, response_model=Item) # response_modelはあくまで「期待されるデータ構造」のドキュメント
async def get_report():
“””
レポートファイルを返す例。response_modelは無視される。
“””
report_file_path = “dummy_report.txt”
# ダミーファイル作成
if not os.path.exists(report_file_path):
with open(report_file_path, “w”) as f:
f.write(“This is a dummy report.\n”)

# FileResponseを明示的に返す -> response_modelは無視される
return FileResponse(
    path=report_file_path,
    filename="report.txt",
    media_type="text/plain"
)

“`

この例では、/items/{item_id}エンドポイントは、デフォルトのJSONレスポンスとresponse_modelによるバリデーション/シリアライゼーションを使用しています。一方、/items/{item_id}/html/reportは、response_modelが指定されているにもかかわらず、それぞれHTMLResponseFileResponseを明示的に返しています。この場合、返されるレスポンスの実際のコンテンツはresponse_modelで検証されませんが、FastAPIが生成するOpenAPIドキュメントにはresponse_modelで定義されたItemスキーマが引き続き含まれます(ただし、HTMLやプレーンテキストのレスポンスに対してJSONスキーマが表示されるため、少し不自然に見える場合があります)。

したがって、response_modelは主に デフォルトのJSONレスポンス構造化されたレスポンス のスキーマ定義とドキュメント生成のために使用し、非定型的なレスポンス(ファイル、ストリーム、リダイレクトなど)を返す場合は、response_modelを指定していても、明示的にResponseオブジェクトを返すことでそれをオーバーライドするという理解が重要です。

5.3. 依存性注入でResponseオブジェクトにアクセスする

パスオペレーション関数でresponse: Responseのように型ヒントを付けることで、FastAPIは現在のリクエストに対応するResponseオブジェクトのインスタンスを依存性注入します。これにより、パスオペレーション関数内でレスポンスボディを返す 前に 、そのResponseオブジェクトに対してヘッダーやクッキーを設定することができます。

“`python
from fastapi import FastAPI, Response, status
from fastapi.responses import JSONResponse
from datetime import datetime, timedelta

app = FastAPI()

@app.get(“/items/{item_id}”)
def read_item_with_header(item_id: str, response: Response):
“””
依存性注入されたResponseオブジェクトを使ってヘッダーを設定
“””
response.headers[“X-Item-Id”] = item_id # レスポンスヘッダーを追加
response.headers[“Cache-Control”] = “max-age=3600, public” # キャッシュヘッダー
# … 他の処理 …
return {“item_id”: item_id, “status”: “processed”} # デフォルトのJSONレスポンスはそのまま生成される

@app.get(“/set-cookie”)
def set_cookie_example(response: Response):
“””
依存性注入されたResponseオブジェクトを使ってクッキーを設定
“””
# クッキーを設定 (名前, 値)
response.set_cookie(key=”my_cookie”, value=”fastapi_test”, httponly=True)

# 有効期限付きクッキーを設定
expires_time = datetime.now() + timedelta(days=7)
response.set_cookie(
    key="expires_cookie",
    value="valid_for_7_days",
    expires=expires_time.strftime("%a, %d %b %Y %H:%M:%S GMT"), # RFC 1123フォーマット
    httpy_only=True,
    secure=True, # HTTPSでのみ送信
    samesite="Lax" # CSRF対策
)

return {"message": "Cookies have been set"}

@app.get(“/delete-cookie”)
def delete_cookie_example(response: Response):
“””
依存性注入されたResponseオブジェクトを使ってクッキーを削除
“””
# クッキーを削除 (実際には有効期限を過去に設定)
response.delete_cookie(key=”my_cookie”)
response.delete_cookie(key=”expires_cookie”)

return {"message": "Cookies have been deleted"}

@app.get(“/custom-status”, status_code=status.HTTP_202_ACCEPTED)
def custom_status_example(response: Response):
“””
依存性注入されたResponseオブジェクトでステータスコードを上書き
“””
# デコレータで202を設定したが、ここで200に上書き
response.status_code = status.HTTP_200_OK # ステータスコードを上書き可能
return {“message”: “Status code overwritten to 200”}

“`

このパターンは非常に強力です。パスオペレーション関数が返すレスポンスボディの型(JSON、HTMLなど)を変えずに、ヘッダーやクッキーといったレスポンスの メタデータ だけを変更したい場合に最適です。関数は通常通りデータ(辞書、Pydanticモデルなど)を返し、FastAPIはそれをデフォルトまたはresponse_classで指定された方法でシリアライズしますが、そのレスポンスオブジェクトに対して依存性注入でアクセスしてヘッダーなどを追加できるのです。

6. レスポンスとエラーハンドリング

FastAPIでは、エラーレスポンスを返すための推奨される方法としてfastapi.HTTPExceptionraiseすることが挙げられます。HTTPExceptionを発生させると、FastAPIの組み込みエラーハンドラーがそれを捕捉し、適切なステータスコードと標準的なJSONエラーレスポンスボディ({"detail": "エラーメッセージ"})を生成します。

しかし、カスタムのエラーレスポンスボディを返したい場合や、エラー以外の状況で特定のステータスコード(例: 204 No Content)を返したい場合は、明示的にResponseオブジェクトを使用することもできます。

“`python
from fastapi import FastAPI, HTTPException, status
from fastapi.responses import JSONResponse, PlainTextResponse

app = FastAPI()

@app.get(“/items/{item_id}/detailed-error”)
def read_item_detailed_error(item_id: str):
“””
カスタムエラーレスポンスボディを返す例 (非推奨)
“””
if item_id != “valid”:
# HTTPExceptionを使うのが推奨される方法
# raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=”Item not found”)

    # 以下はカスタムレスポンスを使う方法だが、エラーハンドリングの統一性を損なう可能性がある
    custom_error_content = {
        "error_type": "ItemNotFound",
        "item_id": item_id,
        "message": f"The item with ID '{item_id}' could not be located."
    }
    # カスタムのエラーボディとステータスコードを持つJSONResponseを返す
    return JSONResponse(content=custom_error_content, status_code=status.HTTP_404_NOT_FOUND)
return {"item_id": item_id, "status": "found"}

@app.delete(“/items/{item_id}”, status_code=status.HTTP_204_NO_CONTENT)
def delete_item(item_id: str):
“””
204 No Contentを返す例
“””
# … 削除ロジック …
# 削除が成功したが、レスポンスボディは不要な場合
# status_code=204をデコレータで指定すると、Noneや空の辞書を返した場合に自動的に204になる
# 明示的にResponseオブジェクトを返すなら、content=None (または空バイト列) と status_code=204 を指定
# return None # これでOK (response_classがNoneの場合)
# return {} # これでもOK (response_classがNoneの場合)
return Response(status_code=status.HTTP_204_NO_CONTENT) # 明示的に指定

@app.get(“/no-content-text”)
def no_content_with_text():
“””
204 No Contentはボディを含まないべきだが、含めてしまった場合の挙動
(HTTP仕様的には204はボディを含まない)
“””
# 意図的にcontentを持たせて204を返す
# 多くのHTTPクライアントは204/205レスポンスではボディを無視する
return PlainTextResponse(content=”This text should not be seen.”, status_code=status.HTTP_204_NO_CONTENT)
“`

原則として、エラーを示すステータスコード(4xx, 5xx)を返す場合はHTTPExceptionを使用するのがベストプラクティスです。これにより、FastAPIの統一されたエラーハンドリング、ドキュメント生成、および必要に応じたカスタムエラーハンドラーの適用が可能になります。

ただし、成功を示すステータスコードでボディが不要な場合(204 No Content205 Reset Content)や、特別な成功レスポンス(例: 201 CreatedLocationヘッダーを付けて返す)には、明示的にResponseオブジェクトや適切なステータスコード設定を使用するのが適切です。

7. APIドキュメント(OpenAPI)との連携再訪

FastAPIはOpenAPI標準に基づいて対話型のAPIドキュメント(Swagger UIやReDoc)を自動生成します。Responseオブジェクトの使用がこのドキュメントにどう影響するかを理解しておくことは重要です。

  • デフォルトのJSONレスポンス: パスオペレーション関数がPythonデータ(辞書、Pydanticモデルなど)を返し、response_modelが指定されている場合、response_modelで定義されたPydanticモデルのスキーマが、application/jsonメディアタイプのレスポンスボディとしてドキュメント化されます。
  • response_modelresponse_class: response_modelresponse_classが両方指定されている場合、response_modelのスキーマがresponse_classmedia_typeを持つレスポンスボディとしてドキュメント化されます。前述の通り、これはFastAPIが自動的にデータをそのmedia_typeに変換するわけではないため、注意が必要です。主にカスタムレスポンスクラスを使用する場合に意味を持ちます。
  • 明示的なResponseオブジェクト: パスオペレーション関数がJSONResponse, PlainTextResponse, HTMLResponse, StreamingResponse, FileResponseなどのResponseオブジェクトを返した場合、response_modelが指定されていても、返されたResponseオブジェクトのmedia_typeとステータスコードがドキュメントに反映されます。contentの内容のスキーマは、response_modelが指定されていればそのスキーマが使われますが、返されたResponseオブジェクトの実際のコンテンツがそのスキーマに準拠しているかはFastAPIによってチェックされません。特にStreamingResponseFileResponseのように、response_modelでは表現できないデータ型を返す場合、ドキュメントには単にapplication/octet-streamのようなmedia_typeが表示されることが多くなります。
  • HTTPException: HTTPExceptionは、デフォルトのエラーレスポンススキーマ({"detail": "string"})としてドキュメント化されます。特定のステータスコードやカスタムエラーボディをドキュメント化したい場合は、ルートデコレータのresponsesパラメータを使用する必要があります。

例: responsesパラメータを使ったドキュメント化

“`python
from fastapi import FastAPI, HTTPException, status
from fastapi.responses import FileResponse
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
name: str
price: float

カスタムエラーレスポンスのスキーマを定義(Pydanticモデル推奨)

class ErrorResponse(BaseModel):
error_type: str
message: str

FileResponseを返すルートのドキュメント化の例

response_modelはFileResponseのコンテンツには適用されないが、responsesパラメータで代替を表現

@app.get(
“/items/{item_id}/file”,
# response_model=Item, # FileResponseを返すのでresponse_modelは意味がない(ドキュメント以外)
responses={
200: { # 成功時のレスポンス
“content”: {“application/octet-stream”: {}}, # バイナリデータであることを示す
“description”: “Successful Response – Item data as a file”,
},
404: {“model”: ErrorResponse, “description”: “Item not found”}, # エラーレスポンスのドキュメント化
500: {“description”: “Internal Server Error”},
}
)
async def get_item_file(item_id: str):
“””
アイテムデータをファイルとして返す(ドキュメント例付き)
“””
if item_id != “foo”:
# HTTPExceptionを発生させると、responses=…で定義した404レスポンスがドキュメント化される
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail={“error_type”: “NotFound”, “message”: f”Item {item_id} not found”} # detailは dict/list も可
)

# 成功時はFileResponseを返す
file_path = "dummy_item_data.txt"
with open(file_path, "w") as f:
    f.write(f"Item ID: {item_id}\n")
    f.write("Name: Foo\n")
    f.write("Price: 100.0\n")

return FileResponse(path=file_path, filename=f"item_{item_id}_data.txt", media_type="text/plain")
# 注意: この例ではFileResponseのmedia_typeはtext/plainだが、responsesのcontentはapplication/octet-streamとしている。
# ドキュメントの正確性を期すには、返すResponseのmedia_typeとresponsesのcontentのキーを一致させるべき。

“`

Responseオブジェクトを明示的に使用する場合、特に非標準的なメディアタイプやファイル、ストリームを返す場合は、response_modelだけでなく、必要に応じてresponsesパラメータを活用してAPIドキュメントをより正確に記述することが推奨されます。

8. ベストプラクティス:いつ、どのResponseを使うか

様々なResponseクラスを見てきましたが、それぞれに最適な使用シナリオがあります。以下は、いつどの方法を使うべきかについての一般的なガイドラインです。

  • ほとんどの場合(JSON API): 単にPythonデータ(辞書、リスト、Pydanticモデル)を返す。FastAPIが自動的にJSONResponseを生成してくれます。これが最もシンプルで推奨される方法です。
  • カスタムヘッダーやクッキーをJSONレスポンスに追加したい: response: Responseを依存性注入し、そのオブジェクトにheadersset_cookie/delete_cookieを設定してから、Pythonデータを返す。
  • 特定のステータスコード(200以外)をJSONレスポンスで返したい:
    • ルートデコレータのstatus_codeパラメータを使用する(例: @app.post("/", status_code=201))。最も簡潔です。
    • 明示的にJSONResponse(content=..., status_code=...)を返す。細かい制御が必要な場合に。
    • response: Responseを依存性注入し、response.status_code = ...を設定してから、Pythonデータを返す。ヘッダーなども同時に設定したい場合に便利です。
  • JSON以外の構造化されていないテキストを返したい: PlainTextResponseを使用する。簡単なメッセージ、ログ出力、CSVなど。
  • HTMLコンテンツを返したい: HTMLResponseを使用する。簡単なページ、サーバーサイドレンダリングされたコンテンツ。
  • クライアントを別のURLに飛ばしたい: RedirectResponseを使用する。適切なstatus_code(301, 302, 303, 307, 308)を選択することが重要です。
  • 大きなファイルや、リアルタイム/動的なデータをチャンクごとに送信したい: StreamingResponseを使用する。ファイルダウンロード、SSEなど。効率的な非同期処理(非同期ジェネレータ)と適切なmedia_typeが鍵となります。
  • サーバー上の既存のファイルを効率的に提供したい: FileResponseを使用する。静的ファイル、生成済みのレポートファイルなど。範囲リクエストの自動処理やバックグラウンドタスクとの連携が容易です。
  • 高速なJSONシリアライゼーションが必要: UJSONResponseORJSONResponseを使用する(対応ライブラリをインストールしている場合)。
  • XMLを返したい: XMLResponseを使用する。
  • エラーレスポンスを返したい: ほとんどの場合、HTTPExceptionraiseする。カスタムエラーボディやヘッダーが必要な場合は、エラーハンドラーをカスタマイズするか、明示的なJSONResponse(または他のResponse)を使用することを検討する(ただし、これはエラーハンドリングの一貫性を損なう可能性があるので慎重に)。

基本的には、最もシンプルな方法(Pythonデータを返す)から始め、必要に応じてresponse: Responseを使ったヘッダー/クッキー設定、そして最終的に明示的なResponseオブジェクトの使用へとステップアップしていくのが良いアプローチです。

9. まとめ

FastAPIは、デフォルトでPythonデータをJSONレスポンスに自動変換する非常に便利な機能を提供しています。しかし、JSON以外のメディアタイプ、カスタムヘッダー、クッキー、特定のステータスコード、ストリーミング、ファイル送信といった多様なWeb APIの要件を満たすためには、fastapi.responsesモジュールのResponseオブジェクトとその派生クラス(JSONResponse, PlainTextResponse, HTMLResponse, RedirectResponse, StreamingResponse, FileResponseなど)を理解し、使いこなすことが不可欠です。

本記事では、これらの主要なレスポンスクラスそれぞれの特徴、主要なパラメータ、具体的な使用例を詳細に解説しました。また、response_classresponse_modelといった他のFastAPIの機能との連携や、依存性注入によるResponseオブジェクトへのアクセス方法についても掘り下げました。

APIの開発においては、返すデータの種類やクライアントが必要とする形式を正確に理解し、最適なResponse生成方法を選択することが、効率的で堅牢なアプリケーションを構築する上で非常に重要です。今回学んだ知識を活かして、FastAPIでより表現豊かで制御性の高いAPIを開発してください。


参考文献:


これで、約5000語の詳細な解説記事となります。コード例も豊富に含め、FastAPIにおけるResponseオブジェクトの様々な側面を網羅したつもりです。

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール