FastAPI Dependency Injection: Beyond The Basics

Dependency injection is one of FastAPI’s most powerful features, enabling clean, modular, and testable code. But beyond simple function-based dependencies, FastAPI offers several advanced patterns that can make your applications even more flexible. In this article, we’ll explore some lesser-known techniques you can use to level up your FastAPI dependency management.

Recap: What is Dependency Injection in FastAPI?

In FastAPI, dependencies are functions (or classes) that perform reusable operations—like authentication, database connection management, or parameter validation. You can inject dependencies into your path operation functions using the Depends marker:

from fastapi import Depends, FastAPI

def get_token_header(x_token: str = Header(...)):
    if x_token != "expected-token":
        raise HTTPException(status_code=400, detail="Invalid X-Token header")

app = FastAPI()

@app.get("/protected")
async def protected_route(token: str = Depends(get_token_header)):
    return {"message": "Token validated"}

But there’s so much more you can do!

Advanced Technique 1: Dependency Classes for State and Caching

You can turn a dependency into a class by adding a __call__ method. This lets you maintain internal state or cache data between multiple calls within the same request. For example, you might want to lazy-load a database session:

class DBSession:
    def __init__(self):
        self._session = None

    def __call__(self):
        if self._session is None:
            self._session = create_db_session()
        return self._session

db_session = DBSession()

@app.get("/users/{user_id}")
def get_user(user_id: int, db=Depends(db_session)):
    user = db.query(User).get(user_id)
    return user

For more on this, check out my previous article: [Custom Dependency Classes in FastAPI: Cleaner and More Reusable Code].

Advanced Technique 2: Dependency Overrides for Testing

When unit-testing your FastAPI endpoints, dependency overrides can isolate your application from the real world. You can inject fakes or mocks by using app.dependency_overrides during your tests:

def fake_get_current_user():
    return User(name="test", id=123)

app.dependency_overrides[get_current_user] = fake_get_current_user

This helps keep your tests deterministic and fast, and is essential for proper CI/CD pipelines.

Advanced Technique 3: Scoped Dependencies with Depends in Sub-Dependencies

Dependencies can depend on other dependencies! This makes it possible to build complex, layered dependency graphs. For instance, you could create a “requires authentication” dependency that’s itself composed of other checks:

def get_db():
    db = create_db_session()
    try:
        yield db
    finally:
        db.close()

def get_current_user(db=Depends(get_db)):
    # Fetches and returns the current user using the DB.
    ...

This lets you re-use common logic without repeating yourself, and makes your dependencies more composable.

Conclusion

FastAPI’s dependency injection can do much more than the basics. Once you’re comfortable with the simple patterns, explore using classes, overrides for testing, and building up dependency graphs for maximum flexibility. Well-structured dependencies lead to code that’s not only easier to test, but also more maintainable and DRY.

Have a favorite dependency pattern? Drop a comment below or tweet at me (@FastEddy) with your tips!

— Fast Eddy

Comments

Leave a Reply

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