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.
What you'll learn
- ✓How Channels extends Django to ASGI
- ✓What a consumer is
- ✓How channel layers broadcast events
- ✓Setting up Redis as a backing store
- ✓A working WebSocket chat example
Prerequisites
- •Comfortable with Django views and Python async basics
What and Why
Plain Django is built on WSGI, a synchronous request-response protocol. It works great for HTML pages and JSON APIs, but it cannot keep a long-lived connection open. WebSockets need that. Django Channels brings ASGI support and gives you a unified way to handle HTTP, WebSocket, and background protocols inside the same project.
Mental Model
Channels reimagines a Django app as a set of consumers, each handling a protocol. Routing decides which consumer handles a connection. A channel layer, usually Redis-backed, lets consumers send messages to each other across processes.
Browser <--ws://--> Daphne/Uvicorn (ASGI)
|
v
ProtocolTypeRouter
/ \
http websocket
| |
Django Consumer
|
v
Channel Layer (Redis)
|
v
Group: chat_room_1 Hands-on Example
Install and configure Channels.
pip install channels channels-redis daphne
# settings.py
INSTALLED_APPS = ["daphne", "django.contrib.auth", "django.contrib.contenttypes",
"django.contrib.sessions", "chat"]
ASGI_APPLICATION = "myproject.asgi.application"
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {"hosts": [("127.0.0.1", 6379)]},
},
}
Wire ASGI.
# 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 chat.routing
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")
application = ProtocolTypeRouter({
"http": get_asgi_application(),
"websocket": AuthMiddlewareStack(URLRouter(chat.routing.websocket_urlpatterns)),
})
Write the consumer and route.
# chat/consumers.py
import json
from channels.generic.websocket import AsyncWebsocketConsumer
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.room = self.scope["url_route"]["kwargs"]["room"]
self.group = f"chat_{self.room}"
await self.channel_layer.group_add(self.group, self.channel_name)
await self.accept()
async def disconnect(self, code):
await self.channel_layer.group_discard(self.group, self.channel_name)
async def receive(self, text_data):
data = json.loads(text_data)
await self.channel_layer.group_send(
self.group,
{"type": "chat.message", "message": data["message"]},
)
async def chat_message(self, event):
await self.send(text_data=json.dumps({"message": event["message"]}))
# chat/routing.py
from django.urls import re_path
from .consumers import ChatConsumer
websocket_urlpatterns = [
re_path(r"^ws/chat/(?P<room>\w+)/$", ChatConsumer.as_asgi()),
]
Connect from the browser.
const ws = new WebSocket("ws://localhost:8000/ws/chat/general/");
ws.onmessage = (e) => console.log(JSON.parse(e.data));
ws.onopen = () => ws.send(JSON.stringify({ message: "hello" }));
Run the server with Daphne or Uvicorn.
daphne -b 0.0.0.0 -p 8000 myproject.asgi:application
Common Pitfalls
- Forgetting to install
daphneorchannels-redis. Without Redis, the in-memory layer cannot share messages across processes. - Calling synchronous ORM code inside an async consumer. Wrap it with
database_sync_to_async. - Sending raw text without JSON framing. Define a small protocol up front.
- Authentication confusion.
AuthMiddlewareStackonly works if the WebSocket carries a session cookie. For tokens, write custom middleware.
Practical Tips
- Use groups to broadcast to many connections. Each connection joins one or more groups by name.
- Keep consumers thin. Push business logic into services so you can unit test it without ASGI.
- Add heartbeats or use
ping_intervalon the client to detect broken connections quickly. - For local development, run Redis in Docker. It saves a lot of “why is the channel layer not working” debugging.
- Use the Channels test client
WebsocketCommunicatorto write integration tests for your consumers.
Wrap-up
Channels gives Django superpowers for real-time features without abandoning the framework you know. Set up ASGI, write a consumer, back it with Redis, and you have a foundation for chat, notifications, live dashboards, and collaborative editing. Start small with one room, then scale by adding groups and background workers as needs grow.
Related articles
- FastAPI FastAPI WebSockets Tutorial
Build real-time features with FastAPI WebSockets. Manage connections, broadcast messages, and handle disconnects cleanly.
- GraphQL GraphQL Subscriptions Tutorial
Add real-time data to your GraphQL API with subscriptions. Learn the transport, pubsub patterns, and a working Apollo example.
- Django Django Admin Customization: A Practical Tutorial
Go beyond the default Django admin: customize ModelAdmin classes, list views, search, filters, inline editing, and admin actions to build a usable backoffice your team will actually enjoy.
- Django Django Caching Strategies
Compare per-view, template fragment, low-level, and per-site caching in Django and learn when each pays off.