Django Channels extends Django to handle WebSockets natively via ASGI. Where a standard Django view handles an HTTP request and returns a response, a Channels consumer handles a persistent WebSocket connection — receiving messages, processing them, and sending responses over the same open connection.
This guide walks through a complete real-time chat or notification setup from scratch.
How It Works
The architecture has four components:
Client (browser) ←→ WebSocket ←→ ASGI server (Daphne) ←→ Django Channels
↕
Channel Layer (Redis)
↕
Other consumers / workers
The channel layer (Redis) is what makes multi-server real-time work. When a consumer on server A wants to send a message to a consumer on server B, it pushes the message to Redis. The channel layer delivers it. Without a channel layer, consumers on different server processes cannot communicate.
Installation
pip install channels channels-redis daphne
Add to INSTALLED_APPS:
INSTALLED_APPS = [
"daphne", # Must be before django.contrib.staticfiles
"channels",
# ...
]
ASGI Configuration
Replace wsgi.py with an asgi.py:
# myproject/asgi.py
import os
from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
import myapp.routing
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")
application = ProtocolTypeRouter({
"http": get_asgi_application(),
"websocket": AuthMiddlewareStack(
URLRouter(myapp.routing.websocket_urlpatterns)
),
})
Configure ASGI_APPLICATION and the channel layer in settings:
# settings.py
ASGI_APPLICATION = "myproject.asgi.application"
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [("127.0.0.1", 6379)],
},
},
}
Writing a Consumer
A consumer is the WebSocket equivalent of a view. This one handles a real-time notification channel:
# myapp/consumers.py
import json
from channels.generic.websocket import AsyncWebsocketConsumer
class NotificationConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.user_id = self.scope["user"].id
self.group_name = f"notifications_{self.user_id}"
# Join the user's notification group
await self.channel_layer.group_add(
self.group_name,
self.channel_name,
)
await self.accept()
async def disconnect(self, close_code):
await self.channel_layer.group_discard(
self.group_name,
self.channel_name,
)
async def receive(self, text_data):
# Handle incoming messages from the client (optional)
data = json.loads(text_data)
# Process or echo
async def notification_message(self, event):
# Called when a message is sent to the group
await self.send(text_data=json.dumps({
"type": "notification",
"message": event["message"],
}))
URL Routing for WebSockets
# myapp/routing.py
from django.urls import re_path
from . import consumers
websocket_urlpatterns = [
re_path(r"ws/notifications/$", consumers.NotificationConsumer.as_asgi()),
]
Sending Messages from Django Code
To push a notification from a Django view or Celery task:
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync
def send_notification(user_id, message):
channel_layer = get_channel_layer()
group_name = f"notifications_{user_id}"
async_to_sync(channel_layer.group_send)(
group_name,
{
"type": "notification_message", # Maps to consumer method
"message": message,
}
)
The type field maps to the consumer method name (with dots replaced by underscores). "notification_message" triggers notification_message() on the consumer.
Call this from a Celery task or a post-save signal and every connected client for that user receives the notification instantly.
Frontend Connection
const ws = new WebSocket(`wss://${location.host}/ws/notifications/`);
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === "notification") {
showNotificationBadge(data.message);
}
};
ws.onclose = () => {
// Reconnect after 3 seconds
setTimeout(() => connectWebSocket(), 3000);
};
Always implement reconnection logic — WebSocket connections drop on network changes, server restarts, and idle timeouts.
Production Deployment with Daphne
Daphne is the ASGI server developed alongside Django Channels. Run it instead of Gunicorn:
daphne -b 0.0.0.0 -p 8000 myproject.asgi:application
Nginx configuration to proxy both HTTP and WebSocket connections:
server {
listen 443 ssl;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /ws/ {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 86400; # Keep WebSocket connections alive
}
}
The Upgrade and Connection headers are required for WebSocket handshake. proxy_read_timeout 86400 prevents Nginx from closing idle WebSocket connections after 60 seconds.
Systemd Service
[Unit]
Description=Daphne ASGI Server
After=network.target
[Service]
User=www-data
WorkingDirectory=/srv/myproject
ExecStart=/srv/myproject/.venv/bin/daphne -b 127.0.0.1 -p 8000 myproject.asgi:application
Restart=always
[Install]
WantedBy=multi-user.target
When to Use Channels vs Alternatives
Channels is the right choice when: - You need WebSockets in an existing Django application - Real-time features are a core product requirement (chat, notifications, live dashboards) - You want to keep everything in one codebase
Consider alternatives when: - You only need simple one-way server-sent events (SSE is simpler than WebSockets) - Real-time is a minor feature and you want to avoid operational complexity (use a third-party service like Pusher or Ably)
If you need real-time features built into a Django application — WebSockets, notifications, live updates — hire me as a Django developer who has run Channels in production with Daphne and Redis.