Websocket Middleware
title: "WebSocket Middleware" description: "Learn how to implement middleware for WebSocket connections including authentication, logging, rate limiting, and message validation." weight: 19 date: 2026-06-28 lastmod: 2026-06-28 tags: ["apis", "websocket"]
Middleware in WebSocket applications processes connections and messages before they reach handlers. Common middleware includes authentication, logging, rate limiting, validation, and transformation.
## What You'll Learn
- WebSocket middleware patterns
- Authentication middleware
- Rate limiting for WebSocket
- Message validation and transformation
- Error handling middleware
## Why It Matters
Middleware keeps your WebSocket code clean and modular. Reusable middleware components handle cross-cutting concerns consistently across all connection handlers.
## Real-World Use
A customer support chat platform uses a middleware pipeline: rate limiting prevents spam, authentication validates user tokens, logging tracks all interactions, and message validation ensures chat messages meet content policies.
## Flow Chart
```mermaid
flowchart LR
A[WebSocket Connection] --> B[Authentication Middleware]
B --> C[Rate Limiting Middleware]
C --> D[Logging Middleware]
D --> E[Message Validation]
E --> F[Handler]
F --> G[Response Middleware]
Code Examples
Example 1: Middleware Pipeline with ws
const WebSocket = require('ws');
class MiddlewarePipeline {
constructor() {
this.middlewares = [];
}
use(fn) {
this.middlewares.push(fn);
return this;
}
execute(context, finalHandler) {
let index = 0;
const next = () => {
const middleware = this.middlewares[index++];
if (middleware) {
middleware(context, next);
} else {
finalHandler(context);
}
};
next();
}
}
// Middleware implementations
const authMiddleware = (ctx, next) => {
const token = ctx.upgradeReq.headers['authorization'];
if (!token || !validateToken(token)) {
ctx.ws.close(4001, 'Unauthorized');
return;
}
ctx.user = decodeToken(token);
next();
};
const loggerMiddleware = (ctx, next) => {
console.log(`[${new Date().toISOString()}] ${ctx.user?.id || 'anonymous'} connected`);
const startTime = Date.now();
const originalSend = ctx.ws.send.bind(ctx.ws);
ctx.ws.send = (data) => {
console.log(`[WS] Sent: ${data.substring(0, 100)}`);
originalSend(data);
};
ctx.ws.on('message', (message) => {
console.log(`[WS] Received from ${ctx.user?.id}: ${message}`);
});
ctx.ws.on('close', () => {
const duration = Date.now() - startTime;
console.log(`[WS] ${ctx.user?.id} disconnected after ${duration}ms`);
});
next();
};
const rateLimiterMiddleware = (ctx, next) => {
// Simple per-user rate limiting
const userId = ctx.user?.id || ctx.ws._socket.remoteAddress;
if (isRateLimited(userId)) {
ctx.ws.close(4002, 'Rate limit exceeded');
return;
}
next();
};
// Usage
const pipeline = new MiddlewarePipeline();
pipeline.use(authMiddleware);
pipeline.use(rateLimiterMiddleware);
pipeline.use(loggerMiddleware);
const server = new WebSocket.Server({ port: 8080 });
server.on('connection', (ws, req) => {
const context = { ws, upgradeReq: req };
pipeline.execute(context, (ctx) => {
console.log('Handler reached for user:', ctx.user?.id);
ws.send('Connected successfully');
ws.on('message', (message) => {
// Handle message
});
});
});
Expected output: Middleware pipeline processes each connection through authentication, rate limiting, and logging before reaching the handler.
Example 2: Socket.IO Middleware
const { Server } = require('socket.io');
const io = new Server(3000);
// Authentication middleware
io.use((socket, next) => {
const token = socket.handshake.auth.token;
if (!token) {
return next(new Error('Authentication required'));
}
try {
const user = verifyJWT(token);
socket.user = user;
next();
} catch (err) {
next(new Error('Invalid token'));
}
});
// Rate limiting middleware
const rateLimit = require('socket.io-rate-limit');
io.use(rateLimit({
max: 100,
timeWindow: 60000,
errorMessage: 'Rate limit exceeded',
}));
// Message validation middleware
io.on('connection', (socket) => {
// Wrap message handler with validation
socket.use((packet, next) => {
const [event, data] = packet;
if (event === 'message') {
if (typeof data.text !== 'string' || data.text.length > 1000) {
return next(new Error('Invalid message format'));
}
if (data.text.trim().length === 0) {
return next(new Error('Message cannot be empty'));
}
}
next();
});
socket.on('message', (data) => {
// This handler is only reached after validation passes
io.emit('message', {
user: socket.user.name,
text: data.text,
timestamp: Date.now(),
});
});
});
Expected output: Socket.IO server with authentication, rate limiting, and message validation middleware chained together.
Example 3: Custom Middleware for Message Transformation
const WebSocket = require('ws');
function createSanitizationMiddleware(options = {}) {
return (ws) => {
const originalSend = ws.send.bind(ws);
// Transform outgoing messages
ws.send = (data) => {
let processed = data;
if (options.stripHtml) {
processed = stripHtmlTags(processed);
}
if (options.maxLength) {
processed = processed.substring(0, options.maxLength);
}
originalSend(processed);
};
// Capture and transform incoming messages
ws.on('message', (message) => {
let processed = message.toString();
if (options.stripHtml) {
processed = stripHtmlTags(processed);
}
if (options.maxLength) {
processed = processed.substring(0, options.maxLength);
}
// Emit processed message for handlers
ws.emit('sanitized-message', processed);
});
};
}
function createCompressionMiddleware() {
return (ws) => {
const originalSend = ws.send.bind(ws);
ws.send = (data) => {
if (typeof data === 'string' && data.length > 1000) {
// Compress large messages
const compressed = compress(data);
originalSend(compressed, { binary: true });
} else {
originalSend(data);
}
};
};
}
// Apply middleware to all connections
const server = new WebSocket.Server({ port: 8080 });
server.on('connection', (ws) => {
createSanitizationMiddleware({
stripHtml: true,
maxLength: 10000,
})(ws);
createCompressionMiddleware()(ws);
ws.on('sanitized-message', (message) => {
ws.send(`Echo: ${message}`);
});
});
Expected output: Connection-level middleware that sanitizes HTML tags and compresses large messages transparently.
Common Mistakes
| Mistake | Explanation |
|---|---|
| Not handling middleware errors | Middleware errors should gracefully close connections or skip handlers |
| Blocking the event loop in middleware | Middleware should be async or use non-blocking operations |
| Forgetting to call next() | Middleware must call next() or the pipeline stalls |
| Modifying shared state in middleware | Middleware should use request-scoped state, not global mutable state |
| Applying serial middleware to async operations | Use async middleware patterns for database lookups or API calls |
Practice Questions
- What is the middleware pattern in WebSocket applications?
- How do you implement authentication as WebSocket middleware?
- How does Socket.IO middleware differ from raw ws middleware?
- How do you handle errors in middleware?
- What are common middleware use cases beyond authentication?
Challenge
Build a WebSocket server with a middleware pipeline that includes authentication (JWT), rate limiting (per user, per second), message validation (schema check), logging (connection and message events), and compression (for large messages). Demonstrate the pipeline with a chat application.
FAQ
Mini Project
Build a secure WebSocket API gateway with a middleware pipeline for an e-commerce platform. Include authentication middleware (JWT), rate limiting (per endpoint), request validation (JSON Schema), audit logging, and response transformation. Wrap all middleware into a reusable package.
What's Next
Learn about sticky sessions for WebSocket
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro