Dependency Injection Basics (Depends)

FastAPI's dependency injection system lets you declare things your endpoint needs (DB sessions, auth users, pagination, clients) as function parameters. FastAPI builds them per-request and caches them within that request.

A First Dependency

from fastapi import FastAPI, Depends, HTTPException

app = FastAPI()

async def pagination(skip: int = 0, limit: int = 20):
    return {"skip": skip, "limit": limit}

@app.get("/items/")
def list_items(page: dict = Depends(pagination)):
    return {"page": page}

Anything you can declare in an endpoint (query params, headers, bodies) can be declared inside a dependency instead. The client still sends ?skip=10&limit=5; the dependency receives them and the endpoint gets the result.

Dependencies That Can Fail: Auth Example

from fastapi import Depends, HTTPException, Header

def verify_token(x_token: str = Header()):
    if x_token != "secret":
        raise HTTPException(status_code=401, detail="Invalid token")
    return x_token

@app.get("/admin/")
def admin_panel(token: str = Depends(verify_token)):
    return {"access": "granted", "token": token}

Raising HTTPException inside a dependency aborts the request before the endpoint runs — dependencies are the natural place for authentication, authorization and rate limiting.

Resources with Cleanup: yield Dependencies

def get_db():
    db = SessionLocal()
    try:
        yield db          # endpoint runs with this value
    finally:
        db.close()        # always runs afterwards (like a context manager)

The code before yield is setup, after is teardown. Perfect for DB sessions, transactions and temp files. (Supported directly in FastAPI; nested yield dependencies work too.)

Dependency Trees and Caching

def get_current_user(token: str = Depends(verify_token), db=Depends(get_db)):
    ...

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

Class Dependencies and Shortcuts

class CommonParams:
    def __init__(self, q: str | None = None, skip: int = 0, limit: int = 20):
        self.q, self.skip, self.limit = q, skip, limit

@app.get("/things/")
def list_things(common: CommonParams = Depends()):
    return {"q": common.q, "skip": common.skip}

Depends() with no argument uses the declared class/callable itself. Classes are great when a dependency needs several related values or configuration.

Quick Questions and Answers

Q: When does a dependency run — and how often?

A: Once per request, before the endpoint, resolved lazily as the dependency tree is built. Results are cached for the duration of that single request only — they are not singletons; use module-level objects (engine, connection pools) for cross-request state.

Q: Can I apply a dependency to a whole router without using its value?

A: Yes: router = APIRouter(dependencies=[Depends(verify_token)]) runs the check on every route but discards the result. Also available per-endpoint: @app.get("/", dependencies=[...]).

Q: Dependencies vs middleware — which one do I use?

A: Middleware runs for every request globally and has no access to route parameters — use it for cross-cutting concerns (CORS, timing, logging). Dependencies are declared per-route (or per-router), can be async, can raise HTTP errors and receive validated parameters — use them for auth, pagination, DB sessions.

Conclusion

Depends is FastAPI's superpower: compose reusable logic (auth, DB, pagination) from small functions, get per-request caching and automatic cleanup with yield, and keep endpoints focused on business logic.