KAHIBARO
Discord Login Register

8.6. Dependency Injection

Why Dependency Injection Matters in FastAPI

In FastAPI, dependency injection is the core mechanism that connects your route functions with everything they need, such as database sessions, configuration, authentication, and more. You already know how to write basic routes. This chapter explains how FastAPI’s dependency system lets you keep those routes clean, testable, and reusable.

Dependency injection, or DI, lets you declare what your function needs, and FastAPI takes care of how to provide it.

FastAPI uses the Depends class to implement DI. You will see it everywhere in real-world FastAPI projects.

The Basic Idea: `Depends`

In plain terms:

A minimal example:

python
from fastapi import FastAPI, Depends
app = FastAPI()
def get_message():
    return "Hello from dependency"
@app.get("/hello")
def read_hello(message: str = Depends(get_message)):
    return {"message": message}

Here:

You can think of Depends as a declarative way to say “I need this”.

Function Dependencies

Any normal Python function can be a dependency. It can:

Example: a dependency that reads a setting from environment variables.

python
import os
from fastapi import FastAPI, Depends
app = FastAPI()
def get_settings():
    db_url = os.getenv("DB_URL", "sqlite:///./test.db")
    debug = os.getenv("DEBUG", "false").lower() == "true"
    return {"db_url": db_url, "debug": debug}
@app.get("/settings")
def read_settings(settings: dict = Depends(get_settings)):
    return settings

FastAPI:

  1. Calls get_settings.
  2. Takes its return value.
  3. Passes it to the settings argument in read_settings.

You do not call get_settings yourself.

Combining Dependencies

Dependencies can depend on other dependencies. This builds a dependency tree.

python
from fastapi import FastAPI, Depends
app = FastAPI()
def get_app_name():
    return "MyApp"
def get_greeting(app_name: str = Depends(get_app_name)):
    return f"Welcome to {app_name}"
@app.get("/greet")
def greet(message: str = Depends(get_greeting)):
    return {"greeting": message}

Execution order for /greet:

  1. FastAPI sees message: str = Depends(get_greeting).
  2. To call get_greeting, it must provide app_name: str = Depends(get_app_name).
  3. It calls get_app_name, gets "MyApp".
  4. Calls get_greeting("MyApp"), gets the greeting string.
  5. Calls greet(message=that_string).

You never manually wire them together. You only declare the dependencies and FastAPI resolves the graph.

Typical Use Cases in Backends

Dependency injection is especially useful for:

You will see examples of each pattern in other chapters, but here we focus on the DI technique itself.

Request-Scoped Dependencies and `yield`

Many backend resources must be created at the start of a request and cleaned up at the end, such as:

FastAPI supports this using a dependency function that uses yield instead of return.

Pattern:

python
from fastapi import Depends
def get_resource():
    # setup
    resource = create_resource()
    try:
        yield resource
    finally:
        # cleanup
        resource.close()

FastAPI:

  1. Runs the code before yield when the dependency is first used.
  2. Gives the yielded value to the dependent function.
  3. After the request is finished, runs the finally block to clean up.

A typical database session dependency (simplified):

python
from fastapi import Depends
from sqlalchemy.orm import Session
def get_db() -> Session:
    db = SessionLocal()  # create session
    try:
        yield db          # use it in routes
    finally:
        db.close()        # always close

Usage in a route:

python
@app.get("/items")
def list_items(db: Session = Depends(get_db)):
    return db.query(Item).all()

The route does not know how the session is created or closed. It only receives a usable db object.

Important rule
Use yield in dependencies when you must guarantee cleanup after the request.
Do not manually close or dispose such resources in your route handlers.

Reusing Dependencies Across Routes

One of the main benefits of DI is reusability. You can attach the same dependency to many routes.

Example: a simple authentication check dependency.

python
from fastapi import Depends, HTTPException, status
def get_token_header(x_token: str | None = None):
    if x_token != "secret-token":
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Invalid X-Token header",
        )
@app.get("/secure-data", dependencies=[Depends(get_token_header)])
def secure_data():
    return {"data": "Top secret"}

