bugl
bugl
HomeLearnPatternsPathsSearchPremium
HomeLearnPatternsPaths

Loading lesson path

Learn/Node.js/Advanced Communication
Node.js•Advanced Communication

Node.js WebSockets

Introduction to WebSockets

WebSockets provide a persistent connection between client and server, allowing for real-time, bidirectional communication.

This is different from traditional HTTP, which follows a request-response model.

  • Real-time updates : Instantly push data to clients
  • Efficient : No need for repeated HTTP requests
  • Bidirectional : Both client and server can send messages
  • Low latency : Messages are sent immediately

WebSockets vs HTTP

Understanding the difference between WebSockets and HTTP is crucial for building real-time applications effectively.

FeatureWebSocketsHTTP
ConnectionPersistent, single connectionNew connection per request
CommunicationBidirectional, full-duplexUnidirectional, request-response
OverheadMinimal after handshakeHeaders with every request
Use CaseReal-time applicationsTraditional web pages, APIs
ExampleChat apps, live feedsLoading web pages, form submissions

Pro Tip: WebSockets begin with an HTTP handshake (status code 101) before upgrading to the WebSocket protocol (ws:// or wss://).

Install the ws Module

First, create a new directory for your project and initialize it:

mkdir websocket-demo
cd websocket-demo
npm init -y

Then, install the ws package:

npm install ws

Note

The ws module is a simple, fast, and thoroughly tested WebSocket client and server implementation.

Creating a WebSocket Server

Let's create a simple WebSocket server that echoes back any message it receives.

Create a new file called server.js :

Example: WebSocket Echo Server

const WebSocket = require('ws');
// Create a WebSocket server on port 8080
const wss = new WebSocket.Server({ port: 8080 });
console.log('WebSocket server is running on ws://localhost:8080');
// Connection event handler
wss.on('connection', (ws) => {
 console.log('New client connected');
 // Send a welcome message to the client
 ws.send('Welcome to the WebSocket server!');
 // Message event handler
 ws.on('message', (message) => {
 console.log(`Received: ${message}`);
 // Echo the message back to the client
 ws.send(`Server received: ${message}`);
 });
 // Close event handler
 ws.on('close', () => {
 console.log('Client disconnected');
 });
});

Understanding the Code

  • We import the ws module
  • Create a new WebSocket server on port 8080
  • Set up event handlers for connections, messages, and disconnections
  • Echo back any received messages to the client
  1. Save the code above as server.js
  1. Run the server: node server.js
  1. The server will start and listen on ws://localhost:8080

Creating a WebSocket Client

Now that we have a WebSocket server, let's create clients to connect to it. We'll create both a Node.js client and a browser client.

Node.js Client

Create a new file called client.js :

const WebSocket = require('ws');
const readline = require('readline');
// Create readline interface for user input
const rl = readline.createInterface({
 input: process.stdin,
 output: process.stdout
});
// Connect to the WebSocket server
const ws = new WebSocket('ws://localhost:8080');
// Connection opened
ws.on('open', () => {
 console.log('Connected to the WebSocket server');
 promptForMessage();
});
// Listen for messages from the server
ws.on('message', (message) => {
 console.log(`Server: ${message}`);
});
// Handle errors
ws.on('error', (error) => {
 console.error('WebSocket error:', error);
});
// Handle connection close
ws.on('close', () => {
 console.log('Disconnected from the server');
 process.exit(0);
});
// Function to prompt user for messages
function promptForMessage() {
 rl.question('Enter a message (or "exit" to quit): ', (message) => {
 if (message.toLowerCase() === 'exit') {
 ws.close();
 rl.close();
 return;
 }
 ws.send(message);
 promptForMessage();
 });
}
  • Save the code above as client.js
  • Make sure the WebSocket server is running
  • Run the client: node client.js
  • Type messages and press Enter to send them to the server
  • Type "exit" to quit

Browser Client

Let's create a simple HTML page with JavaScript to connect to our WebSocket server.

Create a file named index.html :

<!DOCTYPE html>
 <html>
 <head>
 <title>WebSocket Client</title>
 <style>
 body {
 font-family: Arial, sans-serif;
 max-width: 600px;
 margin: 0 auto;
 padding: 20px;
 }
 #messages {
 height: 300px;
 border: 1px solid #ccc;
 overflow-y: auto;
 padding: 10px;
 margin-bottom: 10px;
 }
 .message { margin: 5px 0; }
 </style>
 </head>
 <body>
 <h1>WebSocket Client</h1>
 <div id="status">Connecting to server...</div>
 <div id="messages"></div>
 <div>
 <input type="text" id="messageInput" placeholder="Type your message">
 <button onclick="sendMessage()">Send</button>
 </div>
 <script>
 const status = document.getElementById('status');
 const messages = document.getElementById('messages');
 const messageInput = document.getElementById('messageInput');
 // Connect to the WebSocket server
 const ws = new WebSocket('ws://localhost:8080');
 // Connection opened
 ws.onopen = () => {
 status.textContent = 'Connected to server';
 status.style.color = 'green';
 };
 // Listen for messages
 ws.onmessage = (event) => {
 const message = document.createElement('div');
 message.className = 'message';
 message.textContent = event.data;
 messages.appendChild(message);
 messages.scrollTop = messages.scrollHeight;
 };
 // Handle errors
 ws.onerror = (error) => {
 status.textContent = 'Error: ' + error.message;
 status.style.color = 'red';
 };
 // Handle connection close
 ws.onclose = () => {
 status.textContent = 'Disconnected from server';
 status.style.color = 'red';
 };
 // Function to send a message
 function sendMessage() {
 const message = messageInput.value.trim();
 if (message) {
 ws.send(message);
 messageInput.value = '';
 }
 }
 // Send message on Enter key
 messageInput.addEventListener('keypress', (e) => {
 if (e.key === 'Enter') {
 sendMessage();
 }
 });
 </script>
 </body>
 </html>
  • Save the code above as index.html
  • Make sure the WebSocket server is running
  • Open the HTML file in a web browser
  • Type messages in the input field and click Send or press Enter

