Build a real-time chat room

A live chat everyone shares, built with Socket.io — one Node server broadcasts every message to every open browser over the single public port. Three files, verified echo round-trip, deploy and extend.

Skip the setup This guide has a one-click starter template that installs everything below onto your server.

This is the "wow, it's live" project: type a message in one browser tab and it appears instantly in everyone else's. It's built with Socket.io, a library that handles the real-time connection (and reconnection) for you, riding on the same single public port Falix gives your server. Three files on the Node.js application.

At a glance
You're building A shared, real-time chat room in the browser
You need A Falix server running the Node.js application
Plan Any — free runs while your session timer has time, premium runs 24/7
Time About twenty-five minutes

New to real-time? Real-time apps with WebSockets explains the one-port idea this builds on; Socket.io is a friendlier layer on top of it.

What it does

Feature How
Live messages Socket.io broadcasts each message to every connected client
Join/leave notices The server emits a system message on connect and disconnect
One port, both jobs Socket.io shares the HTTP server that serves the page
No refresh, ever Messages arrive over the open connection
Auto-reconnect Socket.io reconnects a dropped client on its own

The files

package.json:

{
  "name": "chat-room",
  "version": "1.0.0",
  "private": true,
  "main": "index.js",
  "dependencies": {
    "express": "^4.21.2",
    "socket.io": "^4.8.1"
  }
}

index.js — the server:

const express = require('express');
const http = require('node:http');
const path = require('node:path');
const { Server } = require('socket.io');

const app = express();
app.use(express.static(path.join(__dirname, 'public')));

// Socket.io rides on the same HTTP server, so one public port serves both.
const server = http.createServer(app);
const io = new Server(server);

io.on('connection', (socket) => {
  console.log('A user connected:', socket.id);
  io.emit('system', 'A user joined the chat');

  socket.on('chat message', (msg) => {
    // Trim, cap the length, and send it to everyone (including the sender).
    io.emit('chat message', { text: String(msg).slice(0, 500), at: Date.now() });
  });

  socket.on('disconnect', () => {
    io.emit('system', 'A user left the chat');
  });
});

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

public/index.html — the browser client:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Chat room</title>
  <style>
    body { font-family: system-ui, sans-serif; max-width: 40rem; margin: 2rem auto; }
    #log { border: 1px solid #ddd; height: 20rem; overflow-y: auto; padding: 0.8rem; margin-bottom: 0.8rem; }
    .system { color: #888; font-style: italic; }
    form { display: flex; gap: 0.5rem; }
    #msg { flex: 1; font-size: 1rem; padding: 0.5rem; }
  </style>
</head>
<body>
  <h1>Chat room</h1>
  <div id="log"></div>
  <form id="form">
    <input id="msg" autocomplete="off" placeholder="Say something...">
    <button>Send</button>
  </form>

  <!-- Socket.io serves its own browser client from this path. -->
  <script src="/socket.io/socket.io.js"></script>
  <script>
    const socket = io();
    const log = document.getElementById('log');
    function add(text, cls) {
      const div = document.createElement('div');
      if (cls) div.className = cls;
      div.textContent = text;
      log.appendChild(div);
      log.scrollTop = log.scrollHeight;
    }
    socket.on('chat message', (m) => add(m.text));
    socket.on('system', (t) => add(t, 'system'));
    document.getElementById('form').addEventListener('submit', (e) => {
      e.preventDefault();
      const input = document.getElementById('msg');
      if (input.value.trim()) {
        socket.emit('chat message', input.value);
        input.value = '';
      }
    });
  </script>
</body>
</html>

How it works

  • One HTTP server, two jobs. http.createServer(app) makes the server Express handles page requests on; new Server(server) hands that same server to Socket.io. Both live on SERVER_PORT — you never open a second listener, which matters because Falix gives you exactly one public port.
  • io.emit is a broadcast. When a client sends chat message, the server emits it to everyone with io.emit — including the sender, so their own message shows up the same way. Use socket.emit(...) to reply to just one client instead.
  • The browser client is served for you. /socket.io/socket.io.js isn't a file you wrote — Socket.io serves its matching client library from that path automatically, so the front end and back end always agree on the protocol.
  • Messages are capped. String(msg).slice(0, 500) keeps one giant message from becoming everyone's problem — a small but real safeguard.

🎯 Good to know: Reconnection is handled for you. A phone changing networks or a laptop waking from sleep drops the connection; Socket.io quietly re-establishes it and the chat keeps working. That's the main reason to reach for it over raw WebSockets — Socket.io vs plain ws weighs the trade-off.

Run it on Falix

  1. Upload the three files (create public for index.html), or deploy from Git.
  2. Press Start. The console runs npm install (Socket.io and Express land), then prints Listening on port … — your success signal, and the line that flips the server online.
  3. Open your address from the Network page in two browser tabs. Type in one; it appears in both instantly. That's the whole thing working.

The server reads SERVER_PORT and binds 0.0.0.0, so it comes up on your public port with no configuration.

⚠️ Heads up: A page served over HTTPS can't open a plain connection to http://address:port. Once you put a real domain in front with the Network page's Reverse Proxy (automatic SSL), everything — including Socket.io — uses https:// and works; Socket.io upgrades the transport itself. See Domains and HTTPS.

Make it yours

  • Usernames. Prompt for a name on the client and send it with each message; show name: text.
  • Rooms. Socket.io has built-in rooms — socket.join('room1') and io.to('room1').emit(...) split one server into many channels.
  • Recent history. Keep the last 50 messages in an array (or SQLite, like the pastebin) and send them to a client the moment it connects, so newcomers see context.
  • Typing indicators and read counts — emit small events on keypress and render them.

Everything past this is standard Socket.io — the official docs at socket.io/docs cover rooms, acknowledgements, and scaling.

Troubleshooting

  • Chat loads but messages never arrive — the connection didn't open. Almost always the HTTP server bound localhost/127.0.0.1 instead of 0.0.0.0, so nothing outside the container connects. Bind 0.0.0.0.
  • io is not defined in the browser — the <script src="/socket.io/socket.io.js"> line is missing or came after your own script. It must load first; keep it above the code that calls io().
  • Works locally, not deployed — a hardcoded port. Read process.env.SERVER_PORT; that's the only reachable port. See I can't reach my app.
  • Memory climbs with many users — every open socket costs memory, and broadcasting multiplies work. Watch it on the free plan's shared RAM: Out of memory.

Next steps

Was this guide helpful?