Skip to content
AB

FastAPI Lifespan: Managing Databases, Schedulers, and Shared Clients

Give long-lived resources clear startup and shutdown ownership instead of constructing them inside requests.

Ashish BhakhandPublished

Database pools and HTTP clients are designed for reuse. Constructing one inside every endpoint adds connection overhead and makes cleanup uncertain.

FastAPI's lifespan gives the application one place to own long-lived resources:

Code
@asynccontextmanager
async def lifespan(app: FastAPI):
    pool = await asyncpg.create_pool(settings.database_url)
    provider = httpx.AsyncClient(timeout=15)
    scheduler = create_scheduler(pool)
    scheduler.start()

    app.state.pool = pool
    app.state.provider = provider
    try:
        yield
    finally:
        scheduler.shutdown(wait=False)
        await provider.aclose()
        await pool.close()


app = FastAPI(lifespan=lifespan)

Startup occurs before the application accepts requests. Shutdown closes resources even when the server is stopping because of deployment or interruption.

Fail before serving traffic#

If the database pool cannot initialize or essential configuration is invalid, startup should fail. Returning partial functionality and discovering the problem through user requests creates a harder incident.

Optional integrations are different. An analytics client may stay disabled when no token is configured, provided the behavior is explicit.

Reuse, but do not leak request state#

Connection pools, SDK clients, and immutable configuration can live for the process lifetime. User identity, correlation IDs, and request-specific buffers cannot. Store request state on the request or in carefully managed context variables.

Consider deployment topology#

Every worker process runs its own lifespan. Starting APScheduler inside the web application therefore starts one scheduler per worker or replica. That is safe only when jobs are idempotent or protected by distributed ownership. For larger deployments, move scheduled work to a dedicated process or managed scheduler.

Keep tests in control#

Unit tests should not need production startup. Construct a minimal test app with overridden dependencies, or supply a test lifespan with disposable resources. Integration tests can exercise the real lifecycle separately.

Lifespan is resource ownership made visible: creation before use, one reuse policy, and guaranteed cleanup. That clarity matters more than saving a few lines in each endpoint.

← Back to all writing