Here, get_token_header:

You can attach Depends(get_token_header) to as many routes as you want.

Two main ways to use a dependency:

PatternWhen to use
param: Type = Depends(func)You need the return value of the dependency in the function.
dependencies=[Depends(func)]You only need side effects or validation, not the value.

Class-Based Dependencies

Dependencies do not have to be plain functions. They can also be classes that define an __call__ method, or classes that are used to hold parameters and logic.

Simple class dependency

Example of a dependency that uses a class instance:

python
from fastapi import FastAPI, Depends
app = FastAPI()
class Calculator:
    def __init__(self, base: int):
        self.base = base
    def double(self) -> int:
        return self.base * 2
def get_calculator() -> Calculator:
    return Calculator(base=10)
@app.get("/calc")
def calc_value(calc: Calculator = Depends(get_calculator)):
    return {"double": calc.double()}

Here:

Using class as the dependency itself

You can also use a class directly as a dependency. FastAPI will instantiate it, injecting other dependencies into its parameters.

python
from fastapi import Depends
class CommonQueryParams:
    def __init__(
        self,
        q: str | None = None,
        page: int = 1,
        limit: int = 10,
    ):
        self.q = q
        self.page = page
        self.limit = limit
@app.get("/items")
def list_items(params: CommonQueryParams = Depends()):
    return {
        "query": params.q,
        "page": params.page,
        "limit": params.limit,
    }

Explanation:

You can reuse CommonQueryParams in many routes.

Another variant with __call__:

python
class Multiplier:
    def __init__(self, factor: int = 2):
        self.factor = factor
    def __call__(self, value: int) -> int:
        return value * self.factor
@app.get("/multiply/{value}")
def multiply(
    result: int = Depends(Multiplier)  # Multiplier is used as dependency
):
    return {"result": result}

FastAPI:

  1. Creates Multiplier(factor=2).
  2. Sees result: int = Depends(Multiplier).
  3. Calls the created instance as a function with parameters from the request, here value.
  4. Injects the output into result.

Dependency Injection in Path Operations

The most common place to use dependencies is in your path operations.

python
from fastapi import FastAPI, Depends
app = FastAPI()
def get_current_user():
    # Here you would normally read a token, load from DB, etc.
    return {"username": "alice"}
@app.get("/me")
def read_me(current_user: dict = Depends(get_current_user)):
    return current_user

Typical patterns:

Multiple dependencies in one route:

python
from sqlalchemy.orm import Session
def get_db() -> Session:
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()
def get_current_user():
    return {"username": "alice"}
@app.get("/posts")
def list_posts(
    db: Session = Depends(get_db),
    current_user: dict = Depends(get_current_user),
):
    posts = db.query(Post).filter(Post.owner == current_user["username"]).all()
    return posts

FastAPI will resolve all dependencies before calling list_posts:

  1. Create and yield db.
  2. Get current_user.
  3. Call the route handler.

Global and Router-Level Dependencies

Sometimes you want a dependency to run for many routes at once, like a global authentication check or a logging hook.

You can attach dependencies to:

App-level dependencies

python
from fastapi import FastAPI, Depends, Request
app = FastAPI()
def log_request(request: Request):
    print("Incoming:", request.method, request.url)
app = FastAPI(dependencies=[Depends(log_request)])
@app.get("/one")
def route_one():
    return {"route": "one"}
@app.get("/two")
def route_two():
    return {"route": "two"}

Here log_request runs for every request, but its result is not injected as a parameter anywhere.

Router-level dependencies

python
from fastapi import APIRouter
router = APIRouter(
    prefix="/admin",
    dependencies=[Depends(get_token_header)],  # will apply to all routes here
)
@router.get("/users")
def list_admin_users():
    return [{"username": "admin1"}]
@router.get("/stats")
def admin_stats():
    return {"users": 42}
app.include_router(router)

Dependency Injection and Testing

Dependency injection makes testing much easier because you can override dependencies with test versions.

FastAPI exposes app.dependency_overrides to do this.

Suppose you have:

