π± WSGI vs ASGI, Uvicorn & Middleware: Let's Learn from ZERO!
If you've built (or are building) a React + FastAPI project, you've probably run a command like this without thinking much about it:
python -m uvicorn app.main:app --reload --port 8000
But what actually happens when a request travels from your React frontend to your Python backend? π€
In this post, we'll learn step by step, with no prior knowledge required:
- β WSGI vs ASGI
- β Synchronous vs Asynchronous web servers
- β
The ASGI spec's minimal 3-callable interface (
scope,receive,send) - β Uvicorn as an ASGI server
- β ASGI middleware
Let's go! π
1. πΌοΈ The Big Picture
Imagine you open Instagram and click on a post. Behind the scenes:
- You click the post on the frontend (React) π±οΈ
- React sends an HTTP request to the backend
- The backend receives the request
- The backend processes it
- The backend sends a response
- React displays the post
But here's the important question:
β Who actually receives the request and passes it to your backend application?
That's where a web server comes in.
π½οΈ Web server = the waiter in a restaurant
| Restaurant | Web World |
|---|---|
| π§ Customer | Frontend (React) |
| π§βπ³ Waiter | Web server (Uvicorn) |
| π¨βπ³ Kitchen | Backend application (FastAPI) |
| π Food | Response |
The customer gives an order to the waiter, the waiter takes it to the kitchen, the kitchen prepares the food, and the waiter delivers it back.
π Important: FastAPI is your web application framework. Uvicorn is the web server that runs your FastAPI application.
2. π°οΈ What is WSGI?
WSGI = Web Server Gateway Interface.
Don't get scared by the name! π WSGI is simply a set of rules that answers one question:
How should a Python web server talk to a Python web application?
Think of two people, one speaking Telugu and the other Hindi. They need a common language to communicate. WSGI is that common language between a server and a Python app.
π How WSGI works
Client ββHTTP requestβββΆ WSGI Server ββWSGI rulesβββΆ Python App
Client βββHTTP responseββ WSGI Server βββββββββββββββ Python App
β³ Is WSGI synchronous?
Yes, WSGI is fundamentally synchronous. Within a worker, the application handles one request at a time.
Imagine 3 users, and each request takes 5 seconds:
| User | Timeline | What happens |
|---|---|---|
| π© User A (profile) | 0β5 s | Processing A |
| π¨ User B (feed) | 5β10 s | Waiting for A, then processing B |
| π§ User C (notifications) | 10β15 s | Waiting for B, then processing C |
(Simplified: one worker, no parallel processing.)
Examples of WSGI frameworks: Flask and traditional Django.
3. β‘ What is ASGI?
ASGI = Asynchronous Server Gateway Interface.
Like WSGI, ASGI defines rules for server β application communication, but it supports something extra: asynchronous communication!
It supports:
- π HTTP requests and responses
- π WebSockets
- β±οΈ Long-lived connections
- π Asynchronous operations
π Same 3 users, now with async
Each request waits ~5 seconds on a database:
- π© User A starts, then waits for the DB
- π¨ User B starts while A is waiting
- π§ User C starts while A and B are waiting
The server keeps working on other requests while one is waiting. β¨
β οΈ Important: ASGI doesn't automatically make everything async. Your application code and the libraries you use must support async behavior.
4. π Synchronous vs Asynchronous
Remember these two lines:
- Synchronous = Wait until the current operation finishes before moving on.
- Asynchronous = Start an operation, and while it's waiting, work on something else.
π Maggi analogy
- Sync: You put water to boil and stand there doing nothing until it boils. Then you add noodles.
- Async: You put water to boil, and while it boils, you chop vegetables. Waiting time = productive time! π₯
π Technical difference
| Synchronous | Asynchronous |
|---|---|
| Blocking execution | Can be non-blocking |
| Waits for an operation | Can do other work while waiting |
| Usually normal functions | Often uses async and await
|
| Less efficient for many I/O waits | Handles many I/O waits efficiently |
π₯π€ What is I/O?
I/O = Input/Output. Examples:
- Reading from a database ποΈ
- Calling an external API π
- Reading a file π
- Waiting for a network response π‘
π¨ One important distinction
Async does NOT mean everything happens simultaneously. It means your program can switch to another task while one is waiting. Also:
- β Async β multithreading
- β Async β multiprocessing
- β Async does not make CPU-heavy calculations faster
5. βοΈ WSGI vs ASGI: Complete Comparison
| Feature | WSGI | ASGI |
|---|---|---|
| Full form | Web Server Gateway Interface | Asynchronous Server Gateway Interface |
| Execution | Synchronous interface | Asynchronous interface |
| Communication | Request and response | Event-based communication |
| HTTP | β Yes | β Yes |
| WebSockets | β Not natively supported | β Supported |
| Long-lived connections | β Not designed for them | β Supported |
| Python callable | Normal callable | Async callable |
| Example frameworks | Flask (traditional) | FastAPI, Starlette |
π‘ Clarification: WSGI does not mean a server can only process one user at a time. WSGI servers can use multiple workers, threads, or other concurrency techniques. Its limitation is the synchronous application interface, not an absolute inability to handle multiple requests. Similarly, ASGI doesn't mean every request runs simultaneously. It provides an asynchronous way to manage concurrent work.
π¬ Why was ASGI introduced?
Imagine building a chat app:
- User A: "Hi!"
- User B: "Hello!"
- User A: "How are you?"
Messages can arrive at any time. A traditional request-response interface isn't naturally designed to keep a connection open and exchange many messages over time. ASGI supports WebSockets, which allow continuous two-way communication, and that's one of the biggest reasons it exists. π―
6. π The ASGI Spec's 3-Callable Interface
You may have seen this code:
async def application(scope, receive, send):
pass
This is the basic structure of an ASGI application. The spec says an app must be an async callable that accepts exactly three arguments:
scopereceivesend
Let's understand each one. π
6.1 π What is scope?
Think of scope as an information sheet about the incoming connection.
If React sends GET http://localhost:8000/feed, the server hands your app something like this:
scope = {
"type": "http",
"method": "GET",
"path": "/feed",
"scheme": "http",
"http_version": "1.1",
}
| Key | Meaning |
|---|---|
type |
What kind of connection? HTTP or WebSocket? |
method |
GET, POST, PUT, DELETE, etc. |
path |
Which URL path was requested? |
scheme |
HTTP or HTTPS? |
http_version |
Which HTTP version is used? |
π Detail: For HTTP, the scope generally lasts for one request. For WebSockets, it lasts for the lifetime of the connection.
6.2 π₯ What is receive?
When a user logs in, React might send:
{
"username": "deepika",
"password": "mypassword"
}
Your app needs a way to receive incoming messages from the server. That's receive:
message = await receive()
-
receive()asks for the next incoming event -
awaitmeans wait until that event is available - The event is stored in
message
An example message:
{
"type": "http.request",
"body": b'{"username": "deepika"}',
"more_body": False,
}
(The b prefix means the body is bytes.)
π
receivedoes not read directly from the internet. Uvicorn receives the network data and exposes it to your app through the ASGI receive channel.
6.3 π€ What is send?
Once your app has processed the request, it uses send to push the response out to the server:
await send({
"type": "http.response.start",
"status": 200,
"headers": [
(b"content-type", b"text/plain"),
],
})
This says: start an HTTP response, status 200, plain text. Then the body:
await send({
"type": "http.response.body",
"body": b"Login successful!",
})
The server takes these events and builds the actual HTTP response for React. π
π§© Quick comparison
| Argument | Question it answers | Role |
|---|---|---|
scope |
What is this connection about? | Method, URL, protocol, headers, etc. |
receive |
What is coming in? | Gets incoming events from the server |
send |
What should go out? | Sends outgoing events to the server |
π§
scope,receive, andsendare not three separate servers. They are the three arguments the server gives to your ASGI app.
7. π οΈ Let's Write Our Own Basic ASGI App!
Create a file called main.py:
async def app(scope, receive, send):
# Check the request type
if scope["type"] == "http":
# Send response status and headers
await send({
"type": "http.response.start",
"status": 200,
"headers": [
(b"content-type", b"text/plain"),
],
})
# Send response body
await send({
"type": "http.response.body",
"body": b"Hello Deepika!",
})
Run it with:
uvicorn main:app --reload
What's happening?
-
async def app(...)creates an async ASGI application -
scopeholds request/connection info - We check that the connection is HTTP
- We send the response status + headers
- We send the response body
π€― Notice that we haven't used FastAPI anywhere! That's because ASGI is a specification, not a framework. FastAPI is simply a framework that follows this specification.
8. π¦ What is Uvicorn?
Let's connect everything:
- π ASGI = the rules/specification
- π§± ASGI application = an app following those rules
- πͺ Uvicorn = the server that runs the app
Uvicorn is an ASGI web server. It receives HTTP requests from clients, talks to your ASGI app, and sends responses back. Think of it as the bridge between the outside world and your FastAPI app. π
π Breaking down the command
python -m uvicorn app.main:app --reload --port 8000
| Part | Meaning |
|---|---|
python -m |
Run a Python module |
uvicorn |
Start the Uvicorn server |
app.main |
Find main.py inside the app package |
:app |
Find the object named app inside that file |
--reload |
Auto-restart when code changes π |
--port 8000 |
Listen on port 8000 |
βοΈ What happens when you run it?
- Uvicorn starts
- It imports your FastAPI application
- It starts listening on port 8000
- React sends an HTTP request
- Uvicorn receives it
- Uvicorn converts the network request into ASGI information and events
- It calls your app with
scope,receive, andsend - FastAPI processes the request
- FastAPI sends response events back
- Uvicorn converts those events into an HTTP response and sends it to React
π Uvicorn doesn't understand your business logic. It doesn't know how to log in a user, fetch a post, or check a password. That's your application's job.
π€ Are Uvicorn and FastAPI the same?
No!
| Uvicorn π¦ | FastAPI β‘ |
|---|---|
| Web server | Web framework |
| Receives network requests | Defines API endpoints |
| Implements ASGI server functionality | Implements application logic |
| Sends responses to clients | Processes requests and creates responses |
from fastapi import FastAPI
app = FastAPI()
@app.get("/hello")
async def hello():
return {"message": "Hello!"}
-
FastAPI()creates your application -
appis your ASGI-compatible application object -
@app.get("/hello")defines an API endpoint -
uvicornruns it
β¨ You don't have to write scope, receive, and send manually. FastAPI and Starlette handle the ASGI communication internally.
9. π‘οΈ What is ASGI Middleware?
Middleware is code that runs between the web server and your main application. It can inspect, modify, or process incoming requests and outgoing responses.
βοΈ Airport security analogy
Before entering the airport: your identity is checked πͺͺ β your luggage is scanned π§³ β you pass security β β you enter.
Similarly, middleware performs checks before a request reaches your app.
π€· Why do we need middleware?
Instead of repeating the same logic in every endpoint, middleware can handle:
- π Authentication checks
- π CORS (allow/reject requests from particular origins)
- π Logging incoming requests
- β±οΈ Measuring request time
- π¨ Handling certain errors
- π Adding security headers
π How middleware works
Client β Middleware β Application
Client β Middleware β Application
- Client sends a request
- Middleware receives it first
- Middleware performs its checks
- Middleware forwards the request to the actual app
- App processes the request
- Response comes back through middleware
- Middleware can inspect or modify the response
- Response reaches the client
π― Middleware works with both incoming requests and outgoing responses.
9.1 π Raw ASGI middleware in Python
class MyMiddleware:
def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
print("Request received!")
await self.app(scope, receive, send)
print("Response completed!")
Line by line:
Step 1: wrap the real app
def __init__(self, app):
self.app = app
app is the actual application we want to wrap (e.g. your FastAPI app). We store it in self.app.
Step 2: make it a valid ASGI callable
async def __call__(self, scope, receive, send):
Our middleware accepts the same three arguments as a normal ASGI app.
Step 3: runs before the main app
print("Request received!")
Step 4: the most important line! π
await self.app(scope, receive, send)
This forwards the request to the actual application, as if saying: "Middleware is done. Now let FastAPI handle it."
Step 5: runs after the inner app finishes
print("Response completed!")
π This is a simplified ASGI middleware. Real middleware can also wrap
receiveandsendto inspect or modify the messages themselves.
9.2 β‘ Middleware in FastAPI (the easy way)
You don't need to write low-level ASGI middleware every time:
import time
from fastapi import FastAPI, Request
app = FastAPI()
@app.middleware("http")
async def my_middleware(request: Request, call_next):
start = time.time()
response = await call_next(request)
end = time.time()
print("Time taken:", end - start)
return response
-
@app.middleware("http")registers HTTP middleware -
requestcontains incoming request info -
call_next(request)passes the request to the next layer -
responsestores the result - We calculate the time taken
-
return responsesends it back
π This is a convenient FastAPI/Starlette interface, not the raw three-argument ASGI interface.
For a GET /feed request, your terminal might show:
Request received!
Time taken: 0.035 seconds
β¦and the user gets their feed. That's middleware! π
10. π§΅ Putting It All Together in a React + FastAPI Project
Here's the complete journey of a /feed request:
1. βοΈ React frontend
β HTTP request: GET /feed
βΌ
2. π¦ Uvicorn (ASGI web server)
β Receives the network request, creates ASGI scope/receive/send
βΌ
3. π‘οΈ ASGI Middleware
β Logging, security, CORS, etc.
βΌ
4. β‘ FastAPI application (ASGI-compatible)
β Finds the /feed endpoint
βΌ
5. ποΈ Services / Repositories / PostgreSQL
β Business logic and data retrieval
βΌ
Response travels back up to React π
11. π Quick Terminology Cheat Sheet
| Term | Simplest meaning |
|---|---|
| Web server | Receives network requests and sends responses |
| Web framework | Helps developers build backend applications |
| WSGI | Standard for synchronous Python server-app communication |
| ASGI | Standard for asynchronous Python server-app communication |
| Synchronous | Wait for an operation to finish |
| Asynchronous | Work on other tasks while an operation waits |
scope |
Information about an incoming connection |
receive |
Receives incoming event messages |
send |
Sends outgoing event messages |
| Uvicorn | An ASGI web server |
| Middleware | Code that wraps an app and processes requests/responses |
| Event loop | Coordinates and schedules asynchronous tasks |
| Coroutine | An async function that can pause and resume using await
|
12. π§ͺ Test Your Understanding!
Try these before looking at the answers. No stress! β€οΈ
-
What is ASGI?
- A) A Python database
- B) A communication standard between servers and applications
- C) A frontend framework
-
Which one is the actual web server in a FastAPI project?
- A) React
- B) FastAPI
- C) Uvicorn
-
What does
scopecontain?- A) Connection/request information
- B) Database records
- C) Frontend CSS
-
Which argument receives incoming ASGI messages?
- A)
scope - B)
receive - C)
send
- A)
-
What does middleware do?
- A) Stores images
- B) Wraps the application and processes requests/responses
- C) Replaces the database
-
Does async mean everything happens simultaneously?
- A) Yes
- B) No
π Click to reveal answers
receive
13. π€ How to Explain This in an Interview
WSGI and ASGI are standards that define communication between Python web servers and applications. WSGI follows a synchronous application interface, whereas ASGI supports asynchronous communication and protocols like HTTP and WebSockets. Uvicorn is an ASGI server that runs our FastAPI application. The ASGI application accepts three arguments:
scope,receiveandsend.scopecontains connection information,receivehandles incoming events, andsendhandles outgoing events. Middleware sits around the application and can process incoming requests and outgoing responses before passing them along.
14. π Five Things to Remember Forever
- WSGI = Traditional synchronous application interface
- ASGI = Modern asynchronous application interface
-
scope,receive,send= The three arguments of an ASGI app - Uvicorn = The server that runs your FastAPI app
- Middleware = An extra layer that processes requests and responses
π Official Docs for Further Reading
π What's Next?
The best way to make this stick: trace one real request through your own main.py β middleware β router β service β repository. Theory clicks when you see it in your own code! π‘
If this post helped you, drop a β€οΈ or π¦ and share it with a friend who's learning backend development. Happy coding! π©βπ»β¨
Top comments (0)