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 onSERVER_PORT— you never open a second listener, which matters because Falix gives you exactly one public port. io.emitis a broadcast. When a client sendschat message, the server emits it to everyone withio.emit— including the sender, so their own message shows up the same way. Usesocket.emit(...)to reply to just one client instead.- The browser client is served for you.
/socket.io/socket.io.jsisn'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
- Upload the three files (create
publicforindex.html), or deploy from Git. - Press Start. The console runs
npm install(Socket.io and Express land), then printsListening on port …— your success signal, and the line that flips the server online. - 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 — useshttps://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')andio.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.1instead of0.0.0.0, so nothing outside the container connects. Bind0.0.0.0. io is not definedin 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 callsio().- 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.