Dependency Injection
Understand the power of dependency injection in FastAPI for managing dependencies in your applications.
Content
Creating Dependencies
Versions:
Watch & Learn
AI-discovered learning video
Sign in to watch the learning video for this topic.
Creating Dependencies in FastAPI — The Supporting Cast Your Routes Actually Need
You already know how FastAPI validates requests and serializes responses, and you've peeked at cookies, sessions, and streaming responses. Now let's talk about how to stop stuffing logic into your path functions like it's a suitcase for a week-long trip. Enter: Dependencies — the modular, testable, and frankly classy way to give your endpoints what they need.
"A dependency in FastAPI is just a callable that returns something your path operation needs." — Also, it saves your future self from crying.
Why make dependencies? (Short version)
- Reusability: Share DB sessions, auth checks, settings, or clients across routes.
- Separation of concerns: Business logic stays in functions/classes, handlers orchestrate flow.
- Testability: Swap in test doubles with
dependency_overrides.
(We covered how FastAPI handles request/response validation before — think of dependencies as the neat little validators and service-injectors that run before your response is crafted.)
1) Simple function dependencies — the starter pack
A dependency is any callable. The most basic form is a function that returns a value. Use Depends inside a path operation signature to inject it.
from fastapi import FastAPI, Depends
app = FastAPI()
def common_query(q: str | None = None):
return q
@app.get("/items")
def read_items(q: str | None = Depends(common_query)):
return {"q": q}
Key ideas:
- The dependency can have parameters of its own (FastAPI handles injecting query params, headers, etc.)
- Dependencies run before the path function is called and their return values are passed in
2) Class-based dependencies — stateful or DI-friendly objects
When you want an object (e.g., a service) instead of a raw value, classes are great.
class AuthService:
def __init__(self, secret_key: str):
self.secret_key = secret_key
def verify(self, token: str) -> bool:
# imagine heavy crypto here
return token == self.secret_key
def get_auth_service() -> AuthService:
return AuthService(secret_key="oh-so-secret")
@app.get("/secret")
def secret_endpoint(auth: AuthService = Depends(get_auth_service)):
return {"ok": auth.verify("oh-so-secret")}
Tip: For singletons (settings, clients), combine with functools.lru_cache so the service is created once per process.
3) Yield dependencies — cleanup, context managers, DB sessions
Need to open and close resources? Use yield in a dependency. FastAPI executes the code after yield once the request is done.
from typing import Generator
from fastapi import Depends
def get_db() -> Generator:
db = create_db_session()
try:
yield db
db.commit()
except Exception:
db.rollback()
raise
finally:
db.close()
@app.post("/items")
def create_item(item: Item, db = Depends(get_db)):
db.add(item)
return item
This is how you handle transactions, close connections, and avoid leaks.
4) Async vs sync dependencies
- You can define dependencies as
deforasync def. - FastAPI handles both correctly.
- Use
async defwhen the dependency performs I/O (e.g., async DB client); otherwisedefis fine.
5) Sub-dependencies and composition — build Lego blocks
Dependencies can depend on other dependencies. This is how to compose logic cleanly.
def get_token_header(x_token: str | None = Header(None)):
if x_token != "expected":
raise HTTPException(status_code=400, detail="Invalid X-Token")
return x_token
def get_current_user(token: str = Depends(get_token_header)):
# Look up user from token
return {"username": "alice"}
@app.get("/users/me")
def read_users_me(user = Depends(get_current_user)):
return user
This avoids duplication: get_token_header can be reused by multiple auth flows.
6) Dependency injection for testing — override like a boss
FastAPI lets you swap dependencies at test time with app.dependency_overrides. This is huge for unit testing.
def fake_get_db():
yield FakeDB()
app.dependency_overrides[get_db] = fake_get_db
# Run tests with test client; endpoints use fake DB now
You can even override nested dependencies — simple, surgical, powerful.
7) Scope & caching behaviors
- Dependencies executed per-request by default.
- For expensive one-time values (settings, third-party clients), use
lru_cacheon the dependency factory to reuse across requests.
from functools import lru_cache
@lru_cache()
def get_settings():
return Settings()
A note: lru_cache works for sync functions. For async singletons, create them on startup and store in app state.
8) Common real-world dependency patterns
- DB session: yield dependency that commits/rolls back and closes
- Auth: token parsing -> validate -> return user object (often split into sub-deps)
- Settings/config: cached dependency returning Pydantic settings
- Clients (HTTP, Redis): created once on startup or cached
Table: quick comparison
| Use case | Dependency type | Pattern |
|---|---|---|
| Config | sync fn + lru_cache | Singleton settings |
| DB Session | yield fn | open -> yield -> cleanup |
| Auth | fn + sub-deps | token header -> user |
| External client | startup/init or cached | share connection |
Gotchas & Best Practices
- Prefer small, single-responsibility dependencies.
- Use dependency injection to keep path functions thin (they should orchestrate, not babysit services).
- Avoid heavy synchronous work in async routes — use threadpools or async clients.
- Remember to test by overriding dependencies, not by patching internals.
Expert take: "If your path function is longer than a tweet, extract dependencies." It’s comedic but often true.
Closing — TL;DR + Next steps
- Dependencies are callables that FastAPI runs to supply values to path functions. They can be functions, classes, or generators with cleanup.
- Use them for DB sessions, auth, settings, and anything you’d rather not repeat across endpoints.
- Compose dependencies (sub-deps) to create crisp, testable logic. Swap them easily in tests with
dependency_overrides.
Next logical steps (build on what you already learned about request/response handling):
- Implement a production-ready DB session dependency (async or sync depending on your ORM).
- Make an authentication pipeline with sub-dependencies (token parsing -> user lookup -> permission checks).
- Practice writing tests that override real dependencies with fakes.
Go forth and modularize. Your future debugging-self will buy you coffee.
Version note: This builds on the earlier "Introduction to Dependencies" and the request/response topics (validation, serialization, cookies, sessions, streaming), so I'm skipping the very first principles and diving into the part where you actually create and compose dependencies.
Comments (0)
Please sign in to leave a comment.
No comments yet. Be the first to comment!