Careeroza
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' }
}