I’m building a local web chat feature using CivetWeb (C++), SQLite, and a frontend in plain HTML/JS. When a message is sent, the server log shows:
Attempting to send message: product_id=3, sender=1, receiver=Admin, message=1
So the /send_message endpoint is triggered correctly.
However, the new message does not appear on the chat page (message_feature.html), even though I can see it was inserted in the log.
Here’s what I have:
Frontend (message_feature.html)
async function loadConversation() {
const res = await fetch(`/api/conversation?product_id=${productId}`);
if (!res.ok) return;
const msgs = await res.json();
const box = document.getElementById('messages');
box.innerHTML = '';
msgs.sort((a, b) => new Date(a.created_at) - new Date(b.created_at));
msgs.forEach(m => {
const div = document.createElement('div');
div.className = 'msg ' + (m.sender === 'Admin' ? 'received' : 'sent');
div.innerHTML = `<div>${m.message}</div>`;
box.appendChild(div);
});
}
Backend (C++ with CivetWeb)
// POST /send_message
mg_set_request_handler(ctx, "/send_message", [](mg_connection *conn, void *) -> int {
if (strcmp(mg_get_request_info(conn)->request_method, "POST") != 0)
return 405;
const struct mg_request_info *ri = mg_get_request_info(conn);
int len = (int)ri->content_length;
if (len <= 0 || len > 4096) len = 4096;
std::vector<char> post_data(len + 1);
int n = mg_read(conn, post_data.data(), len);
post_data[n] = '\0';
char product_id_str[32] = {0}, sender[128] = {0}, receiver[128] = {0}, message[1024] = {0};
mg_get_var(post_data.data(), n, "product_id", product_id_str, sizeof(product_id_str));
mg_get_var(post_data.data(), n, "sender", sender, sizeof(sender));
mg_get_var(post_data.data(), n, "receiver", receiver, sizeof(receiver));
mg_get_var(post_data.data(), n, "message", message, sizeof(message));
fprintf(stderr, "Attempting to send message: product_id=%s, sender=%s, receiver=%s, message=%s\n",
product_id_str, sender, receiver, message);
sqlite3 *db = safe_open("C:/Users/priva/OneDrive/Desktop/supplierbuyer/products.db");
const char *sql = "INSERT INTO messages (product_id, sender, receiver, message) VALUES (?, ?, ?, ?);";
sqlite3_stmt *stmt = nullptr;
sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr);
sqlite3_bind_int(stmt, 1, atoi(product_id_str));
sqlite3_bind_text(stmt, 2, sender, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 3, receiver, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 4, message, -1, SQLITE_TRANSIENT);
sqlite3_step(stmt);
sqlite3_finalize(stmt);
sqlite3_close(db);
mg_printf(conn, "HTTP/1.1 201 Created\r\nContent-Length: 0\r\n\r\n");
return 201;
}, nullptr);
// GET /api/conversation
mg_set_request_handler(ctx, "/api/conversation", [](mg_connection *conn, void *) -> int {
const mg_request_info *ri = mg_get_request_info(conn);
char product_id_str[32] = {0};
if (ri->query_string)
mg_get_var(ri->query_string, strlen(ri->query_string), "product_id", product_id_str, sizeof(product_id_str));
int product_id = atoi(product_id_str);
sqlite3 *db = safe_open("C:/Users/priva/OneDrive/Desktop/supplierbuyer/products.db");
const char *sql = R"(
SELECT sender, receiver, message, strftime('%Y-%m-%d %H:%M:%S', created_at)
FROM messages WHERE product_id = ? ORDER BY id ASC;
)";
sqlite3_stmt *stmt = nullptr;
sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr);
sqlite3_bind_int(stmt, 1, product_id);
std::string json = "[";
bool first = true;
while (sqlite3_step(stmt) == SQLITE_ROW) {
if (!first) json += ",";
first = false;
const char *sender = (const char *)sqlite3_column_text(stmt, 0);
const char *receiver = (const char *)sqlite3_column_text(stmt, 1);
const char *message = (const char *)sqlite3_column_text(stmt, 2);
const char *created_at = (const char *)sqlite3_column_text(stmt, 3);
char buf[512];
snprintf(buf, sizeof(buf),
R"({"sender":"%s","receiver":"%s","message":"%s","created_at":"%s"})",
sender, receiver, message, created_at);
json += buf;
}
json += "]";
sqlite3_finalize(stmt);
sqlite3_close(db);
mg_printf(conn, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n%s", json.c_str());
return 200;
}, nullptr);
Messages are saved in the same products.db file under a messages table.
I also checked that loadConversation() runs after sending a message, but nothing shows in the chat box.
Here’s the relevant part of my message_feature.html code:
document.getElementById('chatForm').addEventListener('submit', async e => {
e.preventDefault();
const fd = new FormData(e.target);
const userRes = await fetch('/api/get_current_user');
const { username } = await userRes.json();
fd.append('sender', username);
const body = new URLSearchParams(fd);
// Send the message to the backend
const res = await fetch('/send_message', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: body
});
// I wait for the response before refreshing messages
if (res.ok) {
document.getElementById('message').value = '';
loadConversation(); // ✅ Called right after successful response
} else {
alert('Failed to send message.');
}
});
So loadConversation() is only called after the POST request returns successfully (status 201).
However, even then, the chat box doesn’t update — it still shows no messages, even though /send_message logs the insert on the server.
I also tried adding a small delay before calling loadConversation() (using setTimeout(loadConversation, 1000)), but it made no difference — the messages still don’t show up.
Question:
Even though messages are inserted (the log shows they’re sent), they don’t appear when fetched from /api/conversation.
What could cause the frontend to not display them — is it a problem with the query, JSON response, or DOM rendering?
What I Tried
I verified that the
/send_messageendpoint works — it logsAttempting to send message: product_id=3, sender=1, receiver=Admin, message=1every time I click Send.
I checked that the message is inserted into the SQLite database (
messagestable) manually using DB Browser.I confirmed that
/api/conversation?product_id=3returns a JSON array, but sometimes it’s empty or missing the new message.I also added
console.log(msgs)insideloadConversation()inmessage_feature.html— the console shows an empty array[]even though the message exists in the DB.I tried calling
loadConversation()immediately after sending (and also with a 3-second interval), but it still doesn’t update the chat box.
What I Expected
I expected that after sending a message, the new message would:
- Be inserted into the
messagestable. - Appear immediately in the chat box under the correct side (
sentorreceived). - Also appear in the admin view (
messageadmin.html) after a few seconds (since it refreshes automatically).
Full project source code:
👉 https://github.com/misbagas/supplierbuyer
loadConversation()runs after sending a message"... how soon after? Are you waiting for the response from/send_messagefirst? Please include enough code to represent the application logic flow...