Final Child Page

WebSockets provide a full-duplex communication channel over a single, long-lived connection. Unlike the traditional HTTP protocol, where a client must repeatedly send requests to receive updates from the server (known as polling), WebSockets allow the server to push updates to the client in real-time.

WebSockets vs HTTP

Understanding the differences between WebSockets and HTTP is crucial to grasp why and when to use WebSockets:

HTTP:

  • Request/Response model: The client initiates the communication by sending a request, and the server responds.
  • Stateless: Each request is independent, and no connection state is maintained between requests.
  • Ideal for: Standard web pages, API calls, and where real-time communication is not necessary.

WebSocket:

  • Full-duplex communication: Both client and server can send messages to each other independently.
  • Persistent connection: A single connection is maintained, reducing the overhead of establishing new connections.
  • Ideal for: Real-time applications like chat systems, live notifications, and online gaming.

Setting Up a Basic WebSocket Server in Node.js

Let’s start by setting up a basic WebSocket server using Node.js’s built-in http module and the WebSocket API.

File: basic-websocket-server.js

				
					const http = require('http');
const WebSocket = require('ws');

// Create an HTTP server
const server = http.createServer((req, res) => {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('WebSocket server is running.\n');
});

// Create a WebSocket server
const wss = new WebSocket.Server({ server });

// Handle WebSocket connections
wss.on('connection', (ws) => {
    console.log('New client connected');

    // Send a message to the client
    ws.send('Welcome to the WebSocket server!');

    // Receive messages from the client
    ws.on('message', (message) => {
        console.log(`Received: ${message}`);
    });

    // Handle client disconnection
    ws.on('close', () => {
        console.log('Client disconnected');
    });
});

// Start the server
server.listen(8080, () => {
    console.log('Server is listening on port 8080');
});

				
			

Output:

  1. Run the server using:

				
					node basic-websocket-server.js

				
			

2.Open a WebSocket connection using a WebSocket client (e.g., a browser console or a tool like wscat):

				
					const ws = new WebSocket('ws://localhost:8080');
ws.onmessage = (event) => console.log(event.data);
ws.send('Hello, Server!');

				
			

3.The server logs:

				
					New client connected
Received: Hello, Server!
Client disconnected

				
			

Using WebSocket Libraries in Node.js

While the WebSocket API in Node.js is straightforward, using libraries like ws or Socket.IO can simplify development and provide additional features.

The ws Library

ws is a popular WebSocket library that provides a simple and efficient way to work with WebSockets in Node.js.

Installation:

				
					npm install ws

				
			

File: ws-websocket-server.js

				
					const WebSocket = require('ws');

// Create a WebSocket server
const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', (ws) => {
    console.log('New client connected');

    ws.send('Welcome to the WebSocket server!');

    ws.on('message', (message) => {
        console.log(`Received: ${message}`);
        ws.send(`Server received: ${message}`);
    });

    ws.on('close', () => {
        console.log('Client disconnected');
    });
});

console.log('WebSocket server is listening on port 8080');

				
			

Output:

  1. Run the server
				
					node ws-websocket-server.js

				
			

2.Connect using a WebSocket client as before and observe the bidirectional communication.

				
					node ws-websocket-server.js

				
			

The Socket.IO Library

Socket.IO is a more advanced library that builds on top of WebSockets, offering features like rooms, namespaces, and fallbacks to other protocols when WebSockets are not available.

Installation:

				
					npm install socket.io

				
			

File: socketio-server.js

				
					const http = require('http');
const socketIo = require('socket.io');

// Create an HTTP server
const server = http.createServer((req, res) => {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('Socket.IO server is running.\n');
});

// Attach Socket.IO to the server
const io = socketIo(server);

io.on('connection', (socket) => {
    console.log('New client connected');

    socket.emit('message', 'Welcome to the Socket.IO server!');

    socket.on('message', (message) => {
        console.log(`Received: ${message}`);
        socket.emit('message', `Server received: ${message}`);
    });

    socket.on('disconnect', () => {
        console.log('Client disconnected');
    });
});

