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

πŸ”§ Managers - Shared Logic

Managers provide shared functionality that can be accessed by services and controllers. They’re perfect for database connections, external APIs, and business logic.

Key Features:

  • Singleton instances

  • Dependency injection

  • Shared across components

  • Lifecycle management

Example:

import { BaseManager } from '@ioserver/core';

class DatabaseManager extends BaseManager {
  private connection: any;

  constructor(appHandle: any) {
    super(appHandle);
    this.connection = null;
  }

  async connect() {
    // Initialize database connection
    this.connection = await createConnection();
  }

  async findUser(id: string) {
    return await this.connection.query('SELECT * FROM users WHERE id = ?', [
      id,
    ]);
  }

  async createUser(userData: any) {
    const result = await this.connection.query(
      'INSERT INTO users (name, email) VALUES (?, ?)',
      [userData.name, userData.email]
    );
    return { id: result.insertId, ...userData };
  }
}

πŸ‘€ 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;
    }
  }
}