Async Views & the ORM
Write async def views for I/O-bound work, and use the ORM's async methods without shooting yourself in the foot.
Django 5.x has mature async support. An async def view runs on the ASGI stack and can await multiple I/O operations concurrently — calling three external APIs in the time one would take. The win is real, but only for I/O-bound work; async does nothing for CPU-bound code and adds complexity, so reach for it deliberately.
What's safe and what isn't
import httpx
from django.http import JsonResponse
async def dashboard(request):
async with httpx.AsyncClient() as client:
r = await client.get('https://api.example.com/stats')
return JsonResponse(r.json())The ORM is synchronous under the hood, so it exposes a-prefixed async methods: aget(), acreate(), afirst(), acount(), and async for to iterate a queryset. You cannot call the normal blocking versions directly in an async view — Django raises SynchronousOnlyOperation.
Need to call a chunk of sync code (a library, some legacy ORM logic) from async? Wrap it insync_to_async(fn)(). Going the other way, call async code from sync withasync_to_sync. These adapters fromasgirefare the bridge between the two worlds.
Example
import asyncio
import httpx
from django.http import JsonResponse
from asgiref.sync import sync_to_async
async def enriched_profile(request, user_id):
# 1) ORM with async methods — note the 'a' prefixes.
user = await User.objects.select_related('profile').aget(pk=user_id)
# 2) Fan out to three slow services concurrently instead of one-by-one.
async with httpx.AsyncClient(timeout=5) as client:
billing, activity, geo = await asyncio.gather(
client.get(f'https://billing/api/users/{user_id}'),
client.get(f'https://events/api/users/{user_id}/recent'),
client.get(f'https://geo/api/ip/{request.META["REMOTE_ADDR"]}'),
return_exceptions=True, # one failure doesn't sink the others
)
# 3) Iterate a queryset asynchronously.
recent_orders = [
{'id': o.id, 'total': str(o.total)}
async for o in Order.objects.filter(user=user).order_by('-created')[:5]
]
# 4) Call unavoidably-sync code from async safely.
score = await sync_to_async(compute_risk_score)(user)
return JsonResponse({
'user': user.username,
'plan': _safe_json(billing),
'recent_activity': _safe_json(activity),
'orders': recent_orders,
'risk': score,
})
def _safe_json(response):
return response.json() if not isinstance(response, Exception) else NoneWhen to use it
- A developer writes an async view to call an external payment API concurrently with a database lookup, reducing latency.
- A team migrates a slow view to async def so multiple I/O operations run with asyncio.gather() instead of sequentially.
- A developer uses Django's async ORM with sync_to_async to fetch data inside an async view safely.
More examples
Simple async view
Defines an async view using async def; Django 4.1+ supports async views natively with ASGI.
from django.http import JsonResponse
async def health(request):
return JsonResponse({"status": "ok"})Async ORM call with sync_to_async
Wraps a synchronous ORM call in sync_to_async so it can be awaited from within an async view.
from django.http import JsonResponse
from asgiref.sync import sync_to_async
from .models import Post
async def post_count(request):
count = await sync_to_async(Post.objects.count)()
return JsonResponse({"count": count})Concurrent I/O with asyncio.gather
Fetches data from two external APIs concurrently using asyncio.gather, halving the total wait time.
import asyncio
import httpx
from django.http import JsonResponse
async def dashboard_data(request):
async with httpx.AsyncClient() as client:
weather, news = await asyncio.gather(
client.get("https://api.weather.example/current"),
client.get("https://api.news.example/top"),
)
return JsonResponse({"weather": weather.json(), "news": news.json()})
Discussion