Python Fastapi Dependency Injection Guide

Written by

in

FastAPI has quickly become one of the most loved Python web frameworks, thanks to its speed, type‑safety, and developer‑friendly design. One of the core features that sets FastAPI apart is its powerful dependency injection system, which lets you write clean, reusable, and testable code with minimal boilerplate. In this guide we’ll walk through everything you need to know about FastAPI dependency injection—from the basics to advanced patterns—so you can harness its full potential in your next Python project.

What Is Dependency Injection in FastAPI?

In simple terms, dependency injection (DI) is a technique where a function (or class) receives the resources it needs from the outside rather than creating them internally. FastAPI’s DI system is built on Python’s type hints and the Depends class, allowing you to declare dependencies directly in your endpoint signatures.

  • Reusability: Write a single piece of logic (e.g., database connection, authentication) and reuse it across many routes.
  • Testability: Swap real implementations with mocks or stubs during unit tests without touching the endpoint code.
  • Separation of concerns: Keep business logic separate from request‑handling, validation, and security concerns.

Basic Dependency Syntax

The most common way to declare a dependency is by using the Depends function as a default value for a parameter.

from fastapi import FastAPI, Depends

app = FastAPI()

def get_query_param(q: str = "default"):
    return q

@app.get("/items/")
def read_items(query: str = Depends(get_query_param)):
    return {"query": query}

In the example above, get_query_param is injected into read_items. FastAPI automatically calls the dependency, resolves its own parameters, and passes the result to the endpoint.

Key Points to Remember

  • Dependencies can be functions or callable classes.
  • FastAPI resolves dependencies **recursively**—if a dependency itself depends on another, FastAPI will handle the entire chain.
  • The return value of a dependency becomes the value of the parameter that uses Depends.

Dependency Injection for Common Scenarios

1. Database Sessions

Managing a database connection per request is a classic use case. Below is a typical pattern using SQLAlchemy’s SessionLocal factory.

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session
from fastapi import Depends

SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

def get_db() -> Session:
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

@app.get("/users/")
def read_users(db: Session = Depends(get_db)):
    return db.query(User).all()

Notice the use of yield inside the dependency. FastAPI treats it as a “dependency with cleanup”, automatically closing the session after the request finishes.

2. Authentication & Authorization

FastAPI’s DI system shines when handling security. You can create a reusable dependency that extracts the current user from a JWT token.

from fastapi import HTTPException, status, Security
from fastapi.security import OAuth2PasswordBearer

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

def get_current_user(token: str = Security(oauth2_scheme)):
    # Decode token, verify, fetch user, etc.
    user = decode_token(token)
    if not user:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
                            detail="Invalid credentials")
    return user

@app.get("/profile/")
def read_profile(current_user: User = Depends(get_current_user)):
    return {"username": current_user.username, "email": current_user.email}

By injecting get_current_user, every endpoint that needs authentication automatically receives the validated user object.

3. Request‑Scoped Caching

Sometimes you want to compute a value once per request and reuse it across multiple dependencies. FastAPI provides a request.state object that can be used for this purpose.

from fastapi import Request

def expensive_computation(request: Request):
    if not hasattr(request.state, "expensive_result"):
        request.state.expensive_result = compute_heavy_task()
    return request.state.expensive_result

@app.get("/calc/")
def get_result(value: int = Depends(expensive_computation)):
    return {"result": value}

Advanced Dependency Patterns

Using Classes as Dependencies

When your dependency requires internal state or several helper methods, a class can be a cleaner solution.

class Service:
    def __init__(self, db: Session):
        self.db = db

    def get_item(self, item_id: int):
        return self.db.query(Item).filter(Item.id == item_id).first()

def get_service(db: Session = Depends(get_db)):
    return Service(db)

@app.get("/items/{item_id}")
def read_item(item_id: int, service: Service = Depends(get_service)):
    item = service.get_item(item_id)
    if not item:
        raise HTTPException(status_code=404, detail="Item not found")
    return item

This pattern keeps your endpoint thin while encapsulating business logic inside the Service class.

Dependency Overriding for Testing

One of the biggest advantages of FastAPI DI is the ability to replace real dependencies with mocks during tests. FastAPI provides app.dependency_overrides for this purpose.

from fastapi.testclient import TestClient

def fake_get_db():
    # Return a mock or in‑memory DB session
    yield FakeSession()

app.dependency_overrides[get_db] = fake_get_db

client = TestClient(app)

def test_read_users():
    response = client.get("/users/")
    assert response.status_code == 200
    assert response.json() == []  # Expect empty list from fake DB

After the test, you can clear the overrides with app.dependency_overrides.clear(), ensuring isolation between test cases.

Conditional Dependencies

FastAPI lets you create dependencies that only run under certain conditions, such as based on request headers or query parameters.

def check_feature_flag(request: Request):
    if request.headers.get("X-Feature-Flag") != "enabled":
        raise HTTPException(status_code=403, detail="Feature not enabled")
    return True

@app.get("/beta/")
def beta_endpoint(allowed: bool = Depends(check_feature_flag)):
    return {"status": "You have access to the beta feature!"}

Best Practices for FastAPI Dependency Injection

  • Keep dependencies small and focused. A single responsibility makes them easier to test and reuse.
  • Prefer functions for simple tasks. Use classes only when you need state or multiple related methods.
  • Leverage yield for resources that need cleanup. This ensures proper closing of DB connections, file handles, etc.
  • Document your dependencies. Use docstrings and type hints so IDEs can provide autocomplete and developers understand the contract.
  • Group related dependencies. For example, create a security.py module for all auth‑related functions, and a database.py module for DB utilities.
  • Use dependency overrides in tests, not monkey‑patching. This keeps your production code untouched and your test suite deterministic.
  • Cache expensive results per request. Store them on request.state to avoid recomputation across multiple dependencies.

Performance Considerations

FastAPI’s DI system is designed to be lightweight, but it’s still worth understanding its impact on request latency.

  1. Dependency depth: Each additional layer adds a small overhead. Keep the chain reasonable (usually < 5 levels) for optimal speed.
  2. Yield‑based cleanup: Using yield introduces a context‑manager‑like behavior. The overhead is negligible compared to the benefit of guaranteed cleanup.
  3. Singleton vs. per‑request: For resources that are thread‑safe (e.g., a Redis client), instantiate them once at startup and inject the same instance. Avoid creating heavy objects on every request.

Putting It All Together – A Mini Project Example

Below is a concise example of a FastAPI app that demonstrates database access, authentication, and service‑layer logic—all wired together using dependency injection.

from fastapi import FastAPI, Depends, HTTPException, status, Security, Request
from fastapi.security import OAuth2PasswordBearer
from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session

# ---------- Database Setup ----------
SQLALCHEMY_DATABASE_URL = "sqlite:///./example.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
Base = declarative_base()
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
username = Column(String, unique=True, index=True)
hashed_password = Column(String)

Base.metadata.create_all(bind=engine)

# ---------- Dependency: DB ----------
def get_db() -&

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *