In this guide, you’ll learn how to build a real-time Socket.IO server using Node.js and Express, organize communication using namespaces, and keep it running smoothly with PM2. We’ll also walk through configuring Apache Reverse Proxy so you can serve your app on a custom domain. Whether you’re creating a trading platform, chat system, or IoT dashboard, this step-by-step tutorial has you covered.
1. Setting Up the Project
Create a new project and install the required packages:
mkdir signaling-server
cd signaling-server
npm init -y
npm install express socket.io
2. Complete Server Code with Namespaces
const express = require('express');
const app = express();
const http = require('http');
const expressserver = http.createServer(app);
const port = 3000;
const { Server } = require('socket.io');
const io = new Server(expressserver);
// Create namespaces
let buyNsp = io.of("/buy");
let sellNsp = io.of("/sell");
// Buy namespace
buyNsp.on('connection', (socket) => {
console.log('new User Connected ' + socket.id);
// Send a message to all clients in the buy namespace
buyNsp.emit("myEvent", "This is new message from buy namespace");
});
// Sell namespace
sellNsp.on('connection', (socket) => {
// Send a message to all clients in the sell namespace
sellNsp.emit("myEvent", "This is new message from sell namespace");
});
app.get('/', (req, res) => {
res.sendFile(__dirname + '/index.html');
});
expressserver.listen(port, () => {
console.log('Server is listening on port ' + port);
});
Code Explanation
Namespaces (io.of("/buy")): Separate communication channels so that buy-related messages don’t mix with sell-related ones.
buyNsp.emit() and sellNsp.emit(): Send messages only to clients connected to their specific namespace.
Express Route: Serves our index.html test file.
3. Modern Frontend UI (index.html)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Socket.IO — Demo</title>
<style>
:root {
--bg1: #0f172a;
--bg2: #08203a;
--muted: rgba(255, 255, 255, 0.7);
--primary: #7c3aed;
--primary-hover: #9d5cff;
}
body {
margin: 0;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(180deg, var(--bg1), var(--bg2));
font-family: "Segoe UI", sans-serif;
color: white;
}
.container {
background: rgba(255, 255, 255, 0.05);
padding: 30px;
border-radius: 16px;
text-align: center;
backdrop-filter: blur(10px);
}
button {
background: var(--primary);
color: white;
border: none;
padding: 10px 15px;
border-radius: 8px;
cursor: pointer;
}
button:hover {
background: var(--primary-hover);
}
</style>
</head>
<body>
<div class="container">
<h1>Socket.IO Demo</h1>
<h2 id="demo"></h2>
<button onclick="sendMessage()">Send Test Message</button>
</div>
<script src="/socket.io/socket.io.js"></script>
<script>
let socket = io("/buy"); // Connect to BUY namespace
socket.on("myEvent", (data) => {
document.getElementById("demo").textContent = data;
});
function sendMessage() {
socket.emit("myEvent", "Hello from Client!");
}
</script>
</body>
</html>
4. Running with PM2
Install PM2 globally:
sudo npm install -g pm2
Start the server:
pm2 start server.js --name "signaling-server"
View logs:
pm2 logs signaling-server
Restart the app:
pm2 restart signaling-server
Enable auto-start on boot:
pm2 startup
pm2 save
5. Apache Reverse Proxy Setup
If you’re running Apache, create a virtual host file:
<VirtualHost *:80>
ServerName example.com
ProxyRequests off
ProxyPass / http://localhost:3000/
ProxyPassReverse / http://localhost:3000/
ErrorLog ${APACHE_LOG_DIR}/signaling-error.log
CustomLog ${APACHE_LOG_DIR}/signaling-access.log combined
</VirtualHost>
Enable proxy modules and restart Apache:
sudo a2enmod proxy proxy_http
sudo systemctl restart apache2
Example 2<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Live Chat App</title>
<style>
:root {
--bg1: #0f172a;
--bg2: #08203a;
--muted: rgba(255, 255, 255, 0.7);
--primary: #7c3aed;
--primary-hover: #9d5cff;
--accent: #ffd700;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
margin: 0;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: radial-gradient(circle at 20% 20%, rgba(124, 58, 237, 0.12), transparent 25%),
linear-gradient(180deg, var(--bg1), var(--bg2));
color: #e6eef8;
padding: 20px;
}
.container {
background: rgba(255, 255, 255, 0.06);
backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.15);
padding: 30px 25px;
border-radius: 16px;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.35);
width: 100%;
max-width: 620px;
display: flex;
flex-direction: column;
gap: 15px;
animation: fadeIn 0.5s ease;
}
@keyframes fadeIn {
from {
transform: translateY(15px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
h1 {
margin: 0;
font-size: 2rem;
font-weight: 700;
text-align: center;
color: var(--primary-hover);
}
p {
margin: 0;
color: var(--muted);
font-size: 0.95rem;
text-align: center;
}
#messages {
background: rgba(255, 255, 255, 0.05);
border-radius: 10px;
padding: 12px;
height: 200px;
overflow-y: auto;
font-size: 0.9rem;
display: flex;
flex-direction: column;
gap: 8px;
scrollbar-width: thin;
scrollbar-color: var(--primary) transparent;
}
#messages::-webkit-scrollbar {
width: 6px;
}
#messages::-webkit-scrollbar-thumb {
background: var(--primary);
border-radius: 4px;
}
.message {
background: rgba(255, 255, 255, 0.08);
padding: 8px 12px;
border-radius: 8px;
line-height: 1.3;
}
.input-group {
display: flex;
gap: 10px;
}
input[type="text"] {
padding: 12px 14px;
flex: 1;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.2);
background: rgba(255, 255, 255, 0.08);
color: #fff;
font-size: 0.95rem;
outline: none;
transition: border-color 0.3s, box-shadow 0.3s;
}
input[type="text"]::placeholder {
color: rgba(255, 255, 255, 0.5);
}
input[type="text"]:focus {
border-color: var(--primary);
box-shadow: 0 0 8px var(--primary);
}
button {
padding: 12px 20px;
border-radius: 8px;
border: none;
background: var(--primary);
color: white;
font-weight: 600;
cursor: pointer;
transition: background 0.3s, transform 0.2s;
}
button:hover {
background: var(--primary-hover);
transform: translateY(-1px);
}
#demo {
font-size: 1rem;
font-weight: 500;
color: var(--accent);
text-align: center;
}
</style>
</head>
<body>
<div class="container">
<h1>Live Chat App</h1>
<p>Real-time Messaging</p>
<div id="messages">
<div class="message">👋 Welcome to the chat!</div>
</div>
<div class="input-group">
<input type="text" id="input" placeholder="Type a message...">
<button id="sendButton" onclick="sendMessage()">Send</button>
</div>
<h2 id="demo"></h2>
</div>
<script src="/socket.io/socket.io.js"></script>
<script>
let socket = io();
const input = document.getElementById("input");
const msgBox = document.getElementById("messages");
const msg = document.createElement("div");
msg.classList.add("message");
function sendMessage() {
if (input.value.trim() !== "") {
socket.send(input.value);
input.value = "";
}
}
socket.on("chat_send", (message) => {
const msg = document.createElement("div");
msg.classList.add("message");
msg.textContent = message;
msgBox.appendChild(msg);
msgBox.scrollTop = msgBox.scrollHeight;
});
socket.on("cooking", (message) => {
const msg = document.createElement("div");
msg.classList.add("message");
msg.textContent = message;
msgBox.appendChild(msg);
msgBox.scrollTop = msgBox.scrollHeight;
});
socket.on("daal", (message) => {
const msg = document.createElement("div");
msg.classList.add("message");
msg.textContent = message;
msgBox.appendChild(msg);
msgBox.scrollTop = msgBox.scrollHeight;
});
socket.on("rest", (message) => {
const msg = document.createElement("div");
msg.classList.add("message");
msg.textContent = message;
msgBox.appendChild(msg);
msgBox.scrollTop = msgBox.scrollHeight;
}); socket.on("sleep", (message) => {
const msg = document.createElement("div");
msg.classList.add("message");
msg.textContent = message;
msgBox.appendChild(msg);
msgBox.scrollTop = msgBox.scrollHeight;
});
</script>
</body>
</html>
Index.js file
const express = require('express');
const app = express();
const http = require('http');
const expressserver = http.createServer(app);
const port = 3000;
const {
Server
} = require('socket.io');
const io = new Server(expressserver);
io.on('connection', (socket) => {
console.log('New user connected ' + socket.id);
// socket.on('message', (msg) => {
// io.emit('chat_send', msg);
// })
socket.join('kitchen-room');
const countRooms = io.sockets.adapter.rooms.get('kitchen-room').size;
console.log(`kitchen-room has ${countRooms} socket(s)`);
io.sockets.in('kitchen-room').emit('cooking', "Cooking rice" + countRooms);
io.sockets.in('kitchen-room').emit('daal', "Cooking daal");
socket.join('bad-room');
io.sockets.in('bad-room').emit('rest', "im taking rest");
io.sockets.in('bad-room').emit('sleep', "Im sleeping");
})
app.get('/', (req, res) => {
res.sendFile(__dirname + '/index.html');
});
expressserver.listen(port, () => {
console.log('Server is listening on port 3000');
});