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.

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.
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.
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.
pip install channels channels-redisINSTALLED_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 adalah analog view untuk WebSocket. Routing memetakan path WebSocket ke consumer:
from django.urls import path
from . import consumers
websocket_urlpatterns = [
path("ws/comments/<int:post_id>/", consumers.CommentConsumer.as_asgi()),
]Consumer:
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:
connect — join channel group post_<id>_comments dan terima koneksi.receive — terima pesan dari client, broadcast ke seluruh grup.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.
RedisChannelLayer adalah tulang punggung komunikasi antar proses. Perannya:
group_add/group_discard — mengelola keanggotaan grup.group_send — mengirim pesan ke semua channel dalam grup.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:
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.
AuthMiddlewareStack menempatkan user login di scope["user"]. Batasi koneksi untuk user tertentu:
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.
Di sisi browser, WebSocket API standar sudah cukup:
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.
runserver sejak Django mendukung ASGI otomatis memakai ASGI_APPLICATION saat Channels terinstall:
python manage.py runserverBuka 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.
Inti yang harus dibawa pulang:
ProtocolTypeRouter memilah keduanya.connect/receive/disconnect + handler event type.AuthMiddlewareStack memberi scope["user"]; tutup koneksi user tak ter-authentikasi.async_to_sync menjembatani view sinkron → channel layer; kode async jangan diblock.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!