Note

For the browser client to work, you'll need to serve the HTML file through a web server (like http-server or live-server ) due to browser security restrictions.

Testing the Application

  • Start the WebSocket server: node server.js
  • Open multiple browser windows with the client HTML page
  • Send messages from different clients and see them appear in real-time
  • You can also run the Node.js client alongside the browser clients
  • The server maintains a set of all connected clients
  • When a message is received from one client, it's broadcast to all others
  • The client handles connection, disconnection, and error events
  • Messages are displayed in real-time as they're received

WebSocket Events

WebSockets use an event-driven model. Here are the key events:

EventDescription
connection (server)Fired when a client connects to the server
open (client)Fired when the connection is established
messageFired when a message is received
errorFired when an error occurs
closeFired when the connection is closed

Real-world Applications

WebSockets are used in a variety of real-world applications:

  • Chat Applications: Instant message delivery
  • Live Dashboards: Real-time updates of metrics and data
  • Collaborative Tools: Multiple users editing the same document
  • Gaming: Multiplayer online games requiring fast interactions
  • Financial Platforms: Real-time stock tickers and trading platforms
  • IoT Applications: Monitoring and controlling connected devices

Binary Data Transfer

WebSockets support sending binary data, which is more efficient for certain types of data:

// Sending binary data (server-side)
const buffer = Buffer.from([0x48, 0x65, 0x6c, 0x6c, 0x6f]); // 'Hello' in binary
ws.send(buffer, { binary: true });
// Receiving binary data (client-side)
ws.binaryType = 'arraybuffer';
ws.onmessage = (event) => {
 if (event.data instanceof ArrayBuffer) {
 const view = new Uint8Array(event.data);
 console.log('Received binary data:', view);
 }
};

Heartbeats and Connection Monitoring

Implement heartbeats to detect and handle disconnections:

// Server-side heartbeat
function setupHeartbeat(ws) {
 ws.isAlive = true;
 ws.on('pong', () => { ws.isAlive = true; });
}
// Ping all clients every 30 seconds
const interval = setInterval(() => {
 wss.clients.forEach((ws) => {
 if (ws.isAlive === false) return ws.terminate();
 ws.isAlive = false;
 ws.ping();
 });
}, 30000);
// Clean up on server close
wss.on('close', () => {
 clearInterval(interval);
});

Authentication

Always authenticate WebSocket connections

const http = require('http');
const WebSocket = require('ws');
const jwt = require('jsonwebtoken');
const server = http.createServer();
const wss = new WebSocket.Server({ noServer: true });
// Handle upgrade with authentication
server.on('upgrade', (request, socket, head) => {
 try {
 const token = request.url.split('token=')[1];
 if (!token) throw new Error('No token provided');
 // Verify JWT token
 jwt.verify(token, 'your-secret-key', (err, decoded) => {
 if (err) {
 socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
 socket.destroy();
 return;
 }
 // Proceed with WebSocket handshake
 wss.handleUpgrade(request, socket, head, (ws) => {
 ws.user = decoded; // Attach user data to WebSocket
 wss.emit('connection', ws, request);
 });
 });
 } catch (error) {
 socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
 socket.destroy();
}
});

Rate Limiting

Prevent abuse with rate limiting

const rateLimit = require('ws-rate-limit');
// Limit to 100 messages per minute per connection
const limiter = rateLimit({
 windowMs: 60 * 1000, // 1 minute
 max: 100,
 message: 'Too many messages, please slow down!',
});
wss.on('connection', (ws) => {
 limiter(ws);
 // ... rest of your connection handler
});

Input Validation

Always validate incoming messages

const Joi = require('joi');
const messageSchema = Joi.object({
 type: Joi.string().valid('chat', 'join', 'leave').required(),
 username: Joi.string().alphanum().min(3).max(30),
 message: Joi.string().max(1000),
 room: Joi.string().alphanum().max(50),
});
ws.on('message', (data) => {
 try {
 const message = JSON.parse(data);
 const { error, value } = messageSchema.validate(message);
 if (error) {
 throw new Error(`Invalid message: ${error.details[0].message}`);
 }
 // Process valid message...
 } catch (err) {
 ws.send(JSON.stringify({ error: err.message }));
}
});

Compression

Enable per-message deflate to reduce bandwidth usage:

const WebSocket = require('ws');
const wss = new WebSocket.Server({
 port: 8080,
 perMessageDeflate: {
 zlibDeflateOptions: {
 chunkSize: 1024,
 memLevel: 7,
 level: 3
 },
 zlibInflateOptions: {
 chunkSize: 10 * 1024
 },
 // Other options
 clientNoContextTakeover: true,
 serverNoContextTakeover: true,
 concurrencyLimit: 10,
 }
});

Best Practice: For production applications, consider using libraries like Socket.IO which provides additional features like fallbacks for browsers that don't support WebSockets.

Previous

Node.js Socket.IO

Next chapter

Testing & Debugging

Start with Node.js Advanced Debugging