Skip to content

Websocket Middleware

DodaTech 5 min read

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

  1. What is the middleware pattern in WebSocket applications?
  2. How do you implement authentication as WebSocket middleware?
  3. How does Socket.IO middleware differ from raw ws middleware?
  4. How do you handle errors in middleware?
  5. 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

Can I use Express middleware with WebSocket?

Express middleware works only with HTTP. For WebSocket, implement custom middleware or use Socket.IO's use method.

How do middleware errors propagate?

Middleware errors should be caught and either close the connection or pass the error to an error handler.

Can middleware modify the message payload?

Yes, middleware can transform both incoming and outgoing messages before they reach handlers.

What is the performance impact of middleware?

Each middleware adds latency. Keep middleware lightweight and avoid blocking operations in the connection path.

Can I conditionally skip middleware?

Yes, middleware can check conditions and call next() without processing to skip itself.

How do I test WebSocket middleware?

Create a test WebSocket client, connect to the server, and verify that middleware behaviors (auth rejection, rate limiting, etc.) work correctly.

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