Skip to Content

Channels & ASGI

Django Channels extends Django with WebSocket support and long-lived connections via the ASGI protocol.

ASGI Application Setup

The asgi.py file routes HTTP and WebSocket traffic to the appropriate handler:

# project/asgi.py import os from channels.auth import AuthMiddlewareStack from channels.routing import ProtocolTypeRouter, URLRouter from channels.security.websocket import AllowedHostsOriginValidator from django.core.asgi import get_asgi_application from django.urls import path os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings") http_application = get_asgi_application() from apps.chat.consumers import ChatConsumer from apps.notifications.consumers import NotificationConsumer websocket_urlpatterns = [ path("ws/notifications/", NotificationConsumer.as_asgi()), path("ws/chat/<str:room_name>/", ChatConsumer.as_asgi()), ] application = ProtocolTypeRouter({ "http": http_application, "websocket": AllowedHostsOriginValidator( AuthMiddlewareStack(URLRouter(websocket_urlpatterns)) ), })

Key components:

ComponentPurpose
ProtocolTypeRouterRoutes by protocol: "http" for HTTP, "websocket" for WS
URLRouterMatches WebSocket URLs to consumers (like Django’s urlpatterns)
AuthMiddlewareStackPopulates self.scope["user"] from session/cookie auth
AllowedHostsOriginValidatorRejects connections from origins not in ALLOWED_HOSTS

Settings

# settings.py INSTALLED_APPS = [ # ... "channels", ] # Point to your ASGI application ASGI_APPLICATION = "project.asgi.application" # Channel layers — Redis for production CHANNEL_LAYERS = { "default": { "BACKEND": "channels_redis.core.RedisChannelLayer", "CONFIG": { "hosts": [("127.0.0.1", 6379)], }, }, }

For tests and local development without Redis:

# settings_test.py CHANNEL_LAYERS = { "default": { "BACKEND": "channels.layers.InMemoryChannelLayer", }, }

AsyncWebsocketConsumer

The consumer handles the WebSocket lifecycle: connect, receive messages, and disconnect.

import json from channels.generic.websocket import AsyncWebsocketConsumer class ChatConsumer(AsyncWebsocketConsumer): async def connect(self): self.room_name = self.scope["url_route"]["kwargs"]["room_name"] self.room_group = f"chat_{self.room_name}" # Join room group await self.channel_layer.group_add( self.room_group, self.channel_name, ) await self.accept() async def disconnect(self, close_code): # Leave room group await self.channel_layer.group_discard( self.room_group, self.channel_name, ) async def receive(self, text_data): data = json.loads(text_data) message = data["message"] # Broadcast to room group await self.channel_layer.group_send( self.room_group, { "type": "chat.message", "message": message, "sender": self.scope["user"].username, }, ) # Handler for messages of type "chat.message" async def chat_message(self, event): await self.send(text_data=json.dumps({ "message": event["message"], "sender": event["sender"], }))

Lifecycle

MethodWhenTypical actions
connect()Client opens WSValidate, join groups, await self.accept()
receive(text_data)Client sends messageParse JSON, broadcast via group_send
disconnect(close_code)Client closes WSLeave groups, clean up resources

The type field in group_send maps to a handler method: "chat.message" calls chat_message() (dots become underscores).

Channel Groups

Groups enable broadcasting to multiple connected clients.

# Add a channel to a group await self.channel_layer.group_add("room_42", self.channel_name) # Remove a channel from a group await self.channel_layer.group_discard("room_42", self.channel_name) # Send to all channels in a group await self.channel_layer.group_send( "room_42", {"type": "notify", "text": "Hello room"}, )

Sending from Outside a Consumer

Send messages from views, Celery tasks, or management commands:

from channels.layers import get_channel_layer from asgiref.sync import async_to_sync channel_layer = get_channel_layer() # From sync code async_to_sync(channel_layer.group_send)( "room_42", {"type": "chat.message", "message": "Server notification"}, ) # From async code await channel_layer.group_send( "room_42", {"type": "chat.message", "message": "Server notification"}, )

WebSocket Authentication

AuthMiddlewareStack reads session cookies and populates self.scope["user"]:

class SecureConsumer(AsyncWebsocketConsumer): async def connect(self): user = self.scope["user"] if not user.is_authenticated: await self.close() return # User-specific group self.user_group = f"user_{user.id}" await self.channel_layer.group_add( self.user_group, self.channel_name, ) await self.accept() async def disconnect(self, close_code): if hasattr(self, "user_group"): await self.channel_layer.group_discard( self.user_group, self.channel_name, )

For token-based auth (JWT, API keys), write custom middleware:

from channels.middleware import BaseMiddleware from channels.db import database_sync_to_async from django.contrib.auth.models import AnonymousUser class TokenAuthMiddleware(BaseMiddleware): async def __call__(self, scope, receive, send): headers = dict(scope.get("headers", [])) token = headers.get(b"authorization", b"").decode() scope["user"] = await self.get_user_from_token(token) return await super().__call__(scope, receive, send) @database_sync_to_async def get_user_from_token(self, token: str): try: return User.objects.get(auth_token=token) except User.DoesNotExist: return AnonymousUser()

URL Routing

WebSocket routes follow the same path() pattern as Django HTTP URLs:

# project/routing.py from django.urls import path from apps.chat.consumers import ChatConsumer from apps.notifications.consumers import NotificationConsumer websocket_urlpatterns = [ path("ws/chat/<str:room_name>/", ChatConsumer.as_asgi()), path("ws/notifications/", NotificationConsumer.as_asgi()), ]

URL parameters are available via self.scope["url_route"]["kwargs"]:

async def connect(self): room_name = self.scope["url_route"]["kwargs"]["room_name"]

Rules

  1. Always call .as_asgi() on consumer classes in URL patterns
  2. Use AllowedHostsOriginValidator to prevent cross-origin WebSocket connections
  3. Use Redis channel layer in production — InMemoryChannelLayer is for tests only
  4. Close the connection in connect() for unauthenticated users instead of silently accepting
  5. Clean up group memberships in disconnect() to prevent stale channels
  6. Use async_to_sync when sending to channel layers from synchronous code (views, Celery)
Last updated on