Real-time apps with WebSockets

Serve HTTP and a WebSocket from your one public port by attaching a WebSocket server to the HTTP server that's already listening — the pattern that fits Falix exactly.

HTTP is request-and-response. A WebSocket stays open, so the server and the browser can push messages both ways in real time — chat, game state, live dashboards. A Falix app server gives you exactly one public port, and the neat part is that a single port can serve your web page and your WebSocket at once. Here's the pattern.

At a glance
You need A server running the Node.js application
Background Node.js on Falix and the SERVER_PORT / 0.0.0.0 rule from Your first web app
Time Twenty minutes

One port, two protocols

A Falix app server has a single public port — the SERVER_PORT value the panel injects. That sounds like a problem for real-time apps until you notice that a WebSocket server can ride on top of an ordinary HTTP server.

🎯 Good to know: Attach the ws library's WebSocketServer to the same http.Server that's already listening, and the one port answers both normal web requests and WebSocket upgrades.

Install ws from the Packages page (search ws, install, restart), then write index.js:

const http = require('node:http');
const { WebSocketServer, WebSocket } = require('ws');

// An ordinary HTTP server — this is what binds the public port.
const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('HTTP is alive. Open a WebSocket to this same address.');
});

// The WebSocket server rides on the HTTP server — same port.
const wss = new WebSocketServer({ server });

wss.on('connection', (socket) => {
  console.log('Client connected');

  socket.on('message', (data) => {
    const text = data.toString();
    // Broadcast to every client that's still open.
    for (const client of wss.clients) {
      if (client.readyState === WebSocket.OPEN) {
        client.send(text);
      }
    }
  });

  socket.on('close', () => console.log('Client disconnected'));
});

const PORT = process.env.SERVER_PORT || 8080;
server.listen(PORT, '0.0.0.0', () => {
  console.log(`Listening on port ${PORT}`);
});

The whole Falix-shaped insight is new WebSocketServer({ server }): you don't open a second listener, you hand it the HTTP server you already have. server.listen reads SERVER_PORT and binds 0.0.0.0 — the same two rules as any web app — and the WebSocket comes along for free. That Listening line also flips the server to online.

The message loop

  • wss.on('connection', ...) fires once per client; socket is that one connection.
  • socket.on('message', ...) receives what a client sends. data arrives as a buffer, so .toString() turns it into text.
  • The loop over wss.clients sends the message to everyone — a broadcast. Checking readyState === WebSocket.OPEN skips clients that are mid-disconnect, which would otherwise throw.

Send to just the one client with socket.send(...); loop over wss.clients to reach all of them. That single choice is the whole difference between an echo and a chat room.

Connecting from a browser

Point a browser WebSocket at your server's public address and port (from the Network page):

const socket = new WebSocket('ws://YOUR_ADDRESS:PORT');

socket.addEventListener('open', () => socket.send('Hello, server!'));
socket.addEventListener('message', (event) => {
  console.log('Server says:', event.data);
});

Plain ws:// over the raw address and port works immediately. If you put the site behind the Network page's Reverse Proxy for a real domain with automatic SSL, connect with wss:// instead — the secure WebSocket the proxy expects (Domains and HTTPS).

When a connection drops

A WebSocket is meant to stay open, but no long-lived connection lasts forever — a phone switching networks, a laptop going to sleep, a brief hiccup, or a proxy retiring an idle connection can all close it. That's normal, and every real-time app handles it the same way: reconnect from the client. When the socket fires close, wait a moment and open a fresh one:

function connect() {
  const socket = new WebSocket('ws://YOUR_ADDRESS:PORT');
  socket.addEventListener('message', (event) => {
    console.log('Server says:', event.data);
  });
  socket.addEventListener('close', () => {
    setTimeout(connect, 1000); // try again in a second
  });
}
connect();

A light heartbeat helps too: have the client send a tiny message every so often so an idle connection doesn't look dead to anything sitting in between. Don't hard-code a guess about how long a connection "should" survive — assume it can close at any time and reconnect cleanly when it does.

What it's good for

Anything where waiting for a page refresh would feel wrong: live chat, multiplayer game state, collaborative editors, and dashboards that stream metrics as they change. The server above is already a working broadcast hub — build any of those on top of the message loop.

Troubleshooting

  • Connection opens then instantly closes — the HTTP server bound localhost / 127.0.0.1 instead of 0.0.0.0, so nothing outside the container can reach it. Bind 0.0.0.0.
  • Works on your machine, not in production — a hardcoded port (like 8080 or 3000). Read process.env.SERVER_PORT; that's the only reachable port.
  • A browser on an HTTPS page refuses to connect — a secure page can't open a plain ws:// socket. Serve it through the Reverse Proxy and connect with wss://.
  • Memory climbs with many clients — every open socket costs memory, and broadcasting to thousands multiplies the work. Watch it, and see Out of memory; the free plan's 2.5 GB shared RAM fills faster than you'd think.

Next steps

Was this guide helpful?