Hello! Welcome to the final lesson of our foundational module on FastAPI.
In our last session, we saw how FastAPI leverages Python type hints and Pydantic to perform automatic validation and documentation for every incoming request. This is a powerful feature that handles logic on a per-request basis.
Today, we'll shift our focus from the lifecycle of a single request to the lifecycle of the entire application. We'll learn how to execute code once when the application starts up and once when it shuts down.
Learning Outcome: Implement application startup and shutdown logic using lifespan events.
This is a crucial concept for any production-ready application, as it's the standard way to manage shared, long-lived resources like database connections, caches, or machine learning models.
For you, coming from Django, this is FastAPI's integrated and async-native answer to tasks you might handle in settings.py, an app's ready() method, or through separate deployment scripts.
What are Lifespan Events?
Every web application has a life of its own. It's born when you run the server, it lives while it serves requests, and it dies when you stop the server. Lifespan events allow you to hook into the moments of birth and death.
- Startup: Code that runs once before the application starts receiving any requests.
- Shutdown: Code that runs once after the application has finished handling all its requests and is about to stop.
The following article provides a fantastic analogy that distinguishes the application's lifespan from the lifecycle of a single request.
What Actually Is ASGI? A Journey from Confusion to Clarity
This article, 'What Actually Is ASGI?', helps clarify the concept of the lifespan scope. Focus on the restaurant analogy it presents.
Read the short section titled 'Analogy time' under the 'Lifespan: The Orthogonal Dimension' heading. This analogy perfectly frames the purpose of lifespan events.
As the article explains, you don't want to perform expensive setup (like turning on the ovens) for every customer. You do it once when the restaurant opens. Common use cases for lifespan events include:
- Initializing a database connection pool.
- Loading a large machine learning model into memory.
- Setting up a connection to a caching service like Redis.
- Warming up a cache with initial data.
The Modern Approach: lifespan and asynccontextmanager
FastAPI provides a clean, modern way to manage lifespan events using Python's built-in asynccontextmanager. You define a single asynchronous function that handles both startup and shutdown, then pass it to the FastAPI application instance using the lifespan parameter.
The structure is simple and elegant:
- Decorate an
asyncfunction with@asynccontextmanagerfrom thecontextlibmodule. - Write your startup logic before a
yieldstatement. - Write your shutdown logic after the
yieldstatement.
FastAPI takes care of running the code before the yield during startup and the code after the yield during shutdown.

Let's see this in action. The official documentation provides a clear, practical example.
The FastAPI documentation explains the recommended lifespan parameter and demonstrates its use with an asynccontextmanager. This is the primary pattern you should use.
Read the sections 'Lifespan' and 'Async Context Manager'. Pay close attention to the code example. Notice how yield separates the startup logic from the shutdown logic within a single function.
Let's Write Some Code
Let's modify our main.py from the previous lesson to include a simple lifespan event. We'll simulate loading a large configuration file or model into memory on startup and logging a message on shutdown.
# main.py
from contextlib import asynccontextmanager
from fastapi import FastAPI
import time
# A dictionary to store items that are "loaded" during startup
# In a real app, this could be a database connection pool, a ML model, etc.
app_state = {}
@asynccontextmanager
async def lifespan(app: FastAPI):
# Code to run on startup
print("Startup: Simulating loading a large model...")
# Simulate a slow startup task, like loading a model from disk
time.sleep(2)
app_state["model"] = "MyAwesomeMLModel_v1"
print("Startup: Model loaded successfully!")
yield
# Code to run on shutdown
print("Shutdown: Cleaning up resources...")
app_state.clear()
print("Shutdown: Resources cleaned up.")
app = FastAPI(lifespan=lifespan)
@app.get("/")
async def root():
return {"message": f"Hello! The loaded model is: {app_state.get('model', 'Not loaded')}"}
Now, run your application:uvicorn main:app --reload
You will immediately see the startup messages in your terminal and notice a 2-second delay before Uvicorn reports that the application startup is complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
Startup: Simulating loading a large model...
Startup: Model loaded successfully!
INFO: Application startup complete.
If you access http://127.0.0.1:8000, you'll see the message with the model name.
Now, stop the server by pressing CTRL+C in your terminal. You will see the shutdown messages appear before the process exits.
...
INFO: Shutting down
Shutdown: Cleaning up resources...
Shutdown: Resources cleaned up.
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
The following video provides an excellent summary of this modern approach and explains the underlying ASGI protocol, which is great for your deeper architectural understanding.
What you need to know about Startup and Shutdown Events in FastAPI and ASGI Applications
The mCoding channel gives a concise yet detailed explanation of lifespan events, covering both the modern implementation and the underlying ASGI principles.
Watch from the beginning to 3:44. This segment covers: The modern asynccontextmanager approach. Common use cases for startup and shutdown events. A look at the ASGI lifespan protocol that powers this feature.
Sharing State: A Cleaner Alternative to Globals
In our example, we used a global app_state dictionary. While this works, it can become messy. The lifespan protocol offers a cleaner way to create state during startup and make it accessible to your requests. The lifespan function can yield a dictionary of objects. FastAPI then makes this state available on the request.state object.
What is the Lifespan State on FastAPI?
The FastAPI Expert channel demonstrates how to yield state from the lifespan function and access it in your routes via request.state.
Watch from 4:52 to 9:22. This segment shows how to refactor the logic to use a lifespan context manager that yields a client object, and then how to access that client from request.state inside a path operation.
This pattern is highly recommended as it avoids polluting the global namespace and makes dependencies more explicit. We will explore this more when we discuss dependency injection in a later module.
The Deprecated Way: on_event
Before the lifespan parameter was introduced, FastAPI used decorators: @app.on_event("startup") and @app.on_event("shutdown").
# The old, deprecated way
# from fastapi import FastAPI
# app = FastAPI()
# @app.on_event("startup")
# async def startup_event():
# print("Application has started")
# @app.on_event("shutdown")
# def shutdown_event():
# print("Application is shutting down")
While you may see this in older codebases, the official documentation now deprecates it in favor of the lifespan context manager. The primary reason is that lifespan keeps connected logic (like creating and closing a connection pool) within a single, self-contained function, which is cleaner and less error-prone than managing it across two separate functions that might rely on global variables.
Conclusion
You've now learned how to manage the lifecycle of your entire FastAPI application. This is the last of the core foundational concepts before we start building out more complex API functionality.
Key Takeaways:
- Lifespan Events run code once at application startup and once at shutdown.
- They are ideal for managing shared resources like database connection pools, ML models, and caches.
- The modern and recommended approach is to use an
asynccontextmanagerpassed to thelifespanparameter of theFastAPIinstance. - In a lifespan function, code before
yieldruns on startup, and code afteryieldruns on shutdown. - The older
@app.on_event()decorators are deprecated and should be avoided in new projects.
With this, we conclude our first module! You've gone from zero to running a basic FastAPI app, understanding its async nature, using type hints for validation, and now managing its lifecycle.
In our next lesson, we will begin Module 2: Routing and Request Handling. We'll dive into how to define different API endpoints, extract parameters from the URL, and organize your routes effectively, drawing clear comparisons to Django's URL patterns and DRF's ViewSets.