FastAPI Dependency Injection

Learn FastAPI's dependency injection system: how it works, why it's useful, and how to use it in your APIs. This lesson covers the core concepts, step-by-step examples, and best practices.

Focus: using fastapi’s dependency injection system

Sponsored

You've built route handlers that work, but every time you need the current user, a database session, or a shared config, you copy-paste the same boilerplate into each endpoint. That duplication is a breeding ground for bugs, inconsistent behavior, and security holes. FastAPI's dependency injection (DI) system eliminates this pain: it lets you declare what an endpoint needs, and FastAPI automatically provides it — cleanly, testably, and with zero manual wiring. In this lesson, using fastapi’s dependency injection system, you'll move from repetitive handlers to elegant, reusable code that scales.

The problem this lesson solves

Imagine you have five endpoints that all need a database session. Without a plan, you'd write the same setup code five times: create a session, handle errors, close it. Multiply that by authentication, pagination, logging — and your codebase becomes a swamp of copy-pasted logic. Worse, a small change (like adding a new header) forces you to hunt down every endpoint and update it. This is the classic dependency management problem: how do you give each endpoint what it needs without hard-coding it?

For beginners, it's tempting to solve this with global variables or a helper function you call at the top of each handler. But globals break testing and hide state; helper functions still require you to remember to call them. FastAPI's answer is declarative: you describe your dependencies as parameters to your endpoint, and the framework takes care of the rest.

This lesson is step 13 in the FastAPI Backend Development path. By now you know routing and Pydantic models. DI is the glue that lets you build on those foundations — it's how you'll handle auth, database access, and shared logic in every serious FastAPI project.

Core concept / mental model

Think of FastAPI's dependency injection as a restaurant ordering system. Your endpoint is the customer at the counter; it doesn't cook anything itself. Instead, it says, "I'd like a database session, a current user, and a settings object." The kitchen — FastAPI's DI system — takes that order, prepares each item using its own recipe (the dependency function), and hands it over.

Here's the mental model in three parts:

  • Dependency function: A plain Python function that does setup work and returns a value. It can itself depend on other dependencies (the kitchen uses sub-recipes).
  • Parameter declaration: In your path operation, you add a parameter with a type hint that matches the return type of your dependency. FastAPI sees this and knows to call the function and pass the result.
  • The Depends() marker: This is the special signal that tells FastAPI, "This parameter isn't coming from the request body or query string — it's a dependency."

In FastAPI, a dependency is just a callable. It can be a function, a class, or even a generator. The system is entirely based on type hints and the Depends class from fastapi. That's all there is to it — no abstract containers, no service locator patterns, just Python.

Here's a tiny example to make it concrete:

from fastapi import FastAPI, Depends

app = FastAPI()

def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
    return {"q": q, "skip": skip, "limit": limit}

@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
    return commons

Pro tip: The dependency function runs on every request that uses it. For expensive setup, use lru_cache or a generator with yield to control lifecycle.

How it works step by step

Understanding the flow demystifies DI. Here's what happens when a request hits an endpoint with a dependency:

  1. FastAPI receives the request and looks at the path operation's parameters.
  2. It sees a parameter with a Depends(...) default value — that's the trigger.
  3. FastAPI resolves the dependency function: it analyzes that function's own parameters, recursively resolving any nested dependencies.
  4. It builds the dependency tree (in the right order), calls each function, and collects the results.
  5. It passes the resolved value as the argument to your endpoint.
  6. If a dependency is a generator (using yield), FastAPI calls the cleanup code after the response is sent.

Most importantly, dependencies are cached within a single request. If two endpoints depend on the same dependency, it runs only once per request — this is efficient and predictable.

Why does this matter for you? Because it gives you separation of concerns. Authentication logic lives in one function; database session management in another; business rules stay in the endpoint. You test each piece in isolation.

The role of type hints

FastAPI uses type hints to know what to inject. If your dependency returns a User, and your endpoint declares user: User = Depends(get_current_user), you get a User object. The type hint also drives automatic documentation in Swagger UI — dependencies become visible, testable parameters in the docs.

Hands-on walkthrough

Let's build a real example. We'll create a small API with two practical dependencies: a database session provider (simulated) and an authentication dependency. This mirrors what you'll do in production.

First, define the database dependency using a generator to ensure cleanup:

from fastapi import FastAPI, Depends
from contextlib import asynccontextmanager

app = FastAPI()

class Database:
    def __init__(self):
        self.connected = True
    def close(self):
        self.connected = False

async def get_db():
    db = Database()
    try:
        yield db
    finally:
        db.close()  # Always runs, even on exceptions

Now an auth dependency that both validates a token and returns a user object:

from fastapi import Depends, HTTPException, status

def verify_token(authorization: str | None = None):
    # In real life, validate JWT etc.
    if authorization != "secret-token":
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid token"
        )
    return {"user_id": 42, "name": "Ada"}

def get_current_user(token_data: dict = Depends(verify_token)):
    # This is a nested dependency!
    return token_data

Finally, wire it all together in endpoints:

from fastapi import FastAPI, Depends

app = FastAPI()

@app.get("/profile")
async def get_profile(
    db: Database = Depends(get_db),
    user: dict = Depends(get_current_user)
):
    return {"user": user, "db_status": db.connected}

@app.get("/items/{item_id}")
def get_item(item_id: int, db: Database = Depends(get_db)):
    return {"item_id": item_id, "db_status": db.connected}

