Migrate to RunxBuild and earn up to $50 in hosting credit on your first deposit.

Calculate your savings
unxBuild

FastAPI Depends: Dependency Injection Without Hidden Application State

Sean

Platform Writer

Jul 22, 2026
10 min read

FastAPI Depends tells the framework how to resolve a callable for each request, including its own parameters and dependencies, then inject the result into the route.

FastAPI Depends: Dependency Injection Without Hidden Application State

It is most useful for request-scoped concerns such as authentication, database sessions, configuration, and repeated validation. It becomes confusing when every ordinary function call is hidden behind the dependency graph.

Table of contents

Declare a dependency with Annotated

from typing import Annotated
from fastapi import Depends, FastAPI, Query

app = FastAPI()

def paging(limit: int = Query(20, ge=1, le=100)) -> int:
    return limit

@app.get('/items')
def list_items(limit: Annotated[int, Depends(paging)]):
    return {'limit': limit}

Pass the dependency callable, not the result of calling it. FastAPI inspects its signature, resolves parameters, and caches a dependency result within the request by default. Annotated preserves the actual type for editors and static tools while carrying the dependency metadata.

Compose authentication and authorization

Keep credential extraction separate from policy. One dependency can parse and verify a token, another can require a role or resource relationship, and routes can request the resulting principal. Return a typed user object rather than a loose dictionary so downstream code has a clear contract.

Authentication dependencies should fail with deliberate HTTP responses and avoid logging raw credentials. Authorization still belongs close to the protected operation; a generic logged-in dependency does not prove access to a particular project.

Manage database sessions with yield

def get_session():
    session = SessionLocal()
    try:
        yield session
    finally:
        session.close()

SessionDep = Annotated[Session, Depends(get_session)]

A yield dependency creates a resource and guarantees cleanup after the request. Decide transaction ownership explicitly: a route or service should commit intentionally, while the dependency handles close and rollback policy. Hiding automatic commits in cleanup makes failures and partial work difficult to reason about.

Override dependencies in tests

FastAPI’s dependency_overrides mapping can replace external services, users, clocks, or sessions during a test. Restore overrides after each test so state does not leak. Prefer small fakes with the same protocol over mocks that know every internal call.

app.dependency_overrides[get_current_user] = fake_user
try:
    response = client.get('/profile')
finally:
    app.dependency_overrides.clear()

A route that can be tested only by constructing the whole production graph has too many responsibilities. Put business rules in ordinary functions or classes and use dependencies to assemble request boundaries.

Avoid dependency graph abuse

  • Use dependencies for request-scoped cross-cutting concerns
  • Keep business logic callable outside FastAPI
  • Prefer typed aliases for repeated declarations
  • Avoid mutable global singleton state
  • Document caching and cleanup behavior
  • Test authorization failure and cleanup paths

Depends is not a service locator. If a dependency exists only to hide where an object comes from, explicit construction may be clearer. The best graph makes route requirements visible at the signature and keeps the underlying application usable without an HTTP request.

One additional design test is to trace cancellation and failure through the graph. If a client disconnects while a dependency is waiting on a database or remote API, the underlying library must release its connection and the yield cleanup must still run. Measure dependency latency separately from route logic so a slow authentication provider does not look like a slow query. For expensive read-only dependencies, request-local caching is useful; global caching needs explicit expiry and concurrency control. Keep startup resources in the application lifespan rather than constructing a new pool on every request. Finally, document whether each dependency returns a shared client, a request-scoped session, or an immutable value. That lifecycle is part of the interface even when Python’s type annotation cannot express it. Review the graph from the route inward: identity, authorization, database, external clients, and cleanup should each have one owner. When two dependencies both commit, close, or translate the same error, their hidden interaction becomes the next production bug.

How this fits the rest of the stack

A FastAPI dependency graph still needs runtime, secrets, database connections, and logs. The RunxBuild hosting calculator models those pieces together, and the RunxBuild dashboard keeps deployed configuration and runtime output visible.

Useful related references:

FAQ

Should I call the function inside Depends?

No. Pass the callable, such as Depends(get_user), so FastAPI can resolve its parameters.

Why use Annotated?

It keeps the real parameter type visible while attaching FastAPI dependency metadata and supports reusable typed aliases.

Are dependency results cached?

By default FastAPI reuses a dependency result within one request. Configure use_cache only when repeated execution is truly required.

How do I close a database session?

Use a yield dependency with cleanup in finally, and define transaction commit and rollback ownership explicitly.

Can I override dependencies in tests?

Yes. Use app.dependency_overrides and clear the overrides after the test.

#FastAPI#Depends#Python#Dependency Injection#API