Architectureο
IOServer provides a modular architecture with four core component types that promote clean separation of concerns and scalable application design.
Core Componentsο
π Controllers - HTTP Endpointsο
Controllers handle HTTP requests and responses. They map to route definitions and provide RESTful API functionality.
Key Features:
Automatic route mapping from JSON configuration
Middleware support
Error handling
Request validation
Example:
import { BaseController } from '@ioserver/core';
class UserController extends BaseController {
async getUser(request: any, reply: any) {
const { id } = request.params;
const user = await this.appHandle.database.findUser(id);
reply.send(user);
}
async createUser(request: any, reply: any) {
const userData = request.body;
const user = await this.appHandle.database.createUser(userData);
reply.status(201).send(user);
}
}
π‘ Services - Real-time Logicο
Services handle WebSocket connections and real-time events. They provide the core real-time functionality of your application.
Key Features:
Socket.IO integration
Automatic event binding
Namespace support
Real-time messaging
Example:
import { BaseService } from '@ioserver/core';
class ChatService extends BaseService {
async joinRoom(socket: any, data: any, callback?: Function) {
await socket.join(data.room);
socket.broadcast.to(data.room).emit('user_joined', {
user: data.username,
room: data.room,
});
if (callback) callback({ status: 'joined' });
}
async sendMessage(socket: any, data: any, callback?: Function) {
const message = {
id: Date.now(),
user: data.user,
message: data.message,
timestamp: new Date().toISOString(),
};
socket.broadcast.to(data.room).emit('new_message', message);
if (callback) callback({ status: 'sent', messageId: message.id });
}
}
π Watchers - Background Tasksο
Watchers handle background processes, monitoring, and scheduled tasks. They run independently of HTTP requests and WebSocket connections.
Key Features:
Background processing
Scheduled tasks
System monitoring
Independent lifecycle
Example:
import { BaseWatcher } from '@ioserver/core';
class HealthWatcher extends BaseWatcher {
async watch() {
// Check system health every 30 seconds
setInterval(() => {
this.checkSystemHealth();
}, 30000);
// Check database connectivity every 5 minutes
setInterval(() => {
this.checkDatabaseHealth();
}, 300000);
}
private async checkSystemHealth() {
const memoryUsage = process.memoryUsage();
const cpuUsage = process.cpuUsage();
if (memoryUsage.heapUsed > 500 * 1024 * 1024) {
// 500MB
this.appHandle.log(4, '[WARNING] High memory usage detected');
}
// Send health metrics to monitoring service
this.appHandle.send({
namespace: 'admin',
event: 'health_update',
data: { memory: memoryUsage, cpu: cpuUsage },
});
}
private async checkDatabaseHealth() {
try {
await this.appHandle.database.ping();
this.appHandle.log(6, '[INFO] Database health check passed');
} catch (error) {
this.appHandle.log(3, `[ERROR] Database health check failed: ${error}`);
}
}
}
Component Registrationο
Servicesο
server.addService({
name: 'chat', // Namespace (optional, defaults to '/')
service: ChatService, // Service class
middlewares: [], // Optional middleware
});
Controllersο
server.addController({
name: 'api', // Route file name (api.json)
controller: ApiController, // Controller class
prefix: '/api', // Optional URL prefix
middlewares: [], // Optional middleware
});
Managersο
server.addManager({
name: 'database', // Access name in appHandle
manager: DatabaseManager, // Manager class
});
Watchersο
server.addWatcher({
name: 'health', // Watcher name
watcher: HealthWatcher, // Watcher class
});
Application Handleο
The appHandle
provides shared functionality across all components:
interface AppHandle {
send: (options: SendToOptions) => boolean; // Send real-time messages
log: (level: number, text: string) => void; // Logging
verbose: LogLevel; // Current log level
[managerName]: any; // Registered managers
}
Middleware Systemο
Middleware can be applied to services and controllers for cross-cutting concerns:
import { BaseMiddleware } from '@ioserver/core';
class AuthMiddleware extends BaseMiddleware {
handle(appHandle: any) {
return (req: any, reply: any, done: any) => {
const token = req.headers.authorization;
if (!token) {
return reply.status(401).send({ error: 'Unauthorized' });
}
// Validate token
try {
const user = validateToken(token);
req.user = user;
done();
} catch (error) {
reply.status(401).send({ error: 'Invalid token' });
}
};
}
}
Route Configurationο
Routes are defined in JSON files in the routes
directory:
[
{
"method": "GET",
"url": "/users/:id",
"handler": "getUser"
},
{
"method": "POST",
"url": "/users",
"handler": "createUser",
"preValidation": ["validateUserData"]
}
]
Namespacesο
Services can be organized into namespaces for better organization:
// Default namespace (/)
server.addService({ service: DefaultService });
// Custom namespace (/chat)
server.addService({ name: 'chat', service: ChatService });
// Admin namespace (/admin)
server.addService({ name: 'admin', service: AdminService });
Clients connect to namespaces:
// Default namespace
const socket = io('http://localhost:3000');
// Chat namespace
const chatSocket = io('http://localhost:3000/chat');
// Admin namespace
const adminSocket = io('http://localhost:3000/admin');
Error Handlingο
IOServer provides comprehensive error handling:
import { IOServerError } from '@ioserver/core';
class UserService extends BaseService {
async createUser(socket: any, data: any, callback?: Function) {
try {
if (!data.email) {
throw new IOServerError('Email is required', 400);
}
const user = await this.appHandle.database.createUser(data);
if (callback) callback({ status: 'success', user });
} catch (error) {
// Error is automatically handled and sent to client
throw error;
}
}
}