Real-Time Django: Building a Chat App with WebSockets
Django was born in the era of "Request -> Response". You click a link, the server sends a page, and the connection closes. But modern apps need Real-Time features: Chat messages popping up instantly, notification bells ringing, or live graphs updating.
For this, we need WebSockets, and in Django, we use Django Channels.
WSGI vs. ASGI
Standard Django runs on WSGI (Web Server Gateway Interface), which is synchronous (blocking). To handle hundreds of open WebSocket connections simultaneously, we need ASGI (Asynchronous Server Gateway Interface).
Step 1: Installation
We need channels and daphne (the ASGI interface server).
pip install channels daphne channels-redis
Update settings.py:
INSTALLED_APPS = [
'daphne', # Must be at the top
'django.contrib.admin',
# ...
'channels',
]
ASGI_APPLICATION = 'myproject.asgi.application'
# We need Redis to act as the "Channel Layer" (passing messages between users)
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [("127.0.0.1", 6379)],
},
},
}
Step 2: Consumers
In classic Django, we have Views. In Channels, we have Consumers. A consumer handles the WebSocket lifecycle: Connect, Receive, Disconnect.
Create chat/consumers.py:
import json
from channels.generic.websocket import AsyncWebsocketConsumer
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.room_name = "global_chat"
self.room_group_name = f"chat_{self.room_name}"
# Join room group via Redis
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, close_code):
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
# Receive message from WebSocket (Frontend)
async def receive(self, text_data):
data = json.loads(text_data)
message = data['message']
# Send message to room group (Redis)
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat_message',
'message': message
}
)
# Receive message from room group
async def chat_message(self, event):
message = event['message']
# Send message to WebSocket (Frontend)
await self.send(text_data=json.dumps({
'message': message
}))
Step 3: Routing
We need a URL router specifically for WebSockets.
In chat/routing.py:
from django.urls import re_path
from . import consumers
websocket_urlpatterns = [
re_path(r'ws/chat/$', consumers.ChatConsumer.as_asgi()),
]
Step 4: Frontend
The JavaScript is surprisingly simple.
const chatSocket = new WebSocket('ws://' + window.location.host + '/ws/chat/');
chatSocket.onmessage = function(e) {
const data = JSON.parse(e.data);
console.log("New Message:", data.message);
};
chatSocket.onclose = function(e) {
console.error('Chat socket closed unexpectedly');
};
// Sending a message
document.querySelector('#send-btn').onclick = function(e) {
chatSocket.send(JSON.stringify({
'message': 'Hello World!'
}));
};
Conclusion
With Django Channels, you don't need to switch to Node.js to get real-time capabilities. You keep your ORM, your authentication, and your project structure, but gain the power of persistent connections.