How FastAPI Really Works
As developers, we sometimes consider tools like FastAPI as just a means to an end: we want to use it for certain tasks but don’t really care how it works. Just get the job done, right?
Get Shane Zhang’s stories in your inbox
Join Medium for free to get updates from this writer.
But sometimes we start to wonder: How does the magic happen? This post is about that.
A Simplest Possible Example
First, let’s use a simple example of a FastAPI service:
from fastapi import FastAPI
from contextlib import asynccontextmanager
import uvicorn
@asynccontextmanager
async def lifespan(app: FastAPI):
print("Setting up resources...")
resource = {"db": "connected"} # Mock resource setup
app.state.resource = resource
yield # FastAPI app is running here
print("Tearing down resources...")
resource["db"] = "disconnected"
app = FastAPI(lifespan=lifespan)
@app.get("/")
async def read_root():
return {"message": "Hello, World!"}
@app.get("/resource")
async def get_resource():
return {"db_status": app.state.resource.get("db")}
if __name__ == "__main__":
# Run with Uvicorn
uvicorn.run(app, host="127.0.0.1", port=8000)What Happens Step by Step
Let’s break down what happens when we run this FastAPI application.
1. uvicorn.run(app) is Called
- Uvicorn Prepares to Run the FastAPI App:
- Sets up internal resources.
- Creates the event loop.
- Sets up sockets for listening to HTTP requests.
- Configures signal handlers (for shutdown, for example).
- FastAPI Initialization:
- Uvicorn prepares to call the
lifespanfunction defined in the FastAPI app.
2. lifespan() Function is Called
- Before
yield: - Custom code can be executed before
yield. For example, initializing resources or starting cron jobs. - In our code, we print “Setting up resources…” and set up a mock resource.
yieldControl:- The
yieldsignals FastAPI to start serving routes. - Note that FastAPI is just an app; the actual traffic control is handled by Uvicorn.
3. Uvicorn Starts Serving Requests
- Event Loop Management:
- Uvicorn receives HTTP requests and dispatches them to the FastAPI app.
- FastAPI Processes the Request:
- For example, the
@app.get("/")decorator means that if Uvicorn receives a GET request to'/'(the root path), it will run theread_root()function. - The function returns a JSON object (here, a message “Hello, World!”) back to the requester.
4. Shutdown Sequence
- A Shutdown Process is Triggered:
- Pressing
Ctrl+Cor callinguvicorn.Server.handle_exit()will initiate the shutdown process. - Uvicorn Begins the Shutdown Process:
- Uvicorn stops accepting new connections and waits for existing ones to finish.
- Control Returns to the
lifespan()Function: - Remember the
yieldin thelifespancontext manager? Uvicorn resumes execution after theyield. - After
yield: - The code after
yieldruns, typically for cleanup tasks like stopping cron jobs or releasing allocated resources. - In our code, we print “Tearing down resources…” and update the mock resource status.
- Uvicorn Releases Resources:
- Releases sockets and other internal resources.
- This happens after the
lifespan()teardown to ensure resources are still available during cleanup.
Conclusion
Understanding how FastAPI and Uvicorn work under the hood can demystify the “magic” and help you write better applications. The lifespan function allows you to manage resources during the startup and shutdown phases, giving you control over your app's lifecycle.







