FastAPIのDependsは、エンドポイントの引数に「この値はこの関数の戻り値で埋める」と宣言する仕組みです。認証済みのユーザー、DBの接続、ページングの値といった、多くのエンドポイントで同じ手順で作る値を1か所にまとめられます。この記事では、FastAPI 0.162.5で小さなメモAPIを作りながら、Annotatedでの書き方、サブ依存とクラス依存、ルーターやアプリ全体への適用、yieldを使った後処理、dependency_overridesによるテストの差し替えまでをコードで確認する構成です。Dependsに入れない方がよい処理の線引きも扱います。
まとめ:FastAPIのDependsで押さえる書き方と使い分けの結論
新しく書くコードはAnnotated[型, Depends(関数)]の形に揃えます。FastAPIは0.95.0でこの書き方に対応し、公式ドキュメントも推奨しています。Dependsには関数そのものを渡し、括弧を付けて呼びません。呼び出しはFastAPIの役目です。
使い分けの結論は次のとおりです。戻り値を使う前処理(認証済みユーザーの取得、DB接続)は引数のDependsに置きます。戻り値を使わない検査(APIキーの確認など)の配置先は、デコレーターやルーターのdependenciesです。後片付けが要る資源はyieldで書き、exceptで受けた例外は必ず送り直します。
テストではapp.dependency_overridesで認証とDBを差し替え、テストの終わりに必ず空に戻します。一方、業務ルールの判定や重い集計はDependsに入れず、サービス層の関数に置きます。依存関数は「リクエストから値を取り出して整える」ところで止めるのが、長く保守できる線です。
FastAPI 0.162系で依存関数を書いてDependsで注入する最小コード
まずは依存関数を1つ書いて、エンドポイントへ値が渡る流れを確かめます。
fastapi[standard]0.162.5を入れて検証環境を作る手順
2026年11月13日時点のPyPIで、FastAPIの最新は0.162.5(2026年11月11日公開)で、動作要件はPython 3.10以上です。この記事のコードはstr | Noneの型表記を使うため、3.10以上を前提にします。FastAPIを初めて入れる場合は、FastAPIの最短の起動手順とasync/defの使い分けを先に済ませてください。
# 仮想環境を作り、FastAPIとテスト用のpytestを入れる
python3 -m venv .venv
source .venv/bin/activate
pip install "fastapi[standard]==0.162.5" pytest
# main.py を置いたディレクトリで開発サーバーを起動する
fastapi dev main.py
fastapi[standard]には、開発サーバーのfastapi devと、テストで使うTestClientが依存するhttpxが含まれます。
Annotated[型, Depends(関数)]の書き方と関数を呼ばずに渡す理由
最小の形は次のとおりです。common_paramsが依存関数で、エンドポイントの引数paramsにその戻り値が入ります。
from typing import Annotated
from fastapi import Depends, FastAPI
app = FastAPI()
def common_params(q: str | None = None, limit: int = 20):
return {"q": q, "limit": limit}
@app.get("/items")
def read_items(params: Annotated[dict, Depends(common_params)]):
return params
Depends(common_params())と括弧を付けると、モジュールを読み込んだ時点で1回だけ実行され、その戻り値の辞書が渡されます。辞書は呼び出せないため、FastAPIはその依存を解決できずエラーになります。公式チュートリアルのDependenciesにあるとおり、関数は渡すだけで、呼ぶのはFastAPIです。
古いコードではparams: dict = Depends(common_params)のように既定値の位置に書く形も見かけます。動作は同じですが、公式はAnnotatedへの対応を0.95.0で追加し、使う前に0.95.1以上へ上げるよう案内しています。Annotatedの形なら同じ型をDbDepのような名前で再利用でき、引数の並び順の制約も受けません。
依存関数がクエリ・ヘッダーを受け取る仕組みとOpenAPIへの反映
依存関数の引数は、エンドポイントの引数と同じ規則で解釈されます。上のqとlimitはクエリパラメータになり、Header()を付ければヘッダー、Query(ge=1)を付ければ範囲検証つきのクエリになります。検証に通らなければ、エンドポイントの本体は実行されず、返るステータスコードは422です。
依存関数で宣言したパラメータは、OpenAPIの仕様書にもエンドポイントのパラメータとして載ります。/docsのSwagger UIで、依存側で宣言したヘッダーの入力欄が出るのはこのためです。表示の調整はFastAPIのSwagger UIの表示・設定と本番で隠す手順で扱っています。
サブ依存とクラス依存で認証・ページング処理を組み立てる実装手順
ここからは、トークンで利用者を判定し、自分のメモだけを件数上限つきで返すAPIを1ファイルで書きます。依存の連鎖、クラスによる設定値の受け渡し、yieldでの接続の開閉が1つのファイルに入っています。
# main.py
import sqlite3
from typing import Annotated
from fastapi import Depends, FastAPI, Header, HTTPException, Query, status
app = FastAPI()
# 検証用のトークン表。本番ではJWTの署名検証やDB照会に置き換える
TOKENS = {"token-alice": "alice", "token-bob": "bob"}
def get_db():
# リクエストごとに接続を開き、後処理で必ず閉じる
conn = sqlite3.connect("app.db", check_same_thread=False)
conn.execute(
"CREATE TABLE IF NOT EXISTS notes"
"(id INTEGER PRIMARY KEY, owner TEXT, body TEXT)"
)
try:
yield conn
finally:
conn.close()
def get_token(x_api_token: Annotated[str | None, Header()] = None):
if x_api_token is None:
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "token required")
return x_api_token
def get_current_user(token: Annotated[str, Depends(get_token)]):
user = TOKENS.get(token)
if user is None:
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "invalid token")
return user
class Pagination:
def __init__(self, max_limit: int):
self.max_limit = max_limit
def __call__(
self,
limit: Annotated[int, Query(ge=1)] = 20,
offset: Annotated[int, Query(ge=0)] = 0,
):
return {"limit": min(limit, self.max_limit), "offset": offset}
DbDep = Annotated[sqlite3.Connection, Depends(get_db)]
UserDep = Annotated[str, Depends(get_current_user)]
PageDep = Annotated[dict, Depends(Pagination(max_limit=50))]
@app.get("/notes")
def list_notes(db: DbDep, user: UserDep, page: PageDep):
rows = db.execute(
"SELECT id, body FROM notes WHERE owner = ? ORDER BY id LIMIT ? OFFSET ?",
(user, page["limit"], page["offset"]),
).fetchall()
return {"user": user, "page": page, "items": [{"id": r[0], "body": r[1]} for r in rows]}
@app.post("/notes", status_code=201)
def create_note(text: str, db: DbDep, user: UserDep):
cur = db.execute("INSERT INTO notes(owner, body) VALUES (?, ?)", (user, text))
db.commit()
return {"id": cur.lastrowid}
起動したら、別の端末から次のように呼びます。ヘッダーのx-api-tokenは、引数名x_api_tokenのアンダースコアをハイフンに変えた名前です。
# メモを1件作る
curl -X POST -H "x-api-token: token-alice" "http://127.0.0.1:8000/notes?text=hello"
# limit=100 を指定しても、クラス依存の上限で50に切り詰められる
curl -H "x-api-token: token-alice" "http://127.0.0.1:8000/notes?limit=100"
# ヘッダーなしは get_token で止まり、401 {"detail":"token required"} になる
curl -i "http://127.0.0.1:8000/notes"
get_current_userへ依存をつなぐサブ依存の連鎖と実行順序
get_current_userは、自分の引数でget_tokenに依存しています。エンドポイントが宣言しているのはUserDepだけですが、FastAPIはget_token、get_current_user、エンドポイントの順に解決します。公式のSub-dependenciesのとおり、連鎖の深さに制限はありません。
どこかの段でHTTPExceptionを送出すると、そこで処理が止まり、後ろの段もエンドポイントの本体も実行されません。認証をエンドポイントの冒頭に毎回書く代わりに、型を1つ書き足すだけで守りが入るのがこの形の利点です。公式のGet Current Userでは、同じ連鎖の最初の段にOAuth2PasswordBearerを置いています。トークンの中身をJWTとして検証する部分は、JWTの構造と署名検証の仕組みを踏まえて書きます。
__call__付きクラスで上限値を変えられるページング依存を作る
Dependsに渡せるのは関数に限りません。呼び出せるものなら何でもよく、__call__を持つクラスのインスタンスも渡せます。Pagination(max_limit=50)はインスタンスを作った時点で上限を覚え、リクエストのたびに__call__がクエリを読みます。管理画面向けは200件、公開APIは50件のように、同じ処理を設定値違いで使い分けたいときの書き方です。
公式のAdvanced Dependenciesでは、この形を「パラメータ化された依存」として説明しています。クラスそのものをDepends(Pagination)と渡すと、__init__の引数がクエリとして扱われる別の動きになるので、取り違えに注意します。後者はClasses as Dependenciesの書き方で、戻り値の属性にエディタの補完が効く点が辞書を返す関数より有利です。
1リクエスト1回のキャッシュとuse_cache=Falseを使う場面
同じリクエストの中で同じ依存が何度出てきても、FastAPIは1回だけ呼び、その値を使い回します。list_notesとget_current_userの両方がget_dbに依存しても、開く接続は1本です。トランザクションを1つにまとめたいDB接続にとっては好都合な性質です。
呼ぶたびに新しい値が要る場合だけ、Depends(get_value, use_cache=False)と指定します。公式も「高度な場面」と位置づけていて、実務で使うのは、リクエスト内で時刻を2回取り直したい処理など限られた場面です。キャッシュはリクエストをまたがないので、設定ファイルの読み込みのように全体で1回にしたい処理はfunctools.lru_cacheを付けた関数を依存にします。
dependencies引数でルーター全体とアプリ全体に認証を掛ける方法
戻り値を使わない検査は、引数ではなくdependenciesのリストに置けます。エンドポイントの引数が検査用の変数で埋まるのを避けられます。
デコレーターのdependenciesは戻り値を捨てる点と使いどころ
@app.get("/admin/stats", dependencies=[Depends(verify_admin_key)])のように書くと、verify_admin_keyは実行されますが、戻り値はエンドポイントに渡りません。公式のDependencies in path operation decoratorsも、値を返しても使われないと明記しています。
向いているのは、APIキーの照合、メンテナンス中の503応答、IPの許可リストのように「通すか止めるか」だけを決める検査です。利用者の情報を本体で使うなら、引数のUserDepで受けます。両方に同じ依存を書いても、キャッシュにより呼び出しは1回で済みます。
APIRouterとアプリ全体で依存を掛ける範囲を分ける判断基準
同じ指定はAPIRouter(prefix="/admin", dependencies=[Depends(verify_admin_key)])でルーター単位に、FastAPI(dependencies=[Depends(verify_api_key)])でアプリ全体に掛けられます(Global Dependencies)。
アプリ全体に掛けるのは、ヘルスチェックや/docsを含めて例外なく通す検査に限ります。ロードバランサーのヘルスチェックまで認証で止めて、デプロイ後にインスタンスが外される事故はよくある失敗です。認証の要らないルートが1本でもあるなら、ルーター単位で掛けます。ルーターの分け方とprefixの設計は、同じFastAPIの記事群のAPIRouter編で扱う予定です。
yield依存でDBセッションを開閉する書き方と0.121のscope指定
サンプルのget_dbはreturnではなくyieldで値を渡しています。yieldより前が準備、後ろが後片付けです。
try・finallyでクローズするyield依存の基本形と例外の再送出
後片付けはfinallyに書きます。エンドポイントで例外が起きた場合も、接続のクローズは実行される仕組みです。公式のDependencies with yieldによると、既定では後処理はレスポンスをクライアントに送った後に動きます。
見落としやすいのはexceptの扱いです。except Exception:でロールバックしたあとraiseを書き忘れると、クライアントには500が返る一方で、サーバー側に例外のログが残りません。原因の追えない500が出続ける状態になります。ロールバックしたらraiseで送り直すか、意図したHTTPExceptionに変えて送出します。commitをどこに置くかはSQLAlchemyを使う場合に論点が増えるため、get_dbの依存性注入とcommitの置き場所を実測した解説を参照してください。
def get_db():
conn = sqlite3.connect("app.db", check_same_thread=False)
try:
yield conn
except Exception:
conn.rollback()
raise # 送り直さないと500のログが残らない
finally:
conn.close()
0.106から0.121までで変わった後処理のタイミングと移行時の注意
後処理がいつ動くかは、版によって変わってきました。古い記事のコードを写すときに挙動が食い違う原因になるため、Advanced Dependenciesの技術的な詳細に載っている変遷を表にまとめます。
| 版 | yield依存の後処理に関する変更 |
|---|---|
| 0.106.0 | 後処理をレスポンス送信の前に動かす形へ変更 |
| 0.110.0 | 握りつぶした例外は自動転送しない |
| 0.118.0 | 後処理をレスポンス送信の後へ戻す |
| 0.121.0 | scope指定を追加・既定はrequest |
0.121.0以降は、Depends(get_db, scope="function")と書くと、エンドポイントの関数が終わった直後、レスポンスを送る前に後処理が動きます。既定のscope="request"は送信後です。大きなファイルを返す処理の間ずっとDB接続を握っていたくない場合は"function"を選びます。逆にStreamingResponseの生成中にDBを読む処理では、送信前に接続が閉じると読めなくなるため、既定の"request"のままにします。
dependency_overridesでテスト時に認証とDBを差し替える手順
Dependsで組んだ一番の見返りはテストで得られます。本物のトークンもDBファイルも用意せずに、エンドポイントの処理だけを試せます。
pytestのフィクスチャでoverridesを設定して必ず戻す書き方
公式のTesting Dependencies with Overridesのとおり、app.dependency_overridesはただの辞書です。キーに元の依存関数、値に差し替える関数を入れます。
# test_main.py
import sqlite3
import pytest
from fastapi.testclient import TestClient
from main import app, get_current_user, get_db
@pytest.fixture
def client():
conn = sqlite3.connect(":memory:", check_same_thread=False)
conn.execute("CREATE TABLE notes(id INTEGER PRIMARY KEY, owner TEXT, body TEXT)")
def override_db():
yield conn
app.dependency_overrides[get_db] = override_db
app.dependency_overrides[get_current_user] = lambda: "tester"
try:
with TestClient(app) as c:
yield c
finally:
app.dependency_overrides.clear()
conn.close()
def test_create_and_list(client):
assert client.post("/notes", params={"text": "hello"}).status_code == 201
res = client.get("/notes", params={"limit": 100})
assert res.json()["page"]["limit"] == 50
assert res.json()["items"][0]["body"] == "hello"
get_current_userを差し替えると、その下のget_tokenも呼ばれなくなります。ヘッダーを付けずに通るのはこのためです。差し替えはfinallyで必ず消します。消し忘れると、後に続く認証のテストまで「tester」で通ってしまい、壊れた認証を見逃します。
差し替えが効かないときに疑うキーの指定とインポート経路の違い
差し替えが効かないとき、まず疑うのはキーです。辞書のキーは関数オブジェクトそのものなので、エンドポイントが使っている関数と同じものを指していなければ無視されます。別のモジュールで同名のget_dbを定義していないか、from app.deps import get_dbとfrom deps import get_dbのようにインポートの経路が二重になっていないかを確かめます。
クラス依存も同じ規則です。Depends(Pagination(max_limit=50))のようにその場で作ったインスタンスは、テストから同じオブジェクトを指せません。差し替えたい依存は、paginate_50 = Pagination(max_limit=50)のようにモジュールの変数に置いてから使います。
Dependsで書くべき処理とミドルウェアやサービス層に回す処理の線引き
Dependsは便利なぶん、何でも入れたくなります。ここでは入れない方がよい処理をはっきり書きます。
Dependsに入れると失敗するビジネスロジックと重い処理の例
「注文を確定する」「在庫を引き当てる」のような業務の判定を依存関数に入れるのは避けます。エンドポイントの引数を見ただけでは何が起きるか分からず、HTTPを経由しないバッチ処理から同じ処理を呼べなくなるからです。依存関数は、認証済みユーザー、DB接続、検証済みのクエリといった「リクエストから作る材料」を渡すところで止め、判定はサービス層の普通の関数に置きます。
もう1つの失敗は、async defの依存関数の中で同期のDBドライバやrequestsを呼ぶことです。その間はイベントループが止まり、同じワーカーの全リクエストが待たされます。同期の処理を呼ぶ依存はdefで書けば、FastAPIがスレッドプールで実行します。サンプルのget_dbがdefで、check_same_thread=Falseを付けているのはこのためです。依存とエンドポイントが別のスレッドで動くことがあるので、SQLiteの同一スレッド検査を外しています。
全リクエスト共通の処理をミドルウェアに置くべき条件と判断基準
リクエストIDの付与、処理時間の計測、CORS、圧縮のように、エンドポイントに関係なく全リクエストに掛けたい処理はミドルウェアに置きます。依存関数は該当するルートが見つかってから動くため、404の応答や静的ファイルには掛かりません。全部に効かせたい処理を依存で書くと、計測の数字から404が漏れます。
判断はこうです。エンドポイントの引数として値を受け取りたいならDepends、ルートと無関係に全通信を包みたいならミドルウェア、HTTP以外からも呼ぶならサービス層。この線を最初に決めておくと、エンドポイントが増えても依存の数は増えません。FlaskからFastAPIへ移るときに、before_requestの処理をどちらに移すかもこの基準で決まります(FlaskとFastAPIの違いを同じAPIで比較した記事)。既存のAPIを分割し直す設計や、認証の組み直しから相談したい場合は、API開発・システム連携の受託開発で受け付けています。
よくある質問
FastAPIのDependsについて、実装の途中で出てくることの多い質問に、2026年11月時点の版で答えます。
依存関数はasync defとdefのどちらで書けばよいですか?
どちらでも動きます。中でawaitする非同期のクライアント(httpxのAsyncClientや非同期のDBドライバ)を使うならasync def、同期のライブラリを呼ぶならdefです。defの依存はスレッドプールで実行されるため、イベントループを止めません。迷ったときの考え方はエンドポイント側と同じで、FastAPIとは?の記事のasync/defの使い分けがそのまま当てはまります。
DependsとSecurityは何が違いますか?
SecurityはDependsの仲間で、追加でscopesを受け取れます。OAuth2のスコープ(「読み取りのみ」「書き込み可」など)をエンドポイントごとに要求したいときに使い、要求したスコープはOpenAPIの仕様書にも載ります。スコープを使わない認証なら、この記事のようにDependsで対応可能です。
WebSocketのエンドポイントでもDependsは使えますか?
使えます。@app.websocketの引数でも、Depends、Query、Cookieによる宣言が可能です。ただしHTTPExceptionではなくWebSocketExceptionで接続を閉じる点が違います。認証をaccept()の前に済ませる書き方は、FastAPIのWebSocket実装の解説で扱っています。
依存関数でPydanticモデルを返すとリクエストボディと混同されませんか?
混同されません。引数の型がPydanticのモデルでも、Dependsで宣言していればFastAPIはリクエストボディとして読みません。逆に、依存関数の引数にPydanticのモデルを書くと、それはリクエストボディとして解釈されます。モデル定義の基本はPydanticのBaseModelの使い方を参照してください。
依存関数の中でRequestオブジェクトを受け取れますか?
受け取れます。依存関数の引数にrequest: Requestと書けば、エンドポイントと同じRequestが渡ります。クライアントのIPやパスを条件として判定する検査に使用可能です。ただし、ヘッダーの値をそのままログに出すと改行を混ぜたログの偽装につながるため、\rと\nを取り除いてから出力します。
関連記事
- FastAPIとは?最短の起動手順とasync/defの使い分け、0.162の変更点:FastAPI単体の入門と起動手順です。
- FastAPIのベストプラクティス|ディレクトリ構成・SQLAlchemy 2.1の設計・テストを実測で解説:依存を置くディレクトリとDBセッションの設計です。
- FastAPIのWebSocket実装|チャットの接続管理・切断処理とRedisで複数ワーカー対応:WebSocketでの認証とDependsの使い方です。
- FlaskとFastAPIの違い|同じAPIを両方で書いて型検証・非同期・OpenAPIを比較:移行時に依存性注入がどう置き換わるかの比較です。
- JWTとは?構造・署名検証の仕組みとセッション・OAuth/OIDCとの違いを実装視点で解説:認証依存の中で行うトークン検証の土台です。
- FastAPI MCPの実装手順|既存APIをfastapi-mcpでMCPツール化する方法と認証・公開範囲:Dependsの認証をMCPサーバーの入口でも使う例です。