UNPKG

@webxsid/nest-exception

Version:

A centralized exception handling module for NestJS applications. It provides structured error management, logging, and automatic exception handling.

336 lines (259 loc) 9.84 kB
# @webxsid/nest-exception ![Coverage](https://codecov.io/gh/webxsid/nest-exception/branch/main/graph/badge.svg) [![Test & Coverage](https://github.com/webxsid/nest-exception/actions/workflows/test-covergare.yml/badge.svg)](https://github.com/webxsid/nest-exception/actions/workflows/test-covergare.yml) [![NPM Version](https://img.shields.io/npm/v/@webxsid/nest-exception)](https://www.npmjs.com/package/@webxsid/nest-exception) [![License](https://img.shields.io/github/license/webxsid/nest-exception)](LICENSE) A centralized exception handling module for NestJS applications. It provides structured error management, logging, and automatic exception handling. ## Features - **Centralized Error Registry**: Define and manage application errors easily. - **Automatic Error Handling**: Custom `AppException` class integrates seamlessly. - **Flexible Error Registration**: Predefine errors in the module or register dynamically. - **Extendable Error Handling**: Customize error handling with `ExceptionFilter`. - **Stack Trace (Development Mode)**: Automatically captures stack trace for debugging. - **Seamless Integration**: Just import the module and start using it. ## Installation Install the package using npm or yarn: ```bash $ npm install @webxsid/nest-exception # or $ yarn add @webxsid/nest-exception ``` ## Usage ### Importing and Setting Up the Module - Import the `AppExceptionModule` in the root module using `forRoot` or `forRootAsync`: ```typescript import { Module } from '@nestjs/common'; import { AppExceptionModule } from '@webxsid/nest-exception'; @Module({ imports: [AppExceptionModule.forRoot({ isDev: process.env.NODE_ENV === 'development', errors: [ { code: 'E001', message: 'User not found' }, { code: 'E002', message: 'Invalid credentials' }, ], logger: LoggerService // Any implementation of LoggerService })], }) export class AppModule {} ``` #### Async Configuration ```typescript import { Module } from '@nestjs/common'; import { AppExceptionModule } from '@webxsid/nest-exception'; import { ConfigModule, ConfigService } from '@nestjs/config'; @Module({ imports: [ ConfigModule.forRoot(), AppExceptionModule.forRootAsync({ imports: [ConfigModule], useFactory: (configService: ConfigService) => ({ isDev: configService.get('NODE_ENV') === 'development', errors: [ { code: 'E001', message: 'User not found' }, { code: 'E002', message: 'Invalid credentials' }, ], logger: LoggerService // Any implementation of LoggerService }), inject: [ConfigService] }) ], }) export class AppModule {} ``` ### Registering the Global Exception Filter To apply the `AppExceptionFilter` globally in your application, register it in your root module (`AppModule`): ```typescript // app.module.ts import { Module } from '@nestjs/common'; import { APP_FILTER } from '@nestjs/core'; import { AppExceptionFilter } from '@webxsid/nest-exception'; @Module({ providers: [ { provide: APP_FILTER, useClass: AppExceptionFilter, }, ], }) export class AppModule {} ``` ## Error Management ### Registering Errors in the Module Errors can be pre-registered in the module configuration: ```typescript imports: [ AppExceptionModule.forRoot({ errors: [ { code: 'E001', message: 'User not found' }, { code: 'E002', message: 'Invalid credentials' }, ] }) ] ``` ### Registering Errors Dynamically - Use the `ExceptionRegistry` service to register errors at runtime: ```typescript import { Injectable } from '@nestjs/common'; import { ExceptionRegistry } from '@webxsid/nest-exception'; @Injectable() export class AppService { constructor(private readonly exceptionRegistry: ExceptionRegistry) {} registerErrors() { this.exceptionRegistry.registerError({ code: 'E003', message: 'Invalid request' }); } } ``` ### Extending the Exception Handler The `ExceptionHandlerService` allows you to customize how specific exceptions are handled. It supports both **constructor-based** and **predicate-based** handlers to reliably handle errors from first-party code as well as third-party libraries (e.g. Mongo / Mongoose). --- #### Constructor-based handler ```ts import { Injectable, OnModuleInit, HttpStatus, ArgumentsHost } from '@nestjs/common'; import { ExceptionHandlerService } from '@webxsid/nest-exception'; import { MongoError } from 'mongodb'; @Injectable() export class MongoErrorHandler implements OnModuleInit { constructor( private readonly exceptionHandlerService: ExceptionHandlerService, ) {} onModuleInit() { this.exceptionHandlerService.register( MongoError, (exception: MongoError, host: ArgumentsHost) => { const response = host.switchToHttp().getResponse(); response.status(HttpStatus.BAD_REQUEST).json({ statusCode: HttpStatus.BAD_REQUEST, errorCode: 'MONGO_ERROR', message: exception.message, timestamp: new Date().toISOString(), }); }, ); } } ``` --- #### Predicate-based handler (recommended for Mongo / Mongoose) Some libraries throw plain objects instead of real Error instances. In these cases, constructor matching will not work. Use registerWhen to match errors by shape or properties. ```ts import { Injectable, OnModuleInit, HttpStatus, ArgumentsHost } from '@nestjs/common'; import { ExceptionHandlerService } from '@webxsid/nest-exception'; @Injectable() export class MongoErrorHandler implements OnModuleInit { constructor( private readonly exceptionHandlerService: ExceptionHandlerService, ) {} onModuleInit() { this.exceptionHandlerService.registerWhen( (error: any) => error?.name === 'MongoServerError' || error?.code === 11000, (exception, host: ArgumentsHost) => { const response = host.switchToHttp().getResponse(); response.status(HttpStatus.BAD_REQUEST).json({ statusCode: HttpStatus.BAD_REQUEST, errorCode: 'MONGO_ERROR', message: exception.message ?? 'MongoDB error', timestamp: new Date().toISOString(), }); }, ); } } ``` #### Handler resolution order When an exception is caught, handlers are resolved in the following order: 1. Constructor-based handlers (including inheritance via the prototype chain) 2. Predicate-based handlers (canActivate fallback) 3. Global exception filter handling (default behavior) This ensures correct polymorphic behavior while remaining resilient to wrapped or serialized errors. #### Registering the handler Add the handler class to your module providers: ```typescript @Module({ imports: [AppExceptionModule.forRoot(/*...*/)], providers: [MongoErrorHandler] }) export class AppModule {} ``` #### Best practices - Use constructor-based handlers for application-defined exceptions - Use predicate-based handlers for third-party libraries - Keep predicates specific to avoid accidental catch-all handlers - Avoid relying solely on error names when possible ### Throwing Custom Exceptions - Use the `AppException` class to throw predefined errors: ```typescript import { Injectable } from '@nestjs/common'; import { AppException } from '@webxsid/nest-exception'; @Injectable() export class AppService { async getUser(id: string) { const user = await this.userService.findById(id); if (!user) { throw new AppException('E001'); } return user; } } ``` ## How It Works The AppException class simplifies error handling by checking if the provided error code exists in the Exception Registry. Here’s how it behaves in different scenarios: ### 1. ✅ Passing a Registered Error Code If the error code exists in the registry (either pre-registered in the module or added dynamically), AppException will: • Retrieve the corresponding error message and status code. • Construct a structured HTTP response with the correct status, message, and code. ```typescript throw new AppException('E001'); ``` **Output:** ```json { "statusCode": 400, "errorCode": "E001", "message": "User not found", "timestamp": "2021-09-01T12:00:00.000Z" } ``` _(Assuming the error code 'E001' is registered with the message 'User not found' and status code 400)_ ### 2. ❌ Passing an Unregistered Error Code or String If the error code is not found in the registry, AppException will: • Throw an internal server error with the default message and status code. • Log the error using the provided logger service. ```typescript throw new AppException('Something went wrong'); ``` **Output:** ```json { "statusCode": 500, "errorCode": "UNKNOWN_ERROR", "message": "Internal server error", "timestamp": "2021-09-01T12:00:00.000Z" } ``` #### 🛠️ Development Mode (Stack Trace) If **development mode** (isDev: true) is enabled, AppException will also include a stack trace for easier debugging: ```json { "statusCode": 500, "errorCode": "UNKNOWN_ERROR", "message": "Internal server error", "timestamp": "2021-09-01T12:00:00.000Z", "stack": "Error: Internal server error\n at AppService.getUser (/app/app.service.ts:12:13)\n at processTicksAndRejections (internal/process/task_queues.js:93:5)" } ``` ## License This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. ## Acknowledgements - [NestJS](https://nestjs.com/) - [TypeScript](https://www.typescriptlang.org/) - [Jest](https://jestjs.io/) - [ESLint](https://eslint.org/) ## Author [Siddharth Mittal](https://webxsid.com/)