Dependency Injection (Depends)

RoadmapsPython Backend

Scenario

Your endpoint needs to query the database and verify the user is logged in. You could write the auth verification and DB connection code inside the route function itself, but then you'd have to copy-paste it for the next 50 endpoints.

Overview

Dependency injection (DI) via `Depends` in FastAPI is a mechanism to declare what a route needs—like a database connection or current user—and have the framework automatically provide it.

It separates setup code from business logic, prevents massive code duplication across routes, and makes endpoints trivially easy to test by overriding the dependencies.

Where used: FastAPI route definitions, Authentication and authorization flows, Database session management

Why learn this

Mental model

Depends is a restaurant kitchen: the waiter doesn't cook, he declares what the dish needs.

When an endpoint declares a parameter with `Depends(get_db)`, FastAPI pauses before running the endpoint. It runs `get_db()`, takes whatever it returns, and passes it into the endpoint parameter. The endpoint just declares what it needs; FastAPI handles the fetching.

Deep dive

Without dependency injection, an endpoint has to do its own chores: open a database connection, check the headers for a token, validate the token, and handle errors if it's missing. This mixes 'setup' with 'business logic' and leads to massive code duplication across routes.

Doing setup inside the route creates bloated, repeated code.

FastAPI solves this with the `Depends` function. You write a separate function that does the chore (e.g., `def get_db(): ...`). In your endpoint, you add a parameter: `db: Session = Depends(get_db)`. Before calling your endpoint, FastAPI automatically calls `get_db()`, takes its result, and injects it into the `db` parameter.

Depends separates 'getting what you need' from 'using it'.

Dependencies can do more than return values; they can enforce rules. If a dependency raises an `HTTPException` (e.g., an invalid token), the request stops immediately. The endpoint code never runs, guaranteeing that if the endpoint *does* run, the dependency succeeded.

Dependencies act as gatekeepers for authentication and validation.

Because dependencies are declared in the function signature, testing becomes incredibly easy. In your test, you can tell FastAPI, 'Whenever a route asks for `get_db`, give it `get_test_db` instead.' This allows you to swap out real databases or auth systems for mocked ones without changing the endpoint code.

DI makes testing trivial by allowing you to override dependencies.

Code examples

The bad way: Manual setup inside the endpoint

from fastapi import APIRouter, Header, HTTPException

router = APIRouter()

@router.get('/users/me')
def get_current_user(authorization: str = Header(None)):
    # 1. Setup/Auth logic mixed into the endpoint
    if not authorization or not authorization.startswith('Bearer '):
        raise HTTPException(status_code=401, detail='Invalid token')
    token = authorization.split(' ')[1]
    user = fake_verify_token(token)
    
    # 2. Actual business logic
    return {'user': user}

Here, the endpoint does its own authentication. If you add 10 more routes that need the current user, you have to copy-paste the auth logic into every single one.

The FastAPI way: Using Depends

from fastapi import APIRouter, Depends, Header, HTTPException

# The dependency function
def get_current_user(authorization: str = Header(None)):
    if not authorization or not authorization.startswith('Bearer '):
        raise HTTPException(status_code=401, detail='Invalid token')
    token = authorization.split(' ')[1]
    return fake_verify_token(token)

router = APIRouter()

# The endpoint just asks for what it needs
@router.get('/users/me')
def read_user_profile(user: dict = Depends(get_current_user)):
    return {'user': user}

The `get_current_user` function handles the complexity. The endpoint simply declares it needs a user via `Depends`. FastAPI calls the dependency and injects the result.

Overriding dependencies in tests

from fastapi.testclient import TestClient
from main import app, get_current_user

# A fake dependency for testing
def mock_get_current_user():
    return {'id': 1, 'username': 'test_user'}

# Tell the app to use the mock instead of the real dependency
app.dependency_overrides[get_current_user] = mock_get_current_user

client = TestClient(app)

def test_read_user_profile():
    # The request doesn't need a real auth header now!
    response = client.get('/users/me')
    assert response.status_code == 200
    assert response.json() == {'user': {'id': 1, 'username': 'test_user'}}

`app.dependency_overrides` lets you swap out complex dependencies (like database connections or auth calls) with simple test doubles, meaning you don't have to change your application code to test it.

Key points

Common mistakes

Recall questions

Questions & answers

What is dependency injection in FastAPI?

It's a mechanism where you declare what an endpoint needs (like a DB session or a user) in the function signature using `Depends()`. The framework automatically runs the required setup code and passes the result into the endpoint.

You are writing tests for an endpoint that requires an expensive third-party API call to verify a user token. How do you test the endpoint's logic without hitting the API?

I would put the API call in a FastAPI dependency, then use `app.dependency_overrides` in my test suite to replace that dependency with a mock function that returns a dummy user.

Continue learning

Previous: Routers & App Structure

Previous: async vs def Endpoints

Next: Middleware & CORS

Next: Async SQLAlchemy and SQLModel

Return to Python Backend Roadmap