FastAPI Dependency Injection Internals

03 Jun 2026, Updated: 30 Jul 2026 5 min read
1
Dependency Injection is a design pattern where an object receives its dependencies from an external source instead of creating them itself.

In this article, we will explore FastAPI's Dependency Injection system from first principles and understand how it works internally.

The Problem Dependency Injection Solves

Imagine a service that retrieves user information from a database. A straightforward implementation might look like this:
from fastapi import FastAPI

app = FastAPI()

@app.get("/users/{user_id}")
async def get_user(user_id: int):

    db = DatabaseConnection()
    user = db.find_user(user_id)

    return user
At first glance, this seems perfectly reasonable.

However, as applications grow, multiple endpoints often require access to shared resources such as database sessions, Redis connections, authenticated users, configuration objects, Kafka producers, and feature flag services.

If every endpoint creates these dependencies manually, business logic becomes tightly coupled to resource creation. Code duplication increases, testing becomes more difficult, and managing shared resources becomes harder.

A better approach is to separate resource creation from business logic. This is where Dependency Injection becomes useful.

Introducing Depends()

FastAPI provides the Depends() function to declare dependencies. Consider the following example:
from fastapi import Depends
from fastapi import FastAPI

app = FastAPI()

class Database:
    async def get_users(self):
        return [
            {"id": 1, "name": "John"},
            {"id": 2, "name": "Alice"}
        ]

async def get_db():
    return Database()

@app.get("/users")
async def get_users(
    db: Database = Depends(get_db)
):
    users = await db.get_users()
    return users
Here, the endpoint does not create the database connection itself.

Instead, FastAPI executes the get_db() dependency and injects the result into the endpoint. The endpoint receives a ready-to-use dependency. This keeps endpoint logic clean and focused.

A dependency can be a function, a class, or any callable object that FastAPI can execute to produce a value.

What Happens Internally?

When a request arrives, FastAPI does not immediately execute the endpoint.

Instead, it follows a sequence of steps: Request Arrives → Route Matching → Dependency Resolution → Endpoint Execution → Response Returned.

Before the endpoint executes, FastAPI inspects all declared dependencies and resolves them. Only after dependency resolution completes does endpoint execution begin.

This behavior is important because dependencies may perform critical tasks such as authentication, authorization, database initialization, and tenant identification.
from fastapi import Depends

async def get_current_user():
    return {
        "id": 1,
        "name": "John"
    }

@app.get("/profile")
async def profile(
    user = Depends(get_current_user)
):
    return user
When a request reaches the endpoint, FastAPI performs the following operations:

1. Route matching occurs.
2. FastAPI identifies the dependency.
3. get_current_user() executes.
4. The dependency result is cached for the current request.
5. The result is injected into the endpoint.
6. The endpoint executes.

Nested Dependencies

One of the most powerful features of FastAPI's Dependency Injection system is support for nested dependencies. Consider the following example.
from fastapi import Depends

async def get_db():
    return "db"

async def get_repository(
    db = Depends(get_db)
):
    return f"repository({db})"

async def get_service(
    repo = Depends(get_repository)
):
    return f"service({repo})"
Endpoint:
@app.get("/users")
async def get_users(
    service = Depends(get_service)
):
    return service
FastAPI needs to execute not only get_service(), but also every dependency it relies on.

Since get_service() depends on get_repository(), which in turn depends on get_db(), FastAPI automatically constructs a dependency graph and resolves all dependencies before executing the endpoint.

Dependencies are resolved from the deepest dependency to the endpoint. The execution order is: get_db() → get_repository() → get_service() → Endpoint

Dependency Caching

Consider:
async def get_db():
    print("Creating database connection")
    return "db"
Now suppose two dependencies require the same database connection.
async def get_repository(
    db = Depends(get_db)
):
    return db

async def get_service(
    db = Depends(get_db)
):
    return db
Although both dependencies require get_db(), FastAPI executes it only once per request.

The result is cached and reused whenever the same dependency is needed again, preventing duplicate dependency creation and improving efficiency.
By default, FastAPI caches dependency results for the lifetime of a single request.

Database Session Injection

One of the most common production use cases for Dependency Injection is database session management. Consider an async SQLAlchemy session.
from sqlalchemy.ext.asyncio import AsyncSession

async def get_db():
    async with SessionLocal() as session:
        yield session
Endpoint:
@app.get("/users")
async def get_users(
    db: AsyncSession = Depends(get_db)
):
    return await service.get_users(db)
Here, FastAPI automatically provides a database session to the endpoint. The endpoint simply receives a ready-to-use database session without needing to know how it was created.

Dependencies that use yield participate in FastAPI's automatic resource management. Consider:
async def get_db():

    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()
The code before yield creates the resource, and the yielded value is injected into the endpoint.

After the response is returned, FastAPI resumes the dependency and executes the code after yield, allowing resources to be cleaned up automatically.

This pattern is commonly used for managing database sessions, file handles, network connections, and other external resources.

The request lifecycle becomes: Request → Dependency Resolution → Endpoint Execution → Response → Dependency Cleanup

This ensures resources are released correctly even when exceptions occur.

If a dependency raises an exception, endpoint execution never begins. This behavior is commonly used for authentication and authorization.

Conclusion

FastAPI's Dependency Injection system separates resource creation from business logic, making applications easier to maintain, test, and extend.

By automatically resolving dependencies, supporting nested dependency graphs, caching dependency results, and managing resource lifecycles with yield, FastAPI significantly reduces boilerplate code while promoting clean and reusable application design.
Nagesh Chauhan

Nagesh Chauhan

Principal Software Engineer • Java • Python • Distributed Systems • AI/ML

Principal Software Engineer with 14+ years of experience designing and delivering large-scale distributed systems, cloud-native applications, and AI-powered platforms.

Passionate about solving complex engineering problems using strong data structures and algorithms, along with expertise in Java, Spring Boot, Python, System Design, Microservices, Cloud, Kafka, Elasticsearch, and Generative AI.

Share this Article

💬 Comments

Join the Discussion