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_related work 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_AGE and 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

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 .