FastAPI Dependency Injection — How and Why to Use It
Master FastAPI's dependency injection system with Depends().
- FastAPI Depends() declares functions that run before your route logic — framework handles resolution and injection
- Dependencies enable Inversion of Control: you declare what an endpoint needs, FastAPI handles the how
- yield dependencies act as context managers — setup before yield, teardown in finally, even on exceptions
- Sub-dependencies nest recursively: Auth -> Role Check -> DB Lookup creates a validation graph
- By default FastAPI caches dependency results per request — set use_cache=False for side-effect dependencies
- Biggest mistake: copy-pasting auth checks into every route instead of centralizing with Depends()
Think of dependency injection like a restaurant kitchen. The chef (your endpoint) does not go shopping for ingredients (auth, DB sessions, config). The kitchen manager (FastAPI) delivers exactly what the recipe calls for before cooking starts. If the ingredient is unavailable (auth fails), the order never reaches the chef. And if two dishes need the same ingredient, the manager sources it once and shares it across both — no duplication, no waste.
The yield pattern extends this analogy: after the meal is served, the kitchen manager also handles cleanup — washing the dishes, returning equipment to storage — even if something went wrong during cooking. You do not have to remember to clean up. The system guarantees it.
Basic Dependency — Shared Query Parameters
A dependency is just a standard Python callable — a function, a class, or anything with a __call__ method. By wrapping it in Depends(), you tell FastAPI to treat that callable's parameters as if they were declared directly on the endpoint. FastAPI introspects the signature, resolves the parameters from the incoming request (query string, headers, body), and injects the return value into your route handler.
This is the cleanest way to standardize pagination, filtering, sorting, or any shared input-processing logic across your entire API. The key insight is that the dependency function signature becomes part of the API contract — query parameters declared inside a dependency appear in the auto-generated OpenAPI schema exactly as if they were declared on the endpoint itself. API consumers see a consistent interface. Your codebase has one source of truth.
In production, this pattern eliminates an entire class of bugs: parameter drift across endpoints. Without DI, you copy the pagination logic into every route. Six months later, one endpoint caps limit at 100 and another allows limit=10000. One endpoint validates that skip is non-negative and another silently accepts skip=-5. The dependency pattern makes these inconsistencies structurally impossible — there is one function, one validation path, one default value. Changing it changes every endpoint that uses it simultaneously.
- The dependency function signature IS the contract — its parameters become API inputs visible in OpenAPI docs
- FastAPI validates dependency parameters with the same rules as route parameters — Query constraints, type coercion, required vs optional
- The return value is injected into the endpoint as a typed argument — use Annotated for explicit type documentation
- Multiple endpoints share one dependency — single source of truth for validation logic, defaults, and constraints
- Changing the dependency changes every endpoint that uses it simultaneously — no copy-paste drift possible
Authentication Dependency & Logic Branching
Dependencies are the gatekeepers of your routes. By raising an HTTPException inside a dependency, you stop the request before it reaches your route handler. The route never executes, the business logic never runs, and the response is returned immediately with the error status code. This is not just convenient — it is a security property.
The authentication dependency pattern separates the concern of 'who is this request from' from 'what does this request do.' The endpoint never needs to know how authentication works. It does not parse tokens, query user tables, or check API key formats. It receives an authenticated identity object and operates on it. The auth mechanism is entirely contained in the dependency.
This separation has a direct and concrete security benefit: changing your auth mechanism requires changing one function. JWT to OAuth2, API keys to mutual TLS, single-tenant to multi-tenant — the endpoint code is untouched. Without DI, you would need to audit every route handler individually, and that audit will miss something. There is no version of 'update every route manually' that is reliably complete.
The second security benefit is placement. Applied at the router level, auth dependencies create a structural guarantee: every route in the group requires authentication. There is no way to add a new endpoint and forget the auth check — the router applies it automatically. Applied at the route level, auth is a per-developer discipline that fails the moment someone is moving fast and forgets.
- Never put auth logic inside the route handler — it will be missing in some endpoints, always, eventually
- Raising HTTPException in a dependency stops the request before the route executes — this is the mechanism, not a side effect
- Return the authenticated identity object from the dependency, not a boolean — the route needs to know WHO, not just IF
- Use sub-dependencies for role-based access: require_admin depends on validate_api_key, which is cached and not re-executed
- Apply auth at the router level — per-route auth is a discipline that fails under deadline pressure and code review gaps
Depends() annotations when the endpoint looks structurally complete without it.The 'Yield' Pattern: Database Session Management
Managing database connections is where FastAPI's dependency injection either saves you or burns you. You must open the session before the route runs, and you must close it — return it to the pool — after the route completes, regardless of whether the route succeeded, raised an HTTPException, or crashed with an unhandled error. 'Regardless of outcome' is the hard part.
FastAPI's yield dependencies solve this by acting as request-scoped context managers. Code before the yield statement runs during setup — opening the session, acquiring any necessary locks, initializing state. Code after the yield in a finally block runs during teardown — closing the session, releasing locks, cleaning up. FastAPI holds a reference to the generator across the entire request lifecycle and resumes it after the response is sent or the exception is handled.
The critical distinction — the one that causes the most production incidents — is between yield dependencies and plain function dependencies with try/finally blocks. In a plain function dependency, the finally block does not execute when the route raises an HTTPException. FastAPI intercepts the exception at the route execution boundary and converts it to a JSON error response. This interception happens before control returns to the plain function's finally block. The database session is never closed. The connection leaks.
Only yield dependencies get guaranteed teardown because FastAPI explicitly resumes the generator after exception handling. The generator is paused at the yield, the exception is handled, the response is sent, and then FastAPI calls next() on the generator to trigger the finally block. This is not automatic — it is a deliberate design in FastAPI's dependency execution model. If you are acquiring any resource in a dependency — database session, HTTP client, file handle, distributed lock — you must use yield. There is no production-safe alternative.
- yield dependency: setup before yield, teardown in finally — guaranteed to run even on unhandled route exceptions
- Plain function dependency: try/finally does NOT survive FastAPI's HTTPException interception at the route boundary
- This asymmetry is the #1 cause of database connection pool exhaustion in FastAPI production services
- Add
db.rollback()beforedb.close()in the except block — uncommitted transactions should not survive an exception - Monitor pool utilization with a Prometheus metric — alert at 80% of pool_size to catch leaks before exhaustion causes user-visible 503s
rollback() for transactional resources before close().SessionLocal() creation. Never share a session across requests — sessions are not thread-safe and hold transaction state.Database Connection Pool Exhaustion from Missing Yield in Dependency
db.close() in the dependency's finally block could execute.
FastAPI intercepts HTTPException at the route execution level and converts it to a JSON response. This interception happens before control returns to the dependency's finally block in a plain function. Only yield dependencies get a guaranteed callback after the route completes or raises — because FastAPI holds a reference to the generator and explicitly resumes it after the response is handled.
With a pool of 20 connections and roughly one validation error per second, the pool exhausted in approximately 20 seconds. Each failed request left one connection open, held by a session object that was never closed. The connection eventually timed out and was returned to the pool — but by then, the queue of waiting requests had already triggered a cascade of 503s.- Always use yield for resource-managing dependencies — plain try/finally does not survive FastAPI's exception handling interception
- FastAPI only guarantees teardown for yield dependencies — the generator resume is the mechanism, not the try/finally block
- Increasing pool_size to fix exhaustion without diagnosing the leak treats the symptom — the pool will always exhaust eventually if connections are not being released
- Monitor connection pool utilization with a dedicated metric — alert at 80% so you catch leaks before they cause user-visible failures
- Set pool_pre_ping=True to detect stale connections at checkout rather than failing mid-request
db.close() because FastAPI's exception handling intercepts before the dependency's finally block runs. Convert to yield dependency immediately. Then add a connection count metric to verify the fix — if active connections stabilize below pool_size after the change, the diagnosis was correct.Depends() declarations at different levels.app.dependency_overrides.clear() to prevent state from leaking into the next test.Key takeaways
Depends() is the primary tool for Inversion of Control in FastAPICommon mistakes to avoid
5 patternsUsing a plain function instead of yield for database session dependencies
db.rollback() in an except clause before db.close() to prevent uncommitted transactions from surviving exceptions. Add pool utilization monitoring with an alert at 80% of pool_size — if the fix is working, the metric stabilizes at the concurrent request count instead of growing monotonically.Applying auth dependencies at the route level instead of the router level
Not using use_cache=False for dependencies with side effects that must run independently each time they are referenced
Deep nesting of sub-dependencies creating an untraceable execution graph that is impossible to reason about during incident response
Dependency override not working in integration tests — real services called, real database hit, mock is ignored despite being configured
app.dependency_overrides.clear() to prevent state leakage between tests in the same session.Interview Questions on This Topic
Describe the 'Dependency Graph' resolution in FastAPI. How does it handle a scenario where Endpoint A depends on B and C, while both B and C depend on D?
Frequently Asked Questions
That's Python Libraries. Mark it forged?
3 min read · try the examples if you haven't