FastAPIのRouter (APIRouter) とは?初心者向けに基本を徹底解説
FastAPIを使ってAPI開発を始めると、最初に出会うのがルーティングです。@app.get("/")
のようなデコレータを使って、URLとそれを処理する関数を結びつけるのは非常に直感的で分かりやすいですね。しかし、APIが大きくなってエンドポイントが増えてくると、一つのファイルに全てのルート定義を書くのが難しくなってきます。そんなときに役立つのが、FastAPIの強力な機能である APIRouter
です。
この記事では、FastAPI初心者の方を対象に、APIRouter
がなぜ必要なのか、何ができるのか、そしてどのように使うのかを、基本的なことから応用的な機能まで、豊富なコード例とともに徹底的に解説します。約5000語というボリュームで、APIRouter
の全てを理解し、あなたのFastAPIプロジェクトをより整理された、管理しやすいものにするための知識を身につけていきましょう。
1. はじめに:なぜAPIRouterが必要なのか?
1.1 FastAPIとは?
まずは簡単にFastAPIについて触れておきましょう。FastAPIは、PythonでAPIを構築するための、モダンで高速(High performance)なWebフレームワークです。ビルドにはStarlette(Web部分)とPydantic(データ検証部分)が使われており、その主な特徴は以下の通りです。
- 高速: StarletteとUvicorn(またはHypercornなど)のおかげで、非常に高いパフォーマンスを発揮します。
- コードが書きやすい: Pythonの標準的な型ヒントを利用して、コードの補完やエラーチェックが強力に行えます。
- ドキュメントの自動生成: 標準でOpenAPIやJSON Schemaに基づいたAPIドキュメント(Swagger UIやReDoc)を自動生成してくれます。これは開発者だけでなく、APIを利用する側にとっても非常に便利です。
- 型ヒントによるデータ検証: Pydanticを使って、リクエストのデータ(パスパラメータ、クエリパラメータ、リクエストボディ)の検証やシリアライズを簡単に行えます。
- 依存性注入(Dependency Injection): コードの再利用性やテスト容易性を高める強力な機能です。
これらの特徴により、FastAPIは人気が急上昇しているフレームワークの一つです。
1.2 シンプルなアプリと一つのファイルの問題点
FastAPIで最もシンプルな「Hello, World!」のようなAPIを作る場合、通常は以下のように一つのファイルに全てのコードを書きます。
“`python
main.py
from fastapi import FastAPI
app = FastAPI()
@app.get(“/”)
def read_root():
return {“Hello”: “World”}
@app.get(“/items/{item_id}”)
def read_item(item_id: int, q: str | None = None):
return {“item_id”: item_id, “q”: q}
@app.put(“/items/{item_id}”)
def update_item(item_id: int, item: dict):
return {“item_name”: item[“name”], “item_id”: item_id}
… もっと多くのエンドポイントが続く …
“`
このコードは非常にシンプルで分かりやすいですね。エンドポイントが2つや3つ程度であれば、これで十分です。しかし、もしあなたのAPIが以下のような状況になったらどうなるでしょう?
- ユーザー管理関連のエンドポイント(
/users/
,/users/{user_id}
,/users/me
など) - 商品管理関連のエンドポイント(
/items/
,/items/{item_id}
,/items/{item_id}/reviews
など) - 注文管理関連のエンドポイント(
/orders/
,/orders/{order_id}
など) - 認証関連のエンドポイント(
/login
,/logout
,/register
など) - … さらに多くの機能が追加される …
これらのエンドポイント全てを一つのファイル、例えば main.py
に記述していくと、コードはすぐに数百行、数千行にも膨れ上がってしまいます。
一つのファイルに全てを書くことのデメリットは明らかです。
- 可読性の低下: コードが長くなりすぎると、どこに何が書かれているのか探しにくくなります。特定の機能に関連するエンドポイントがファイルのあちこちに散らばってしまい、全体像を把握するのが難しくなります。
- 保守性の低下: 特定のエンドポイントを修正したり、新しい機能を追加したりする際に、他の部分への影響を考慮するのが難しくなります。ちょっとした修正が予期せぬバグを引き起こすリスクも高まります。
- コードの再利用性の低さ: 同じような処理を複数のエンドポイントで使う場合でも、うまく共通化しにくい構造になります。
- テストの難しさ: 一つの大きな塊としてしかテストできないため、特定の機能だけを単体でテストするのが難しくなります。
- チーム開発の難しさ: 複数の開発者が同じファイルを同時に編集することになり、コンフリクトが発生しやすくなります。
こうした問題を解決し、APIコードを機能ごとやリソースごとに整理するための仕組みが APIRouter
です。
1.3 この記事で学ぶこと
この記事を通して、あなたは以下のことを学びます。
APIRouter
がコード分割と整理にどのように役立つか。APIRouter
の基本的な使い方(インスタンス作成、ルート定義、メインアプリへのマウント)。APIRouter
を使った具体的なコード分割の例。APIRouter
の便利な機能(prefix
,tags
,dependencies
,responses
など)の使い方。- より大きなFastAPIプロジェクトでのファイル構成のヒント。
これらの知識を身につけることで、あなたはFastAPIを使って、より大規模で保守しやすいAPIを構築できるようになるでしょう。
2. APIRouterの導入
2.1 APIRouterとは何か?
APIRouter
は、FastAPIアプリケーション内で、特定のパスプレフィックス(例: /users
や /items
)の下にある全てのエンドポイントや、特定の機能に関連するエンドポイントをグループ化するためのクラスです。
FastAPI
クラス自体も内部的には APIRouter
を継承しています。つまり、FastAPI
インスタンスに @app.get(...)
のようにルートを定義するのと、APIRouter
インスタンスに @router.get(...)
のようにルートを定義するのは、基本的な考え方として同じです。
APIRouter
の主な役割は、ルート定義、依存関係、タグ、レスポンスモデルなどの設定を、メインの FastAPI
アプリケーションとは独立して管理することです。これにより、コードを論理的に分割し、整理することができます。
2.2 なぜAPIRouterを使うのか?(メリットの再確認)
APIRouter
を使う最大の理由は、前述した「一つのファイルに全てを書くことの問題点」を解決するためです。具体的には、以下のようなメリットがあります。
- コードの分割と整理: 機能ごと、リソースごとにコードを別々のファイルやモジュールに分割できます。これにより、各ファイルが短くなり、特定の機能に関連するコードを見つけやすくなります。
- 保守性の向上: 各ルーターが独立しているため、ある機能のコードを変更しても、他の機能への影響を最小限に抑えることができます。
- 再利用性: 特定のルーターを別のFastAPIアプリケーションに組み込むことが比較的容易になります(完全に独立させる場合は工夫が必要ですが、概念的には可能です)。
- テスト容易性: ルーター単位でテストコードを書くことが容易になり、より効率的にAPIの各部分をテストできます。
- チーム開発の効率化: 各開発者が異なるルーターファイルを担当することで、コンフリクトを減らし、並行開発を進めやすくなります。
- OpenAPIドキュメントの整理: タグやプレフィックスを使って、自動生成されるAPIドキュメント(Swagger UIなど)を分かりやすく整理できます。
2.3 APIRouterの基本的な使い方
APIRouter
の使い方は非常にシンプルです。以下の3つのステップで行います。
APIRouter
インスタンスを作成する。- 作成したルーターインスタンスに、通常通り
@router.get(...)
のようなデコレータを使ってルートを定義する。 - メインの
FastAPI
アプリケーションインスタンスに、router
インスタンスをapp.include_router()
メソッドを使って「マウント」する。
例を見てみましょう。ユーザー関連のエンドポイントを管理するルーターを作成し、メインアプリに組み込む場合を考えます。
ステップ1: ユーザー関連ルーターファイルの作成
まず、ユーザー関連のルートを記述するための新しいファイルを作成します。例えば routers/users.py
としましょう。
“`python
routers/users.py
from fastapi import APIRouter, HTTPException
1. APIRouter インスタンスを作成
router = APIRouter()
ダミーのユーザーデータ
fake_users_db = {
“foo”: {“username”: “foo”, “full_name”: “Foo Bar”},
“baz”: {“username”: “baz”, “full_name”: “Baz Qux”},
}
2. 作成したルーターインスタンスにルートを定義
@router.get(“/users/”, tags=[“users”])
async def read_users():
“””
全てのユーザー情報を取得します。
“””
return fake_users_db
@router.get(“/users/me”, tags=[“users”])
async def read_user_me():
“””
認証されたユーザー自身の情報を取得します。(この例ではダミー)
“””
return {“username”: “fakecurrentuser”}
{username} はパスパラメータ
@router.get(“/users/{username}”, tags=[“users”])
async def read_user(username: str):
“””
特定のユーザー情報を取得します。
“””
if username not in fake_users_db:
raise HTTPException(status_code=404, detail=”User not found”)
return fake_users_db[username]
新しいユーザーを作成するエンドポイント(POSTメソッドの例)
仮のリクエストボディのモデルを定義(Pydanticモデルを使うのが一般的だが、ここでは簡単な例としてdictを想定)
from typing import Dict
@router.post(“/users/”, tags=[“users”])
async def create_user(user_data: Dict[str, str]):
“””
新しいユーザーを作成します。(この例では実際には保存しません)
“””
username = user_data.get(“username”)
full_name = user_data.get(“full_name”)
if not username or not full_name:
raise HTTPException(status_code=400, detail=”Username and full_name are required”)
# 実際にはここでデータベースに保存するなどの処理を行います
print(f"Creating user: {username} with full name {full_name}")
return {"message": "User created successfully", "username": username}
“`
このファイルでは、APIRouter()
を呼び出して router
というインスタンスを作成しています。そして、この router
インスタンスに対して @router.get
, @router.post
などのデコレータを使って、ユーザー関連のエンドポイントを定義しています。
注目すべきは、これらのパスが /users/
や /users/me
のように記述されている点です。後でメインアプリに組み込む際に、これらのパスに特定のプレフィックスを追加することもできます(これについては後述します)。
tags=["users"]
は、OpenAPIドキュメント(Swagger UIなど)でこれらのエンドポイントを「users」というグループにまとめるための設定です。これも APIRouter
の便利な機能の一つです。
ステップ2: メインアプリケーションファイルの修正
次に、メインのFastAPIアプリケーションファイル(例えば main.py
)を修正し、先ほど作成したユーザー関連ルーターを読み込んで組み込みます。
“`python
main.py
from fastapi import FastAPI
ユーザー関連ルーターをインポート
from routers import users # routersディレクトリ内のusers.pyをインポートすることを想定
app = FastAPI()
3. app.include_router() を使ってルーターをマウント
app.include_router(users.router)
メインアプリ自身のルート(例:ヘルスチェックやトップページなど)はそのままmain.pyに書くことも可能
@app.get(“/”)
async def read_root():
return {“message”: “Welcome to the API”}
もし、商品関連ルーターもあるなら、同様にインポートしてマウント
from routers import items # 仮にitems.pyルーターがあるとする
app.include_router(items.router)
“`
この main.py
では、まず from routers import users
として、先ほど作成した routers/users.py
ファイル内の router
インスタンスをインポートしています(ここで routers
はPythonのパッケージとして認識されるディレクトリ構造になっていると仮定しています)。
そして、app.include_router(users.router)
を呼び出すことで、users.router
で定義された全てのエンドポイントがメインの app
インスタンスに組み込まれます。
include_router
メソッドには様々なオプションを指定できますが、最も一般的な使い方は、引数として APIRouter
インスタンスを渡すだけです。
これで設定は完了です。アプリケーションを実行してみましょう。
bash
uvicorn main:app --reload
ブラウザで http://127.0.0.1:8000/docs
を開くと、自動生成されたAPIドキュメントが表示されるはずです。そこには、main.py
で定義した /
に加えて、routers/users.py
で定義した /users/
, /users/me
, /users/{username}
といったエンドポイントが、「users」というタグでグループ化されて表示されているでしょう。
この時点で、あなたは APIRouter
を使ってコードを分割し、APIドキュメントを整理するという最初のステップを成功させました!
2.4 複数のルーターをマウントする
当然ながら、あなたは複数の APIRouter
を作成し、それぞれ異なる機能(例: /items
, /orders
, /auth
など)を担当させることができます。そして、それら全てを main.py
でまとめて include_router
していくのが一般的なパターンです。
例:商品関連ルーターを追加する場合
ステップ1: 商品関連ルーターファイルの作成
routers/items.py
というファイルを作成します。
“`python
routers/items.py
from fastapi import APIRouter, HTTPException
from typing import Dict
router = APIRouter()
ダミーの商品データ
fake_items_db = {
“foo”: {“name”: “Foo”},
“bar”: {“name”: “Bar”},
“baz”: {“name”: “Baz”},
}
@router.get(“/items/”, tags=[“items”])
async def read_items():
“””
全ての商品情報を取得します。
“””
return fake_items_db
@router.get(“/items/{item_id}”, tags=[“items”])
async def read_item(item_id: str):
“””
特定の商品の情報を取得します。
“””
if item_id not in fake_items_db:
raise HTTPException(status_code=404, detail=”Item not found”)
return fake_items_db[item_id]
@router.post(“/items/”, tags=[“items”])
async def create_item(item_data: Dict[str, str]):
“””
新しい商品を作成します。(この例では実際には保存しません)
“””
item_id = item_data.get(“item_id”)
name = item_data.get(“name”)
if not item_id or not name:
raise HTTPException(status_code=400, detail=”item_id and name are required”)
# 実際にはここでデータベースに保存するなどの処理を行います
print(f"Creating item: {item_id} with name {name}")
return {"message": "Item created successfully", "item_id": item_id}
“`
ステップ2: メインアプリケーションファイルの修正
main.py
に items.router
もインポートしてマウントします。
“`python
main.py
from fastapi import FastAPI
from routers import users # usersルーターをインポート
from routers import items # itemsルーターをインポート
app = FastAPI()
app.include_router(users.router) # usersルーターをマウント
app.include_router(items.router) # itemsルーターをマウント
@app.get(“/”)
async def read_root():
return {“message”: “Welcome to the API”}
“`
これで、/users/
, /items/
などのエンドポイントが、それぞれのルーターで定義された通りに機能するようになります。http://127.0.0.1:8000/docs
を見ると、「users」と「items」のタグでグループ化されたエンドポイントが表示されていることが確認できます。
このように APIRouter
を使うことで、アプリケーションの規模が大きくなっても、関連するコードをまとめて管理しやすくなります。
3. APIRouterの詳細な機能
APIRouter
には、コードをより効率的に整理し、APIドキュメントを改善するための便利な機能がいくつかあります。ここでは、特によく使われる prefix
, tags
, dependencies
, responses
について詳しく見ていきましょう。
3.1 prefix (パスプレフィックス)
prefix
オプションは、include_router
メソッドでルーターをマウントする際に指定します。指定したプレフィックスが、そのルーター内で定義されている全てのエンドポイントパスの前に自動的に付加されます。
これは非常に便利な機能です。なぜなら、ルーターファイル内でエンドポイントパスを定義する際に、特定のプレフィックス(例: /users
や /items
)を毎回記述する必要がなくなるからです。ルーターファイル内では /
や /{item_id}
のように相対的なパスで定義しておき、メインアプリでマウントする際にまとめてプレフィックスを設定できます。
例:/users
プレフィックスを使用する場合
ステップ1: ユーザー関連ルーターファイルの修正(パスを相対的に記述)
routers/users.py
を修正し、パスを /
や /{username}
のように、プレフィックスなしで記述します。
“`python
routers/users.py (修正版)
from fastapi import APIRouter, HTTPException
from typing import Dict
1. APIRouter インスタンスを作成
router = APIRouter(tags=[“users”]) # ここでタグを設定することも可能
ダミーのユーザーデータ(省略)
fake_users_db = {
“foo”: {“username”: “foo”, “full_name”: “Foo Bar”},
“baz”: {“username”: “baz”, “full_name”: “Baz Qux”},
}
2. 作成したルーターインスタンスにルートを定義(プレフィックスなしで記述)
@router.get(“/”) # => /users/ となる(プレフィックスが /users の場合)
async def read_users():
“””
全てのユーザー情報を取得します。
“””
return fake_users_db
@router.get(“/me”) # => /users/me となる
async def read_user_me():
“””
認証されたユーザー自身の情報を取得します。(この例ではダミー)
“””
return {“username”: “fakecurrentuser”}
{username} はパスパラメータ => /users/{username} となる
@router.get(“/{username}”)
async def read_user(username: str):
“””
特定のユーザー情報を取得します。
“””
if username not in fake_users_db:
raise HTTPException(status_code=404, detail=”User not found”)
return fake_users_db[username]
新しいユーザーを作成するエンドポイント => /users/ となる
@router.post(“/”)
async def create_user(user_data: Dict[str, str]):
“””
新しいユーザーを作成します。(この例では実際には保存しません)
“””
username = user_data.get(“username”)
full_name = user_data.get(“full_name”)
if not username or not full_name:
raise HTTPException(status_code=400, detail=”Username and full_name are required”)
print(f"Creating user: {username} with full name {full_name}")
return {"message": "User created successfully", "username": username}
“`
ルーターインスタンス作成時に tags=["users"]
を指定しました。これにより、このルーターで定義される全てのエンドポイントにデフォルトでこのタグが適用されます(個別のエンドポイントで別のタグを指定することも可能)。
ステップ2: メインアプリケーションファイルの修正 (prefix
を指定してマウント)
main.py
で include_router
を呼び出す際に、prefix="/users"
オプションを追加します。
“`python
main.py (prefix指定版)
from fastapi import FastAPI
from routers import users # usersルーターをインポート
from routers import items # itemsルーターをインポート
app = FastAPI()
usersルーターをマウント。prefix=”/users” を指定
app.include_router(users.router, prefix=”/users”)
itemsルーターもprefixを指定してマウント
routers/items.py のパスも相対的に修正する必要がある
app.include_router(items.router, prefix=”/items”)
@app.get(“/”)
async def read_root():
return {“message”: “Welcome to the API”}
“`
これで、users.router
で定義された /
, /me
, /{username}
といったパスは、それぞれ /users/
, /users/me
, /users/{username}
という完全なパスとして機能するようになります。
prefix
を使うことで、ルーターファイル内では「このルーターのトップからの相対パス」としてエンドポイントを定義できるため、コードがより分かりやすくなります。また、後からAPIのURL構造を変更したくなった場合でも、ルーターファイル自体を修正する必要はなく、main.py
の include_router
の呼び出し部分で prefix
の値を変えるだけで対応できます。
prefix_include_in_schema
include_router
の prefix
オプションに関連して、もう一つ prefix_include_in_schema
というオプションがあります。これは、指定したプレフィックスを OpenAPI スキーマ(そして Swagger UI など)に含めるかどうかを制御します。デフォルトは True
なので、通常はプレフィックスがドキュメントにも表示されます。もし何らかの理由でプレフィックスをドキュメントに表示したくない場合は、prefix_include_in_schema=False
を指定します。
ほとんどの場合、このオプションを変更する必要はありません。
3.2 tags (タグ)
tags
オプションは、APIRouter
を作成する際、または個別のルート(@router.get(...)
など)を定義する際に指定できます。指定したタグは、OpenAPIドキュメント(Swagger UIやReDoc)でエンドポイントをグループ化するために使用されます。
APIRouter(tags=["mytag"])
のようにルーターレベルで指定すると、そのルーター内の全てのエンドポイントにデフォルトでタグが適用されます。@router.get("/myendpoint", tags=["anothertag"])
のように個別のルートで指定すると、そのルートにのみタグが適用されます。もしルーターレベルでタグが設定されていても、個別のルートの設定が優先されます。- タグはリストで指定するため、一つのエンドポイントに複数のタグを付けることも可能です(例:
tags=["users", "admin"]
)。
例:タグを使ったドキュメントの整理
先ほどの routers/users.py
の例では、APIRouter(tags=["users"])
のようにルーターレベルでタグを設定しました。これにより、このルーター内の全てのエンドポイントがSwagger UIで「users」というグループにまとめられます。
もし、管理者だけがアクセスできるユーザー管理エンドポイントがある場合、以下のようにタグを追加することもできます。
“`python
routers/users.py (タグの例)
from fastapi import APIRouter, HTTPException
from typing import Dict
ルーターレベルでデフォルトタグを設定
router = APIRouter(tags=[“users”])
… 他のユーザー関連エンドポイント(省略) …
@router.get(“/admin/users/”, tags=[“users”, “admin”]) # 複数のタグを指定
async def read_all_users_for_admin():
“””
管理者用の全てのユーザー情報を取得します。
“””
# 実際にはここで管理者の認証・認可チェックが必要です
return fake_users_db
@router.delete(“/admin/users/{username}”, tags=[“users”, “admin”])
async def delete_user_for_admin(username: str):
“””
管理者用の特定のユーザーを削除します。
“””
if username not in fake_users_db:
raise HTTPException(status_code=404, detail=”User not found”)
# 実際にはここで削除処理を行います
del fake_users_db[username]
return {"message": f"User {username} deleted successfully"}
“`
このようにタグを適切に使うことで、APIドキュメントが非常に見やすく、ナビゲートしやすくなります。利用者は、どの機能に関するエンドポイントなのかを一目で把握できます。
3.3 dependencies (依存関係)
dependencies
オプションも、APIRouter
を作成する際、または個別のルート定義で指定できます。これは、指定したルーターやルートが処理される前に必ず実行されるべき依存関係(Dependency)を指定するためのものです。
依存関係注入(Dependency Injection)はFastAPIの非常に強力な機能の一つで、共通のロジック(認証、認可、データベース接続の取得、設定値の読み込みなど)を複数のエンドポイントで使い回すのに役立ちます。APIRouter
の dependencies
オプションを使うと、そのルーター内の全てのエンドポイントに共通の依存関係を簡単に適用できます。
例:簡単な認証依存関係をルーター全体に適用する
まず、認証を行うための依存関係関数を作成します。
“`python
dependencies.py (仮)
from fastapi import Header, HTTPException
async def get_api_key(x_api_key: str = Header()):
“””
リクエストヘッダーからAPIキーを取得し、検証する依存関係。
実際にはデータベースなどでAPIキーを検証します。
“””
if x_api_key != “fake-api-key”: # ダミーのAPIキーで検証
raise HTTPException(status_code=401, detail=”Invalid API Key”)
return x_api_key
async def get_current_user():
“””
現在のユーザーを取得する依存関係。(ここではダミー)
実際には認証トークンなどを検証してユーザー情報を返します。
“””
# 仮に認証済みとみなし、ダミーユーザーを返す
return {“username”: “authenticated_user”}
“`
次に、この依存関係を users.router
に適用します。ここでは、ユーザー関連のエンドポイントは認証されたユーザーのみがアクセスできるものと仮定します。
“`python
routers/users.py (dependenciesの例)
from fastapi import APIRouter, HTTPException, Depends
from typing import Dict
認証依存関係をインポート
from dependencies import get_api_key, get_current_user
1. APIRouter インスタンスを作成し、dependencies を指定
このルーターの全てのエンドポイントは、get_api_key と get_current_user を実行する
router = APIRouter(
prefix=”/users”, # prefixも一緒に指定
tags=[“users”],
dependencies=[Depends(get_api_key), Depends(get_current_user)], # ここで依存関係を指定
responses={404: {“description”: “Not found”}}, # 共通のレスポンスも指定可能
)
ダミーのユーザーデータ(省略)
fake_users_db = {
“foo”: {“username”: “foo”, “full_name”: “Foo Bar”},
“baz”: {“username”: “baz”, “full_name”: “Baz Qux”},
“authenticated_user”: {“username”: “authenticated_user”, “full_name”: “Authenticated User”},
}
2. 作成したルーターインスタンスにルートを定義
これらのエンドポイントは、自動的に上記の dependencies が実行される
@router.get(“/”)
async def read_users():
“””
全てのユーザー情報を取得します。
“””
# get_api_keyとget_current_userは既に実行済み
return fake_users_db
@router.get(“/me”)
async def read_user_me(current_user: dict = Depends(get_current_user)):
“””
認証されたユーザー自身の情報を取得します。
ルーターレベルのdependenciesに含まれているが、ここで明示的にDepends()として引数に指定すると、
その依存関係の戻り値を受け取ることができる。
“””
# ここでは get_current_user() の戻り値 (ダミーユーザー情報) を利用
return current_user
@router.get(“/{username}”)
async def read_user(username: str):
“””
特定のユーザー情報を取得します。
“””
if username not in fake_users_db:
raise HTTPException(status_code=404, detail=”User not found”)
return fake_users_db[username]
@router.post(“/”)
async def create_user(user_data: Dict[str, str]):
“””
新しいユーザーを作成します。
“””
username = user_data.get(“username”)
full_name = user_data.get(“full_name”)
if not username or not full_name:
raise HTTPException(status_code=400, detail=”Username and full_name are required”)
print(f"Creating user: {username} with full name {full_name}")
return {"message": "User created successfully", "username": username}
“`
この例では、APIRouter
インスタンスを作成する際に dependencies=[Depends(get_api_key), Depends(get_current_user)]
を指定しています。これにより、users.router
に含まれる /
, /me
, /{username}
, /
(POST) といった全てのエンドポイントが呼び出される前に、get_api_key
関数と get_current_user
関数が実行されるようになります。
get_api_key
は、リクエストヘッダーの X-API-Key
をチェックし、無効であれば HTTPException
でエラーレスポンスを返します。この場合、エンドポイントの関数は実行されません。get_current_user
は(この例ではダミーですが)認証済みユーザーの情報を返します。
/me
エンドポイントのように、依存関係の戻り値をエンドポイント関数内で利用したい場合は、通常通り引数に Depends(...)
として指定します。FastAPIは賢く、ルーターレベルの依存関係として既に実行されたものであれば、再度実行せず、その戻り値を引数に渡してくれます。
このように dependencies
をルーターレベルで使うことで、認証や共通のデータ取得など、複数のエンドポイントで必要な前処理を重複なく、簡潔に記述することができます。これはコードのDRY (Don’t Repeat Yourself) 原則を守り、保守性を高める上で非常に重要です。
3.4 responses (レスポンス)
responses
オプションも、APIRouter
を作成する際、または個別のルート定義で指定できます。これは、そのルーターやルートから返される可能性のあるレスポンスについて、メタデータを定義するためのものです。このメタデータは、OpenAPIドキュメント(Swagger UIなど)で表示されます。
特に、特定のエラーコード(例: 404 Not Found, 401 Unauthorized, 403 Forbidden, 500 Internal Server Error など)に対する共通のレスポンス形式を定義しておくと便利です。
APIRouter(responses={...})
のようにルーターレベルで指定すると、そのルーター内の全てのエンドポイントにデフォルトで共通のレスポンス定義が追加されます。@router.get("/myendpoint", responses={...})
のように個別のルートで指定すると、そのルート固有のレスポンス定義を追加できます。個別のルートの設定はルーターレベルの設定とマージされます。同じステータスコードに対して定義がある場合は、ルートレベルの設定が優先されます。
例:共通のエラーレスポンスを定義する
先ほどの users.py
の例で、ユーザーが見つからない場合に 404 エラーを返していました。
“`python
routers/users.py (一部抜粋)
@router.get(“/{username}”)
async def read_user(username: str):
if username not in fake_users_db:
raise HTTPException(status_code=404, detail=”User not found”)
return fake_users_db[username]
“`
この 404 エラーレスポンスの形式を、ルーターレベルで定義してみましょう。
“`python
routers/users.py (responsesの例)
from fastapi import APIRouter, HTTPException, Depends
from typing import Dict
認証依存関係をインポート(省略)
from dependencies import get_api_key, get_current_user
1. APIRouter インスタンスを作成し、responses を指定
router = APIRouter(
prefix=”/users”, # prefixも一緒に指定
tags=[“users”],
# dependencies=[Depends(get_api_key), Depends(get_current_user)], # 依存関係(省略)
responses={ # ここで共通のレスポンスを指定
404: {“description”: “Not found”, “content”: {“application/json”: {“example”: {“detail”: “Item not found”}}}},
401: {“description”: “Unauthorized”, “content”: {“application/json”: {“example”: {“detail”: “Invalid API Key”}}}},
},
)
ダミーのユーザーデータ(省略)
fake_users_db = {
“foo”: {“username”: “foo”, “full_name”: “Foo Bar”},
“baz”: {“username”: “baz”, “full_name”: “Baz Qux”},
“authenticated_user”: {“username”: “authenticated_user”, “full_name”: “Authenticated User”},
}
… 他のユーザー関連エンドポイント …
@router.get(“/{username}”)
async def read_user(username: str):
“””
特定のユーザー情報を取得します。
“””
# このエンドポイント固有のレスポンス定義を追加することも可能
# 例: responses={200: {“description”: “User found”}, 400: {“description”: “Bad Request”}}
if username not in fake_users_db:
raise HTTPException(status_code=404, detail=”User not found”)
return fake_users_db[username]
… 他のエンドポイント …
“`
ルーターインスタンス作成時に responses
オプションを辞書形式で指定しました。キーはHTTPステータスコード(整数)、値はそのステータスコードに対するレスポンスのメタデータです。ここでは、description
(ドキュメントに表示される説明)と content
(レスポンスボディの形式や例)を指定しています。
content
の値は OpenAPI 3.0 の仕様に基づいています。application/json
はJSON形式のレスポンスボディであることを示し、example
にはレスポンスボディの例を記述できます。
この設定により、Swagger UIの /users/{username}
エンドポイントのドキュメントには、標準の 200 OK レスポンスに加えて、404 Not Found および 401 Unauthorized のレスポンス定義が表示されるようになります(401はdependenciesで例外を発生させているため)。これはAPIの利用者が、どのようなエラーレスポンスを受け取る可能性があるかを事前に知る上で非常に役立ちます。
ルーターレベルで共通のエラーレスポンスを定義しておけば、各エンドポイントで重複して記述する必要がなくなり、コードがスッキリします。
3.5 その他のAPIRouterオプション
APIRouter
には、他にもいくつかのオプションがあります。
default_response_class
: ルーター全体に適用されるデフォルトのレスポンスクラス(例:JSONResponse
,HTMLResponse
など)を指定します。dependencies
: ルーター全体に適用される依存関係のリスト。callbacks
: コールバックURLを定義するためのもの。Webhookのようなシナリオで使われます。これはやや高度な機能です。deprecated
: そのルーター内の全てのエンドポイントが非推奨であることを示すブール値。include_in_schema
: そのルーター内の全てのエンドポイントを OpenAPI スキーマに含めるかどうかを指定するブール値。
これらのオプションを使うことで、ルーターの振る舞いをより細かく制御できます。
4. APIRouterを使ったコード分割のベストプラクティスとファイル構成
ここまで、APIRouter
の基本的な使い方と便利な機能を見てきました。これらの知識を活かして、より大規模なFastAPIプロジェクトをどのように構成すれば良いか、具体的なファイル構造の例とともに考えてみましょう。
4.1 推奨されるファイル構造
一般的なFastAPIプロジェクトで、APIRouter
を活用した推奨されるファイル構造は以下のようになります。
.
├── main.py # メインのFastAPIアプリケーションインスタンスを作成し、ルーターをマウントするファイル
├── requirements.txt # プロジェクトの依存関係を記述するファイル
├── .env # 環境変数を管理するファイル(オプション)
├── app/ # アプリケーションコードを格納するディレクトリ
│ ├── __init__.py # Pythonパッケージとして認識させるためのファイル
│ ├── main.py # メインのFastAPIインスタンス定義など(トップレベルのmain.pyと統合しても良い)
│ ├── api/ # APIエンドポイント関連のコードを格納するディレクトリ
│ │ ├── __init__.py # Pythonパッケージとして認識させるためのファイル
│ │ ├── v1/ # APIバージョン1のコード(必要に応じてバージョンごとにディレクトリ分け)
│ │ │ ├── __init__.py # この例ではルーターを自動収集するために使用
│ │ │ ├── endpoints/ # 各リソースのエンドポイント(ルーター定義)を格納
│ │ │ │ ├── __init__.py # Pythonパッケージとして認識させるためのファイル
│ │ │ │ ├── users.py # ユーザー関連ルーター
│ │ │ │ ├── items.py # 商品関連ルーター
│ │ │ │ └── ... # 他のリソース関連ルーター
│ │ │ └── deps.py # 共通の依存関係(認証、DBセッションなど)
│ │ ├── v2/ # APIバージョン2のコード(別のバージョンが必要な場合)
│ │ │ └── ...
│ ├── core/ # コア設定やユーティリティ関数など
│ │ ├── __init__.py
│ │ ├── config.py # 設定値の読み込み
│ │ ├── db.py # データベース接続関連
│ │ └── ...
│ ├── models/ # Pydanticモデルやデータベースモデル
│ │ ├── __init__.py
│ │ ├── user.py # ユーザーモデル
│ │ ├── item.py # 商品モデル
│ │ └── ...
│ ├── crud/ # データベース操作(Create, Read, Update, Delete)関連の関数
│ │ ├── __init__.py
│ │ ├── user.py
│ │ └── ...
│ ├── schemas/ # Pydanticによるリクエスト/レスポンスモデル(models/ と統合することもある)
│ │ ├── __init__.py
│ │ ├── user.py
│ │ └── ...
│ └── services/ # ビジネスロジックや外部サービスとの連携
│ └── ...
└── tests/ # テストコード
├── __init__.py
├── api/
│ └── ...
└── ...
この構造はあくまで一例であり、プロジェクトの規模やチームの好みに合わせて調整できます。重要なのは、関連するコードを論理的なディレクトリにまとめることです。
ここでは、APIエンドポイントの定義を app/api/v1/endpoints/
ディレクトリに配置し、各ファイル(users.py
, items.py
など)がそれぞれ APIRouter
インスタンスを持つ構成を想定しています。
4.2 メインアプリケーションでのルーターの収集とマウント
上記のような構造の場合、メインの main.py
(あるいは app/main.py
) では、これらのルーターファイルから APIRouter
インスタンスを収集し、まとめて FastAPI
アプリケーションにマウントする必要があります。
一つの方法は、各ルーターファイル(users.py
, items.py
など)で定義した router
インスタンスを個別にインポートして app.include_router
でマウントすることです。
“`python
app/api/v1/endpoints/users.py
from fastapi import APIRouter
router = APIRouter(prefix=”/users”, tags=[“users”])
@router.get(“/”)
async def read_users():
return [{“username”: “Foo”}, {“username”: “Baz”}]
app/api/v1/endpoints/items.py
from fastapi import APIRouter
router = APIRouter(prefix=”/items”, tags=[“items”])
@router.get(“/”)
async def read_items():
return [{“name”: “Item One”}, {“name”: “Item Two”}]
main.py (または app/main.py)
from fastapi import FastAPI
各ルーターファイルを個別にインポート
from app.api.v1.endpoints import users
from app.api.v1.endpoints import items
app = FastAPI()
各ルーターをマウント
app.include_router(users.router)
app.include_router(items.router)
@app.get(“/”)
async def read_root():
return {“message”: “Welcome to the API”}
“`
この方法はシンプルで分かりやすいですが、ルーターの数が増えるにつれて、main.py
でのインポートと include_router
の記述量が増えてしまいます。
別のよりスケーラブルな方法として、ルーターを配置したディレクトリ(例: app/api/v1/endpoints/
)内の全てのルーターを自動的に収集してマウントする仕組みを作ることも可能です。これは、app/api/v1/endpoints/__init__.py
ファイルを利用することで実現できます。
“`python
app/api/v1/endpoints/init.py
このディレクトリ内の全てのルーターモジュールをインポート
このファイルを編集して、各ルーターをインポートしていく
from . import users # users.py から router インスタンスをインポート
from . import items # items.py から router インスタンスをインポート
ここに他のルーターも追加していく
from . import orders
from . import auth
収集したルーターインスタンスをリストなどで管理することも可能ですが、
単にインポートしておき、メインのFastAPIアプリで include_router 時に利用するのが一般的です。
または、以下のように、この init.py でルーターをマージした新しいルーターを作成し、
それをメインアプリで include_router する方法もあります。
例:このディレクトリ内の全てのルーターを include した親ルーターを作成
from fastapi import APIRouter
api_router = APIRouter()
各ルーターを api_router に include_router する
api_router.include_router(users.router)
api_router.include_router(items.router)
api_router.include_router(orders.router)
api_router.include_router(auth.router)
メインアプリは、この api_router を include すれば良い
“`
そして、メインの main.py
(または app/main.py
) では、この api_router
をインポートしてマウントします。
“`python
main.py (または app/main.py) – 自動収集の例
from fastapi import FastAPI
endpoints ディレクトリの init.py で定義した api_router をインポート
from app.api.v1.endpoints import api_router
app = FastAPI(title=”My Awesome API”)
バージョン1のAPIとして include_router する (ここでは /v1 をプレフィックスとして指定)
app.include_router(api_router, prefix=”/v1″)
@app.get(“/”)
async def read_root():
return {“message”: “Welcome to the API”}
“`
この方法の利点は、新しいルーターファイル(例: orders.py
)を追加した場合に、app/api/v1/endpoints/__init__.py
にそのルーターをインポートして api_router.include_router()
を追加するだけで済むことです。メインの main.py
は変更する必要がありません。これにより、メインファイルが肥大化するのを防ぎ、責任範囲を明確に分けることができます。
さらに進んで、app/api/v1/endpoints/__init__.py
の中で、ディレクトリ内の全てのファイル(__init__.py
を除く)から router
変数を動的にインポートして api_router
に追加するような仕組みを作ることも不可能ではありませんが、可読性やIDEのコード補完などを考えると、上記の例のように明示的にインポートする方が管理しやすい場合が多いです。
どの方法を採用するかはプロジェクトの規模やチームの慣習によりますが、APIRouter
を活用してコードを複数のファイルに分割し、メインアプリでそれらをまとめて管理するという基本的な考え方は共通です。
4.3 APIRouterと依存関係の活用
コードを分割する際に、共通の依存関係を app/api/v1/deps.py
のようなファイルにまとめておくのは良いプラクティスです。そして、これらの依存関係を APIRouter
レベルで dependencies
オプションを使って適用します。
例えば、app/api/v1/deps.py
に以下のような依存関係を定義します。
“`python
app/api/v1/deps.py
from fastapi import Depends, HTTPException, status
from typing import Annotated
仮のユーザーモデル(Pydanticなどを使うのが一般的)
class User:
def init(self, username: str):
self.username = username
def get_current_user(token: str):
“””
ダミーの認証関数。トークンを検証してユーザーオブジェクトを返す。(実際はDB参照などを行う)
“””
# 仮の検証ロジック
if token != “fake-super-secret-token”:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=”Invalid authentication credentials”,
headers={“WWW-Authenticate”: “Bearer”},
)
# 認証成功、ダミーユーザーを返す
return User(username=”authenticated_user”)
型ヒントエイリアスとして定義しておくと便利
AuthenticatedUser = Annotated[User, Depends(get_current_user)]
仮の管理者権限チェック依存関係
def is_admin(current_user: AuthenticatedUser):
“””
ユーザーが管理者かどうかをチェックする依存関係。(ここではダミー)
“””
if current_user.username != “admin”: # 例として特定のユーザー名を管理者とする
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=”Operation not permitted”,
)
# 管理者権限あり
return True
“`
そして、この依存関係をルーターファイルでインポートし、必要に応じてルーター全体や特定のエンドポイントに適用します。
“`python
app/api/v1/endpoints/users.py (依存関係活用例)
from fastapi import APIRouter, Depends
from typing import Dict
依存関係をインポート
from app.api.v1.deps import AuthenticatedUser, is_admin # get_current_userはAuthenticatedUserに含まれる
ユーザー関連ルーター。認証されたユーザーのみアクセス可能とする。
AuthenticatedUser は get_current_user の Depends() が含まれているので、そのままリストに追加
router = APIRouter(
prefix=”/users”,
tags=[“users”],
dependencies=[Depends(AuthenticatedUser)], # ルーター全体に認証依存関係を適用
)
ダミーデータ(省略)
fake_users_db = {
“foo”: {“username”: “foo”, “full_name”: “Foo Bar”},
“baz”: {“username”: “baz”, “full_name”: “Baz Qux”},
“authenticated_user”: {“username”: “authenticated_user”, “full_name”: “Authenticated User”},
}
@router.get(“/”)
async def read_users():
“””
全てのユーザー情報を取得します。(認証必須)
“””
return fake_users_db
@router.get(“/me”)
async def read_user_me(current_user: AuthenticatedUser): # ここでも Depends() を指定することで、依存関係の戻り値を取得
“””
認証されたユーザー自身の情報を取得します。
“””
return {“username”: current_user.username} # 依存関係から取得したユーザー情報を利用
@router.get(“/{username}”)
async def read_user(username: str):
“””
特定のユーザー情報を取得します。(認証必須)
“””
# ルーターレベルの依存関係により、既に認証済み
if username not in fake_users_db:
raise HTTPException(status_code=404, detail=”User not found”)
return fake_users_db[username]
管理者のみアクセス可能なエンドポイント
@router.get(“/admin/all”, dependencies=[Depends(is_admin)]) # このエンドポイントのみに追加の依存関係を適用
async def read_all_users_admin(current_user: AuthenticatedUser):
“””
管理者用の全てのユーザー情報を取得します。(認証 & 管理者権限必須)
“””
# is_admin 依存関係が成功した場合のみ実行される
# current_user はルーターレベルの依存関係から取得
print(f”Admin user {current_user.username} accessing all users”)
return fake_users_db
“`
この例では、users.router
全体に dependencies=[Depends(AuthenticatedUser)]
を設定することで、このルーター内の全てのエンドポイントに認証を適用しています。/me
のように依存関係の戻り値を利用したい場合は、改めてエンドポイント関数の引数に Depends()
を指定します。
また、/admin/all
のように特定の(サブ)セットのエンドポイントにのみ追加の依存関係(ここでは is_admin
による管理者権限チェック)を適用したい場合は、そのエンドポイントのデコレータで dependencies=[Depends(is_admin)]
のように指定します。これはルーターレベルの依存関係に追加される形で適用されます。
このように、APIRouter
の dependencies
オプションとFastAPIの依存関係注入機能を組み合わせることで、認証、認可、データベースセッション管理など、多くのエンドポイントで共通して必要な処理を非常に効率的かつきれいに実装できます。
5. APIRouterのネスト(高度な使い方)
基本的な使い方では、複数のルーターをメインの FastAPI
アプリケーションに直接 include_router
していました。しかし、APIRouter
は、別の APIRouter
を include_router
することも可能です。これは、より複雑なURL構造を持つAPIや、バージョン管理されたAPIなどを構築する際に役立ちます。
例:ユーザーに関連するアイテムや注文のエンドポイント
例えば、以下のようなURL構造を持つAPIを考えます。
/users/{user_id}/items/
(特定のユーザーに関連するアイテム一覧)/users/{user_id}/items/{item_id}
(特定のユーザーに関連する特定のアイテム)/users/{user_id}/orders/
(特定のユーザーに関連する注文一覧)
このような構造の場合、/users/{user_id}
というプレフィックスを持つルーターの中に、さらに /items
や /orders
というプレフィックスを持つルーターをネストして組み込むことができます。
構成例:
app/
├── api/
│ ├── v1/
│ │ ├── __init__.py
│ │ ├── endpoints/
│ │ │ ├── __init__.py
│ │ │ ├── users.py # /users/{user_id} レベルのルーター
│ │ │ ├── user_items.py # /items レベルのルーター(users.py にネストされる)
│ │ │ ├── user_orders.py# /orders レベルのルーター(users.py にネストされる)
│ │ │ └── items.py # /items レベルのルーター(トップレベル)
│ │ └── deps.py
├── ...
まず、ネストされる側のルーターを定義します。
“`python
app/api/v1/endpoints/user_items.py
from fastapi import APIRouter, HTTPException
このルーターは /users/{user_id} の下にネストされることを想定
パスは相対的に / や /{item_id} のように定義
router = APIRouter(tags=[“user-items”]) # 専用のタグを付ける
ダミーデータ(ユーザーごとのアイテムを想定)
fake_user_items_db = {
“foo”: [{“name”: “Foo’s Item 1”}, {“name”: “Foo’s Item 2”}],
“baz”: [{“name”: “Baz’s Item A”}],
}
user_id は親ルーターで取得されるので、ここではパスパラメータとしては受け取らない
user_id を取得するには、親ルーターで定義された依存関係を利用するなど工夫が必要
例:@router.get(“/”, dependencies=[Depends(get_user_from_path)])
ここではシンプルに、エンドポイント関数で user_id を引数として受け取る方法を仮定する
(実際には、親ルーターのパスパラメータをネストされたルーターのエンドポイント関数で
直接受け取るには、少し工夫(StateやDepends)が必要です。
最も簡単なのは、親ルーターのエンドポイント関数内でネストされたルーターの関数を呼ぶことですが、
ここではルーターを include_router でネストする例を示します)
ネストした場合、親ルーターのパスパラメータ(user_id)は、
ネストされたルーターのエンドポイント関数でそのまま利用できます!これは FastAPi の便利な仕様です。
@router.get(“/”) # => /users/{user_id}/items/ となる (親が /users/{user_id}、自身が /items)
async def read_user_items(user_id: str): # 親ルーターのパスパラメータを受け取れる
“””
特定のユーザーに関連するアイテム一覧を取得します。
“””
if user_id not in fake_user_items_db:
raise HTTPException(status_code=404, detail=”User not found or has no items”)
return fake_user_items_db[user_id]
@router.get(“/{item_id}”) # => /users/{user_id}/items/{item_id} となる
async def read_user_item(user_id: str, item_id: int): # 親と自身のパスパラメータを受け取れる
“””
特定のユーザーに関連する特定のアイテムを取得します。
“””
if user_id not in fake_user_items_db or item_id >= len(fake_user_items_db[user_id]):
raise HTTPException(status_code=404, detail=”User or Item not found”)
return fake_user_items_db[user_id][item_id]
“`
同様に user_orders.py
も作成します。
“`python
app/api/v1/endpoints/user_orders.py
from fastapi import APIRouter, HTTPException
router = APIRouter(tags=[“user-orders”])
fake_user_orders_db = {
“foo”: [{“order_id”: “ord1”, “amount”: 100}, {“order_id”: “ord2”, “amount”: 250}],
“baz”: [{“order_id”: “ordA”, “amount”: 50}],
}
@router.get(“/”) # => /users/{user_id}/orders/ となる
async def read_user_orders(user_id: str):
“””
特定のユーザーに関連する注文一覧を取得します。
“””
if user_id not in fake_user_orders_db:
raise HTTPException(status_code=404, detail=”User not found or has no orders”)
return fake_user_orders_db[user_id]
“`
次に、これらのルーターを /users/{user_id}
をプレフィックスとする親ルーターに include_router
します。
“`python
app/api/v1/endpoints/users.py (ネストの親ルーターとして修正)
from fastapi import APIRouter, HTTPException
ネストする子ルーターをインポート
from . import user_items # user_items.py をインポート
from . import user_orders # user_orders.py をインポート
ユーザー個別エンドポイントと、子ルーターを include するための親ルーター
prefix にパスパラメータが含まれているのが特徴
router = APIRouter(
prefix=”/users/{user_id}”, # ここにパスパラメータを含むプレフィックスを指定
tags=[“users”], # このルーター自体のタグ
)
{user_id} パスパラメータを持つ親ルーター自身のルート
@router.get(“/”) # => /users/{user_id} となる
async def read_user(user_id: str):
“””
特定のユーザー情報を取得します。
“””
# ダミーデータ(トップレベルの users.py とは別の想定)
fake_users_detail_db = {
“foo”: {“username”: “foo”, “full_name”: “Foo Bar”, “email”: “[email protected]”},
“baz”: {“username”: “baz”, “full_name”: “Baz Qux”, “email”: “[email protected]”},
}
if user_id not in fake_users_detail_db:
raise HTTPException(status_code=404, detail=”User not found”)
return fake_users_detail_db[user_id]
子ルーターを include_router する
prefix は省略可能。省略した場合、子ルーターのパスは親ルーターのパスにそのまま追加される
例えば、user_items.router 内の “/” は /users/{user_id} + “/” = /users/{user_id}/ となる。
しかし、user_items.py のルーターはアイテム関連なので、プレフィックスは /items としたい。
なので、ここで prefix=”/items” を指定する。
router.include_router(user_items.router, prefix=”/items”) # => /users/{user_id}/items/… となる
user_orders ルーターも同様に include_router
router.include_router(user_orders.router, prefix=”/orders”) # => /users/{user_id}/orders/… となる
“`
最後に、メインの app/api/v1/endpoints/__init__.py
や main.py
で、この /users/{user_id}
をプレフィックスとする users.router
をマウントします。
“`python
app/api/v1/endpoints/init.py (修正版)
from fastapi import APIRouter
トップレベルのルーターをインポート
user_items や user_orders は users に include されるので、ここでは直接インポートしない
from . import users # users.py ルーター (これが親ルーター)
from . import items # items.py ルーター (これはトップレベル)
api_router = APIRouter()
トップレベルのルーターを include
users ルーターは既に内部で /users/{user_id} プレフィックスを持っているため、
ここでの include_router に prefix を指定する必要はない。
ただし、トップレベルの /users/ エンドポイントが必要な場合は、別途ルーターを用意するか、
users.py をさらに分割する必要があるかもしれない。
ここでは、/users/{user_id} 以下のエンドポイントのみを提供する例とする。
api_router.include_router(users.router)
api_router.include_router(items.router, prefix=”/items”)
main.py (または app/main.py) は変更なし
from fastapi import FastAPI
from app.api.v1.endpoints import api_router
app = FastAPI(title=”My Awesome API”)
app.include_router(api_router, prefix=”/v1″)
@app.get(“/”)
async def read_root():
return {“message”: “Welcome to the API”}
“`
これで、以下のようなエンドポイントが有効になります。
/v1/users/{user_id}
(users.py の@router.get("/")
)/v1/users/{user_id}/items/
(user_items.py の@router.get("/")
が users.py でprefix="/items"
で include された結果)/v1/users/{user_id}/items/{item_id}
(user_items.py の@router.get("/{item_id}")
が users.py でprefix="/items"
で include された結果)/v1/users/{user_id}/orders/
(user_orders.py の@router.get("/")
が users.py でprefix="/orders"
で include された結果)/v1/items/
(items.py の@router.get("/")
が main.py でprefix="/items"
で include された結果)
ネストされたルーターのエンドポイント関数が、親ルーターのパスパラメータ(この例では user_id
)を直接引数として受け取れるのは非常に便利です。
このネスト機能は、APIのURL構造が複雑になった場合に、コードを論理的に整理するための強力な手段となります。ただし、ネストしすぎるとコードの流れが分かりにくくなる可能性もあるため、適切なレベルでの利用が推奨されます。
6. テストとの関連性
APIRouter
を使ってコードをルーター単位で分割することは、テスト容易性の向上にも貢献します。FastAPIには TestClient
という便利なクラスがあり、これを使うと、実際にサーバーを起動せずにアプリケーション(またはルーター)に対してリクエストをシミュレーションできます。
“`python
tests/test_users.py (例)
from fastapi.testclient import TestClient
from routers.users import router # テストしたいルーターをインポート
テストクライアントを作成
ここではユーザー関連ルーター単体をテストクライアントに渡す
client = TestClient(router)
ダミーデータにアクセスするためのテスト関数
def test_read_users():
response = client.get(“/”) # ルーター内の相対パスを指定
assert response.status_code == 200
assert response.json() == {
“foo”: {“username”: “foo”, “full_name”: “Foo Bar”},
“baz”: {“username”: “baz”, “full_name”: “Baz Qux”},
}
def test_read_user():
response = client.get(“/foo”) # ルーター内の相対パス + パスパラメータ
assert response.status_code == 200
assert response.json() == {“username”: “foo”, “full_name”: “Foo Bar”}
def test_read_user_not_found():
response = client.get(“/nonexistent”)
assert response.status_code == 404
assert response.json() == {“detail”: “User not found”}
… 他のテストケース …
“`
もしルーターに依存関係(例: 認証)が設定されている場合、テスト時にはその依存関係を「オーバーライド」してダミーの値を返すように設定することが可能です。これにより、依存関係が正しく機能するかどうかのテストと、ルーターのビジネスロジックのテストを分離できます。これは依存関係注入と TestClient
の組み合わせによる強力な機能ですが、ここでは APIRouter
の文脈で「ルーター単位でテストしやすい」というメリットがあることを押さえておきましょう。
APIRouter
によって機能が分割されているため、特定の機能(例: ユーザー管理)に関連するテストを、そのルーターファイルや対応するテストファイルに集約できます。これにより、テストコードも整理され、管理しやすくなります。
7. まとめ
この記事では、FastAPIの APIRouter
について、初心者の方にも分かりやすく基本から応用までを解説しました。
まず、シンプルなFastAPIアプリケーションでは全てを一つのファイルに書くのが容易であるものの、プロジェクトが拡大するにつれて保守性や可読性が低下するという問題点を指摘しました。
そして、APIRouter
がこの問題を解決するための主要なツールであることを紹介し、その定義、必要性、そして基本的な使い方(インスタンス作成、ルート定義、メインアプリへの include_router
によるマウント)を具体的なコード例とともに説明しました。
さらに、APIRouter
の強力なオプションである prefix
、tags
、dependencies
、responses
について、それぞれがどのような目的で使われ、API開発においてどのようなメリットをもたらすのかを詳細に解説しました。prefix
によるURL構造の整理、tags
によるAPIドキュメントのグループ化、dependencies
による共通処理の適用、responses
によるエラーレスポンスのドキュメント化は、FastAPIを使ったAPI開発を効率化し、APIの質を高める上で非常に重要です。
また、APIRouter
のより高度な使い方として、ルーターをネストしてより複雑なURL階層を構築する方法を紹介しました。
最後に、APIRouter
を活用した大規模FastAPIプロジェクトの推奨されるファイル構造の例を示し、コード分割がテスト容易性の向上にも繋がることを述べました。
APIRouter
は、FastAPIで本格的なAPI開発を行う上で避けては通れない、非常に重要な概念です。この記事を通して、あなたが APIRouter
を自信を持って使いこなし、より構造化された、保守しやすいAPIを構築できるようになることを願っています。
これで、約5000語のFastAPI APIRouterに関する詳細な解説記事は完了です。FastAPIでの次のステップに進む準備ができたはずです!