Building a Real Time Chat App Using Django Channels

Introduction

Real-time applications like chat apps, notifications, and live dashboards require instant communication between the server and client. Traditional HTTP requests are one-way and cannot push updates to clients immediately. This is where WebSockets and Django Channels come in.

Django Channels extends the Django framework to support asynchronous communication and WebSockets, allowing you to build interactive, real-time applications.

In this guide, we will build a fully functional real-time chat app using:

  • Django
  • Django Channels
  • WebSockets
  • Redis (as the channel layer backend)

By the end, you will understand how to structure a Channels project, manage WebSocket connections, and broadcast messages to multiple clients.

Step 1: Setting Up the Project

Start by creating a new Django project.

django-admin startproject chat_project
cd chat_project
python manage.py startapp chat

Install necessary dependencies:

pip install channels channels_redis

Add channels and your app to INSTALLED_APPS in settings.py:

INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'channels',
'chat',
]

Step 2: Configure ASGI

Unlike regular Django projects that use WSGI, Channels uses ASGI for asynchronous communication.

In chat_project/asgi.py:

import os
import django
from channels.routing import get_default_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'chat_project.settings')
django.setup()
application = get_default_application()

Update settings.py to define the ASGI application:

ASGI_APPLICATION = 'chat_project.asgi.application'

Step 3: Configure Redis Channel Layer

Django Channels requires a channel layer to handle communication between consumers. Redis is the most common backend.

Install Redis on your system or use Docker:

docker run -p 6379:6379 redis

Add channel layer settings in settings.py:

CHANNEL_LAYERS = {
"default": {
    "BACKEND": "channels_redis.core.RedisChannelLayer",
    "CONFIG": {
        "hosts": [("127.0.0.1", 6379)],
    },
},
}

Step 4: Creating the Chat Consumer

A consumer is analogous to a Django view but for WebSockets. Consumers handle events like connect, disconnect, and receive.

Create chat/consumers.py:

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_name = f'chat_{self.room_name}'
    # Join 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 room group
    await self.channel_layer.group_discard(
        self.room_group_name,
        self.channel_name
    )
# Receive message from WebSocket
async def receive(self, text_data):
    text_data_json = json.loads(text_data)
    message = text_data_json['message']
    username = text_data_json['username']
    # Send message to room group
    await self.channel_layer.group_send(
        self.room_group_name,
        {
            'type': 'chat_message',
            'message': message,
            'username': username
        }
    )
# Receive message from room group
async def chat_message(self, event):
    message = event['message']
    username = event['username']
    # Send message to WebSocket
    await self.send(text_data=json.dumps({
        'message': message,
        'username': username
    }))

Step 5: Define WebSocket Routing

Create chat/routing.py to define WebSocket URLs:

from django.urls import re_path
from . import consumers

websocket_urlpatterns = [
re_path(r'ws/chat/(?P<room_name>\w+)/$', consumers.ChatConsumer.as_asgi()),
]

Then, update chat_project/routing.py:

from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
import chat.routing

application = ProtocolTypeRouter({
'http': get_asgi_application(),
'websocket': AuthMiddlewareStack(
    URLRouter(
        chat.routing.websocket_urlpatterns
    )
),
})

Step 6: Create Chat Templates

Create chat/templates/chat/room.html:

<!DOCTYPE html>
<html>
<head>
&lt;title&gt;Chat Room&lt;/title&gt;
</head> <body>
&lt;h2&gt;Room: {{ room_name }}&lt;/h2&gt;
&lt;div id="chat-log"&gt;&lt;/div&gt;
&lt;input id="chat-message-input" type="text" size="100"&gt;
&lt;input id="chat-message-submit" type="button" value="Send"&gt;
&lt;script&gt;
    const roomName = "{{ room_name }}";
    const username = prompt("Enter your username:");
    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 += &amp;lt;p&amp;gt;&amp;lt;b&amp;gt;${data.username}:&amp;lt;/b&amp;gt; ${data.message}&amp;lt;/p&amp;gt;;
    };
    chatSocket.onclose = function(e) {
        console.error('Chat socket closed unexpectedly');
    };
    document.getElementById('chat-message-submit').onclick = function(e) {
        const messageInputDom = document.getElementById('chat-message-input');
        const message = messageInputDom.value;
        chatSocket.send(JSON.stringify({
            'message': message,
            'username': username
        }));
        messageInputDom.value = '';
    };
