🌱 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:

  1. You click the post on the frontend (React) 🖱️
  2. React sends an HTTP request to the backend
  3. The backend receives the request
  4. The backend processes it
  5. The backend sends a response
  6. 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:

  1. 👩 User A starts, then waits for the DB
  2. 👨 User B starts while A is waiting
  3. 🧑 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:

  1. scope
  2. receive
  3. send

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
  • await means 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.)

📌 receive does 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, and send are 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?

  1. async def app(...) creates an async ASGI application
  2. scope holds request/connection info
  3. We check that the connection is HTTP
  4. We send the response status + headers
  5. 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?

  1. Uvicorn starts
  2. It imports your FastAPI application
  3. It starts listening on port 8000
  4. React sends an HTTP request
  5. Uvicorn receives it
  6. Uvicorn converts the network request into ASGI information and events
  7. It calls your app with scope, receive, and send
  8. FastAPI processes the request
  9. FastAPI sends response events back
  10. 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
  • app is your ASGI-compatible application object
  • @app.get("/hello") defines an API endpoint
  • uvicorn runs 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 
  1. Client sends a request
  2. Middleware receives it first
  3. Middleware performs its checks
  4. Middleware forwards the request to the actual app
  5. App processes the request
  6. Response comes back through middleware
  7. Middleware can inspect or modify the response
  8. 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 receive and send to 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
  • request contains incoming request info
  • call_next(request) passes the request to the next layer
  • response stores the result
  • We calculate the time taken
  • return response sends 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! ❤️

  1. What is ASGI?

    • A) A Python database
    • B) A communication standard between servers and applications
    • C) A frontend framework
  2. Which one is the actual web server in a FastAPI project?

    • A) React
    • B) FastAPI
    • C) Uvicorn
  3. What does scope contain?

    • A) Connection/request information
    • B) Database records
    • C) Frontend CSS
  4. Which argument receives incoming ASGI messages?

    • A) scope
    • B) receive
    • C) send
  5. What does middleware do?

    • A) Stores images
    • B) Wraps the application and processes requests/responses
    • C) Replaces the database
  6. Does async mean everything happens simultaneously?

    • A) Yes
    • B) No

👀 Click to reveal answers
  1. B: ASGI is a communication standard between servers and applications
  2. C: Uvicorn is the web server
  3. A: Connection/request information
  4. B: receive
  5. B: Middleware wraps the app and processes requests/responses
  6. B: No, async means switching tasks while waiting, not running everything at once

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, receive and send. scope contains connection information, receive handles incoming events, and send handles 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

  1. WSGI = Traditional synchronous application interface
  2. ASGI = Modern asynchronous application interface
  3. scope, receive, send = The three arguments of an ASGI app
  4. Uvicorn = The server that runs your FastAPI app
  5. 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! 👩‍💻✨