Middlewares
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();
};
}
}