Django Channels: Real-Time WebSockets
Add real-time WebSocket support to Django with Channels — consumers, routing, channel layers, groups, and building a live chat.
What you'll learn
- ✓How Django Channels extends Django beyond HTTP
- ✓How to write WebSocket consumers for real-time communication
- ✓How channel layers and groups enable broadcasting
- ✓How to build a live chat room step by step
- ✓How to deploy Channels with Daphne and Redis
Prerequisites
- •A working Django project
- •Basic understanding of WebSockets and async concepts
- •Redis installed locally
Django was built for HTTP: request in, response out. But modern applications need real-time features — live chat, notifications, dashboards that update without refreshing. Django Channels extends Django to handle WebSockets, long-polling, and other protocols by replacing the WSGI server with an ASGI server.
Installation and Setup
pip install channels channels-redis
Configure Channels in your Django project:
# settings.py
INSTALLED_APPS = [
"daphne", # Must be before django.contrib.staticfiles
"channels",
# ... your apps
"django.contrib.staticfiles",
]
ASGI_APPLICATION = "myproject.asgi.application"
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [("127.0.0.1", 6379)],
},
},
}
Create the ASGI application:
# myproject/asgi.py
import os
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")
django_asgi_app = get_asgi_application()
from chat.routing import websocket_urlpatterns # Import after django setup
application = ProtocolTypeRouter({
"http": django_asgi_app,
"websocket": AuthMiddlewareStack(
URLRouter(websocket_urlpatterns)
),
})
ProtocolTypeRouter dispatches connections by protocol — HTTP goes to Django as usual, WebSocket connections go to your consumers.
Writing Your First Consumer
A consumer is the WebSocket equivalent of a view. It handles connect, receive, and disconnect events.
Synchronous Consumer
# chat/consumers.py
import json
from channels.generic.websocket import WebsocketConsumer
class ChatConsumer(WebsocketConsumer):
def connect(self):
self.accept()
def disconnect(self, close_code):
pass
def receive(self, text_data):
data = json.loads(text_data)
message = data["message"]
# Echo the message back
self.send(text_data=json.dumps({
"message": message,
}))
Async Consumer (Recommended)
Async consumers handle more concurrent connections without blocking:
# chat/consumers.py
import json
from channels.generic.websocket import AsyncWebsocketConsumer
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
await self.accept()
async def disconnect(self, close_code):
pass
async def receive(self, text_data):
data = json.loads(text_data)
message = data["message"]
await self.send(text_data=json.dumps({
"message": message,
}))
Routing
WebSocket URLs are defined separately from HTTP URLs:
# chat/routing.py
from django.urls import re_path
from . import consumers
websocket_urlpatterns = [
re_path(r"ws/chat/(?P<room_name>\w+)/$", consumers.ChatConsumer.as_asgi()),
]
The as_asgi() class method creates an ASGI application instance, similar to as_view() for HTTP views.
Channel Layers and Groups
The echo consumer above only talks to a single connection. For real-time features, you need to broadcast messages to multiple clients. Channel layers (backed by Redis) enable this.
Groups: Broadcasting to Multiple Clients
Groups let you add multiple WebSocket connections to a named channel and broadcast to all of them:
# chat/consumers.py
import json
from channels.generic.websocket import AsyncWebsocketConsumer
class ChatRoomConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.room_name = self.scope["url_route"]["kwargs"]["room_name"]
self.room_group_name = f"chat_{self.room_name}"
# Join the room group
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name,
)
await self.accept()
async def disconnect(self, close_code):
# Leave the room group
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name,
)
async def receive(self, text_data):
data = json.loads(text_data)
message = data["message"]
username = data.get("username", "Anonymous")
# Broadcast to the room group
await self.channel_layer.group_send(
self.room_group_name,
{
"type": "chat.message", # Maps to chat_message method
"message": message,
"username": username,
},
)
async def chat_message(self, event):
"""Handle messages broadcast to the group."""
await self.send(text_data=json.dumps({
"message": event["message"],
"username": event["username"],
}))
Key concepts:
self.channel_name— unique identifier for this WebSocket connectiongroup_add— adds a channel to a named groupgroup_send— sends a message to every channel in the grouptype: "chat.message"— determines which method handles the event (dots become underscores:chat_message)
Building a Live Chat Room
The Template
<!-- chat/templates/chat/room.html -->
<!DOCTYPE html>
<html>
<body>
<h1>Chat Room: {{ room_name }}</h1>
<div id="chat-log" style="height: 400px; overflow-y: scroll; border: 1px solid #ccc; padding: 10px;"></div>
<input id="chat-input" type="text" placeholder="Type a message..." />
<button id="chat-send">Send</button>
<script>
const roomName = "{{ room_name }}";
const chatSocket = new WebSocket(
`ws://${window.location.host}/ws/chat/${roomName}/`
);
chatSocket.onmessage = function (e) {
const data = JSON.parse(e.data);
const chatLog = document.getElementById("chat-log");
chatLog.innerHTML += `<p><strong>${data.username}:</strong> ${data.message}</p>`;
chatLog.scrollTop = chatLog.scrollHeight;
};
chatSocket.onclose = function () {
console.log("WebSocket closed unexpectedly");
};
document.getElementById("chat-send").onclick = function () {
const input = document.getElementById("chat-input");
chatSocket.send(JSON.stringify({
message: input.value,
username: "{{ request.user.username|default:'Guest' }}",
}));
input.value = "";
};
document.getElementById("chat-input").onkeyup = function (e) {
if (e.key === "Enter") {
document.getElementById("chat-send").click();
}
};
</script>
</body>
</html>
The View
# chat/views.py
from django.shortcuts import render
def chat_room(request, room_name):
return render(request, "chat/room.html", {"room_name": room_name})
# chat/urls.py
from django.urls import path
from . import views
urlpatterns = [
path("<str:room_name>/", views.chat_room, name="chat-room"),
]
Persisting Messages to the Database
Use database_sync_to_async to call Django ORM methods from async consumers:
from channels.db import database_sync_to_async
from .models import Message, Room
class ChatRoomConsumer(AsyncWebsocketConsumer):
async def receive(self, text_data):
data = json.loads(text_data)
message = data["message"]
username = data.get("username", "Anonymous")
# Save to database
await self.save_message(username, message)
# Broadcast to group
await self.channel_layer.group_send(
self.room_group_name,
{
"type": "chat.message",
"message": message,
"username": username,
},
)
@database_sync_to_async
def save_message(self, username, message):
room = Room.objects.get(name=self.room_name)
Message.objects.create(room=room, username=username, content=message)
Authentication in WebSockets
AuthMiddlewareStack in asgi.py populates self.scope["user"] from the session cookie, just like request.user in HTTP views:
async def connect(self):
self.user = self.scope["user"]
if self.user.is_anonymous:
await self.close()
return
await self.accept()
For token-based auth (JWT), write custom middleware:
# chat/middleware.py
from channels.middleware import BaseMiddleware
from channels.db import database_sync_to_async
from django.contrib.auth.models import AnonymousUser
from rest_framework_simplejwt.tokens import AccessToken
from django.contrib.auth import get_user_model
User = get_user_model()
class JWTAuthMiddleware(BaseMiddleware):
async def __call__(self, scope, receive, send):
query_string = scope.get("query_string", b"").decode()
token = dict(
param.split("=") for param in query_string.split("&") if "=" in param
).get("token")
scope["user"] = await self.get_user(token) if token else AnonymousUser()
return await super().__call__(scope, receive, send)
@database_sync_to_async
def get_user(self, token_str):
try:
token = AccessToken(token_str)
return User.objects.get(id=token["user_id"])
except Exception:
return AnonymousUser()
Sending Messages from Outside Consumers
Trigger WebSocket messages from views, Celery tasks, or management commands:
# In a view or task
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
"chat_general",
{
"type": "chat.message",
"message": "System: server maintenance in 5 minutes",
"username": "System",
},
)
This is useful for notifications triggered by background tasks, admin actions, or webhooks.
Deployment
In production, replace Django’s development server with Daphne (ASGI server):
pip install daphne
daphne -b 0.0.0.0 -p 8000 myproject.asgi:application
Or use uvicorn:
pip install uvicorn
uvicorn myproject.asgi:application --host 0.0.0.0 --port 8000
Put an ASGI-aware reverse proxy (Nginx) in front:
upstream channels_backend {
server localhost:8000;
}
server {
location /ws/ {
proxy_pass http://channels_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
location / {
proxy_pass http://channels_backend;
proxy_set_header Host $host;
}
}
The Upgrade and Connection headers are required for WebSocket connections to pass through Nginx.
Summary
Django Channels brings real-time capabilities to Django through ASGI and WebSocket consumers. Use channel layers with Redis to broadcast messages across groups. Handle authentication with AuthMiddlewareStack or custom JWT middleware. Deploy with Daphne or uvicorn behind Nginx. For most real-time features — chat, notifications, live dashboards — Channels integrates naturally with Django’s auth, ORM, and middleware ecosystem.
Related articles
- Django Django Channels and WebSockets Tutorial
Add real-time features to Django with Channels. Build a WebSocket chat consumer, set up the ASGI server, and broadcast events with channel layers.
- FastAPI WebSocket Endpoints in FastAPI for Real-Time Features
Build real-time features with FastAPI WebSockets including chat rooms, live notifications, and connection management patterns.
- GraphQL GraphQL Subscriptions for Real-Time Data
Build real-time features with GraphQL subscriptions using WebSockets, including setup with Apollo Server and client-side integration.
- Web Building Real-Time Features with WebSockets and SSE
Implement real-time features using WebSocket and Server-Sent Events: connection management, reconnection, scaling with Redis pub/sub, and choosing the right protocol.