Skip to content

NestJS Custom Exceptions

Sometimes an error case is specific enough to your domain that a generic built-in exception doesn't communicate intent clearly. This lesson covers creating custom exception classes by extending HttpException.

Creating a Custom Exception Class

A custom exception extends HttpException, calling super() with a response body and status code, then can be thrown and caught exactly like any built-in exception, while giving the error a clear, self-documenting name.

import { HttpException, HttpStatus } from '@nestjs/common';

export class InsufficientStockException extends HttpException {
  constructor(productId: string) {
    super(
      { errorCode: 'INSUFFICIENT_STOCK', message: `Product ${productId} is out of stock` },
      HttpStatus.CONFLICT,
    );
  }
}

Using the HttpStatus enum instead of a raw number (409) keeps the intent readable and avoids magic numbers scattered through the codebase.

Throwing and Catching a Custom Exception

if (product.stock < quantity) {
  throw new InsufficientStockException(product.id);
}

@Catch(InsufficientStockException)
export class StockExceptionFilter implements ExceptionFilter {
  catch(exception: InsufficientStockException, host: ArgumentsHost) {
    // custom formatting specific to this exception type
  }
}
  • Custom exceptions behave exactly like built-in ones once caught by Nest's exception layer.
  • A dedicated @Catch(YourException) filter can add exception-specific formatting or side effects (like alerting).
  • Custom exceptions are especially useful for domain errors with no natural HTTP status equivalent.
  • Keep custom exception classes in a shared exceptions/ folder so they're easy to discover and reuse.

Custom Exception Cheatsheet

The essentials for defining a domain-specific exception.

Piece Purpose
extends HttpException Inherits Nest's automatic formatting behavior
super(response, status) Sets the response body and status code
HttpStatus enum Avoids magic status code numbers
Descriptive class name Documents the specific error case at a glance

Building a Domain-Specific Exception Hierarchy

Larger applications sometimes benefit from a base custom exception class per domain (like OrderException), with more specific exceptions extending it, letting a single filter catch the whole family while still preserving specific error details.

export abstract class OrderException extends HttpException {}

export class InsufficientStockException extends OrderException {
  constructor(productId: string) {
    super({ message: `Out of stock: ${productId}` }, HttpStatus.CONFLICT);
  }
}

export class PaymentDeclinedException extends OrderException {
  constructor() {
    super({ message: 'Payment was declined' }, HttpStatus.PAYMENT_REQUIRED);
  }
}

Custom Exceptions Beyond HTTP

If a piece of business logic needs to be reused outside an HTTP context (like a CLI command or a scheduled job), consider throwing a plain domain error class instead of one tied to HttpException, then mapping it to the correct HTTP response only at the boundary where it's caught.

Common Mistakes

  • Extending Error instead of HttpException and expecting Nest's exception layer to format it automatically.
  • Creating a new custom exception for every minor variation instead of parameterizing one class.
  • Coupling core business logic exceptions directly to HTTP status codes when the logic is meant to be reused outside HTTP.
  • Not documenting custom error codes anywhere clients or teammates can discover them.

Key Takeaways

  • Custom exceptions extend HttpException and behave identically to built-in ones once thrown.
  • The HttpStatus enum keeps status codes readable instead of relying on magic numbers.
  • A shared base exception class per domain lets one filter catch a whole family of related errors.
  • Logic meant to be reused outside HTTP should avoid throwing HttpException directly.

Pro Tip

Name custom exception classes after the business rule they represent (InsufficientStockException, not Error409), a well-named exception often makes a try/catch block or exception filter self-documenting without needing a comment.