Run this with uvicorn main:app --reload. Visit http://localhost:8000/profile and you'll see {"detail":"Invalid token"}. Add the header Authorization: secret-token and you'll get:

{
  "user": {"user_id": 42, "name": "Ada"},
  "db_status": true
}

Notice how get_current_user itself depends on verify_token. FastAPI resolves both automatically. The database is injected fresh for each request and closed afterward — thanks to the yield generator pattern.

Using classes as dependencies

Sometimes a dependency needs to hold state or configuration. You can use a class instead of a function:

from fastapi import Depends

class Settings:
    def __init__(self):
        self.app_name = "MyAPI"
        self.debug = False

@app.get("/info")
async def get_info(settings: Settings = Depends(Settings)):
    return {"app": settings.app_name, "debug": settings.debug}

This is handy for dependency configuration. FastAPI even supports a shortcut: you can omit Depends() and just use the class as a type hint — FastAPI will automatically instantiate it.

Compare options / when to choose what

FastAPI gives you multiple ways to share code. Here's a comparison to help you choose:

Approach Pros Cons Use when…
Depends() with a function Simple, testable, cached per request Requires a function definition Most cases: auth, DB, common params
Generator (yield) dependency Cleanup guaranteed (DB close, file close) Slightly more complex syntax Managing resources that must be released
Class dependency Encapsulates config, reusable Adds boilerplate for tiny jobs When dependency has methods/state
Global variable Zero setup Hard to test, hidden state, concurrency issues Never — avoid it
Manual call in handler Simple to understand Duplicated code, error-prone Only for one-off cases in a script

Rule of thumb: Use a function with Depends() for 90% of your needs. Switch to a generator when you need cleanup (database sessions, file handles). Use a class when the dependency's logic is substantial.

Troubleshooting & edge cases

Even experienced devs hit these. Here are the common issues:

  • Type mismatch: Your endpoint's parameter type hint doesn't match the dependency's return type. FastAPI will still pass the object, but your IDE and static checks will flag it. Always align the types.
  • Missing Depends(): If you forget Depends(), FastAPI treats the parameter as a request body or query parameter — it won't inject your dependency. Error messages can be confusing, so verify the default value.
  • Generator dependencies flaky: If your yield appears after you try to return or raise, bad things happen. The generator must yield exactly once.
  • Circular dependencies: If A depends on B, and B depends on A, FastAPI will raise an error about circular dependencies. Break the cycle by injecting a shared object instead. (Avoid this design!)
  • Dependencies run per request, not per call: If you test a function that uses a dependency directly (not through FastAPI), you might expect it to run once. It will run once per request — fine. But if you call the endpoint multiple times in a test, the dependency will run each time.
  • Caching in tests: Use dependency_overrides to replace a dependency with a mock. This is essential for testing; FastAPI's app.dependency_overrides dictionary lets you swap out dependencies easily.

What you learned & what's next

You've now mastered using fastapi's dependency injection system. You understand the core idea: declare dependencies as parameters and let FastAPI resolve them. You can implement practical dependencies like database sessions and auth, and you know how to choose between functions, generators, and classes. You've also seen how to troubleshoot common pitfalls.

This skill is foundational — you'll use DI in authentication, database sessions, pagination, and more. Next in the track, you'll dive into handling authentication and security, where DI becomes your main tool for protecting endpoints. Get ready to build secure APIs with confidence.

Before you move on

  • Write a dependency that reads a header and returns a custom object.
  • Create a generator dependency that logs start and end times.
  • Use app.dependency_overrides to mock a database in a test.

Practice recap

Build a small app with a get_user dependency that reads a custom header and returns a user object. Then create a generator dependency that logs request start and end times. Finally, use dependency_overrides to replace the user dependency with a mock in a test — you'll see how easy unit testing becomes.

Common mistakes

  • Forgetting to add Depends() as the default value — FastAPI treats the parameter as a request body or query param, and your dependency never runs.
  • Raising an HTTPException inside a generator dependency after yield — cleanup code runs, but the exception behavior can surprise you; raise before yield.
  • Using a global mutable object instead of a dependency — leads to concurrency bugs and makes testing nearly impossible.
  • Creating circular dependencies without a clear boundary — FastAPI raises an error; design your dependency graph to be acyclic.

Variations

  1. Use a class as a dependency with the Depends keyword, or even without it — FastAPI auto-instantiates if you use the class as a type hint.
  2. Use yield to create context-manager-style dependencies that clean up after the response is sent.
  3. Swap dependencies in tests via app.dependency_overrides to mock external services like databases.

Real-world use cases

  • Authenticating users with a JWT token and loading the user object from a database for every protected route.
  • Managing a database session per request, ensuring it closes even when an exception is raised.
  • Providing shared pagination and filtering parameters to multiple list endpoints without duplicating code.

Key takeaways

  • FastAPI DI uses type hints and the Depends() marker to inject callable results into endpoints.
  • Generator dependencies with yield guarantee cleanup for resources like database sessions.
  • Dependencies can themselves depend on others, building a clean, testable graph.
  • Choose function dependencies for simple logic, generators for resource management, and classes for stateful setups.
  • Always align return types between dependency and endpoint parameter to avoid subtle bugs.
  • Use dependency_overrides for testing to replace real dependencies with mocks.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.