server.listen(8080, () => {
    console.log('Server is listening on port 8080');
});

				
			

Output:

  1. Run the server:
				
					node socketio-server.js

				
			

2.Use the Socket.IO client library to connect and exchange messages.

				
					node socketio-server.js

				
			

Broadcasting Messages with WebSockets

Broadcasting allows sending a message to all connected clients or a subset of clients. Both ws and Socket.IO provide mechanisms for broadcasting.

Broadcasting with ws

File: ws-broadcast.js

				
					const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', (ws) => {
    ws.on('message', (message) => {
        // Broadcast the message to all clients
        wss.clients.forEach((client) => {
            if (client !== ws && client.readyState === WebSocket.OPEN) {
                client.send(`Broadcast: ${message}`);
            }
        });
    });
});

console.log('WebSocket server with broadcasting is listening on port 8080');

				
			

Broadcasting with Socket.IO

File: socketio-broadcast.js

				
					const http = require('http');
const socketIo = require('socket.io');

const server = http.createServer((req, res) => {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('Socket.IO server is running.\n');
});

const io = socketIo(server);

io.on('connection', (socket) => {
    socket.on('message', (message) => {
        // Broadcast the message to all clients except the sender
        socket.broadcast.emit('message', `Broadcast: ${message}`);
    });
});

server.listen(8080, () => {
    console.log('Socket.IO server with broadcasting is listening on port 8080');
});

				
			

Implementing Rooms and Namespaces in WebSockets

Rooms and namespaces help organize WebSocket connections, especially in larger applications where different types of data are handled separately.

Rooms in Socket.IO

Rooms allow grouping clients together, so they only receive messages intended for that group.

File: socketio-rooms.js

				
					const http = require('http');
const socketIo = require('socket.io');

const server = http.createServer((req, res) => {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('Socket.IO server with rooms is running.\n');
});

const io = socketIo(server);

io.on('connection', (socket) => {
    socket.on('joinRoom', (room) => {
        socket.join(room);
        socket.to(room).emit('message', `${socket.id} joined room ${room}`);
    });

    socket.on('message', (message, room) => {
        socket.to(room).emit('message', `${socket.id}: ${message}`);
    });
});

server.listen(8080, () => {
    console.log('Socket.IO server with rooms is listening on port 8080');
});

				
			

Namespaces in Socket.IO

Namespaces allow creating separate communication channels within the same server.

File: socketio-namespaces.js

				
					const http = require('http');
const socketIo = require('socket.io');

const server = http.createServer((req, res) => {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
   
    res.end('Socket.IO server with namespaces is running.\n');
});

const io = socketIo(server);

// Default namespace
io.on('connection', (socket) => {
    console.log('Client connected to the default namespace');
    socket.emit('message', 'Welcome to the default namespace!');
});

// Custom namespace
const chatNamespace = io.of('/chat');

chatNamespace.on('connection', (socket) => {
    console.log('Client connected to the /chat namespace');

    socket.emit('message', 'Welcome to the chat namespace!');

    socket.on('message', (message) => {
        console.log(`Chat message: ${message}`);
        chatNamespace.emit('message', `Chat: ${message}`);
    });

    socket.on('disconnect', () => {
        console.log('Client disconnected from /chat namespace');
    });
});

server.listen(8080, () => {
    console.log('Socket.IO server with namespaces is listening on port 8080');
});

				
			

Output:

  1. Run the server:

				
					node socketio-namespaces.js

				
			

2.Connect to the default namespace using:

				
					const socket = io('http://localhost:8080');
socket.on('message', (msg) => console.log(msg));
socket.emit('message', 'Hello from default namespace');

				
			

3.Connect to the /chat namespace using:

				
					const chatSocket = io('http://localhost:8080/chat');
chatSocket.on('message', (msg) => console.log(msg));
chatSocket.emit('message', 'Hello from chat namespace');

				
			

4.The server will differentiate between the two namespaces and handle their communication separately.

Error Handling in WebSocket Connections

Error handling is crucial in WebSocket communication, as it ensures your application can gracefully handle issues like connection failures or unexpected disconnections.

