Spin Up a Tiny Socket.IO Server for Local Debugging
This is a short, practical guide to run a Socket.IO service locally so you can build and debug any socket client (web, mobile, or server). You’ll get a streaming endpoint that emits sample data every 2 seconds, plus a minimal client to verify events.
What you’ll need
- •Node.js 18+
- •A terminal
- •An editor
1) Project setup
Create a new folder and add a package.json with the essentials:
{
"name": "socket-server",
"version": "1.0.0",
"description": "Tiny Socket.IO server for local debugging",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
},
"license": "ISC",
"dependencies": {
"express": "^5.1.0",
"socket.io": "^4.8.1"
},
"devDependencies": {
"nodemon": "^3.1.0"
}
}Install:
npm install2) Server — `server.js`
This server accepts connections from any origin and emits a stream-data event every 2 seconds. It logs connects and disconnects. We also clear the timer on shutdown.
// server.js
const express = require("express");
const http = require("http");
const { Server } = require("socket.io");
const app = express();
const server = http.createServer(app);
const io = new Server(server, { cors: { origin: "*" } });
io.on("connection", (socket) => {
console.log("Connected:", socket.id);
socket.on("disconnect", (reason) => {
console.log("-- Disconnected:", socket.id, "reason:", reason);
});
});
// Emit sample data every 2s
const interval = setInterval(() => {
const data = {
timestamp: Date.now(),
value: Math.random(),
};
io.emit("stream-data", data);
}, 2000);
// Simple health endpoint
app.get("/", (_req, res) => res.json({ ok: true }));
// Clean shutdown
function shutdown() {
console.log("\nShutting down...");
if (interval) clearInterval(interval);
io.close(() => {
server.close(() => process.exit(0));
});
}
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
console.log(`Socket running on http://localhost:${PORT}`);
});Run it:
npm run start
# or with hot reload
npm run devYou should see:
Socket running on http://localhost:30003) Minimal web client — quick sanity check
Create a simple client.html next to server.js and open it in your browser. It will connect and print events.
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Socket.IO Client</title>
<style>
body { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; padding: 16px; }
#log { white-space: pre-wrap; }
</style>
<script src="https://cdn.socket.io/4.8.1/socket.io.min.js"></script>
</head>
<body>
<h1>Socket.IO Client</h1>
<div id="log"></div>
<script>
const log = (msg) => (document.getElementById("log").textContent += msg + "\n");
const socket = io("http://localhost:3000", { transports: ["websocket"] });
socket.on("connect", () => log("connected: " + socket.id));
socket.on("disconnect", (reason) => log("disconnected: " + reason));
socket.on("stream-data", (payload) => log("stream-data: " + JSON.stringify(payload)));
window.addEventListener("beforeunload", () => {
socket.close();
});
</script>
</body>
</html>Open client.html in the browser. You should see stream-data entries every 2 seconds.
4) Example: simulated device feed (optional)
If you need more “device-like” messages (e.g., chargers), swap the emitter with this example that updates or inserts devices by deviceId. This mirrors the logic you sketched and demonstrates incremental updates.
// Replace the 2s interval with this 1s device ticker
const devices = new Map();
function upsertDevice(msg) {
devices.set(msg.deviceId, msg);
io.emit("stream-data", msg);
}
function randomDeviceId() {
return "charge-00" + Math.floor(Math.random() * 10);
}
function makeDevice() {
return {
deviceId: randomDeviceId(),
timestamp: Date.now(),
status: Math.random() > 0.2 ? "online" : "offline",
currentPowerKw: parseFloat((Math.random() * 100).toFixed(1)),
activeSessions: Math.floor(Math.random() * 3),
alarms: Math.random() > 0.8 ? ["highTemperature"] : [],
};
}
// Seed a couple of starters
upsertDevice({
deviceId: "charge-001",
timestamp: Date.now(),
status: "online",
currentPowerKw: 33.4,
activeSessions: 1,
alarms: [],
});
upsertDevice({
deviceId: "charge-002",
timestamp: Date.now(),
status: "offline",
currentPowerKw: 21.8,
activeSessions: 0,
alarms: ["noConnection", "error"],
});
// Tick every second
const deviceInterval = setInterval(() => {
upsertDevice(makeDevice());
}, 1000);
// Remember to clear both on shutdown
function shutdown() {
console.log("\nShutting down...");
if (deviceInterval) clearInterval(deviceInterval);
if (interval) clearInterval(interval);
io.close(() => {
server.close(() => process.exit(0));
});
}
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);This produces realistic updates that your client can merge by deviceId. If an id exists, overwrite it; if not, add it. That keeps your UI in sync with the stream.
5) Minimal Node client (for integration tests)
If you prefer to test from Node, run this client in a separate process:
// node-client.js
const { io } = require("socket.io-client");
const socket = io("http://localhost:3000", { transports: ["websocket"] });
socket.on("connect", () => console.log("connected:", socket.id));
socket.on("stream-data", (msg) => console.log("stream-data:", msg));
socket.on("disconnect", (reason) => console.log("disconnected:", reason));
process.on("SIGINT", () => {
socket.close();
process.exit(0);
});Run:
node node-client.jsYou should see live messages in the terminal.
6) Event names you can use
- •
connection(server-side) — when a client connects - •
disconnect(both sides) — when either end closes - •
stream-data(custom) — your live payload - •You can add more channels like
device-updated,telemetry,heartbeat
Keep event names short and consistent.
7) Common tweaks
- •Port: set
PORT=4000to move the server. - •CORS: lock
origindown to your app’s URL when you move beyond local testing. - •Transport: force WebSocket in clients with
{ transports: ["websocket"] }if you want to skip long-polling.
8) Clean shutdown
For local dev, avoid orphaned intervals and open sockets. Clear timers and close the server on SIGINT/SIGTERM as shown above. If you add more timers, clear them in the same shutdown() function.
You’re done
You now have a tiny Socket.IO service that emits predictable data and a minimal client to consume it. Use this to iterate fast on your UI state, reconnection logic, and data merge strategy.