FastAPI Dependency Injection Basics
Dependency injection is a way to share reusable logic across multiple routes without copying and pasting code. You write the logic once as a function, and FastAPI runs it automatically before your route function executes.
The Problem It Solves
Imagine every route needs to read a database connection and check if the user is logged in:
Without dependency injection:
@app.get("/orders")
def get_orders():
db = get_database() ← repeated
user = check_auth() ← repeated
return db.query_orders()
@app.get("/products")
def get_products():
db = get_database() ← repeated again
user = check_auth() ← repeated again
return db.query_products()
With dependency injection, you write get_database() and check_auth() once and inject them:
@app.get("/orders")
def get_orders(db = Depends(get_database), user = Depends(check_auth)):
return db.query_orders()
@app.get("/products")
def get_products(db = Depends(get_database), user = Depends(check_auth)):
return db.query_products()
How Depends Works
Client sends request to /orders
│
▼
FastAPI sees Depends(get_database)
→ runs get_database()
→ result stored as db
│
FastAPI sees Depends(check_auth)
→ runs check_auth()
→ result stored as user
│
▼
get_orders(db, user) runs with
already-resolved values
Writing Your First Dependency
from fastapi import FastAPI, Depends
app = FastAPI()
def get_query_settings(skip: int = 0, limit: int = 10):
return {"skip": skip, "limit": limit}
@app.get("/users")
def list_users(settings: dict = Depends(get_query_settings)):
return {"skip": settings["skip"], "limit": settings["limit"]}
@app.get("/products")
def list_products(settings: dict = Depends(get_query_settings)):
return {"skip": settings["skip"], "limit": settings["limit"]}
GET /users?skip=20&limit=5
→ get_query_settings(skip=20, limit=5) runs first
→ returns {"skip": 20, "limit": 5}
→ list_users receives it as settings
Both routes share the same pagination logic. Change get_query_settings once and both routes update.
Dependencies Can Have Dependencies
def get_db():
return "database_connection"
def get_current_user(db = Depends(get_db)):
return {"user": "Priya", "db": db}
@app.get("/me")
def my_profile(user = Depends(get_current_user)):
return user
FastAPI resolves them in order: get_db() → returns db get_current_user(db) → returns user my_profile(user) → returns response
Class-Based Dependencies
For dependencies with multiple configurable options, use a class with __call__:
class PaginationParams:
def __init__(self, page: int = 1, per_page: int = 20):
self.page = page
self.per_page = per_page
self.skip = (page - 1) * per_page
@app.get("/articles")
def list_articles(pagination = Depends(PaginationParams)):
return {
"page": pagination.page,
"per_page": pagination.per_page,
"skip": pagination.skip
}
Dependencies Without Return Values
Some dependencies only validate or check something without returning data. Use them as guards:
from fastapi import Header, HTTPException
def verify_api_key(x_api_key: str = Header()):
if x_api_key != "my-secret-key":
raise HTTPException(status_code=403, detail="Invalid API key")
@app.get("/admin", dependencies=[Depends(verify_api_key)])
def admin_panel():
return {"panel": "admin data"}
Here dependencies=[...] on the route decorator runs the check without injecting a value into the function.
Key Points
- Dependency injection with
Depends()runs a function before your route and passes its result in. - Write shared logic once and reuse it across many routes.
- Dependencies can depend on other dependencies — FastAPI resolves the whole chain automatically.
- Use class-based dependencies for configurable, stateful logic.
- Use
dependencies=[Depends(...)]for guard-style checks that do not return values.
