npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@altbzh/nestjs-exceptions

v0.1.0

Published

Production-ready exception handling for NestJS with multi-context support (HTTP, RPC, WebSocket)

Downloads

148

Readme

@altbzh/nestjs-exceptions

Production-ready exception handling for NestJS with multi-context support (HTTP, RPC, WebSocket)

npm version License: MIT TypeScript NestJS

A comprehensive exception handling system for NestJS applications that provides type-safe, context-aware error handling across HTTP, RPC (gRPC/microservices), and WebSocket communication. Built with enterprise applications in mind, featuring automatic context detection, standardized error responses, and structured logging.

✨ Features

  • 🔒 Type-Safe Exception Handling - Full TypeScript support with generic error details
  • 🌐 Multi-Context Support - Seamless handling across HTTP, RPC, and WebSocket
  • 🎯 Automatic Context Detection - Smart detection using multiple strategies
  • 📊 Standardized Error Responses - Consistent error format across all contexts
  • 🔢 Comprehensive Error Codes - 20+ predefined error codes for common scenarios
  • 🔍 Global Exception Filter - Centralized error handling and transformation
  • 📝 Structured Logging - Rich request context with every error log
  • 🛡️ Environment-Aware - Different error details for development vs production
  • Zero Dependencies - Only peer dependencies on NestJS packages

📦 Installation

npm install @altbzh/nestjs-exceptions

Peer Dependencies

Install the required NestJS packages if you haven't already:

npm install @nestjs/common @nestjs/core reflect-metadata rxjs

Optional Dependencies

For RPC/microservices support:

npm install @nestjs/microservices

For WebSocket support:

npm install @nestjs/websockets @nestjs/platform-socket.io

🚀 Quick Start

1. Register the Global Exception Filter

In your main.ts:

import { NestFactory } from '@nestjs/core';
import { GlobalExceptionFilter } from '@altbzh/nestjs-exceptions';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // Register global exception filter
  app.useGlobalFilters(app.get(GlobalExceptionFilter));

  await app.listen(3000);
}
bootstrap();

2. Throw Exceptions in Your Code

import { Injectable } from '@nestjs/common';
import { NotFoundException, ErrorCode } from '@altbzh/nestjs-exceptions';

@Injectable()
export class UserService {
  async findById(id: number) {
    const user = await this.repository.findOne(id);

    if (!user) {
      throw new NotFoundException(ErrorCode.RESOURCE_NOT_FOUND, `User with ID ${id} not found`, {
        resourceType: 'User',
        resourceId: id,
      });
    }

    return user;
  }
}

3. Standardized Error Response

Your API will automatically return consistent error responses:

{
  "error": "resource_not_found",
  "message": "User with ID 123 not found",
  "details": {
    "resourceType": "User",
    "resourceId": 123
  },
  "timestamp": "2024-01-15T10:30:00.000Z"
}

🧩 Core Concepts

Exception Hierarchy

The library provides a clear exception hierarchy:

AppException (abstract base)
├── HttpAppException (HTTP/REST APIs)
│   ├── BadRequestException (400)
│   ├── UnauthorizedException (401)
│   ├── ForbiddenException (403)
│   ├── NotFoundException (404)
│   ├── ConflictException (409)
│   └── InternalServerErrorException (500)
├── RpcAppException (Microservices/gRPC)
└── WsAppException (WebSocket)

Context Detection

The library automatically detects the execution context:

  1. ExecutionContextStrategy (Highest Priority) - Uses NestJS ArgumentsHost
  2. AsyncStorageStrategy (Medium Priority) - Uses Node.js AsyncLocalStorage
  3. StackTraceStrategy (Lowest Priority) - Analyzes stack traces (dev only)

Manual override is available when needed:

import { NotFoundException, ContextType } from '@altbzh/nestjs-exceptions';

throw new NotFoundException(
  'resource_not_found',
  'User not found',
  { userId: 123 },
  { context: ContextType.HTTP }, // Manual override
);

Error Codes

Predefined error codes organized by category:

// Authentication & Authorization
ErrorCode.AUTHENTICATION_REQUIRED;
ErrorCode.INVALID_CREDENTIALS;
ErrorCode.TOKEN_EXPIRED;
ErrorCode.INSUFFICIENT_PERMISSIONS;

// Validation
ErrorCode.VALIDATION_FAILED;
ErrorCode.INVALID_INPUT;

// Resources
ErrorCode.RESOURCE_NOT_FOUND;
ErrorCode.RESOURCE_ALREADY_EXISTS;

// Server Errors
ErrorCode.INTERNAL_SERVER_ERROR;
ErrorCode.SERVICE_UNAVAILABLE;

See full list of error codes →

Exception Metadata

