Components

Middlewares

HTTP and WebSocket request guards with BaseMiddleware.

Middlewares

Middlewares intercept HTTP requests (Fastify preValidation hook) or WebSocket connections (Socket.IO namespace use hook). The same BaseMiddleware class works for both transports — the returned function signature determines which context it is used in.

Base class

import { BaseMiddleware, AppHandle } from "ioserver";

abstract class BaseMiddleware {
  abstract handle(appHandle: AppHandle): (req: any, reply: any, done: any) => void;
}

handle() receives appHandle at instantiation time (so it can access managers) and returns the middleware function that Fastify or Socket.IO will call on each request/connection.

HTTP middleware

For HTTP controllers. The returned function matches the Fastify preValidation hook signature (request, reply, done).

import { BaseMiddleware, AppHandle, IOServerError } from "ioserver";

export class AuthMiddleware extends BaseMiddleware {
  handle(appHandle: AppHandle) {
    return (req: any, reply: any, done: any) => {
      const authHeader = req.headers.authorization as string | undefined;

      if (!authHeader?.startsWith("Bearer ")) {
        return reply.code(401).send({
          statusCode: 401,
          error: "Unauthorized",
          message: "Bearer token required",
        });
      }

      const token = authHeader.slice(7);

      try {
        const userId = appHandle.sessionManager.validate(token);
        if (!userId) {
          return reply.code(401).send({
            statusCode: 401,
            error: "Unauthorized",
            message: "Invalid or expired token",
          });
        }
        req.userId = userId;
        done();
      } catch (err) {
        appHandle.log(3, `Auth middleware error: ${err}`);
        return reply.code(500).send({ statusCode: 500, error: "Internal Server Error" });
      }
    };
  }
}

Apply to a controller:

server.addController({
  name: "api",
  controller: ApiController,
  middlewares: [AuthMiddleware],
});

WebSocket middleware

For Socket.IO namespaces. The returned function is called with (socket, next) — matching Socket.IO's namespace.use() signature.

export class SocketAuthMiddleware extends BaseMiddleware {
  handle(appHandle: AppHandle) {
    return (socket: any, next: any) => {
      const token = socket.handshake.auth?.token as string | undefined;

      if (!token) {
        return next(new Error("Authentication required"));
      }

      const userId = appHandle.sessionManager.validate(token);
      if (!userId) {
        return next(new Error("Invalid token"));
      }

      // Attach user data to socket for use in service methods
      socket.userId = userId;
      next();
    };
  }
}

Apply to a service:

server.addService({
  name: "chat",
  service: ChatService,
  middlewares: [SocketAuthMiddleware],
});

Multiple middlewares

Pass an array — they are applied in order:

server.addController({
  name: "admin",
  controller: AdminController,
  middlewares: [AuthMiddleware, RateLimitMiddleware, AuditLogMiddleware],
});

Accessing request data set by middleware

In HTTP controllers, data attached to request in the middleware is available in the handler:

export class ApiController extends BaseController {
  async getProfile(request: any, reply: any): Promise<void> {
    // request.userId was set by AuthMiddleware
    const user = await this.appHandle.userManager.findById(request.userId);
    reply.send(user);
  }
}

In WebSocket services, data attached to socket in the middleware is available in event handlers:

export class ChatService extends BaseService {
  async send_message(socket: any, data: any, callback?: Function): Promise<void> {
    // socket.userId was set by SocketAuthMiddleware
    this.appHandle.log(6, `Message from user ${socket.userId}`);
  }
}

Common middleware patterns

Role-based access control

export class AdminMiddleware extends BaseMiddleware {
  handle(appHandle: AppHandle) {
    return (req: any, reply: any, done: any) => {
      const role = appHandle.userManager.getRole(req.userId);
      if (role !== "admin") {
        return reply.code(403).send({
          statusCode: 403,
          error: "Forbidden",
          message: "Admin access required",
        });
      }
      done();
    };
  }
}

Request logging

export class LoggingMiddleware extends BaseMiddleware {
  handle(appHandle: AppHandle) {
    return (req: any, _reply: any, done: any) => {
      appHandle.log(6, `${req.method} ${req.url}`);
      done();
    };
  }
}

Rate limiting

export class RateLimitMiddleware extends BaseMiddleware {
  handle(appHandle: AppHandle) {
    return (req: any, reply: any, done: any) => {
      const allowed = appHandle.rateLimiter.consume(req.ip);
      if (!allowed) {
        return reply.code(429).send({
          statusCode: 429,
          error: "Too Many Requests",
          message: "Rate limit exceeded. Please slow down.",
        });
      }
      done();
    };
  }
}
Copyright © 2026