Error Handling
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
| Parameter | Type | Default | Description |
|---|---|---|---|
message | string | — | Human-readable error message |
statusCode | number | 500 | HTTP 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
callbackwas 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:
| Code | Condition |
|---|---|
| 400 | Invalid port, missing name, reserved name |
| 404 | Route file not found |
| 409 | Duplicate registration |
| 500 | Fastify/Socket.IO startup failure, instantiation error |