Error Handling with ws

File: ws-error-handling.js

				
					const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', (ws) => {
    ws.on('error', (err) => {
        console.error('WebSocket error:', err);
    });

    ws.on('message', (message) => {
        if (message === 'error') {
            ws.emit('error', new Error('Intentional error triggered by client'));
        } else {
            ws.send(`Server received: ${message}`);
        }
    });
});

console.log('WebSocket server with error handling is listening on port 8080');

				
			

Output:

  1. Run the server:

				
					node ws-error-handling.js

				
			

2.Connect and trigger an error:

				
					const ws = new WebSocket('ws://localhost:8080');
ws.onmessage = (event) => console.log(event.data);
ws.send('error');

				
			

3.The server will log the error and continue to run, demonstrating how to handle errors gracefully.

Security Considerations in WebSockets

Security is a critical aspect of any WebSocket implementation. Here are some key considerations:

Use Secure WebSockets (wss)

Always use wss:// (WebSocket Secure) in production environments to ensure that communication is encrypted using TLS/SSL.

Authentication and Authorization

Ensure that only authenticated users can establish WebSocket connections. You can do this by:

  • Validating tokens or session IDs when the WebSocket connection is established.
  • Implementing custom middleware to check user credentials before allowing access.

File: socketio-auth.js

				
					const http = require('http');
const socketIo = require('socket.io');

const server = http.createServer((req, res) => {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('Socket.IO server with authentication is running.\n');
});

const io = socketIo(server);

// Middleware to check authentication
io.use((socket, next) => {
    const token = socket.handshake.query.token;
    if (isValidToken(token)) {
        return next();
    }
    return next(new Error('Authentication error'));
});

io.on('connection', (socket) => {
    console.log('Client connected after authentication');
    socket.emit('message', 'Welcome to the secure Socket.IO server!');
});

function isValidToken(token) {
    // Perform token validation (this is just a dummy example)
    return token === 'valid-token';
}

server.listen(8080, () => {
    console.log('Socket.IO server with authentication is listening on port 8080');
});

				
			

Output:

  1. Run the server:

				
					node socketio-auth.js

				
			

2.Attempt to connect with and without a valid token:

				
					const socket = io('http://localhost:8080', { query: { token: 'valid-token' } });
socket.on('message', (msg) => console.log(msg));

				
			

3.Without a valid token, the connection will be rejected.

Rate Limiting

Implement rate limiting to prevent abuse, such as a flood of messages from a single client. You can achieve this by tracking the number of messages a client sends within a certain period and blocking the client if they exceed a threshold.

Scaling WebSocket Applications

As your WebSocket application grows, scaling becomes essential to handle more connections and ensure performance.

Horizontal Scaling

To scale horizontally, you can deploy your WebSocket server across multiple instances. However, this requires synchronizing state across servers.

Using Redis for Pub/Sub

Socket.IO supports horizontal scaling using Redis for Pub/Sub. This allows messages to be broadcast across all instances.

Installation:

				
					npm install socket.io-redis

				
			

File: socketio-redis.js

				
					const http = require('http');
const socketIo = require('socket.io');
const redisAdapter = require('socket.io-redis');

const server = http.createServer((req, res) => {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('Socket.IO server with Redis scaling is running.\n');
});

const io = socketIo(server);

// Use Redis adapter for scaling
io.adapter(redisAdapter({ host: 'localhost', port: 6379 }));

io.on('connection', (socket) => {
    console.log('Client connected');
    socket.on('message', (message) => {
        io.emit('message', `Broadcast: ${message}`);
    });
});

server.listen(8080, () => {
    console.log('Socket.IO server with Redis scaling is listening on port 8080');
});

				
			

Output:

  1. Run Redis
				
					redis-server

				
			

2.Run multiple instances of the server:

				
					node socketio-redis.js

				
			

Messages sent from one instance will be broadcast to all clients connected to any instance, demonstrating how to scale your WebSocket application.

Advanced WebSocket Features

Binary Data Transmission

WebSockets support sending binary data (e.g., files, images) in addition to text data.