All exceptions support typed metadata:

interface ResourceErrorDetails {
  resourceType: string;
  resourceId: string | number;
  action?: 'create' | 'read' | 'update' | 'delete';
}

throw new NotFoundException<ResourceErrorDetails>(ErrorCode.RESOURCE_NOT_FOUND, 'User not found', {
  resourceType: 'User',
  resourceId: 123,
  action: 'read',
});

💻 Usage Examples

HTTP Exception Handling

import { Injectable } from '@nestjs/common';
import {
  BadRequestException,
  NotFoundException,
  ConflictException,
  ErrorCode,
} from '@altbzh/nestjs-exceptions';

@Injectable()
export class UserService {
  async create(email: string, password: string) {
    // Validation error
    if (!this.isValidEmail(email)) {
      throw new BadRequestException(ErrorCode.VALIDATION_FAILED, 'Invalid email format', {
        field: 'email',
        value: email,
      });
    }

    // Conflict error
    const existing = await this.findByEmail(email);
    if (existing) {
      throw new ConflictException(
        ErrorCode.RESOURCE_ALREADY_EXISTS,
        'User with this email already exists',
        { email },
      );
    }

    return this.repository.create({ email, password });
  }

  async findById(id: number) {
    const user = await this.repository.findOne(id);

    // Not found error
    if (!user) {
      throw new NotFoundException(ErrorCode.RESOURCE_NOT_FOUND, `User with ID ${id} not found`, {
        resourceType: 'User',
        resourceId: id,
      });
    }

    return user;
  }
}

RPC Exception Handling

import { Injectable } from '@nestjs/common';
import { RpcAppException, GrpcStatus } from '@altbzh/nestjs-exceptions';

@Injectable()
export class UserRpcService {
  async getUser(id: number) {
    const user = await this.repository.findOne(id);

    if (!user) {
      throw new RpcAppException({
        errorCode: 'user_not_found',
        message: 'User does not exist',
        details: { userId: id },
        grpcStatus: GrpcStatus.NOT_FOUND, // gRPC status code
      });
    }

    return user;
  }

  async authenticateUser(credentials: { email: string; password: string }) {
    const user = await this.validateCredentials(credentials);

    if (!user) {
      throw new RpcAppException({
        errorCode: 'invalid_credentials',
        message: 'Authentication failed',
        grpcStatus: GrpcStatus.UNAUTHENTICATED,
      });
    }

    return { token: this.generateToken(user) };
  }
}

WebSocket Exception Handling

import { WebSocketGateway, SubscribeMessage, MessageBody } from '@nestjs/websockets';
import { WsAppException } from '@altbzh/nestjs-exceptions';

@WebSocketGateway()
export class ChatGateway {
  @SubscribeMessage('subscribe')
  handleSubscribe(@MessageBody() data: { topicId: string }) {
    const topic = this.topics.get(data.topicId);

    if (!topic) {
      throw new WsAppException({
        errorCode: 'invalid_subscription',
        message: 'Topic does not exist',
        details: { topicId: data.topicId },
        wsCode: 1008, // Policy Violation
      });
    }

    return { success: true, topicId: data.topicId };
  }

  @SubscribeMessage('sendMessage')
  handleMessage(@MessageBody() data: { message: string }) {
    if (!data.message || data.message.length > 1000) {
      throw new WsAppException({
        errorCode: 'invalid_message',
        message: 'Message must be between 1-1000 characters',
        wsCode: 1003, // Unsupported Data
      });
    }

    this.broadcast(data.message);
    return { success: true };
  }
}

📚 API Reference

For detailed API documentation, see:

Key Exports

// Base Classes
import {
  AppException,
  HttpAppException,
  RpcAppException,
  WsAppException,
} from '@altbzh/nestjs-exceptions';

// HTTP Exceptions
import {
  BadRequestException,
  UnauthorizedException,
  ForbiddenException,
  NotFoundException,
  ConflictException,
  InternalServerErrorException,
} from '@altbzh/nestjs-exceptions';

// Filters & Services
import {
  GlobalExceptionFilter,
  ExceptionLogger,
  ExceptionTransformer,
} from '@altbzh/nestjs-exceptions';

// Constants & Types
import { ErrorCode, ContextType, GrpcStatus } from '@altbzh/nestjs-exceptions';

// Utilities
import { ContextDetector } from '@altbzh/nestjs-exceptions';

⚙️ Configuration

Custom Exception Filter

You can extend the GlobalExceptionFilter to customize behavior:

import { Injectable, ExecutionContext } from '@nestjs/common';
import { GlobalExceptionFilter } from '@altbzh/nestjs-exceptions';

