Building Real-Time Features with Django, HTMX & WebSockets
July 28, 2026 · Ahmad M. Waddah · 42 reads
⚡ Stack: Django · HTMX · WebSockets · PostgreSQL · Docker
❓ The Problem
Social platforms need real-time updates — new posts, likes, comments, notifications. Traditionally this means either:
- Polling — clients hammer the server every N seconds (wasteful, slow)
- SPA + WebSockets — React/Vue + Socket.io (heavy, complex tooling)
Neither fits a Django project that values simplicity and server-rendered HTML.
🛠️ The Approach
1. Django Channels for WebSocket Handling
# consumers.py
class NotificationConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.user = self.scope["user"]
if self.user.is_authenticated:
await self.channel_layer.group_add(
f"user_{self.user.id}", self.channel_name
)
await self.accept()
Channels sits alongside your existing Django app — no need to rewrite anything. It handles the WebSocket lifecycle and message routing.
2. HTMX for DOM Updates
The magic is that HTMX swaps HTML, not JSON. When a new post arrives via WebSocket:
<div hx-ext="ws" ws-connect="/ws/feed/">
<div id="feed-stream">
{% for post in posts %}
{% include "feed/_post.html" %}
{% endfor %}
</div>
</div>
A server-sent message triggers hx-swap on #feed-stream — prepending the new post HTML. No client-side templates, no state management, no re-rendering.
3. Triggering from Django ORM
# signals.py
@receiver(post_save, sender=Post)
async def notify_followers(sender, instance, created, **kwargs):
if created:
await async_to_sync(channel_layer.group_send)(
f"user_feed_{instance.author.id}",
{
"type": "feed.update",
"html": render_post_partial(instance),
},
)
📈 Results
| Metric | Before (polling) | After (WebSocket + HTMX) |
|---|---|---|
| Requests/sec | 42 | 3 |
| Avg latency | 1200ms | 180ms |
| Bandwidth | 2.3 MB/min | 120 KB/min |
💡 Key Takeaways
- HTMX + WebSockets let you keep Django's templating system end-to-end
- Channel layers (Redis) scale horizontally — one process per core
- No frontend framework = faster iterations, smaller bundle, easier debugging
- The
async_to_syncbridge lets ORM signals push to WebSocket groups seamlessly
🔗 For a deeper look, check out the Pulse Feed project.