Async Dependencies & Lifespan
Learn how to use async dependencies and the lifespan context manager to manage application startup and shutdown resources.
🎯 What You'll Learn
- •Use the lifespan context manager for app startup and shutdown logic
- •Create async dependencies that provide shared resources
- •Understand resource management with lifespan vs deprecated on_event
- •Build endpoints that consume async dependencies
Async Dependencies & Lifespan
What You'll Learn
In this lesson you will learn how to manage application lifecycle events and create async dependencies in FastAPI. You will understand:
- How the
lifespancontext manager replaces the deprecatedon_eventdecorators - How to initialize and clean up shared resources at startup and shutdown
- How to create async dependency functions
- How to wire everything together with
Depends
Theory
Application Lifecycle
Every web application needs to perform setup when it starts (connect to databases, load configuration, warm up caches) and cleanup when it stops (close connections, flush buffers). FastAPI provides the lifespan context manager for this purpose.
The Old Way: on_event (Deprecated)
Previously, FastAPI used @app.on_event("startup") and @app.on_event("shutdown") decorators:
# DEPRECATED - do not use in new code
@app.on_event("startup")
async def startup():
app.state.db = connect_to_database()
@app.on_event("shutdown")
async def shutdown():
app.state.db.close()
This approach has been deprecated because it separates logically related code (setup and teardown of the same resource) into two different functions.
The New Way: Lifespan Context Manager
The lifespan context manager keeps startup and shutdown logic together:
from contextlib import asynccontextmanager
from fastapi import FastAPI
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup: runs before the app starts accepting requests
app.state.db = {"items": []}
print("Database initialized")
yield
# Shutdown: runs after the app stops accepting requests
app.state.db.clear()
print("Database cleaned up")
app = FastAPI(lifespan=lifespan)
Everything before yield runs at startup. Everything after yield runs at shutdown. The yield itself is where the application runs and handles requests.
Storing State with app.state
FastAPI (via Starlette) provides app.state as a place to store application-wide data:
@asynccontextmanager
async def lifespan(app: FastAPI):
app.state.db = {"users": [], "items": []}
app.state.config = {"debug": True}
yield
Any data attached to app.state is accessible from any request via request.app.state.
Async Dependencies
Dependencies in FastAPI can be async functions. This is useful when the dependency needs to perform async I/O:
from fastapi import Depends, Request
async def get_db(request: Request):
return request.app.state.db
@app.get("/items/")
async def list_items(db: dict = Depends(get_db)):
return {"items": db["items"]}
The dependency function get_db is called for every request. It reads the shared database from app.state and provides it to the endpoint.
Dependencies with Yield (Async)
You can also create async dependencies that perform cleanup after the request:
async def get_db_session(request: Request):
session = request.app.state.db.create_session()
try:
yield session
finally:
await session.close()
This pattern is especially useful for database sessions or other resources that need to be released after each request.
Key Concepts
Lifespan Flow
- Application starts
- Lifespan code before
yieldexecutes (startup) - Application accepts and handles requests
- Application receives shutdown signal
- Lifespan code after
yieldexecutes (shutdown)
Dependency Injection Chain
Dependencies can depend on other dependencies, forming a chain:
async def get_db(request: Request):
return request.app.state.db
async def get_items_service(db: dict = Depends(get_db)):
return db["items"]
@app.get("/items/")
async def list_items(items: list = Depends(get_items_service)):
return {"items": items, "count": len(items)}
Best Practices
- Always use lifespan instead of
on_eventdecorators in new code - Store shared resources in
app.staterather than global variables when possible - Keep lifespan logic minimal -- just initialization and cleanup
- Use async dependencies when accessing async resources; use sync dependencies for simple logic
- Handle errors in lifespan -- wrap startup code in try/except to fail gracefully
- Use yield dependencies for resources that need per-request cleanup (like DB sessions)
Additional Resources
💡 Hint
Use '@asynccontextmanager async def lifespan(app)' to initialize resources at startup. Store them in app.state. Create an async dependency function that reads from app.state and yields the resource.
Ready to Practice?
Now that you understand the theory, let's put it into practice with hands-on coding!
Start Interactive Lesson