Skip to content

Dependency Injection

In every API you have shared concerns: getting a DB session, reading the current user from a token, or validating common query parameters. In Express you solve this with middleware. In NestJS you use its IoC container with @Injectable() providers. FastAPI uses Depends() — a lightweight, composable DI system declared entirely via type hints.

Depends() vs Express middleware vs NestJS providers

Section titled “Depends() vs Express middleware vs NestJS providers”
TypeScript
// Express middleware (global side-effect style)
function authMiddleware(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.status(401).json({ error: 'Unauthorized' });
req.user = verifyToken(token); // attach to req
next();
}
app.use(authMiddleware);
// NestJS — @Injectable() provider
@Injectable()
export class AuthService {
getCurrentUser(token: string): User { ... }
}
// inject via constructor
constructor(private authService: AuthService) {}
Python
# FastAPI — Depends()
from fastapi import FastAPI, Depends, HTTPException, Header
from typing import Optional
app = FastAPI()
# A dependency is just a callable
def get_current_user(authorization: Optional[str] = Header(None)):
if not authorization or not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Unauthorized")
token = authorization.removeprefix("Bearer ")
return {"user_id": 1, "token": token} # return the value you need
# Inject with Depends() — runs before the handler
@app.get("/profile")
async def get_profile(current_user: dict = Depends(get_current_user)):
return {"user": current_user}

A very common pattern is a shared “pagination” dependency that multiple routes share — the equivalent of a custom Express middleware or a NestJS PaginationDto.

TypeScript
// NestJS — shared DTO
export class PaginationDto {
@IsOptional() @Type(() => Number) @IsInt() @Min(0)
skip?: number = 0;
@IsOptional() @Type(() => Number) @IsInt() @Max(100)
limit?: number = 10;
}
@Get()
findAll(@Query() pagination: PaginationDto) { ... }
Python
# FastAPI — pagination as a Depends
from fastapi import FastAPI, Depends
app = FastAPI()
class PaginationParams:
def __init__(self, skip: int = 0, limit: int = 10):
if skip < 0:
raise HTTPException(status_code=400, detail="skip must be >= 0")
if limit > 100:
raise HTTPException(status_code=400, detail="limit must be <= 100")
self.skip = skip
self.limit = limit
@app.get("/items")
async def list_items(pagination: PaginationParams = Depends()):
return {"skip": pagination.skip, "limit": pagination.limit}
@app.get("/users")
async def list_users(pagination: PaginationParams = Depends()):
return {"skip": pagination.skip, "limit": pagination.limit}

The most important dependency in a real app is the database session. FastAPI’s Depends() handles the lifecycle — open, yield to the handler, close on completion. This mirrors the “unit of work” pattern NestJS handles via @InjectRepository().

TypeScript
// NestJS — TypeORM via constructor injection
@Injectable()
export class ItemsService {
constructor(
@InjectRepository(Item)
private readonly itemRepo: Repository<Item>,
) {}
async findAll() { return this.itemRepo.find(); }
}
Python
# FastAPI — SQLAlchemy session via Depends
from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session
from app.database import SessionLocal
app = FastAPI()
# Generator dependency — yields session, cleans up after
def get_db():
db = SessionLocal()
try:
yield db # ← handler receives this value
finally:
db.close() # ← always runs, even on exception
@app.get("/items/{item_id}")
async def get_item(item_id: int, db: Session = Depends(get_db)):
item = db.get(Item, item_id)
if not item:
raise HTTPException(status_code=404, detail="Item not found")
return item

Dependencies can depend on other dependencies — forming a chain. This is how you layer auth → permissions → resource fetching.

TypeScript
// NestJS guard chain
@UseGuards(AuthGuard, RolesGuard)
@Roles('admin')
@Get(':id')
findOne(@Param('id') id: string) { ... }
Python
# FastAPI — dependencies compose naturally
from fastapi import FastAPI, Depends
app = FastAPI()
def get_current_user(token: str = Depends(get_token)):
return decode_token(token)
def require_admin(user: dict = Depends(get_current_user)):
if user["role"] != "admin":
raise HTTPException(status_code=403, detail="Admin only")
return user
@app.delete("/items/{item_id}")
async def delete_item(item_id: int, _: dict = Depends(require_admin)):
return {"deleted": item_id}

Run this locally — needs FastAPI + a server. Playground is skipped for this lesson because Depends() requires the FastAPI request lifecycle to execute.

What is the FastAPI equivalent of NestJS's @Injectable() service for a shared DB session?
In FastAPI, what does `yield` inside a dependency function enable?
How does FastAPI know a route parameter should be resolved via DI rather than from the URL or request body?