FastAPI runs on Uvicorn, an ASGI server made for Python code that runs at the same time. Django is older and has more features, but from version 3.0, it can also operate on ASGI with Uvicorn. Once you set up Django on Uvicorn and make queries and caching work better, you can get the same speed for most things.
1. Start Django with Uvicorn
The best way to improve performance is to switch to an ASGI server.
Install Uvicorn
pip install uvicorn
Make sure your project has a asgi.py
file, which is made automatically in Django 3+. Then turn on the server:
uvicorn myproject.asgi:application --host 0.0.0.0 --port 8000 --workers 4
Why Uvicorn
- Built on uvloop (faster event loop in C).
- Native support for async views.
- Lower latency and better concurrency than WSGI servers.
If you use a process manager like Supervisor or systemd, you can add:
[Unit]
Description=Django with Uvicorn
After=network.target
[Service]
User=www-data
Group=www-data
WorkingDirectory=/path/to/project
ExecStart=/path/to/venv/bin/uvicorn myproject.asgi:application --workers 4 --host 0.0.0.0 --port 8000
Restart=always
[Install]
WantedBy=multi-user.target
2. Use async views where possible
Why use httpx
instead of requests
:
It lets you send HTTP requests (GET, POST, etc.) and handle responses, similar to requests
, but it also supports asynchronous programming (async/await
). That means you can make many API calls at once without blocking your Django or FastAPI app, ideal for performance and concurrency.
import httpx
from django.http import JsonResponse
async def price_view(request):
async with httpx.AsyncClient() as client:
r = await client.get('https://api.example.com/price')
return JsonResponse(r.json())
For ORM queries, still use sync code or wrap it with sync_to_async
:
from asgiref.sync import sync_to_async
from django.contrib.auth.models import User
@sync_to_async
def get_user(pk):
return User.objects.get(pk=pk)
async def user_view(request):
user = await get_user(1)
return JsonResponse({'username': user.username})
3. Optimize your database
- Use
select_related()
andprefetch_related()
. - Add indexes for frequent queries.
- Use caching for heavy queries.
- Use
pgbouncer
for Postgres connection pooling.
Example:
posts = Post.objects.select_related('author').all()
4. Enable caching with Redis
Install Redis and configure Django:
pip install django-redis
Add this to settings.py
:
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
}
}
}
Cache heavy views:
from django.views.decorators.cache import cache_page
@cache_page(60)
def home(request):
return render(request, 'home.html')
5. Offload background work
Use Celery or Dramatiq to handle slow tasks like emails or large file uploads asynchronously.
6. Serve static files efficiently
Use WhiteNoise for small deployments or a CDN (Cloudflare, S3 + CloudFront) for large ones.
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
# ...
]
7. Monitor performance
- Use
django-silk
for profiling. - Collect metrics with
django-prometheus
. - Track errors using Sentry.
Example Benchmark
Running the same Django app under Uvicorn vs Gunicorn (WSGI):
Server | Avg Latency | Req/s |
---|---|---|
Gunicorn (WSGI) | 90 ms | 700 |
Uvicorn (ASGI) | 40 ms | 1400 |
Final Thoughts
FastAPI may always win in pure async benchmarks, but Django + Uvicorn can be nearly as fast for most production workloads — and you keep Django’s ORM, admin, and ecosystem.
Checklist:
- Install Uvicorn and use ASGI
- Use async views for IO-heavy routes
- Add Redis caching
- Optimize DB queries
- Offload background work
- Serve static files with CDN or WhiteNoise