&lt;/script&gt;
</body> </html>

Step 7: Create URL Patterns

In chat/urls.py:

from django.urls import path
from . import views

urlpatterns = [
path('&lt;str:room_name&gt;/', views.room, name='room'),
]

In chat/views.py:

from django.shortcuts import render

def room(request, room_name):
return render(request, 'chat/room.html', {
    'room_name': room_name
})

Include the app URLs in chat_project/urls.py:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
path('admin/', admin.site.urls),
path('chat/', include('chat.urls')),
]

Step 8: Running the Application

Run Redis locally first (if using Docker):

docker run -p 6379:6379 redis

Then start the Django development server:

python manage.py runserver

Visit: http://127.0.0.1:8000/chat/room1/

Open the same URL in multiple tabs, enter usernames, and send messages — all tabs will update in real-time.


Step 9: Enhancing the Chat

1. Persisting Messages

To store chat history, create a Message model in chat/models.py:

from django.db import models

class Message(models.Model):
room_name = models.CharField(max_length=100)
username = models.CharField(max_length=50)
content = models.TextField()
timestamp = models.DateTimeField(auto_now_add=True)

Update the consumer to save messages:

from .models import Message

@receiver(order_created)
async def receive(self, text_data):
...
# Save message to database
Message.objects.create(
    room_name=self.room_name,
    username=username,
    content=message
)

2. Display Chat History

In views.py, fetch previous messages:

from .models import Message

def room(request, room_name):
messages = Message.objects.filter(room_name=room_name)
return render(request, 'chat/room.html', {
    'room_name': room_name,
    'messages': messages
})

Update room.html to display historical messages:

<div id="chat-log">
{% for msg in messages %}
    &lt;p&gt;&lt;b&gt;{{ msg.username }}:&lt;/b&gt; {{ msg.content }}&lt;/p&gt;
{% endfor %}
</div>

3. Styling

Add simple CSS for better appearance:

<style>
#chat-log {
    border: 1px solid black;
    height: 300px;
    overflow-y: scroll;
    padding: 10px;
}
#chat-message-input {
    width: 80%;
}
</style>

4. Authentication

You can integrate Django authentication so users automatically use their account usernames instead of entering them manually.

username = "{{ request.user.username }}"

Step 10: Production Considerations

  1. Use Redis as the production channel layer.
  2. Deploy with ASGI-compatible servers like Daphne or Uvicorn:
pip install daphne
daphne chat_project.asgi:application
  1. Secure WebSocket connections with HTTPS/WSS.
  2. Use Celery for asynchronous notifications or message processing if needed.

Step 11: Scaling

To scale the chat app:

  • Add multiple worker nodes with Channels and Redis.
  • Load balance using Nginx or HAProxy.
  • Use Redis Pub/Sub for inter-process communication.
  • Limit messages per room or paginate history to reduce memory usage.

Step 12: Testing

Unit Test Example for Consumer:

from channels.testing import WebsocketCommunicator
from chat_project.asgi import application
import pytest
import asyncio

@pytest.mark.asyncio
async def test_chat_consumer():
communicator = WebsocketCommunicator(application, "/ws/chat/testroom/")
connected, subprotocol = await communicator.connect()
assert connected
await communicator.send_json_to({"message": "Hello", "username": "Alice"})
response = await communicator.receive_json_from()
assert response&#91;'message'] == "Hello"
await communicator.disconnect()

This ensures your consumer behaves correctly.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *