Understanding Depends

Difficulty: Intermediate

Dependency injection (DI) is a design pattern where a function receives its dependencies from the outside rather than creating them internally. FastAPI's dependency injection system, centered around the Depends function, is one of its most powerful features. It enables clean code reuse, testability, and separation of concerns.

In FastAPI, a dependency is any callable (function, class, or callable object) that can receive the same parameters as a path operation function. Dependencies can have their own dependencies, forming a dependency tree. FastAPI resolves this tree automatically, calling each dependency in the correct order and passing results to the functions that need them.

The simplest dependency is a function that extracts common parameters. For example, many endpoints might need pagination parameters (skip and limit). Instead of repeating these parameters in every endpoint, you define a function that takes them and use Depends to inject the result.

Dependencies can do much more than parameter extraction. Common use cases include: database session management (creating and closing sessions), authentication (verifying tokens and extracting user info), authorization (checking permissions), rate limiting, logging, caching, and input preprocessing.

Dependencies are resolved per-request. Each request creates a new call to the dependency function. If the same dependency is used multiple times in the same request (even through sub-dependencies), FastAPI calls it only once and caches the result for that request. This prevents issues like creating multiple database sessions for a single request.

Classes can also be used as dependencies. A class dependency is instantiated for each request, with its __init__ parameters resolved just like function parameters. This is useful for dependencies that need to maintain state or provide methods. The class instance is passed to the handler.

Dependencies can be async functions too. Async dependencies work just like async handlers - they run on the event loop and can await other coroutines. Sync dependencies run in a thread pool, just like sync handlers.

Code examples

Basic Dependency Function

from fastapi import FastAPI, Depends, Query

app = FastAPI()

# Dependency function for pagination
def pagination_params(
    skip: int = Query(default=0, ge=0),
    limit: int = Query(default=20, ge=1, le=100),
):
    return {"skip": skip, "limit": limit}

@app.get("/items")
def list_items(pagination: dict = Depends(pagination_params)):
    return {"pagination": pagination, "items": []}

@app.get("/users")
def list_users(pagination: dict = Depends(pagination_params)):
    return {"pagination": pagination, "users": []}

# Both endpoints accept skip and limit query params
# GET /items?skip=10&limit=5
# GET /users?skip=0&limit=50
# The dependency handles validation and defaults

The pagination_params function is called by FastAPI before the handler. Its return value is injected as the 'pagination' parameter. Both endpoints reuse the same pagination logic without repeating the parameters.

Class-Based Dependencies

from fastapi import FastAPI, Depends, Query

app = FastAPI()

class Pagination:
    def __init__(
        self,
        page: int = Query(default=1, ge=1),
        per_page: int = Query(default=20, ge=1, le=100),
    ):
        self.page = page
        self.per_page = per_page
        self.skip = (page - 1) * per_page

@app.get("/articles")
def list_articles(pagination: Pagination = Depends()):
    # Note: Depends() without argument uses the type hint
    return {
        "page": pagination.page,
        "per_page": pagination.per_page,
        "skip": pagination.skip,
    }

# Class dependencies are instantiated per request
# GET /articles?page=3&per_page=10
# -> {"page": 3, "per_page": 10, "skip": 20}

When a class is used as a dependency, FastAPI instantiates it for each request. The __init__ parameters are resolved from the request (query, path, body). Depends() without arguments infers the dependency from the type hint.

Authentication Dependency

from fastapi import FastAPI, Depends, HTTPException, Header

app = FastAPI()

# Dependency that extracts and validates API key
def verify_api_key(
    x_api_key: str = Header(alias="X-API-Key"),
):
    valid_keys = {"key-123", "key-456"}
    if x_api_key not in valid_keys:
        raise HTTPException(status_code=401, detail="Invalid API key")
    return x_api_key

# Dependency that gets the current user from a token
def get_current_user(
    api_key: str = Depends(verify_api_key),
):
    # In a real app, decode the token and fetch the user
    users = {"key-123": {"id": 1, "name": "Alice"}}
    return users.get(api_key, {"id": 0, "name": "Unknown"})

@app.get("/me")
def read_current_user(user: dict = Depends(get_current_user)):
    return user

@app.get("/protected")
def protected_route(api_key: str = Depends(verify_api_key)):
    return {"message": "You have access", "key": api_key}

# Both endpoints require X-API-Key header
# GET /me with header X-API-Key: key-123
# -> {"id": 1, "name": "Alice"}

verify_api_key extracts and validates the API key header. get_current_user depends on verify_api_key (sub-dependency) and uses the validated key to look up the user. Dependencies that raise HTTPException act as guards, blocking access before the handler runs.

Key points

Concepts covered

Depends, Dependency Injection, Reusable Logic, Callable, Inversion of Control