Skip to content
C Codeloom
Django

Django Channels and WebSockets Tutorial

Add real-time features to Django with Channels. Build a WebSocket chat consumer, set up the ASGI server, and broadcast events with channel layers.

·3 min read · By Codeloom
Intermediate 10 min read

What you'll learn

  • How Channels extends Django to ASGI
  • What a consumer is
  • How channel layers broadcast events
  • Setting up Redis as a backing store
  • A working WebSocket chat example

Prerequisites

  • Comfortable with Django views and Python async basics

What and Why

Plain Django is built on WSGI, a synchronous request-response protocol. It works great for HTML pages and JSON APIs, but it cannot keep a long-lived connection open. WebSockets need that. Django Channels brings ASGI support and gives you a unified way to handle HTTP, WebSocket, and background protocols inside the same project.

Mental Model

Channels reimagines a Django app as a set of consumers, each handling a protocol. Routing decides which consumer handles a connection. A channel layer, usually Redis-backed, lets consumers send messages to each other across processes.

Browser <--ws://--> Daphne/Uvicorn (ASGI)
                        |
                        v
                ProtocolTypeRouter
                /            \
             http             websocket
              |                  |
            Django           Consumer
                                |
                                v
                        Channel Layer (Redis)
                                |
                                v
                        Group: chat_room_1
Channels architecture

Hands-on Example

Install and configure Channels.

pip install channels channels-redis daphne
# settings.py
INSTALLED_APPS = ["daphne", "django.contrib.auth", "django.contrib.contenttypes",
                  "django.contrib.sessions", "chat"]

ASGI_APPLICATION = "myproject.asgi.application"

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

Wire ASGI.

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

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

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

Write the consumer and route.

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

class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.room = self.scope["url_route"]["kwargs"]["room"]
        self.group = f"chat_{self.room}"
        await self.channel_layer.group_add(self.group, self.channel_name)
        await self.accept()

    async def disconnect(self, code):
        await self.channel_layer.group_discard(self.group, self.channel_name)

    async def receive(self, text_data):
        data = json.loads(text_data)
        await self.channel_layer.group_send(
            self.group,
            {"type": "chat.message", "message": data["message"]},
        )

    async def chat_message(self, event):
        await self.send(text_data=json.dumps({"message": event["message"]}))
# chat/routing.py
from django.urls import re_path
from .consumers import ChatConsumer

websocket_urlpatterns = [
    re_path(r"^ws/chat/(?P<room>\w+)/$", ChatConsumer.as_asgi()),
]

Connect from the browser.

const ws = new WebSocket("ws://localhost:8000/ws/chat/general/");
ws.onmessage = (e) => console.log(JSON.parse(e.data));
ws.onopen = () => ws.send(JSON.stringify({ message: "hello" }));

Run the server with Daphne or Uvicorn.

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

Common Pitfalls

  • Forgetting to install daphne or channels-redis. Without Redis, the in-memory layer cannot share messages across processes.
  • Calling synchronous ORM code inside an async consumer. Wrap it with database_sync_to_async.
  • Sending raw text without JSON framing. Define a small protocol up front.
  • Authentication confusion. AuthMiddlewareStack only works if the WebSocket carries a session cookie. For tokens, write custom middleware.

Practical Tips

  • Use groups to broadcast to many connections. Each connection joins one or more groups by name.
  • Keep consumers thin. Push business logic into services so you can unit test it without ASGI.
  • Add heartbeats or use ping_interval on the client to detect broken connections quickly.
  • For local development, run Redis in Docker. It saves a lot of “why is the channel layer not working” debugging.
  • Use the Channels test client WebsocketCommunicator to write integration tests for your consumers.

Wrap-up

Channels gives Django superpowers for real-time features without abandoning the framework you know. Set up ASGI, write a consumer, back it with Redis, and you have a foundation for chat, notifications, live dashboards, and collaborative editing. Start small with one room, then scale by adding groups and background workers as needs grow.