Skip to content
EventFabric CQRS Framework

Exceptions

EventFabric provides a set of structured exceptions for handling errors in your application. These exceptions have and optional status code and can include additional details for debugging.

An in Depth Example

This guide also has an in depth example of a working application built with EventFabric. Combining DDD, CQRS and Event Sourcing.

Check out the In Depth Example page to learn how everything is connected and works out in a real-world application.

The basic Exception class has an optional status code that can be set when creating the exceptions and you can assign any number as a value.

However, EventFabric comes with some built-in exceptions that use the related HTTP status codes. As HTTP status codes are standardized and well-known we thought it would be a good idea to use them even though the Exceptions itself are transport agnostic.

ExceptionStatus CodeUse Case
GenericException500Internal server errors, unexpected failures
InvalidInputException400Validation errors, malformed requests
NotFoundException404Resource not found
UnauthorizedException401Authentication required or failed
ForbiddenException403Authorization failed, access denied

All exceptions accept an optional message and details object:

import {
    ForbiddenException,
    GenericException,
    InvalidInputException,
    NotFoundException,
    UnauthorizedException,
} from "@eventfabric-cqrs/core";

// Generic server error (500)
throw new GenericException("Something went wrong");

// Invalid input with details (400)
throw new InvalidInputException("The input is invalid", {
    field: "email",
    reason: "Invalid email format",
});

// Unauthorized (401)
throw new UnauthorizedException();

// Forbidden (403)
throw new ForbiddenException();

// Not found with details (404)
throw new NotFoundException("User not found", {
    errorCode: "USER_NOT_FOUND",
    userId: "12345",
});

Use fromError() to convert a standard JavaScript error while preserving the stack trace:

import { GenericException } from "@eventfabric-cqrs/core";

try {
    await someExternalService.call();
} catch (error) {
    const exception = new GenericException();
    exception.fromError(error);
    throw exception;
}

If you need to manually handle Zod validation:

import { InvalidInputException } from "@eventfabric-cqrs/core";
import { z } from "zod";

const UserSchema = z.object({
    email: z.email(),
    name: z.string().min(1),
});

try {
    UserSchema.parse({ email: "invalid", name: "" });
} catch (error) {
    const exception = new InvalidInputException();
    exception.fromZodError(error);
    throw exception;
}

Create custom exceptions by extending the base Exception class:

import { Exception } from "@eventfabric-cqrs/core";

export class RateLimitException extends Exception {
    constructor(message?: string, details?: Record<string, unknown>) {
        super(
            "RATE_LIMIT_EXCEEDED",
            message ?? "Rate limit exceeded",
            details,
            429 // Too Many Requests
        );
    }
}

// Usage
throw new RateLimitException("Too many requests", {
    retryAfter: 60,
    limit: 100,
});

When using the @nimbus-cqrs/hono package, exceptions are automatically converted to HTTP responses:

import { onError } from "@nimbus-cqrs/hono";
import { Hono } from "hono";

const app = new Hono();

// Configure error handler
app.onError(onError);

// Exceptions thrown in routes are converted to JSON responses
app.get("/users/:id", async (c) => {
    throw new NotFoundException("User not found", {
        userId: c.req.param("id"),
    });
    // Returns: { "error": "NOT_FOUND", "message": "User not found", "details": { "userId": "123" } }
    // Status: 404
});

Choose the most specific exception type for the situation:

// ✅ Good - Specific exception
throw new NotFoundException("Order not found");

// ❌ Bad - Generic exception for known error
throw new GenericException("Order not found");

Add details that help with debugging:

throw new InvalidInputException("Invalid order data", {
    errorCode: "INVALID_ORDER",
    field: "quantity",
    value: -5,
    constraint: "must be positive",
});

Include machine- and human-readable error codes for client handling:

throw new NotFoundException("User not found", {
    errorCode: "USER_NOT_FOUND", // Clients can check this AND translate it to a human-readable error message in multiple languages
    userId: id,
});