DEV Community

Uchit Chakma
Uchit Chakma

Posted on

Building a Real-Time Chat Feature with Django Channels and React

Building a Real-Time Chat Feature with Django Channels and React

Real-time features have become table stakes for modern web applications. Whether it is a customer support widget, a collaborative tool, or a social platform, users expect instant communication without page refreshes. In this article, I will walk through how we built a production-ready real-time chat feature using Django Channels and React at UCDREAMS.

Why Django Channels?

Django is traditionally synchronous. It handles one request at a time per worker. This works fine for standard HTTP requests, but WebSocket connections require persistent, bidirectional communication. Django Channels extends Django to handle WebSockets, background tasks, and asynchronous protocols alongside traditional HTTP.

The beauty of Channels is that it does not replace Django. It layers on top, letting you keep your existing models, ORM, authentication, and admin panel while adding real-time capabilities. For a team already invested in Django, this is a massive advantage over introducing an entirely separate real-time server.

Setting Up the Backend

Start by installing Django Channels and a channel layer. Redis is the recommended backend for production use:

channels==4.0.0
channels-redis==4.2.0
daphne==4.0.0
Enter fullscreen mode Exit fullscreen mode

Configure your Django settings:

INSTALLED_APPS = [
    ...
    "channels",
]

ASGI_APPLICATION = "your_project.asgi.application"

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

Building the WebSocket Consumer

The consumer handles WebSocket connections:

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}"
        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)

    async def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message = text_data_json["message"]
        await self.channel_layer.group_send(
            self.room_group_name,
            {"type": "chat_message", "message": message, "user": self.scope["user"].username},
        )

    async def chat_message(self, event):
        await self.send(text_data=json.dumps({
            "message": event["message"],
            "user": event["user"],
        }))
Enter fullscreen mode Exit fullscreen mode

The React Frontend

On the React side, we use the native WebSocket API:

import React, { useState, useEffect, useRef } from "react";

const Chat = ({ roomName, username }) => {
  const [messages, setMessages] = useState([]);
  const [input, setInput] = useState("");
  const ws = useRef(null);

  useEffect(() => {
    ws.current = new WebSocket(`ws://localhost:8000/ws/chat/${roomName}/`);
    ws.current.onmessage = (event) => {
      const data = JSON.parse(event.data);
      setMessages((prev) => [...prev, data]);
    };
    return () => ws.current.close();
  }, [roomName]);

  const sendMessage = () => {
    if (input.trim()) {
      ws.current.send(JSON.stringify({ message: input }));
      setInput("");
    }
  };

  return (
    <div className="chat-container">
      <div className="messages">
        {messages.map((msg, i) => (
          <div key={i} className="message"><strong>{msg.user}:</strong> {msg.message}</div>
        ))}
      </div>
      <div className="input-area">
        <input value={input} onChange={(e) => setInput(e.target.value)}
          onKeyPress={(e) => e.key === "Enter" && sendMessage()} />
        <button onClick={sendMessage}>Send</button>
      </div>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Handling Authentication

In production, you will want authenticated WebSocket connections. Django Channels provides AuthMiddlewareStack which makes the user available in self.scope.user. For token-based auth with React, pass the token during connection.

Scaling Considerations

For production deployment, consider these points:

  1. Redis Clustering - Use Redis Sentinel or Redis Cluster for high availability
  2. Daphne for Production - Run Daphne as your ASGI server
  3. Horizontal Scaling - Django Channels with Redis allows multiple app servers to share the channel layer
  4. Message Persistence - Store messages in your database for history
  5. Rate Limiting - Implement per-user rate limiting
  6. Connection Management - Handle reconnection with exponential backoff

Real-World Implementation

At UCDREAMS (https://ucdreams.com), we have implemented this architecture for several client projects. One notable implementation powers a real-time customer support dashboard handling thousands of concurrent connections. The same pattern extends beyond chat to live notifications, collaborative editing, and real-time analytics.

You can also check out Captionator (https://captionator.ucdreams.com), a project where real-time WebSocket communication plays a key role in delivering captioning services with minimal latency.

Conclusion

Django Channels plus React gives you a powerful, scalable foundation for real-time features. The learning curve is gentle if you already know Django and React, and the architecture scales from a simple chat room to complex real-time applications. For custom web application development tailored to your business needs, reach out to UCDREAMS.

Top comments (0)