Custom Exceptions

Domain Exceptions

Create exceptions that speak your business language. Services throw them; the client gets a clear code.

src/orders/exceptions/out-of-stock.exception.ts

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

export class OutOfStockException extends HttpException {
  constructor(productId: number, requested: number, available: number) {
    super(
      {
        code: 'OUT_OF_STOCK',
        message: `Only ${available} left for product ${productId}`,
        details: { productId, requested, available },
      },
      HttpStatus.UNPROCESSABLE_ENTITY,
    );
  }
}

// orders.service.ts
// if (product.stock < item.quantity) throw new OutOfStockException(product.id, item.quantity, product.stock);

Example

curl -X POST http://localhost:3000/orders -H "Content-Type: application/json" -H "Authorization: Bearer <token>" \
  -d '{"items":[{"productId":7,"quantity":5}],"paymentMethod":"UPI"}'

Example output

HTTP/1.1 422 Unprocessable Entity
{"code":"OUT_OF_STOCK","message":"Only 2 left for product 7","details":{"productId":7,"requested":5,"available":2}}

Framework-free Domain Errors

Some teams keep services free of HTTP: they throw plain domain errors and a filter maps them to status codes. Then the same service works in a queue worker or a CLI script.

Example

A mapping table from domain errors to HTTP responses:

class DomainError extends Error {}
class NotFoundError extends DomainError {}
class OutOfStockError extends DomainError {
  constructor(readonly productId: number, readonly available: number) { super('Only ' + available + ' left'); }
}
class PaymentDeclinedError extends DomainError {}

const statusFor = new Map<Function, number>([
  [NotFoundError, 404],
  [OutOfStockError, 422],
  [PaymentDeclinedError, 402],
]);

function toHttp(e: unknown) {
  if (e instanceof DomainError) {
    const status = statusFor.get(e.constructor) ?? 400;
    const code = e.constructor.name.replace(/Error$/, '').replace(/([a-z])([A-Z])/g, '$1_$2').toUpperCase();
    return { status, body: { code, message: e.message } };
  }
  return { status: 500, body: { code: 'INTERNAL', message: 'Internal server error' } };
}

console.log(toHttp(new NotFoundError('Order ORD-9 not found')));
console.log(toHttp(new OutOfStockError(7, 2)));
console.log(toHttp(new PaymentDeclinedError('Card declined by bank')));
console.log(toHttp(new TypeError("Cannot read properties of undefined (reading 'id')")));

Output

{
  status: 404,
  body: { code: 'NOT_FOUND', message: 'Order ORD-9 not found' }
}
{ status: 422, body: { code: 'OUT_OF_STOCK', message: 'Only 2 left' } }
{
  status: 402,
  body: { code: 'PAYMENT_DECLINED', message: 'Card declined by bank' }
}
{
  status: 500,
  body: { code: 'INTERNAL', message: 'Internal server error' }
}