Async Django cheatsheet.

Async view (FBV)

import asyncio
from django.http import JsonResponse

async def view(request):
    await asyncio.sleep(0.1)
    return JsonResponse({"ok": True})

Async CBV

from django.views import View

class MyView(View):
    async def get(self, request):
        return JsonResponse({"ok": True})

ASGI server

uv add uvicorn
uv run uvicorn config.asgi:application --reload
# config/asgi.py
import os
from django.core.asgi import get_asgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
application = get_asgi_application()

Async ORM (Django 5+)

async def view(request):
    posts = []
    async for post in Post.objects.all():
        posts.append(post.title)
    return JsonResponse({"posts": posts})

# Or:
posts = await Post.objects.all().aexists()
post = await Post.objects.aget(id=1)
count = await Post.objects.acount()
posts = [p async for p in Post.objects.filter(published=True)]
await Post.objects.acreate(title="x")
await post.asave()
await post.adelete()

Most ORM methods have async equivalents with a prefix.

sync_to_async (legacy ORM / external sync code)

from asgiref.sync import sync_to_async

async def view(request):
    posts = await sync_to_async(list)(Post.objects.all())
    return JsonResponse({"count": len(posts)})

sync_to_async runs sync code in a thread, async-friendly.

async_to_sync (reverse)

from asgiref.sync import async_to_sync

def sync_view(request):
    result = async_to_sync(async_func)(arg)

httpx in views

import httpx

async def fetch_view(request):
    async with httpx.AsyncClient() as client:
        r = await client.get("https://api.example.com/x")
    return JsonResponse(r.json())

Concurrent requests

import asyncio

async def view(request):
    async with httpx.AsyncClient() as client:
        users, posts = await asyncio.gather(
            client.get("https://api/users"),
            client.get("https://api/posts"),
        )
    return JsonResponse({"users": users.json(), "posts": posts.json()})

Parallel I/O, much faster than sequential.

Streaming response

from django.http import StreamingHttpResponse
import asyncio

async def gen():
    for i in range(10):
        yield f"event: {i}\n"
        await asyncio.sleep(1)

async def stream_view(request):
    return StreamingHttpResponse(gen(), content_type="text/plain")

Server-sent events

async def sse(request):
    async def stream():
        while True:
            data = await fetch_data()
            yield f"data: {data}\n\n"
            await asyncio.sleep(2)
    return StreamingHttpResponse(stream(), content_type="text/event-stream")

Async middleware

class AsyncMiddleware:
    sync_capable = False
    async_capable = True
    
    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

Django auto-detects sync/async; you can do both with sync_capable = True and detect.

Async signals

from django.dispatch import receiver

@receiver(post_save, sender=Post)
async def handle(sender, instance, **kwargs):
    await notify(instance.id)

Caching

from django.core.cache import cache

await cache.aset("key", value, timeout=60)
value = await cache.aget("key")
await cache.adelete("key")

When async helps

Async helps when:

  • Many concurrent slow I/O calls (DB, HTTP, file).
  • Server-sent events / WebSockets.
  • LLM streaming.

Doesn’t help when:

  • CPU-bound work.
  • One simple DB query per request.
  • Mixed sync deps (you’ll wrap with sync_to_async anyway).

Don’t mix sync DB in async view

# BAD: sync ORM in async view
async def view(request):
    posts = Post.objects.all()        # SynchronousOnlyOperation
    return ...

# GOOD: async ORM
async def view(request):
    posts = [p async for p in Post.objects.all()]

If you can’t avoid sync code, wrap with sync_to_async.

Channels (WebSockets)

uv add channels channels-redis
INSTALLED_APPS = [..., "channels"]
ASGI_APPLICATION = "config.asgi.application"
CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels_redis.core.RedisChannelLayer",
        "CONFIG": {"hosts": [("localhost", 6379)]},
    },
}
# consumers.py
from channels.generic.websocket import AsyncWebsocketConsumer

class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        await self.channel_layer.group_add("chat", self.channel_name)
        await self.accept()
    
    async def disconnect(self, code):
        await self.channel_layer.group_discard("chat", self.channel_name)
    
    async def receive(self, text_data):
        await self.channel_layer.group_send("chat", {
            "type": "chat.message",
            "message": text_data,
        })
    
    async def chat_message(self, event):
        await self.send(text_data=event["message"])
# routing.py
from django.urls import path
from .consumers import ChatConsumer

websocket_urlpatterns = [
    path("ws/chat/", ChatConsumer.as_asgi()),
]
# config/asgi.py
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
from blog.routing import websocket_urlpatterns

application = ProtocolTypeRouter({
    "http": get_asgi_application(),
    "websocket": AuthMiddlewareStack(URLRouter(websocket_urlpatterns)),
})

Common mistakes

  • Sync ORM in async view → SynchronousOnlyOperation.
  • time.sleep() in async (blocks) → use asyncio.sleep.
  • Mixing sync and async middleware in unexpected order.
  • Running with runserver — only single-threaded; use uvicorn.
  • ATOMIC_REQUESTS=True with async views — incompatible.

Read this next

If you want my Django async + Channels setup, 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 .