If you have worked with FastAPI, you have probably run something like this:

uvicorn main:app

The command looks simple, but an important piece of modern Python web architecture sits behind it.

Uvicorn is not FastAPI. FastAPI is not the server either. Between them is a contract called ASGI.

ASGI stands for Asynchronous Server Gateway Interface. It is a specification that defines how a server receiving network connections communicates with a Python application.

A useful mental model is:

client
   ↓ HTTP / WebSocket
ASGI server
   ↓ ASGI
Python framework / application
   ↓
your code

For example:

browser
   ↓
Uvicorn
   ↓ ASGI
FastAPI
   ↓
Python endpoint

That small diagram explains a distinction that is easy to miss when starting with FastAPI: ASGI is neither a framework nor a server. It is the interface that lets both sides understand each other.

The problem ASGI is designed to solve

Before ASGI, the dominant standard for Python web applications was — and remains highly important — WSGI, the Web Server Gateway Interface.

WSGI was enormously useful because it decoupled servers from frameworks. A WSGI-compatible server could run a WSGI-compatible application without both products having to be designed specifically for each other.

That model fits the classic web request cycle very well:

request
   ↓
process
   ↓
response

But the web stopped being only a sequence of short requests followed by one final response.

Modern applications increasingly need things such as:

  • WebSockets;
  • long-lived connections;
  • streaming;
  • progressively delivered events;
  • large amounts of concurrent I/O;
  • applications that receive and send multiple events during one connection.

The ASGI specification itself explains that WSGI is inherently tied to the traditional HTTP request/response cycle, while ASGI was created to represent protocols and connections that can produce multiple events over time.

That is the most important conceptual difference.

ASGI turns a connection into events

A modern ASGI application can be reduced conceptually to an asynchronous function with this shape:

async def application(scope, receive, send):
    ...

Those three parameters capture the core of the protocol.

scope

scope describes the connection.

It is a dictionary containing information such as the protocol type, path, and other metadata associated with the connection.

For example:

scope["type"]

might be:

http

or:

websocket

receive

receive is an awaitable callable that lets the application receive events.

The body of an HTTP request, for example, arrives through events such as:

http.request

A WebSocket can produce events such as:

websocket.connect
websocket.receive
websocket.disconnect

send

send lets the application send events back to the server.

For an HTTP response, an application may first send:

http.response.start

and then:

http.response.body

For a WebSocket it might send:

websocket.send

This is why ASGI naturally fits bidirectional and long-lived connections better than a purely synchronous request/response model.

A minimal ASGI application

You normally do not write ASGI applications directly, but doing it once makes the architecture much easier to understand:

async def app(scope, receive, send):
    assert scope["type"] == "http"

    await send({
        "type": "http.response.start",
        "status": 200,
        "headers": [(b"content-type", b"text/plain")],
    })

    await send({
        "type": "http.response.body",
        "body": b"Hello from ASGI",
    })

There is no FastAPI here.

There is no Django.

There is not even a Request or Response abstraction.

There is only the ASGI contract.

In a real application, the framework translates these low-level events into a much more convenient programming model.

FastAPI is an ASGI application

With FastAPI, we normally write something like:

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
async def home():
    return {"message": "Hello"}

Then we run:

uvicorn main:app

That command roughly means:

uvicorn
  imports main.py
  finds the app object
  treats it as an ASGI application
  opens network sockets
  translates network traffic into ASGI events
  hands those events to FastAPI

FastAPI matches the route, runs our code, and builds the response; Uvicorn handles the network-facing server work.

ASGI is the shared boundary between them.

FastAPI’s own documentation states this directly: FastAPI is an ASGI web framework, and serving it requires an ASGI server program such as Uvicorn or an alternative such as Hypercorn.

So what exactly is Uvicorn?

Uvicorn is an ASGI server.

Its responsibility lives closer to this layer:

socket / HTTP
      ↓
   Uvicorn
      ↓
     ASGI

FastAPI lives on the other side:

     ASGI
      ↓
   FastAPI
      ↓
application logic

That separation is valuable because components can be replaced independently.

Conceptually, an ASGI application can run on another compatible server without being rewritten around one specific server implementation.

Django can speak ASGI too

ASGI is not exclusive to FastAPI.

Django supports both WSGI and ASGI. A modern Django project can expose an application object through its ASGI configuration and run it with compatible servers such as Uvicorn, Daphne, Hypercorn, or Granian.

That allows Django to participate in asynchronous architectures, but there is an important warning: running behind ASGI does not automatically make all your code non-blocking.

If an async handler calls a synchronous library that blocks the thread, the event loop cannot magically remove that blocking work.

async does not mean “faster at everything”

This is one of the most common misconceptions.

ASGI is especially useful for workloads dominated by I/O:

waiting for an API
waiting for a database
waiting for Redis
waiting for a remote file
waiting for socket messages

While one coroutine is waiting, the event loop can advance other tasks.

Imagine 1,000 connections spending much of their lifetime waiting on external systems. An asynchronous model can make very effective use of those idle periods.

But if the workload is instead:

encoding video
compressing large files
running heavy CPU inference
processing millions of numbers

the problem is different.

CPU-bound work still consumes CPU. Those workloads may require separate processes, worker pools, task queues, or other forms of parallelism.

ASGI improves the concurrency model for specific kinds of workloads; it does not repeal the laws of computation.

WSGI vs ASGI

A simplified comparison:

CapabilityWSGIASGI
Traditional HTTPYesYes
Synchronous modelNativeSupported
async/awaitNot as the native modelYes
WebSocketsNot part of the standardYes
Long-lived connectionsAwkwardNatural
Multiple events per connectionNot the central modelYes
Typical frameworksClassic Flask, Django WSGIFastAPI, Starlette, Django ASGI

That does not mean WSGI is “dead.”

For many traditional CRUD applications, a synchronous architecture remains entirely reasonable. ASGI was also designed with interoperability for existing WSGI applications in mind.

The right choice depends on the workload and architecture, not simply on which acronym is newer.

Why WebSockets make the difference obvious

A classic HTTP endpoint usually has a short lifetime:

client → request → response → done

A WebSocket looks more like this:

client connects
      ↓
server accepts
      ↓
client sends message
      ↓
server responds
      ↓
client sends another message
      ↓
server may push events
      ↓
...
      ↓
connection closes

The application must react to multiple events throughout the same connection.

ASGI’s scope + receive + send model was designed precisely to represent this style of interaction.

A useful way to remember it

If you remember only one thing, make it this:

ASGI = contract
Uvicorn = server
FastAPI / Starlette / Django = framework or application
async/await = execution model that ASGI can take advantage of

Or visually:

Internet
   ↓
Uvicorn / Hypercorn / Daphne
   ↓
========== ASGI ==========
   ↓
FastAPI / Starlette / Django
   ↓
Your business logic

ASGI is not the application the user sees.

It is not the server opening the network port.

It is not the framework where you define @app.get().

It is the common language between those layers.

Once that boundary is clear, much of Python’s asynchronous web ecosystem stops looking like a collection of unrelated names and starts looking like a coherent architecture.

Sources