Lesson 7 · FastAPI deep dive
FastAPI Tutorial #7: Dependency Injection with Depends()
Video seven of the eighteen-part series: sharing logic across endpoints the FastAPI way. Depends, dependencies with parameters, yield-based setup and…
- CourseFastAPI deep dive
- Lesson7 of 12
- Video17 min
- FormatJupyter notebook · 10 code cells
What you'll learn
- The Problem - Repeated Logic Across Endpoints
- Depends() - a Simple Function Dependency
- Dependencies Can Use Query, Path, and Header Just Like Handlers
- Multiple Dependencies on One Endpoint
- Depends() with yield - Setup and Teardown
- Router-Wide and App-Wide Dependencies
- Dependencies Are Cached Per Request
- Overriding Dependencies for Testing
Data
No separate download needed — the notebook creates or downloads everything it uses.
📓 Full notebook
Download .ipynbFastAPI Deep-Dive, Video 7: Dependency Injection Basics#
- Video seven of the eighteen-part series: sharing logic across endpoints the FastAPI way.
- Depends, dependencies with parameters, yield-based setup and teardown, and overriding for tests.
- Let's get into it.
Part 1: The Problem - Repeated Logic Across Endpoints#
from fastapi import FastAPI
from fastapi.testclient import TestClient
app = FastAPI()
@app.get('/items')
def list_items(skip: int = 0, limit: int = 10):
return {'skip': skip, 'limit': limit, 'source': 'items'}
@app.get('/orders')
def list_orders(skip: int = 0, limit: int = 10):
return {'skip': skip, 'limit': limit, 'source': 'orders'}
client = TestClient(app)
print(client.get('/items?skip=5&limit=20').json())
Part 2: Depends() - a Simple Function Dependency#
from fastapi import Depends
def pagination(skip: int = 0, limit: int = 10):
return {'skip': skip, 'limit': limit}
@app.get('/items-v2')
def list_items_v2(page: dict = Depends(pagination)):
return {**page, 'source': 'items'}
@app.get('/orders-v2')
def list_orders_v2(page: dict = Depends(pagination)):
return {**page, 'source': 'orders'}
print(client.get('/items-v2?skip=5&limit=20').json())
print(client.get('/orders-v2').json())
Part 3: Dependencies Can Use Query, Path, and Header Just Like Handlers#
from fastapi import Query
def search_params(q: str = Query(default='', min_length=0, max_length=50), active_only: bool = True):
return {'q': q, 'active_only': active_only}
@app.get('/search-items')
def search_items(params: dict = Depends(search_params)):
return params
print(client.get('/search-items?q=mug&active_only=false').json())
print(client.get('/search-items').json())
Part 4: Multiple Dependencies on One Endpoint#
def get_token(x_token: str = 'anonymous'):
return x_token
@app.get('/dashboard')
def dashboard(page: dict = Depends(pagination), token: str = Depends(get_token)):
return {'page': page, 'token': token}
print(client.get('/dashboard?skip=2&limit=5').json())
Part 5: Depends() with yield - Setup and Teardown#
events = []
def fake_db_session():
events.append('open')
try:
yield {'connected': True}
finally:
events.append('close')
@app.get('/records')
def list_records(db: dict = Depends(fake_db_session)):
events.append('use')
return db
print(client.get('/records').json())
print(events)
Part 6: Router-Wide and App-Wide Dependencies#
from fastapi import HTTPException, Header
checked = []
def verify_access(x_token: str = Header(default='anonymous')):
checked.append(x_token)
if x_token != 'secret':
raise HTTPException(status_code=403, detail='forbidden')
@app.get('/admin/stats', dependencies=[Depends(verify_access)])
def admin_stats():
return {'users': 42}
print(client.get('/admin/stats', headers={'x-token': 'secret'}).json())
print(client.get('/admin/stats').status_code)
print(checked)
Part 7: Dependencies Are Cached Per Request#
call_count = {'n': 0}
def counted_dependency():
call_count['n'] += 1
return call_count['n']
@app.get('/twice')
def uses_twice(a: int = Depends(counted_dependency), b: int = Depends(counted_dependency)):
return {'a': a, 'b': b, 'total_calls': call_count['n']}
print(client.get('/twice').json())
Part 8: Overriding Dependencies for Testing#
def real_greeting():
return 'hello from the real dependency'
@app.get('/greet')
def greet(message: str = Depends(real_greeting)):
return {'message': message}
print(client.get('/greet').json())
def fake_greeting():
return 'hello from the fake test dependency'
app.dependency_overrides[real_greeting] = fake_greeting
print(client.get('/greet').json())
app.dependency_overrides = {}
Part 9: Sub-Dependencies - a Dependency Depending on Another#
def get_raw_token(x_token: str = Header(default='anonymous')):
return x_token
def get_current_user(token: str = Depends(get_raw_token)):
if token == 'secret':
return {'username': 'admin'}
return {'username': 'guest'}
@app.get('/profile')
def profile(user: dict = Depends(get_current_user)):
return user
print(client.get('/profile', headers={'x-token': 'secret'}).json())
print(client.get('/profile').json())
Part 10: A Real Pattern - Pagination Plus Resource Lookup#
catalog_app = FastAPI()
PRODUCTS = [{'id': 1, 'name': 'Mug'}, {'id': 2, 'name': 'Plate'}, {'id': 3, 'name': 'Cup'}]
def pagination_params(skip: int = 0, limit: int = 10):
return {'skip': skip, 'limit': limit}
def get_product_or_404(product_id: int):
for product in PRODUCTS:
if product['id'] == product_id:
return product
raise HTTPException(status_code=404, detail='product not found')
@catalog_app.get('/catalog')
def browse_catalog(page: dict = Depends(pagination_params)):
items = PRODUCTS[page['skip']: page['skip'] + page['limit']]
return {'items': items, 'page': page}
@catalog_app.get('/catalog/{product_id}')
def get_catalog_item(product: dict = Depends(get_product_or_404)):
return product
catalog_client = TestClient(catalog_app)
print(catalog_client.get('/catalog?limit=2').json())
print(catalog_client.get('/catalog/2').json())
print(catalog_client.get('/catalog/999').status_code)
Wrap-Up: What You Learned#
- Depends lets a plain function be declared once and reused across as many endpoints as needed.
- Dependencies can use Query, Path, and Header helpers, keeping constraints in one shared place.
- A single endpoint can declare multiple independent Depends parameters at once.
- A yield-based dependency runs setup before the yield and teardown after, once per request.
- dependencies=[Depends(...)] runs a check for its side effects without injecting a value.
- The same dependency used more than once in a request is only called once, and cached.
- app.dependency_overrides swaps a dependency for tests without touching endpoint code.
- Dependencies can depend on other dependencies, forming a resolved chain automatically.
- Pagination and resource-lookup dependencies are a common, reusable production pattern.
- That wraps up dependency injection basics. Next up: Class-based and Sub-Dependencies.
Found this useful?
All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.



