Rohan Yeole - Homepage Rohan Yeole

Real-Time Django: WebSockets with Django Channels and Redis

May 10, 20261 min read

Django Channels extends Django to handle WebSockets natively via ASGI. Where a standard Django view handles an HTTP request and returns a response, a Channels consumer handles a persistent WebSocket connection — receiving messages, processing them, and sending responses over the same open connection.

This guide walks through a complete real-time chat or notification setup from scratch.

How It Works

The architecture has four components:

Client (browser) ←→ WebSocket ←→ ASGI server (Daphne) ←→ Django Channels
                                                                 ↕
                                                          Channel Layer (Redis)
                                                                 ↕
                                                    Other consumers / workers

The channel layer (Redis) is what makes multi-server real-time work. When a consumer on server A wants to send a message to a consumer on server B, it pushes the message to Redis. The channel layer delivers it. Without a channel layer, consumers on different server processes cannot communicate.

Installation

pip install channels channels-redis daphne

Add to INSTALLED_APPS:

INSTALLED_APPS = [
    "daphne",  # Must be before django.contrib.staticfiles
    "channels",
    # ...
]

ASGI Configuration

Replace wsgi.py with an asgi.py:

# 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 myapp.routing

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")

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

Configure ASGI_APPLICATION and the channel layer in settings:

# settings.py
ASGI_APPLICATION = "myproject.asgi.application"

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

Writing a Consumer

A consumer is the WebSocket equivalent of a view. This one handles a real-time notification channel:

# myapp/consumers.py
import json
from channels.generic.websocket import AsyncWebsocketConsumer

class NotificationConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.user_id = self.scope["user"].id
        self.group_name = f"notifications_{self.user_id}"

        # Join the user's notification group
        await self.channel_layer.group_add(
            self.group_name,
            self.channel_name,
        )
        await self.accept()

    async def disconnect(self, close_code):
        await self.channel_layer.group_discard(
            self.group_name,
            self.channel_name,
        )

    async def receive(self, text_data):
        # Handle incoming messages from the client (optional)
        data = json.loads(text_data)
        # Process or echo

    async def notification_message(self, event):
        # Called when a message is sent to the group
        await self.send(text_data=json.dumps({
            "type": "notification",
            "message": event["message"],
        }))

URL Routing for WebSockets

# myapp/routing.py
from django.urls import re_path
from . import consumers

websocket_urlpatterns = [
    re_path(r"ws/notifications/$", consumers.NotificationConsumer.as_asgi()),
]

Sending Messages from Django Code

To push a notification from a Django view or Celery task:

from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync

def send_notification(user_id, message):
    channel_layer = get_channel_layer()
    group_name = f"notifications_{user_id}"

    async_to_sync(channel_layer.group_send)(
        group_name,
        {
            "type": "notification_message",  # Maps to consumer method
            "message": message,
        }
    )

The type field maps to the consumer method name (with dots replaced by underscores). "notification_message" triggers notification_message() on the consumer.

Call this from a Celery task or a post-save signal and every connected client for that user receives the notification instantly.

Frontend Connection

const ws = new WebSocket(`wss://${location.host}/ws/notifications/`);

ws.onmessage = (event) => {
    const data = JSON.parse(event.data);
    if (data.type === "notification") {
        showNotificationBadge(data.message);
    }
};

ws.onclose = () => {
    // Reconnect after 3 seconds
    setTimeout(() => connectWebSocket(), 3000);
};

Always implement reconnection logic — WebSocket connections drop on network changes, server restarts, and idle timeouts.

Production Deployment with Daphne

Daphne is the ASGI server developed alongside Django Channels. Run it instead of Gunicorn:

daphne -b 0.0.0.0 -p 8000 myproject.asgi:application

Nginx configuration to proxy both HTTP and WebSocket connections:

server {
    listen 443 ssl;
    server_name example.com;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    location /ws/ {
        proxy_pass http://127.0.0.1:8000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_read_timeout 86400;  # Keep WebSocket connections alive
    }
}

The Upgrade and Connection headers are required for WebSocket handshake. proxy_read_timeout 86400 prevents Nginx from closing idle WebSocket connections after 60 seconds.

Systemd Service

[Unit]
Description=Daphne ASGI Server
After=network.target

[Service]
User=www-data
WorkingDirectory=/srv/myproject
ExecStart=/srv/myproject/.venv/bin/daphne -b 127.0.0.1 -p 8000 myproject.asgi:application
Restart=always

[Install]
WantedBy=multi-user.target

When to Use Channels vs Alternatives

Channels is the right choice when: - You need WebSockets in an existing Django application - Real-time features are a core product requirement (chat, notifications, live dashboards) - You want to keep everything in one codebase

Consider alternatives when: - You only need simple one-way server-sent events (SSE is simpler than WebSockets) - Real-time is a minor feature and you want to avoid operational complexity (use a third-party service like Pusher or Ably)

If you need real-time features built into a Django application — WebSockets, notifications, live updates — hire me as a Django developer who has run Channels in production with Daphne and Redis.

Chat with me on WhatsApp