As an experienced Python developer, real-time communication apps are ubiquitous, ranging from customer support chats to social networking platforms, and Django provides a robust framework for building such apps efficiently.
In this tutorial, I’ll walk you through how to build a real-time chat app in Django. I’ll share practical steps and full source code, so you can create a chat app that works smoothly and can be adapted for use in real-world scenarios, like connecting support agents with customers.
Let’s get in.
What You Need Before Starting
Before we get started, ensure you have:
- Python 3.8+ installed
- Django 4.x installed (
pip install django) - Channels package installed for WebSocket support (
pip install channels) - Redis is installed and running (used as a channel layer backend)
Read Union Operation on Django Models
Step 1: Set Up Your Django Project
First, create a new Django project and app.
django-admin startproject chatproject
cd chatproject
python manage.py startapp chatappAdd the new app and Channels to your 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', # Add Channels
'chatapp', # Your chat app
]Configure Channels as the default backend by adding this to settings.py:
ASGI_APPLICATION = 'chatproject.asgi.application'
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
'hosts': [('127.0.0.1', 6379)],
},
},
}Step 2: Create the ASGI Application
Django’s default is WSGI, but for real-time apps, we use ASGI.
Create asgi.py in your project folder (chatproject/asgi.py):
import os
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application
import chatapp.routing
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'chatproject.settings')
application = ProtocolTypeRouter({
"http": get_asgi_application(),
"websocket": AuthMiddlewareStack(
URLRouter(
chatapp.routing.websocket_urlpatterns
)
),
})Step 3: Define WebSocket Routing
Inside your app folder, create a new file 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()),
]This setup allows users to join chat rooms dynamically.
Check out Python Filter Not in Django
Step 4: Create the WebSocket Consumer
Consumers handle WebSocket connections. Create consumers.py in your app:
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 = self.scope["user"].username if self.scope["user"].is_authenticated else "Anonymous"
# 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: Create Chat Views and Templates
In views.py of chatapp, add:
from django.shortcuts import render
def index(request):
return render(request, 'chatapp/index.html')
def room(request, room_name):
return render(request, 'chatapp/room.html', {
'room_name': room_name
})Create URLs in chatapp/urls.py:
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
path('<str:room_name>/', views.room, name='room'),
]Include these URLs in the project’s urls.py:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('chat/', include('chatapp.urls')),
]Read Check if Python Dictionary is Empty
Templates
Create templates/chatapp/index.html:
<!DOCTYPE html>
<html>
<head>
<title>Chat Rooms</title>
</head>
<body>
<h2>Enter Chat Room</h2>
<form action="" method="get" onsubmit="goToRoom(event)">
<input type="text" id="room-name-input" placeholder="Room name..." required>
<button type="submit">Enter</button>
</form>
<script>
function goToRoom(event) {
event.preventDefault();
const roomName = document.getElementById('room-name-input').value;
window.location.href = `/chat/${roomName}/`;
}
</script>
</body>
</html>Create templates/chatapp/room.html:
<!DOCTYPE html>
<html>
<head>
<title>Chat Room - {{ room_name }}</title>
</head>
<body>
<h2>Room: {{ room_name }}</h2>
<div id="chat-log" style="height:300px; border:1px solid black; overflow-y:scroll;"></div>
<input id="chat-message-input" type="text" size="100" autofocus placeholder="Type your message...">
<button id="chat-message-submit">Send</button>
<script>
const roomName = "{{ room_name }}";
const chatLog = document.getElementById('chat-log');
const chatInput = document.getElementById('chat-message-input');
const chatButton = document.getElementById('chat-message-submit');
const wsScheme = window.location.protocol === "https:" ? "wss" : "ws";
const chatSocket = new WebSocket(
wsScheme + '://' + window.location.host + '/ws/chat/' + roomName + '/'
);
chatSocket.onmessage = function(e) {
const data = JSON.parse(e.data);
const message = document.createElement('div');
message.innerHTML = `<strong>${data.username}:</strong> ${data.message}`;
chatLog.appendChild(message);
chatLog.scrollTop = chatLog.scrollHeight;
};
chatSocket.onclose = function(e) {
console.error('Chat socket closed unexpectedly');
};
chatButton.onclick = function() {
const message = chatInput.value;
if (message.trim() === '') return;
chatSocket.send(JSON.stringify({
'message': message
}));
chatInput.value = '';
};
chatInput.addEventListener("keyup", function(event) {
if (event.key === 'Enter') {
chatButton.click();
}
});
</script>
</body>
</html>Step 6: Run Your Chat App
Make migrations and run the server:
python manage.py migrate
python manage.py runserverVisit http://127.0.0.1:8000/chat/ in your browser, enter a room name, and start chatting in real-time!
I executed the above example code and added the screenshot below.


Read Check if Python Dictionary is Empty
Additional Tips from My Experience
- Authentication: You can easily add user authentication to personalize chats.
- Styling: Use CSS frameworks like Bootstrap to improve UI/UX.
- Deployment: For production, configure Redis on your server and use Daphne or Uvicorn as ASGI servers.
- Scaling: Channels and Redis enable horizontal scaling for many users.
Building a real-time chat app in Django is surprisingly straightforward once you grasp Channels and WebSocket concepts. This project is an excellent way to deepen your understanding of asynchronous programming in Django and build scalable communication tools.
If you want to explore more advanced features, consider adding message persistence with Django models or private messaging between users.
I hope you found this guide helpful. Happy coding!
You may like to read other Django articles:
- Compare Two Integers in Python Django
- Payment Gateway Integration with Django
- How to Add Items to Cart in Django in Python?

I am Bijay Kumar, a Microsoft MVP in SharePoint. Apart from SharePoint, I started working on Python, Machine learning, and artificial intelligence for the last 5 years. During this time I got expertise in various Python libraries also like Tkinter, Pandas, NumPy, Turtle, Django, Matplotlib, Tensorflow, Scipy, Scikit-Learn, etc… for various clients in the United States, Canada, the United Kingdom, Australia, New Zealand, etc. Check out my profile.