python
def get_db() -> Session:
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()
@app.get("/items")
def list_items(db: Session = Depends(get_db)):
    return db.query(Item).all()

In tests you can override get_db:

python
from fastapi.testclient import TestClient
from sqlalchemy.orm import Session
client = TestClient(app)
def override_get_db() -> Session:
    # use a test database or a fake object
    db = TestSessionLocal()
    try:
        yield db
    finally:
        db.close()
app.dependency_overrides[get_db] = override_get_db
def test_list_items():
    response = client.get("/items")
    assert response.status_code == 200

During tests:

You can override any dependency in this way, for example get_current_user to simulate authenticated users.

Optional Dependencies

Sometimes a dependency is not mandatory. You can allow it to fail without raising an error by using Depends with use_cache and default or by catching exceptions inside the dependency.

A common pattern is to make a dependency return None if authentication is missing:

python
from fastapi import Depends, Header, HTTPException
def get_optional_token(x_token: str | None = Header(default=None)):
    if x_token is None:
        return None
    if x_token != "secret-token":
        raise HTTPException(status_code=403, detail="Invalid token")
    return x_token
@app.get("/maybe-secure")
def maybe_secure(x_token: str | None = Depends(get_optional_token)):
    if x_token is None:
        return {"message": "Public access"}
    return {"message": "Private access", "token": x_token}

The route can then behave differently depending on whether the dependency returned something or None.

Caching Dependency Results per Request

By default, FastAPI calls each dependency once per request for each unique combination of parameters. If multiple parameters or dependencies refer to the same dependency with the same arguments, FastAPI reuses the result.

Example:

python
def get_config():
    print("Config dependency called")
    return {"debug": True}
@app.get("/a")
def route_a(cfg: dict = Depends(get_config)):
    return cfg
@app.get("/b")
def route_b(
    cfg1: dict = Depends(get_config),
    cfg2: dict = Depends(get_config),
):
    return {"same_object": cfg1 is cfg2}

For /b in a single request:

You can disable this behavior in rare cases by setting use_cache=False:

python
@app.get("/c")
def route_c(
    cfg1: dict = Depends(get_config, use_cache=False),
    cfg2: dict = Depends(get_config, use_cache=False),
):
    return {"same_object": cfg1 is cfg2}  # will be False

In most backend use cases, keep the default caching because it saves work and keeps consistency inside a single request.

Important rule
Within a single request, a dependency is called at most once for a given parameter combination, unless you set use_cache=False.
This makes dependencies predictable and efficient.

Common Patterns and Examples

To make the concepts concrete, here are a few patterns that you will frequently use.

Database session per request

python
from fastapi import Depends
from sqlalchemy.orm import Session
def get_db() -> Session:
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()
@app.post("/users")
def create_user(user_in: UserCreate, db: Session = Depends(get_db)):
    user = User(**user_in.model_dump())
    db.add(user)
    db.commit()
    db.refresh(user)
    return user

Current user from token

python
from fastapi import Depends, HTTPException, status, Header
def get_current_user(authorization: str | None = Header(default=None)):
    if authorization is None:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Missing Authorization header",
        )
    scheme, _, token = authorization.partition(" ")
    if scheme.lower() != "bearer" or token != "valid-token":
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid token",
        )
    return {"username": "alice"}
@app.get("/profile")
def read_profile(current_user: dict = Depends(get_current_user)):
    return current_user

Common pagination parameters

python
from fastapi import Query, Depends
class Pagination:
    def __init__(
        self,
        page: int = Query(1, ge=1),
        size: int = Query(10, ge=1, le=100),
    ):
        self.page = page
        self.size = size
@app.get("/products")
def list_products(p: Pagination = Depends()):
    skip = (p.page - 1) * p.size
    limit = p.size
    return {"page": p.page, "size": p.size, "skip": skip, "limit": limit}

Summary

Dependency injection in FastAPI:

You will rely on this system heavily when adding authentication, database access, configuration, and more to your FastAPI backends.

Views: 9

Comments

Please login to add a comment.

Don't have an account? Register now!