Api

Error Handling

IOServerError — structured errors with HTTP status codes for services and controllers.

Error Handling

IOServerError

IOServerError is the standard error class for IOServer applications. It extends the native Error class with an HTTP status code.

import { IOServerError } from "ioserver";

Constructor

new IOServerError(message: string, statusCode: number = 500): IOServerError
ParameterTypeDefaultDescription
messagestringHuman-readable error message
statusCodenumber500HTTP status code

Properties

class IOServerError extends Error {
  public statusCode: number;
  public name: string;  // Always "IOServerError"
}

Examples

throw new IOServerError("User not found", 404);
throw new IOServerError("Invalid email format", 400);
throw new IOServerError("Admin access required", 403);
throw new IOServerError("Rate limit exceeded", 429);
throw new IOServerError("Database unavailable", 503);
throw new IOServerError("Something went wrong");  // statusCode defaults to 500

Error handling in Controllers (HTTP)

Fastify's error handler (configured by IOServer) serialises IOServerError to a structured JSON response:

{
  "statusCode": 404,
  "error": "IOServerError",
  "message": "User not found"
}

Other errors (with .status property) or plain Error instances are handled similarly but use 500 as the default status code.

Pattern:

export class UserController extends BaseController {
  async getUser(request: any, reply: any): Promise<void> {
    const { id } = request.params as { id: string };

    if (!id?.match(/^[0-9a-f-]{36}$/)) {
      throw new IOServerError("Invalid user ID format", 400);
    }

    const user = await this.appHandle.userManager.findById(id);
    if (!user) throw new IOServerError("User not found", 404);

    reply.send(user);
  }
}

Error handling in Services (WebSocket)

IOServer catches any throw from a service method and returns a structured error to the client:

  • If a callback was provided: callback({ status: "error", type, message, statusCode })
  • Otherwise: socket.emit("error", { status: "error", type, message, statusCode })

Error payload:

{
  status: "error",
  type: "IOServerError",         // error.constructor.name
  message: "Username is required",
  statusCode: 400,
}

Pattern:

export class AuthService extends BaseService {
  async login(socket: any, data: { username: string }, callback?: Function): Promise<void> {
    if (!data?.username?.trim()) {
      throw new IOServerError("Username is required", 400);
    }

    if (this.usernames.has(data.username.toLowerCase())) {
      throw new IOServerError("Username already taken", 409);
    }

    // ...
  }
}

Client-side (browser):

socket.emit("login", { username: "" }, (response) => {
  if (response.status === "error") {
    console.error(`${response.statusCode}: ${response.message}`);
    // "400: Username is required"
  }
});

// Without callback — listen on "error"
socket.on("error", (err) => {
  console.error(err.message); // "Username is required"
});

Checking instanceof

The prototype chain is correctly fixed, so instanceof checks work:

try {
  someOperation();
} catch (err) {
  if (err instanceof IOServerError) {
    console.log(err.statusCode); // 404
  }
}

Framework-level errors

IOServer itself throws IOServerError for misconfiguration and startup failures:

CodeCondition
400Invalid port, missing name, reserved name
404Route file not found
409Duplicate registration
500Fastify/Socket.IO startup failure, instantiation error
Copyright © 2026