add-error-type

Add a new custom error type for domain-specific errors. Use when creating errors for specific business rules or HTTP status codes. Triggers on "add error", "custom error", "error type".

$ 설치

git clone https://github.com/madooei/backend-template /tmp/backend-template && cp -r /tmp/backend-template/.claude/skills/add-error-type ~/.claude/skills/backend-template

// tip: Run this command in your terminal to install the skill


name: add-error-type description: Add a new custom error type for domain-specific errors. Use when creating errors for specific business rules or HTTP status codes. Triggers on "add error", "custom error", "error type".

Add Error Type

Adds a new domain error type that extends BaseError. All custom errors are defined in src/errors.ts and automatically handled by the global error handler.

Quick Reference

File: src/errors.ts Base class: BaseError Auto HTTP mapping: errorCode number maps to HTTP status

Existing Error Types

Error ClassHTTP StatusUse Case
BadRequestError400Invalid input, validation fails
UnauthenticatedError401Missing or invalid credentials
UnauthorizedError403Lacks permission for action
NotFoundError404Resource doesn't exist
InternalServerError500Unexpected server errors
ServiceUnavailableError503External service down

Instructions

Step 1: Add Error Class to src/errors.ts

export class {ErrorName}Error extends BaseError {
  constructor(
    message: string = "Default error message",
    options?: Omit<BaseErrorOptions, "errorCode">,
  ) {
    super(message, { ...options, errorCode: {HTTP_STATUS_CODE} });
  }
}

Step 2: Use in Services/Controllers

import { {ErrorName}Error } from "@/errors";

// Throw when condition is met
if (someCondition) {
  throw new {ErrorName}Error("Specific error message");
}

// With cause for debugging
throw new {ErrorName}Error("Error message", { cause: originalError });

Common HTTP Status Codes

CodeNameWhen to Use
400Bad RequestMalformed request, validation failure
401UnauthenticatedNo credentials or invalid credentials
403ForbiddenValid credentials but lacks permission
404Not FoundResource doesn't exist
409ConflictResource state conflict (duplicate, etc)
422Unprocessable EntitySemantic errors in valid syntax
429Too Many RequestsRate limiting
500Internal Server ErrorUnexpected server-side errors
502Bad GatewayUpstream service returned invalid
503Service UnavailableServer temporarily unavailable
504Gateway TimeoutUpstream service timeout

Examples

Conflict Error (409)

export class ConflictError extends BaseError {
  constructor(
    message: string = "Resource conflict",
    options?: Omit<BaseErrorOptions, "errorCode">,
  ) {
    super(message, { ...options, errorCode: 409 });
  }
}

// Usage
if (await repository.findByEmail(email)) {
  throw new ConflictError("User with this email already exists");
}

Rate Limit Error (429)

export class RateLimitError extends BaseError {
  constructor(
    message: string = "Too many requests",
    options?: Omit<BaseErrorOptions, "errorCode">,
  ) {
    super(message, { ...options, errorCode: 429 });
  }
}

// Usage
if (requestCount > limit) {
  throw new RateLimitError("Rate limit exceeded. Try again later.");
}

Validation Error (422)

export class ValidationError extends BaseError {
  constructor(
    message: string = "Validation failed",
    options?: Omit<BaseErrorOptions, "errorCode">,
  ) {
    super(message, { ...options, errorCode: 422 });
  }
}

// Usage with cause containing field errors
throw new ValidationError("Invalid input data", {
  cause: { field: "email", message: "Invalid email format" },
});

Gateway Error (502)

export class BadGatewayError extends BaseError {
  constructor(
    message: string = "Bad Gateway",
    options?: Omit<BaseErrorOptions, "errorCode">,
  ) {
    super(message, { ...options, errorCode: 502 });
  }
}

// Usage
if (!upstreamResponse.ok) {
  throw new BadGatewayError("Upstream service returned invalid response");
}

Using HttpError for One-off Status Codes

For status codes that don't need a dedicated class:

import { HttpError } from "@/errors";

// One-off 451 (Unavailable For Legal Reasons)
throw new HttpError(451, "Content unavailable in your region");

// One-off 507 (Insufficient Storage)
throw new HttpError(507, "Storage quota exceeded");

BaseError Structure

export class BaseError extends Error {
  public readonly cause?: unknown;
  public readonly errorCode?: ErrorCode;

  constructor(message: string, options?: BaseErrorOptions) {
    super(message);
    this.name = this.constructor.name;
    this.cause = options?.cause;
    this.errorCode = options?.errorCode;
    Object.setPrototypeOf(this, new.target.prototype);
  }

  public toJSON(): { error: string; code?: ErrorCode; cause?: string } {
    const json: { error: string; code?: ErrorCode; cause?: string } = {
      error: this.message,
    };
    if (this.errorCode !== undefined) {
      json.code = this.errorCode;
    }
    if (this.cause instanceof Error && this.cause.message) {
      json.cause = this.cause.message;
    }
    return json;
  }
}

Global Error Handler

The globalErrorHandler in src/errors.ts automatically:

  1. Logs the error
  2. Converts BaseError instances to JSON responses
  3. Maps errorCode to HTTP status
  4. Wraps unknown errors in InternalServerError
export const globalErrorHandler = (err: Error, c: Context<AppEnv>) => {
  console.error(err);

  if (err instanceof BaseError) {
    return createErrorResponse(c, err); // Uses errorCode as HTTP status
  } else if (err instanceof HTTPException) {
    return c.json({ error: err.message }, err.status);
  } else {
    const internalError = new InternalServerError(
      "An unexpected error occurred",
      { cause: err },
    );
    return createErrorResponse(c, internalError);
  }
};

What NOT to Do

  • Do NOT catch and re-throw as generic Error (loses type info)
  • Do NOT return error responses manually (use error classes)
  • Do NOT use non-standard HTTP status codes without good reason
  • Do NOT forget to set errorCode (defaults to 500)
  • Do NOT put stack traces in error messages (use cause for debugging)

See Also

  • create-middleware - Global error handler setup
  • create-utility-service - Error handling in services
  • create-controller - Throwing errors from controllers