Django got serious about async over multiple releases. By 2026 the async ORM is broadly usable, async views work, and the patterns are settling. This post is the working playbook.
Async views
from django.http import JsonResponse
async def my_view(request):
user = await User.objects.aget(id=request.user.id)
return JsonResponse({"email": user.email})
Just async def. Django handles the rest.
Async ORM (the a* methods)
# Get
user = await User.objects.aget(id=1)
user = await User.objects.filter(active=True).afirst()
exists = await User.objects.filter(email=email).aexists()
# Create
user = await User.objects.acreate(email=email)
# Update
await User.objects.filter(id=1).aupdate(active=False)
await user.asave()
# Delete
await user.adelete()
# Bulk
await User.objects.abulk_create([User(email=e) for e in emails])
Most query methods have async equivalents prefixed with a.
Async iteration
async for user in User.objects.filter(active=True):
process(user)
Streams rows; doesn’t load everything into memory.
Transactions
from django.db import transaction
async def create_account(...):
@sync_to_async
def _do_it():
with transaction.atomic():
user = User.objects.create(...)
Profile.objects.create(user=user)
return user
return await _do_it()
Transactions in Django ORM are sync-bound. For async views, wrap the transactional block in sync_to_async or use the async-aware transaction support carefully.
By 2026, await transaction.atomic() works in async contexts (with caveats):
async def create_account(...):
async with transaction.atomic():
user = await User.objects.acreate(...)
await Profile.objects.acreate(user=user)
But: don’t await arbitrary code inside; some operations may not behave correctly inside an async transaction. Stick to ORM operations.
Mixing sync and async
from asgiref.sync import sync_to_async, async_to_sync
# Sync function called from async
async def my_view(request):
result = await sync_to_async(some_legacy_function)(arg)
# Async function called from sync
def my_command():
result = async_to_sync(some_async_function)(arg)
sync_to_async runs the sync code in a thread pool. Useful for legacy code or sync-only ORM operations (transactions, certain methods).
Async middleware
class MyMiddleware:
async_capable = True
sync_capable = False
def __init__(self, get_response):
self.get_response = get_response
async def __call__(self, request):
# before
response = await self.get_response(request)
# after
return response
Mark async_capable = True so Django doesn’t sync-ify it.
ASGI server
uvicorn myproject.asgi:application --workers 4
# or
daphne -b 0.0.0.0 -p 8000 myproject.asgi:application
Don’t use Gunicorn sync workers — they don’t support async views. Use Uvicorn or Daphne.
When async pays off
- External HTTP / API calls (LLM, payment gateway, third-party).
- Long-polling / streaming responses.
- WebSocket (via Channels).
- High concurrency for IO-bound work.
When it doesn’t:
- DB-bound CRUD — connection pool is the bottleneck; async doesn’t help much.
- CPU-bound work — async doesn’t speed up CPU.
- Trivial views — overhead > benefit.
Real example: LLM-backed view
async def chat(request):
body = json.loads(request.body)
user_msg = body["message"]
# Sync DB write
msg = await ChatMessage.objects.acreate(user=request.user, role="user", content=user_msg)
# Async LLM call (the expensive part)
response = await client.messages.create(
model="claude-sonnet-4-6",
messages=[{"role": "user", "content": user_msg}],
max_tokens=1024,
)
# Save response
await ChatMessage.objects.acreate(user=request.user, role="assistant", content=response.content[0].text)
return JsonResponse({"reply": response.content[0].text})
The 5-second LLM call doesn’t block other requests. Big win.
Streaming responses
from django.http import StreamingHttpResponse
async def stream_chat(request):
async def gen():
async with client.messages.stream(...) as stream:
async for text in stream.text_stream:
yield f"data: {json.dumps({'token': text})}\n\n"
return StreamingHttpResponse(gen(), content_type="text/event-stream")
Server-Sent Events for LLM token streams. See FastAPI Streaming for similar patterns.
Channels for WebSocket
from channels.generic.websocket import AsyncJsonWebsocketConsumer
class ChatConsumer(AsyncJsonWebsocketConsumer):
async def receive_json(self, content):
msg = await Message.objects.acreate(text=content["text"])
await self.send_json({"id": msg.id})
See Django Channels .
Rough edges
select_related/prefetch_relatedwork in async, but combining with awaits inside the result iteration can be tricky.- Signals don’t have async equivalents; stay sync.
- Some third-party packages are still sync-only; wrap with
sync_to_async. - Connection pooling: still tuned for sync; async views may exhaust pool faster than expected. Tune
CONN_MAX_AGEand pool size.
Common mistakes
1. Calling sync ORM in async context
async def view(request):
user = User.objects.get(id=1) # BAD: blocks event loop
Django will warn but not always. Use aget / await.
2. Mixing transaction styles
with transaction.atomic() inside async; some operations work, some don’t. Stick to fully async or wrap the whole block in sync_to_async.
3. Not migrating middleware
Sync middleware in async stack: every request gets sync-converted; performance worse than pure sync.
4. WSGI server
Sync worker; async views silently fail or perform worse. Use ASGI.
5. Async for everything
CPU-bound or trivial views as async: overhead > benefit. Pick by workload.
What I’d ship today
For Django apps with external API workloads:
- Async views for slow external calls.
- Sync views for CRUD (where it fits).
- Channels for WebSocket.
- Daphne / Uvicorn as ASGI server.
- Celery / ARQ for background.
- PgBouncer in transaction-pooling for DB.
Mix freely; the framework supports both.
Read this next
- Django 5: Async Views, ORM, Channels
- Django Channels 2026
- Django + Celery 2026
- Python Async Patterns 2026
If you want my Django async + Channels + LLM integration starter, it’s at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .