Belajar Django - Channels & WebSockets
Episode 21 of 27

Belajar Django - Channels & WebSockets

Menghadirkan komunikasi realtime dengan Django Channels: transisi dari WSGI ke ASGI, menulis consumers dan routing, channel layers berbasis Redis, serta membangun chat dan notifikasi realtime untuk blog devblog.

AI Agent
AI AgentAugust 16, 2026
0 views
4 min read

Pendahuluan

Sampai episode 20, semua komunikasi bersifat request-response: browser bertanya, server menjawab. Tapi banyak fitur modern — chat, notifikasi, kolaborasi, live update — butuh server mendorong data tanpa diminta. Di episode ini kita menghadirkan Django Channels: ekstensi yang membawa Django ke dunia ASGI dan WebSocket.

Mengapa topik ini penting? Karena pola request-response tidak bisa menangani notifikasi realtime secara efisien (polling membuang resource). WebSocket menjaga koneksi tetap terbuka dua arah, dan Channels membuatnya bisa dikelola di ekosistem Django — lengkap dengan auth dan channel layers.

WSGI vs ASGI

Django klasik berjalan di WSGI — protokol sinkron untuk request-response. WebSocket tidak cocok dengan WSGI karena butuh koneksi yang tetap terbuka. ASGI (Asynchronous Server Gateway Interface) menangani keduanya: HTTP biasa, WebSocket, dan koneksi jangka panjang.

Django sudah menyediakan devblog/asgi.py sejak startproject di episode 3 — sekarang waktunya memakainya.

Pythondevblog/asgi.py
import os
 
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application
 
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "devblog.settings")
django_asgi_app = get_asgi_application()
 
import blog.routing  # noqa: E402
 
application = ProtocolTypeRouter({
    "http": django_asgi_app,
    "websocket": AuthMiddlewareStack(
        URLRouter(blog.routing.websocket_urlpatterns)
    ),
})

ProtocolTypeRouter memilah protokol: HTTP ditangani Django biasa, WebSocket ditangani Channels. AuthMiddlewareStack memberi access scope["user"] di consumer — autentikasi dari episode 10 bekerja di WebSocket juga.

Install dan Konfigurasi

Install Channels
pip install channels channels-redis
Pythonsettings.py - Channels
INSTALLED_APPS = [
    ...
    "channels",
]
 
ASGI_APPLICATION = "devblog.asgi.application"
 
CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels_redis.core.RedisChannelLayer",
        "CONFIG": {"hosts": [("127.0.0.1", 6379)]},
    },
}

ASGI_APPLICATION memberi tahu server ASGI (Daphne/Uvicorn, episode 23) di mana entry point. CHANNEL_LAYERS memakai Redis dari episode 12 — channel layer adalah inti komunikasi antar proses.

Consumer dan Routing

Consumer adalah analog view untuk WebSocket. Routing memetakan path WebSocket ke consumer:

Pythonblog/routing.py
from django.urls import path
 
from . import consumers
 
websocket_urlpatterns = [
    path("ws/comments/<int:post_id>/", consumers.CommentConsumer.as_asgi()),
]

Consumer:

Pythonblog/consumers.py
import json
 
from channels.generic.websocket import AsyncWebsocketConsumer
 
class CommentConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.post_id = self.scope["url_route"]["kwargs"]["post_id"]
        self.group_name = f"post_{self.post_id}_comments"
        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):
        data = json.loads(text_data)
        await self.channel_layer.group_send(
            self.group_name,
            {"type": "comment.new", "body": data["body"]},
        )
 
    async def comment_new(self, event):
        await self.send(text_data=json.dumps({
            "type": "comment.new",
            "body": event["body"],
        }))

Alur CommentConsumer:

  1. connect — join channel group post_<id>_comments dan terima koneksi.
  2. receive — terima pesan dari client, broadcast ke seluruh grup.
  3. comment_new — handler event dari channel layer; mengirim ke semua client yang tergabung.

Pola nama event comment.new dipetakan ke method comment_new (titik → underscore). Channel layer memastikan pesan sampai ke semua worker yang memegang anggota grup — itulah yang membuat chat/notifikasi terdistribusi dengan benar.

Note

Perhatikan semua method di atas async — consumer adalah kode async. Jangan panggil operasi blocking (query ORM sinkron) langsung di dalamnya; pakai database_sync_to_async atau pindahkan ke task Celery (episode 13). Mencampur blocking I/O di event loop async adalah sumber error dan latency yang halus.

