Only show guides for your app
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.
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.
| 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 |
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>
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./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.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.
public for index.html), or deploy from Git.npm install (Socket.io and Express land), then prints Listening on port … — your success signal, and the line that flips the server online.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.
name: text.socket.join('room1') and io.to('room1').emit(...) split one server into many channels.Everything past this is standard Socket.io — the official docs at socket.io/docs cover rooms, acknowledgements, and scaling.
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().process.env.SERVER_PORT; that's the only reachable port. See I can't reach my app.