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"
}

Errors carrying a .status property are serialised the same way, using that status code. Any other (unhandled) Error is masked: its real message is logged server-side (log level 3) and the client receives a generic 500 response — { "statusCode": 500, "error": "Internal Server Error", "message": "Internal Server Error" } — so internal details (stack traces, database driver messages, file paths) are never leaked. Always throw an IOServerError when you want a specific message and status code to reach the client.

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