Base Classes
Base Classes
All base classes are exported from ioserver and receive an AppHandle in their constructor.
Import
import {
BaseService,
BaseController,
BaseManager,
BaseWatcher,
BaseMiddleware,
} from "ioserver";
BaseService
abstract class BaseService {
protected appHandle: AppHandle;
constructor(appHandle: AppHandle);
}
Extend this class to create a WebSocket service. Every public method whose name does not start with _ and is not constructor is automatically registered as a Socket.IO event handler on the service's namespace.
Method signature (required):
async methodName(socket: any, data: any, callback?: Function): Promise<void>
Example:
class PingService extends BaseService {
async ping(socket: any, _data: any, callback?: Function): Promise<void> {
if (callback) callback({ pong: true, ts: Date.now() });
else socket.emit("pong", { ts: Date.now() });
}
// Not exposed as a WS event
private _validate(data: any): boolean {
return data !== null && data !== undefined;
}
}
See Services for full documentation.
BaseController
abstract class BaseController {
protected appHandle: AppHandle;
constructor(appHandle: AppHandle);
}
Extend this class to create an HTTP controller. Methods are mapped to routes via a JSON route file — they are not auto-discovered.
Method signature (Fastify handlers):
async methodName(request: FastifyRequest, reply: FastifyReply): Promise<void>
Example:
class HealthController extends BaseController {
async getHealth(request: any, reply: any): Promise<void> {
reply.send({ status: "ok", ts: new Date().toISOString() });
}
}
See Controllers for full documentation.
BaseManager
abstract class BaseManager {
protected appHandle: AppHandle;
constructor(appHandle: AppHandle);
// Optional lifecycle hook — called automatically after instantiation
async start?(): Promise<void>;
}
Extend this class to create a shared singleton. The optional start() method is called automatically (non-blocking) after addManager().
Example:
class TokenManager extends BaseManager {
private readonly secret: string;
constructor(appHandle: AppHandle) {
super(appHandle);
this.secret = process.env.JWT_SECRET ?? "change-me";
}
async start(): Promise<void> {
this.appHandle.log(6, "TokenManager initialized");
}
sign(payload: object): string {
// sign JWT...
return "token";
}
verify(token: string): object | null {
// verify JWT...
return null;
}
}
See Managers for full documentation.
BaseWatcher
abstract class BaseWatcher {
protected appHandle: AppHandle;
constructor(appHandle: AppHandle);
abstract watch(): Promise<void>; // Called at server.start()
abstract stop(): void; // Called at server.stop()
}
Both abstract methods must be implemented. watch() is called at server.start() (non-blocking). stop() is called at server.stop().
Example:
class HeartbeatWatcher extends BaseWatcher {
private intervalId: ReturnType<typeof setInterval> | null = null;
async watch(): Promise<void> {
this.intervalId = setInterval(() => {
this.appHandle.log(7, `Heartbeat — heap: ${Math.round(process.memoryUsage().heapUsed / 1e6)}MB`);
}, 10_000);
}
stop(): void {
if (this.intervalId !== null) {
clearInterval(this.intervalId);
this.intervalId = null;
}
}
}
See Watchers for full documentation.
BaseMiddleware
abstract class BaseMiddleware {
abstract handle(appHandle: AppHandle): (req: any, reply: any, done: any) => void;
}
Extend this class to create HTTP and/or WebSocket middleware. BaseMiddleware is the only base class that does not receive appHandle in its constructor — instead, appHandle is passed to handle() each time the middleware is instantiated.
BaseMiddleware is instantiated fresh each time a middleware is applied to a route or namespace (unlike managers which are singletons).HTTP middleware example:
class LoggingMiddleware extends BaseMiddleware {
handle(appHandle: AppHandle) {
return (req: any, _reply: any, done: any) => {
appHandle.log(6, `→ ${req.method} ${req.url}`);
done();
};
}
}
WebSocket middleware example:
class WsAuthMiddleware extends BaseMiddleware {
handle(appHandle: AppHandle) {
return (socket: any, next: any) => {
const token = socket.handshake.auth?.token;
if (!token) return next(new Error("Token required"));
const userId = appHandle.sessionManager?.validate(token);
if (!userId) return next(new Error("Invalid token"));
socket.userId = userId;
next();
};
}
}
See Middlewares for full documentation.