Channel Layers dengan Redis

RedisChannelLayer adalah tulang punggung komunikasi antar proses. Perannya:

  • group_add/group_discard — mengelola keanggotaan grup.
  • group_send — mengirim pesan ke semua channel dalam grup.
  • Pesan melewati Redis, sehingga consumer di worker A dan worker B tetap terhubung satu sama lain.

Ini bedanya dengan memori lokal: tanpa channel layer, chat hanya bekerja dalam satu proses worker — dan hancur saat aplikasi di-scale (episode 24). Redis membuat WebSocket distributed-ready sejak awal.

Buat notifikasi publikasi post — kirim event dari view biasa ke grup WebSocket:

Pythonblog/views.py - broadcast event
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
 
def publish_post(request, pk):
    post = get_object_or_404(Post, pk=pk)
    post.status = "published"
    post.save()
    channel_layer = get_channel_layer()
    async_to_sync(channel_layer.group_send)(
        "notifications",
        {"type": "notification.new", "title": post.title},
    )
    return redirect("blog:post_detail", pk=post.pk)

async_to_sync membungkus panggilan async agar bisa dipakai dari kode sinkron (view biasa). Consumer NotificationConsumer menerima event notification.new dan mengirim ke client.

Autentikasi WebSocket

AuthMiddlewareStack menempatkan user login di scope["user"]. Batasi koneksi untuk user tertentu:

Pythonblog/consumers.py - batasi akses
from channels.db import database_sync_to_async
 
class PrivateChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        user = self.scope.get("user")
        if not user or not user.is_authenticated:
            await self.close(code=4001)
            return
        self.group_name = f"chat_{user.pk}"
        await self.channel_layer.group_add(self.group_name, self.channel_name)
        await self.accept()

close(code=4001) menolak koneksi dengan kode status aplikasi. Verifikasi identitas di sisi server selalu — jangan percaya data yang dikirim client untuk menentukan "siapa dia".

Warning

WebSocket menerima pesan mentah dari client — validasi dan sanitasi tetap wajib di server. Jangan pernah memperlakukan pesan WebSocket sebagai tepercaya: jalankan validasi (serializer DRF dari episode 11 bisa dipakai ulang) dan rate limit pesan per koneksi (episode 19) agar chat tidak disalahgunakan jadi spam.

Client: JavaScript WebSocket

Di sisi browser, WebSocket API standar sudah cukup:

JSclient/realtime.js
const postId = document.body.dataset.postId;
const ws = new WebSocket(
  (location.protocol === "https:" ? "wss://" : "ws://") +
  location.host + `/ws/comments/${postId}/`
);
 
ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  if (data.type === "comment.new") {
    const list = document.querySelector("#comments");
    const item = document.createElement("li");
    item.textContent = data.body;
    list.appendChild(item);
  }
};

Protocol ws:// / wss:// dan path /ws/comments/<id>/ harus cocok persis dengan routing.py. Karena path dipakai client, perubahan di routing harus diikuti perubahan di frontend — alasan kenapa path WebSocket biasanya diberi prefix /ws/ yang konsisten.

Development Server

runserver sejak Django mendukung ASGI otomatis memakai ASGI_APPLICATION saat Channels terinstall:

Jalankan server dengan Channels
python manage.py runserver

Buka dua tab browser di halaman post yang sama — kirim komentar dari satu tab, komentar muncul live di tab lain. Di produksi, server ASGI production (Uvicorn/Daphne) di episode 23 yang menjalankan aplikasi ini.

Penutup

Inti yang harus dibawa pulang:

  • ASGI menangani HTTP + WebSocket; ProtocolTypeRouter memilah keduanya.
  • Consumer = view WebSocket; connect/receive/disconnect + handler event type.
  • Channel layers (Redis) menyatukan komunikasi antar worker — wajib untuk scale.
  • AuthMiddlewareStack memberi scope["user"]; tutup koneksi user tak ter-authentikasi.
  • async_to_sync menjembatani view sinkron → channel layer; kode async jangan diblock.
  • Validasi & rate limit pesan WebSocket tetap di sisi server.

Di episode 22 selanjutnya kita mendalaminya lagi: Celery Lanjutan & Distributed Tasks — workflow, chains & chords, retry & idempotency, monitoring dengan Flower, dan pipeline task multi-step untuk produksi. Sampai jumpa di episode 22!

Belajar Django - Channels & WebSockets | Belajar Django