Router Lifespan and Event Handlers
Manage startup and shutdown logic for specific groups of routes using the lifespan parameter in APIRouter. This allows you to initialize resources (like database connections or caches) only when the router is active and clean them up when the application stops.
Using Lifespan Context Managers
The preferred way to manage router lifecycle is by defining an asynccontextmanager. This single function handles both startup and shutdown logic.
from contextlib import asynccontextmanager
from fastapi import APIRouter, FastAPI
@asynccontextmanager
async def router_lifespan(app: FastAPI):
# Startup: logic here runs before the app starts receiving requests
print("Router starting up")
yield {"router_active": True}
# Shutdown: logic here runs after the app finishes processing requests
print("Router shutting down")
router = APIRouter(lifespan=router_lifespan)
@router.get("/")
async def read_items(request: Request):
# Access state yielded by the lifespan
return {"status": request.state.router_active}
app = FastAPI()
app.include_router(router)
Key details:
- Automatic Wrapping: If you provide a standard async generator function to
lifespan,APIRouterautomatically wraps it with@asynccontextmanager. - Sync Support: Synchronous generator functions are also supported and wrapped internally using
_wrap_gen_lifespan_context.
Sharing State across Nested Routers
When you include a router with its own lifespan into another router or the main FastAPI app, their states are merged. This allows path operations to access resources defined at any level of the router hierarchy via request.state.
@asynccontextmanager
async def sub_lifespan(app: FastAPI):
yield {"sub_resource": "db_connection"}
@asynccontextmanager
async def parent_lifespan(app: FastAPI):
yield {"parent_resource": "config_data"}
sub_router = APIRouter(lifespan=sub_lifespan)
parent_router = APIRouter(lifespan=parent_lifespan)
# Lifespans are merged here
parent_router.include_router(sub_router, prefix="/sub")
@sub_router.get("/check")
async def check_state(request: Request):
# Accesses both parent and sub-router state
return {
"parent": request.state.parent_resource,
"sub": request.state.sub_resource
}
Using Legacy Event Handlers
While lifespan is recommended, you can still use legacy event handlers for simple startup or shutdown tasks. These are managed internally by the _DefaultLifespan class.
Using the Decorator
The @router.on_event decorator is the traditional way to register handlers, though it is now deprecated.
router = APIRouter()
@router.on_event("startup")
async def startup_event():
print("Router started")
@router.on_event("shutdown")
def shutdown_event():
print("Router stopped")
Using Constructor Parameters
You can also pass lists of callables directly to the APIRouter constructor.
def on_start():
print("Starting...")
router = APIRouter(on_startup=[on_start])
Troubleshooting
State Key Conflicts
When lifespans are merged during include_router, if a parent and a child lifespan both yield a dictionary with the same key, the parent's value takes precedence.
# Parent yields {"mode": "prod"}
# Child yields {"mode": "dev"}
# Resulting request.state.mode will be "prod"
Path Prefix Requirements
When configuring APIRouter, ensure your prefix follows the internal validation rules enforced in fastapi/routing.py:
- It must start with a forward slash
/. - It must NOT end with a forward slash
/.
# Correct
router = APIRouter(prefix="/items")
# Incorrect (will raise AssertionError)
router = APIRouter(prefix="items")
router = APIRouter(prefix="/items/")
Lifespan Execution Order
When using nested lifespans, the startup logic executes from the outermost (app) to the innermost (sub-router). Shutdown logic executes in the reverse order (innermost to outermost).