FastAPI can serve synchronous Flask-like views, but its real power and performance come from asynchronous request handling, which Django doesn’t natively support.
Let’s see what this looks like in practice.
Imagine we have a simple web application that needs to fetch data from an external API.
Django (Synchronous)
In Django, this would typically involve a view function that looks something like this:
# views.py
from django.http import JsonResponse
import requests
def get_external_data_django(request):
try:
response = requests.get("https://api.example.com/data", timeout=5)
response.raise_for_status() # Raise an exception for bad status codes
data = response.json()
return JsonResponse(data)
except requests.exceptions.RequestException as e:
return JsonResponse({"error": str(e)}, status=500)
When a request hits this Django view, the entire Python process handling that request will block. The requests.get call is synchronous. While it’s waiting for the external API to respond, that worker process can’t do anything else. If you have a lot of concurrent requests hitting this endpoint, you’ll need many worker processes to handle them, which can consume significant resources.
FastAPI (Asynchronous)
Now, let’s look at the equivalent in FastAPI, leveraging async/await:
# main.py
from fastapi import FastAPI
import httpx # httpx is an async HTTP client
app = FastAPI()
@app.get("/data")
async def get_external_data_fastapi():
try:
async with httpx.AsyncClient() as client:
response = await client.get("https://api.example.com/data", timeout=5)
response.raise_for_status()
data = response.json()
return data
except httpx.RequestError as e:
return {"error": str(e)} # FastAPI handles JSON serialization automatically
Here, get_external_data_fastapi is an async function. When await client.get(...) is called, the request to the external API is sent, but importantly, the event loop is not blocked. The Python process can switch to handling other incoming requests or tasks while waiting for the external API’s response. Once the response arrives, the event loop will resume execution of this async function. This means a single worker process can handle many more concurrent I/O-bound operations efficiently.
The Mental Model: Event Loops and Concurrency
The core difference boils down to how they handle I/O-bound operations (like waiting for network requests, database queries, or file reads).
-
Django (Traditional WSGI): Relies on a multi-process or multi-threaded model. Each process/thread is assigned a request. If that request involves waiting for I/O, the entire process/thread is blocked. To handle more concurrent requests, you scale by adding more processes/threads. This is often referred to as "concurrency through parallelism."
-
FastAPI (ASGI): Built on an asynchronous event loop. A single process can manage thousands of concurrent connections. When an
awaitkeyword is encountered, the current task is suspended, and the event loop can switch to another ready task. This is "concurrency through non-blocking I/O." It’s incredibly efficient for tasks that spend a lot of time waiting.
What Problem Does This Solve?
This asynchronous capability in FastAPI is crucial for applications that:
- Make frequent calls to external APIs.
- Perform long-running database queries.
- Need to handle a high volume of concurrent users with minimal latency.
- Incorporate real-time features like WebSockets.
Django, while incredibly robust and feature-rich for traditional web development, requires more effort (e.g., using Celery for background tasks or specific async libraries within views) to achieve similar levels of I/O concurrency.
The Levers You Control
FastAPI:
async defvs.def: You explicitly mark your endpoints asasyncto enable non-blocking behavior. Regulardefendpoints will run in a thread pool, behaving more like traditional synchronous frameworks.- Async Libraries: Using
httpxfor HTTP requests,asyncpgorSQLAlchemy’s async support for databases, and other async-native libraries is key to unlocking FastAPI’s performance. - Background Tasks: FastAPI has built-in support for running tasks in the background (
BackgroundTasks) without blocking the response. - WebSockets: Native, first-class support for WebSockets.
Django:
async defViews: Django has introduced support forasync defviews (since version 3.1), allowing you to write asynchronous code within your views. However, the underlying ASGI server and the broader ecosystem are still catching up compared to FastAPI’s async-first design.- ASGI Servers: To run async Django views, you need an ASGI server like Uvicorn or Daphne, not a traditional WSGI server like Gunicorn (though Gunicorn can run Uvicorn workers).
- Celery/Background Jobs: For truly I/O-bound tasks that should not impact request handling at all, Celery is the de facto standard for offloading work to separate worker processes.
The One Thing Most People Don’t Know
You can actually run synchronous Django views within FastAPI, or even run asynchronous FastAPI routes alongside your Django application using the same ASGI server. This interoperability is often overlooked when framing them as strict competitors. For instance, you could have a FastAPI instance serving your API endpoints and a separate Django instance handling your admin interface, all running under the same Uvicorn server.
The next concept you’ll likely grapple with is how to manage dependencies and background processing when you start scaling these asynchronous applications.