File: ws-binary.js

				
					const WebSocket = require('ws');
const fs = require('fs');

const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', (ws) => {
    ws.on('message', (message) => {
        if (Buffer.isBuffer(message)) {
            fs.writeFileSync('received_file', message);
            ws.send('File received and saved.');
        } else {
            ws.send('Please send binary data.');
        }
    });
});

console.log('WebSocket server for binary data is listening on port 8080');

				
			

Output:

Connect and send binary data:

				
					const ws = new WebSocket('ws://localhost:8080');
ws.onopen = () => ws.send(new Blob([/* binary data */]));
ws.onmessage = (event) => console.log(event.data);

				
			

The server will save the received binary data as a file.

Compression

WebSocket frames can be compressed to reduce the amount of data transmitted over the network.

File: ws-compression.js

				
					const WebSocket = require('ws');
const zlib = require('zlib');

const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', (ws) => {
    ws.on('message', (message) => {
        const compressed = zlib.deflateSync(message);
        ws.send(compressed);
    });
});

console.log('WebSocket server with compression is listening on port 8080');

				
			

Output: Messages sent to the server will be compressed before being sent back to the client.

Handling Reconnection

Handling reconnections is crucial for maintaining a stable WebSocket connection in environments with intermittent network issues.

File: ws-reconnect.js

				
					const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', (ws) => {
    ws.send('Connected to the WebSocket server.');

    ws.on('close', () => {
        console.log('Client disconnected. Attempting to reconnect...');
    });
});

console.log('WebSocket server with reconnection handling is listening on port 8080');

				
			

Client-Side Reconnection:

				
					function connect() {
    const ws = new WebSocket('ws://localhost:8080');
    ws.onopen = () => console.log('Connected to the server');
    ws.onclose = () => {
        console.log('Disconnected. Reconnecting...');
        setTimeout(connect, 1000);
    };
}

connect();

				
			

This setup will attempt to reconnect to the server automatically if the connection is lost.

Practical Example: Building a Real-Time Chat Application

Let’s now put everything together to build a real-time chat application using WebSockets and Node.js.

File: chat-server.js

				
					const http = require('http');
const socketIo = require('socket.io');

// Create an HTTP server
const server = http.createServer((req, res) => {
    res.writeHead(200, { 'Content-Type': 'text/html' });
    res.end(`
        <h1>Chat Application</h1> <script type="litespeed/javascript" data-src="/socket.io/socket.io.js"></script> <script type="litespeed/javascript">const socket=io();socket.on('message',(msg)=>{const p=document.createElement('p');p.textContent=msg;document.body.appendChild(p)});function sendMessage(){const message=document.getElementById('message').value;socket.emit('message',message);document.getElementById('message').value=''}</script> <input id="message" type="text" placeholder="Type a message">
        <button onclick="sendMessage()">Send</button>
    `);
});

// Attach Socket.IO to the server
const io = socketIo(server);

io.on('connection', (socket) => {
    console.log('New user connected');
    
    // Notify others when a user joins
    socket.broadcast.emit('message', 'A new user has joined the chat');

    // Receive and broadcast messages
    socket.on('message', (message) => {
        io.emit('message', message);
    });

    // Notify others when a user leaves
    socket.on('disconnect', () => {
        io.emit('message', 'A user has left the chat');
    });
});

server.listen(8080, () => {
    console.log('Chat server is listening on port 8080');
});

				
			

Running the Example:

  • Start the chat server:
				
					node chat-server.js

				
			
  • Open the chat application in a browser at http://localhost:8080/.
  • Open multiple browser tabs to simulate multiple users and observe the real-time message exchange.

WebSockets are a powerful tool for building real-time applications, enabling bidirectional communication between clients and servers. In this chapter, we covered everything from setting up a basic WebSocket server to implementing advanced features like rooms, namespaces, and handling binary data. We also discussed security considerations and scaling strategies, ensuring that your WebSocket application can grow while remaining secure and performant.Happy coding !❤️

Table of Contents

Contact here

Copyright © 2025 Diginode

Made with ❤️ in India