@Injectable()
export class CustomExceptionFilter extends GlobalExceptionFilter {
  // Override to add custom logging
  catch(exception: unknown, host: ArgumentsHost): void {
    // Add custom logic here
    this.customLogger.log('Exception caught', exception);

    // Call parent implementation
    super.catch(exception, host);
  }
}

Custom Exception Logger

Customize logging behavior by extending ExceptionLogger:

import { Injectable } from '@nestjs/common';
import { ExceptionLogger } from '@altbzh/nestjs-exceptions';

@Injectable()
export class CustomExceptionLogger extends ExceptionLogger {
  logException(exception: unknown, context: ExecutionContext): void {
    // Add custom logging logic (e.g., send to external service)
    this.sendToMonitoringService(exception);

    // Call parent implementation
    super.logException(exception, context);
  }

  private sendToMonitoringService(exception: unknown): void {
    // Integration with Sentry, DataDog, etc.
  }
}

Environment-Specific Configuration

Control error detail visibility:

// main.ts
const app = await NestFactory.create(AppModule);

if (process.env.NODE_ENV === 'production') {
  // Production: minimal error details
  app.useGlobalFilters(
    new GlobalExceptionFilter(new ExceptionTransformer(), new ExceptionLogger()),
  );
} else {
  // Development: detailed error information including stack traces
  app.useGlobalFilters(
    new GlobalExceptionFilter(new ExceptionTransformer(), new ExceptionLogger()),
  );
}

Module Registration

Register exception handling services in your module:

import { Module } from '@nestjs/common';
import {
  GlobalExceptionFilter,
  ExceptionLogger,
  ExceptionTransformer,
} from '@altbzh/nestjs-exceptions';

@Module({
  providers: [GlobalExceptionFilter, ExceptionLogger, ExceptionTransformer],
  exports: [GlobalExceptionFilter],
})
export class ExceptionModule {}

🎯 Best Practices

1. Use Specific Exception Classes

// ✅ Good - Specific exception with context
throw new NotFoundException(ErrorCode.RESOURCE_NOT_FOUND, 'User not found', { userId: 123 });

// ❌ Bad - Generic error
throw new Error('User not found');

2. Provide Meaningful Error Details

// ✅ Good - Rich context for debugging
throw new BadRequestException(ErrorCode.VALIDATION_FAILED, 'Email validation failed', {
  field: 'email',
  value: userInput.email,
  reason: 'Invalid format',
  expectedFormat: '[email protected]',
});

// ❌ Bad - Minimal context
throw new BadRequestException(ErrorCode.VALIDATION_FAILED, 'Validation failed');

3. Use Error Codes for Client-Side Handling

// Backend
throw new UnauthorizedException(ErrorCode.TOKEN_EXPIRED, 'Authentication token has expired', {
  expiresAt: token.expiresAt,
});

// Frontend can handle specifically
if (error.error === 'token_expired') {
  refreshToken();
}

4. Leverage Context Detection

// ✅ Good - Let the library detect context automatically
throw new NotFoundException(ErrorCode.RESOURCE_NOT_FOUND, 'User not found');

// ⚠️ Only override when necessary
throw new NotFoundException(
  ErrorCode.RESOURCE_NOT_FOUND,
  'User not found',
  {},
  { context: ContextType.HTTP },
);

5. Create Domain-Specific Exceptions

// Create reusable exceptions for your domain
export class UserNotFoundException extends NotFoundException {
  constructor(userId: number) {
    super(ErrorCode.RESOURCE_NOT_FOUND, `User with ID ${userId} not found`, {
      resourceType: 'User',
      resourceId: userId,
      action: 'read',
    });
  }
}

export class InvalidUserCredentialsException extends UnauthorizedException {
  constructor(email: string) {
    super(ErrorCode.INVALID_CREDENTIALS, 'Invalid email or password', {
      email,
      loginAttempt: new Date().toISOString(),
    });
  }
}

6. Handle Async Errors Properly

// ✅ Good - Let errors bubble up
async findUser(id: number) {
  const user = await this.repository.findOne(id);
  if (!user) {
    throw new NotFoundException(/* ... */);
  }
  return user;
}

// ❌ Bad - Catching and swallowing errors
async findUser(id: number) {
  try {
    return await this.repository.findOne(id);
  } catch (error) {
    console.log(error); // Don't do this
    return null;
  }
}

📖 Documentation

🤝 Contributing

Contributions are welcome! Please read our Contributing Guide for details on our code of conduct and the process for submitting pull requests.

📝 License

This project is licensed under the MIT License - see the LICENSE file for details.

🔗 Links

💡 Support

If you have any questions or need help, please:

  1. Check the documentation
  2. Search existing issues
  3. Create a new issue if needed

Built with ❤️ for the NestJS community