Skip to content

WebSocket chat

This guide shows how to build a small real-time chat system with Vix WebSocket.

What you will build

HTTP routes: GET /, GET /health, GET /rooms

WebSocket events: chat.message, chat.join, chat.leave, app.ping

Public headers

cpp
#include <vix.hpp>
#include <vix/websocket.hpp>
#include <vix/websocket/AttachedRuntime.hpp>

Setup

bash
vix new websocket-chat
cd websocket-chat

The Vix WebSocket model

Typed messages:

json
{ "type": "chat.message", "payload": { "user": "Ada", "text": "Hello" } }

Runtime struct

cpp
struct ChatRuntime
{
  vix::config::Config config{".env"};
  std::shared_ptr<vix::executor::RuntimeExecutor> executor{
      std::make_shared<vix::executor::RuntimeExecutor>(1u)};
  vix::App app{executor};
  vix::websocket::Server ws{config, executor};
};

Register lifecycle handlers

cpp
static void register_ws_lifecycle(vix::websocket::Server &ws)
{
  ws.on_open([&ws](vix::websocket::Session &session)
             { ws.broadcast_json("system.connected", {"message", "A client connected"}); });

  ws.on_close([&ws](vix::websocket::Session &session)
              { ws.broadcast_json("system.disconnected", {"message", "A client disconnected"}); });

  ws.on_error([](vix::websocket::Session &session, const std::string &error) {});
}

Register the chat protocol

cpp
static void register_ws_protocol(vix::websocket::Server &ws)
{
  ws.on_typed_message(
      [&ws](vix::websocket::Session &session,
            const std::string &type,
            const vix::json::kvs &payload)
      {
        if (type == "app.ping")
        {
          ws.broadcast_json("app.pong", {"status", "ok", "transport", "websocket"});
          return;
        }
        if (type == "chat.message" || type == "chat.join" || type == "chat.leave")
        {
          ws.broadcast_json(type, payload);
          return;
        }
        ws.broadcast_json("app.unknown", {"type", type, "message", "Unknown event type"});
      });
}

Run HTTP and WebSocket together

cpp
static void run(ChatRuntime &runtime)
{
  const int http_port = runtime.config.getServerPort();
  vix::run_http_and_ws(runtime.app, runtime.ws, runtime.executor, http_port);
}

int main()
{
  ChatRuntime runtime;
  configure(runtime);
  run(runtime);
  return 0;
}

Compact alternative: serve_http_and_ws

cpp
vix::serve_http_and_ws(".env", 8080, [](auto &app, auto &ws) {

  app.get("/", [](auto &, auto &res) {
    res.json({"framework", "Vix.cpp"});
  });

  ws.on_typed_message([&ws](auto &, const std::string &type, const vix::json::kvs &payload) {
    if (type == "chat.message") ws.broadcast_json("chat.message", payload);
  });

});
EventDirectionPurpose
chat.joinClient -> serverJoins a chat room or session.
chat.leaveClient -> serverLeaves a chat room or session.
chat.messageBoth directionsSends or receives chat content.
chat.errorServer -> clientReports a chat-related error.
app.pingClient -> serverSends a health check request.
app.pongServer -> clientReturns a health check response.
system.connectedServer -> clientConfirms the client connected.
system.disconnectedServer -> clientConfirms the client disconnected.

Add .env

dotenv
SERVER_PORT=8080
WEBSOCKET_PORT=9090
SERVER_TLS_ENABLED=false

Common mistakes

Not validating incoming payloads

cpp
const std::string text = payload.get_string_or("text", "");
if (text.empty()) {
  ws.broadcast_json(
    "chat.error",
    {"error", "text is required"}
  );

  return;
}
ws.broadcast_json("chat.message", payload);

Trusting the username from the payload

For production, use the authenticated user from the server, not user-supplied names.

Broadcasting private data to everyone

Use rooms or targeted sends for private chats.

Production checklist

  • Use HTTPS and WSS
  • Authenticate WebSocket clients
  • Validate all payloads
  • Use rooms for private chat
  • Persist important messages
  • Run behind Nginx
  • Run under systemd

What to use next

Released under the MIT License.