Why are my messages not showing in real-time using HTMX and WebSockets in Django?
I'm building a simple two-way chat system as a part of my web-app using HTMX WebSockets (htmx-ext-ws). The goal is to display incoming messages in real time without requiring a manual page refresh.
The setup mostly works — messages are being saved to the database, and the WebSocket connection seems to establish correctly. However, new messages only appear after a page reload, and not in real time as expected.
Here’s an overview of what I have:
#chat.html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chat</title>
<script src="https://unpkg.com/htmx.org@1.9.12"></script>
<script src="https://unpkg.com/hyperscript.org@0.9.12"></script>
<script src="https://unpkg.com/htmx-ext-ws@2.0.2"></script>
<link rel="stylesheet" href="{% static 'css/chat.css' %}">
</head>
<body>
<div class="chat-container">
<div class="chat-header">
Chat Room
</div>
<div class="chat_messages" >
{% for message in messages %}
<div class="message-row {% if message.author.user == user %}my-message{% else %}other-message{% endif %}">
{% include "chat_rt/chat_message.html" %}
</div>
{% empty %}
<div>No messages yet.</div>
{% endfor %}
</div>
<form class="chat-input-area" id="chat-form" autocomplete="off"
method="POST"
hx-ext="ws"
ws-connect="ws://127.0.0.1:8000/ws/chatroom/private-chat"
ws-send
_="on htmx:wsAfterSend set #chat-input.value to ''">
{% csrf_token %}
{{ form.body }}
<button type="submit">Send</button>
</form>
</div>
<script src="{% static 'js/chat2.js' %}"></script>
</body>
</html>
#chat_message.html
{% if message.author.user == user %}
<div class="message-content">
<div class="message-meta right">
<span class="time">{{ message.created|date:"H:i" }}</span>
</div>
<div class="body">{{ message.body }}</div>
</div>
{% else %}
<div class="message-content">
<img class="profile-pic" src="{{ message.author.profile_pic.url }}" alt="{{ message.author.username }}">
<div>
<div class="message-meta left">
<span class="author">{{ message.author.username }}</span>
<span class="time">{{ message.created|date:"H:i" }}</span>
</div>
<div class="body">{{ message.body }}</div>
</div>
</div>
{% endif %}
#chat_message_p.html(the partial)
<div id="chat-messages" hx-swap-oob="beforeend">
{% include "chat_rt/chat_message.html" %}
</div>
#consumers.py
from channels.generic.websocket import WebsocketConsumer
from django.shortcuts import get_object_or_404
from django.template.loader import render_to_string
import json
from .models import ChatGroup, GroupMessage
class ChatroomConsumer(WebsocketConsumer):
def connect(self):
print("WebSocket connecting...")
self.user = self.scope['user']
self.chatroom = get_object_or_404(ChatGroup, group_name='private_chat')
print("WebSocket connected!")
self.accept()
def receive(self, text_data):
print("Received from client:", text_data)
text_data_json = json.loads(text_data)
body = text_data_json['body']
message = GroupMessage.objects.create(
body=body,
author=self.user.profile,
group=self.chatroom,
)
context = {
'message': message,
'user': self.user,
}
html = render_to_string("chat_rt/partials/chat_message_p.html", context=context)
try:
self.send(text_data=json.dumps({
"content": html,
"target": "#chat-messages",
"swap": "beforeend"
}))
print("WebSocket send successful")
except Exception as e:
print("WebSocket send failed:", e)