├── eslint.config.js ├── .lintstagedrc.json ├── prettier.config.js ├── sample ├── i18n │ └── en │ │ ├── error.json │ │ └── validation.json ├── main.ts ├── app.controller.ts └── app.module.ts ├── .npmrc ├── .husky └── pre-commit ├── renovate.json ├── lib ├── types │ ├── validation-error-exception-detail.type.ts │ ├── validation-error-exception-message.type.ts │ └── validation-error-exception.type.ts ├── exceptions │ ├── uuid.exception.ts │ ├── validate-field.exception.ts │ └── validate.exception.ts ├── interfaces │ └── validation-error.interface.ts ├── filters │ ├── base-exception.filter.ts │ ├── kafka-exception.filter.ts │ ├── grpc-exception.filter.ts │ ├── validator-exception.filter.ts │ └── http-exception.filter.ts └── index.ts ├── cspell.json ├── nest-cli.json ├── .gitignore ├── tsconfig.json ├── .github └── workflows │ └── publish.yml ├── package.json └── README.md /eslint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@hodfords/nestjs-eslint-config'); 2 | -------------------------------------------------------------------------------- /.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib/**/*.ts": ["eslint --fix --max-warnings 0"] 3 | } 4 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@hodfords/nestjs-prettier-config'); 2 | -------------------------------------------------------------------------------- /sample/i18n/en/error.json: -------------------------------------------------------------------------------- 1 | { 2 | "field_malformed": "The {field} is malformed." 3 | } 4 | -------------------------------------------------------------------------------- /sample/i18n/en/validation.json: -------------------------------------------------------------------------------- 1 | { 2 | "field_malformed": "This field is malformed." 3 | } 4 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | //registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN} 2 | registry=https://registry.npmjs.org/ 3 | always-auth=true -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | changedFiles="$(git diff --name-only --cached)" 2 | npm run cspell --no-must-find-files ${changedFiles} 3 | npm run lint-staged 4 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:base"], 4 | "prConcurrentLimit": 5, 5 | "assignees": ["hodfords_dung_senior_dev"], 6 | "labels": ["renovate"] 7 | } 8 | -------------------------------------------------------------------------------- /lib/types/validation-error-exception-detail.type.ts: -------------------------------------------------------------------------------- 1 | export type ValidationErrorExceptionDetail = { 2 | message: string; 3 | detail: { 4 | property?: string; 5 | [item: string]: string | undefined; 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /lib/types/validation-error-exception-message.type.ts: -------------------------------------------------------------------------------- 1 | import { ValidationErrorExceptionDetail } from './validation-error-exception-detail.type'; 2 | 3 | export type ValidationErrorExceptionMessage = string | ValidationErrorExceptionDetail; 4 | -------------------------------------------------------------------------------- /cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2", 3 | "language": "en", 4 | "words": ["nestjs", "typeorm", "hodfords", "postbuild", "npmjs"], 5 | "flagWords": ["hte"], 6 | "ignorePaths": ["node_modules", "test", "*.spec.ts", "cspell.json"] 7 | } 8 | -------------------------------------------------------------------------------- /lib/exceptions/uuid.exception.ts: -------------------------------------------------------------------------------- 1 | import { HttpException, HttpStatus } from '@nestjs/common'; 2 | 3 | export class UuidException extends HttpException { 4 | constructor(public field: string) { 5 | super({}, HttpStatus.BAD_REQUEST); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /sample/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | 4 | async function bootstrap(): Promise { 5 | const app = await NestFactory.create(AppModule); 6 | 7 | await app.listen(3000); 8 | } 9 | bootstrap(); 10 | -------------------------------------------------------------------------------- /lib/types/validation-error-exception.type.ts: -------------------------------------------------------------------------------- 1 | import { ValidationErrorExceptionMessage } from './validation-error-exception-message.type'; 2 | 3 | export type ValidationErrorException = { 4 | children?: Record; 5 | messages: ValidationErrorExceptionMessage[]; 6 | }; 7 | -------------------------------------------------------------------------------- /nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "./", 4 | "entryFile": "sample/main.js", 5 | "compilerOptions": { 6 | "assets": [ 7 | { 8 | "include": "sample/**/*", 9 | "outDir": "dist/sample" 10 | }, 11 | { 12 | "include": "lib/**/*", 13 | "outDir": "dist/lib" 14 | } 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/interfaces/validation-error.interface.ts: -------------------------------------------------------------------------------- 1 | import { ValidationErrorExceptionMessage } from '../types/validation-error-exception-message.type'; 2 | 3 | export interface ValidationError { 4 | target?: Record; 5 | property: string; 6 | value?: any; 7 | children?: ValidationError[]; 8 | contexts?: { 9 | [type: string]: any; 10 | }; 11 | constraints?: { 12 | [type: string]: ValidationErrorExceptionMessage; 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /lib/exceptions/validate-field.exception.ts: -------------------------------------------------------------------------------- 1 | import { ValidateException } from './validate.exception'; 2 | 3 | export class ValidateFieldException extends ValidateException { 4 | constructor(property: string, message: string, constraint: string, detail?: NodeJS.Dict) { 5 | super([ 6 | { 7 | property, 8 | constraints: { 9 | [constraint]: { message, detail: detail || {} } as any 10 | } 11 | } 12 | ]); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # OS 14 | .DS_Store 15 | 16 | # Tests 17 | /coverage 18 | /.nyc_output 19 | 20 | # IDEs and editors 21 | /.idea 22 | .project 23 | .classpath 24 | .c9/ 25 | *.launch 26 | .settings/ 27 | *.sublime-workspace 28 | 29 | # IDE - VSCode 30 | .vscode/* 31 | !.vscode/settings.json 32 | !.vscode/tasks.json 33 | !.vscode/launch.json 34 | !.vscode/extensions.json 35 | 36 | .env -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "moduleResolution": "node", 5 | "esModuleInterop": true, 6 | "declaration": true, 7 | "removeComments": true, 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "allowSyntheticDefaultImports": true, 11 | "target": "es2017", 12 | "sourceMap": true, 13 | "outDir": "dist", 14 | "baseUrl": "./", 15 | "incremental": true 16 | }, 17 | "exclude": [ 18 | "tests", 19 | "src", 20 | "dist", 21 | "node_modules" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Package to npmjs 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | lint: 8 | uses: hodfords-solutions/actions/.github/workflows/lint.yaml@main 9 | build: 10 | uses: hodfords-solutions/actions/.github/workflows/publish.yaml@main 11 | with: 12 | build_path: dist/lib 13 | secrets: 14 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 15 | update-docs: 16 | uses: hodfords-solutions/actions/.github/workflows/update-doc.yaml@main 17 | needs: build 18 | secrets: 19 | DOC_SSH_PRIVATE_KEY: ${{ secrets.DOC_SSH_PRIVATE_KEY }} 20 | -------------------------------------------------------------------------------- /sample/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | import { UuidException, ValidateFieldException } from '../lib'; 3 | 4 | @Controller() 5 | export class AppController { 6 | constructor() {} 7 | 8 | @Get('uuid-exception') 9 | getUuidException(): void { 10 | throw new UuidException('id'); // Translation key: 'error.field_malformed' 11 | } 12 | 13 | @Get('validate-field-exception') 14 | getValidateFieldException(): void { 15 | throw new ValidateFieldException('id', 'field_malformed', 'field_malformed'); // Translation key: 'validation.field_malformed' 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/filters/base-exception.filter.ts: -------------------------------------------------------------------------------- 1 | import { ArgumentsHost } from '@nestjs/common'; 2 | 3 | export abstract class BaseExceptionFilter { 4 | protected isMicroservice: boolean = false; 5 | 6 | protected getLanguage(host: ArgumentsHost): string { 7 | return host.switchToHttp().getRequest().i18nLang; 8 | } 9 | 10 | protected responseError(host: ArgumentsHost, code: number, message: string, errors: string | object = null): void { 11 | const ctx = host.switchToHttp(); 12 | ctx.getResponse().status(code).json({ 13 | message: message, 14 | errors: errors 15 | }); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './exceptions/uuid.exception'; 2 | export * from './exceptions/validate-field.exception'; 3 | export * from './exceptions/validate.exception'; 4 | export * from './filters/base-exception.filter'; 5 | export * from './filters/http-exception.filter'; 6 | export * from './filters/kafka-exception.filter'; 7 | export * from './filters/grpc-exception.filter'; 8 | export * from './filters/validator-exception.filter'; 9 | export * from './interfaces/validation-error.interface'; 10 | export * from './types/validation-error-exception-detail.type'; 11 | export * from './types/validation-error-exception-message.type'; 12 | export * from './types/validation-error-exception.type'; 13 | -------------------------------------------------------------------------------- /lib/filters/kafka-exception.filter.ts: -------------------------------------------------------------------------------- 1 | import { ArgumentsHost, Catch } from '@nestjs/common'; 2 | import { HttpExceptionFilter } from './http-exception.filter'; 3 | import { Observable, throwError } from 'rxjs'; 4 | 5 | @Catch() 6 | export class KafkaExceptionFilter extends HttpExceptionFilter { 7 | protected isMicroservice: boolean = true; 8 | 9 | protected responseError( 10 | host: ArgumentsHost, 11 | code: number, 12 | message: string, 13 | errors: string | object = null 14 | ): Observable { 15 | return throwError(() => 16 | JSON.stringify({ 17 | message: message, 18 | errors: errors, 19 | code: code 20 | }) 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/filters/grpc-exception.filter.ts: -------------------------------------------------------------------------------- 1 | import { status } from '@grpc/grpc-js'; 2 | import { ArgumentsHost, Catch } from '@nestjs/common'; 3 | import { Observable, throwError } from 'rxjs'; 4 | import { HttpExceptionFilter } from './http-exception.filter'; 5 | 6 | @Catch() 7 | export class GrpcExceptionFilter extends HttpExceptionFilter { 8 | protected isMicroservice: boolean = true; 9 | 10 | protected responseError( 11 | host: ArgumentsHost, 12 | code: number, 13 | message: string, 14 | errors: string | object = null 15 | ): Observable { 16 | return throwError(() => ({ 17 | message: JSON.stringify({ 18 | message: message, 19 | errors: errors, 20 | code: code 21 | }), 22 | code: status.ABORTED 23 | })); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /sample/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { HeaderResolver } from 'nestjs-i18n'; 3 | import path from 'path'; 4 | import { HttpExceptionFilter } from '../lib'; 5 | 6 | import { RequestResolver, TranslationModule } from '@hodfords/nestjs-cls-translation'; 7 | import { APP_FILTER } from '@nestjs/core'; 8 | import { AppController } from './app.controller'; 9 | 10 | const i18nConfig = TranslationModule.forRoot({ 11 | fallbackLanguage: 'en', 12 | loaderOptions: { 13 | path: path.join(__dirname, 'i18n/'), 14 | watch: true 15 | }, 16 | resolvers: [new HeaderResolver(['language'])], 17 | defaultLanguageKey: 'language', 18 | clsResolvers: [new RequestResolver([{ key: 'language', type: 'headers' }])] 19 | }); 20 | 21 | @Module({ 22 | imports: [i18nConfig], 23 | controllers: [AppController], 24 | providers: [ 25 | { 26 | provide: APP_FILTER, 27 | useClass: HttpExceptionFilter 28 | } 29 | ] 30 | }) 31 | export class AppModule {} 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@hodfords/nestjs-exception", 3 | "version": "11.0.2", 4 | "description": "A NestJS module for handling and customizing exceptions with ease.", 5 | "scripts": { 6 | "prebuild": "rimraf dist", 7 | "build": "tsc", 8 | "start:dev": "npm run prebuild && nest start --watch", 9 | "postbuild": "cp package.json dist/lib && cp README.md dist/lib && cp .npmrc dist/lib", 10 | "prepare": "is-ci || husky", 11 | "format": "prettier --write \"lib/**/*.ts\"", 12 | "cspell": "cspell", 13 | "lint": "eslint \"lib/**/*.ts\" --fix --max-warnings 0", 14 | "lint-staged": "lint-staged" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/hodfords-solutions/nestjs-exception.git" 19 | }, 20 | "keywords": [], 21 | "author": "", 22 | "license": "ISC", 23 | "bugs": { 24 | "url": "https://github.com/hodfords-solutions/nestjs-exception/issues" 25 | }, 26 | "homepage": "https://github.com/hodfords-solutions/nestjs-exception#readme", 27 | "dependencies": { 28 | "@grpc/grpc-js": "1.13.0" 29 | }, 30 | "devDependencies": { 31 | "@hodfords/nestjs-cls-translation": "11.0.2", 32 | "@hodfords/nestjs-eslint-config": "11.0.1", 33 | "@hodfords/nestjs-prettier-config": "11.0.1", 34 | "@nestjs/cli": "11.0.5", 35 | "@nestjs/common": "11.0.11", 36 | "@nestjs/platform-express": "^11.0.11", 37 | "@nestjs/core": "11.0.11", 38 | "@types/node": "22.13.10", 39 | "cspell": "8.17.5", 40 | "eslint": "9.22.0", 41 | "express": "4.21.2", 42 | "husky": "9.1.7", 43 | "is-ci": "4.1.0", 44 | "lint-staged": "15.5.0", 45 | "lodash": "4.17.21", 46 | "rimraf": "6.0.1", 47 | "rxjs": "7.8.2", 48 | "typeorm": "0.3.21", 49 | "typescript": "5.8.2" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/exceptions/validate.exception.ts: -------------------------------------------------------------------------------- 1 | import { HttpException, HttpStatus } from '@nestjs/common'; 2 | import { isString } from '@nestjs/common/utils/shared.utils'; 3 | import { ValidationError } from '../interfaces/validation-error.interface'; 4 | import { ValidationErrorExceptionDetail } from '../types/validation-error-exception-detail.type'; 5 | import { ValidationErrorException } from '../types/validation-error-exception.type'; 6 | 7 | export class ValidateException extends HttpException { 8 | constructor(errors: ValidationError[]) { 9 | super({}, HttpStatus.UNPROCESSABLE_ENTITY); 10 | this['response' as any] = this.convertValidationErrors(errors); 11 | } 12 | 13 | private convertValidationErrors( 14 | errors: ValidationError[], 15 | parent: ValidationError = null 16 | ): Record { 17 | const newErrors = {}; 18 | errors.forEach((error) => { 19 | if (!parent || error.property !== parent.property) { 20 | newErrors[error.property] = this.convertValidationError(error); 21 | } 22 | }); 23 | return newErrors; 24 | } 25 | 26 | private convertValidationError(error: ValidationError): ValidationErrorException { 27 | const newError: ValidationErrorException = { 28 | children: undefined, 29 | messages: [] 30 | }; 31 | if (error.constraints) { 32 | newError.messages = Object.values(error.constraints).map((message: ValidationErrorExceptionDetail) => { 33 | if (isString(message)) { 34 | return { 35 | message, 36 | detail: { 37 | property: error.property 38 | } 39 | }; 40 | } 41 | return message; 42 | }); 43 | } 44 | if (error.children && Object.keys(error.children).length) { 45 | error.children.forEach((child: ValidationError) => { 46 | if (child.property === error.property && child.constraints) { 47 | newError.messages = newError.messages.concat(Object.values(child.constraints)); 48 | } 49 | }); 50 | newError.children = this.convertValidationErrors(error.children, error); 51 | } 52 | 53 | return newError; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/filters/validator-exception.filter.ts: -------------------------------------------------------------------------------- 1 | import { trans } from '@hodfords/nestjs-cls-translation'; 2 | import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common'; 3 | import { startCase } from 'lodash'; 4 | import { ValidateException } from '../exceptions/validate.exception'; 5 | import { BaseExceptionFilter } from './base-exception.filter'; 6 | 7 | @Catch() 8 | export class ValidatorExceptionFilter extends BaseExceptionFilter implements ExceptionFilter { 9 | constructor( 10 | isMicroservice: boolean, 11 | public responseError: (host: ArgumentsHost, code: number, message: string, errors: string | object) => void 12 | ) { 13 | super(); 14 | this.isMicroservice = isMicroservice; 15 | } 16 | 17 | catch(exception: ValidateException, host: ArgumentsHost): void { 18 | const language = this.getLanguage(host); 19 | const response = exception.getResponse(); 20 | this.convertValidationErrors(response, language); 21 | return this.responseError(host, exception.getStatus(), exception.message, exception.getResponse()); 22 | } 23 | 24 | convertValidationErrors(validatorError, language: string): void { 25 | for (const key of Object.keys(validatorError)) { 26 | const messages = []; 27 | for (const message of validatorError[key].messages) { 28 | messages.push(this.getValidationMessage(message, language)); 29 | } 30 | validatorError[key].messages = messages; 31 | if (validatorError[key].children && Object.keys(validatorError[key].children).length) { 32 | this.convertValidationErrors(validatorError[key].children, language); 33 | } 34 | } 35 | } 36 | 37 | getValidationMessage(validatorMessage, language: string): string { 38 | let translateMessage = ''; 39 | let args = {}; 40 | const key = `validation.${validatorMessage?.message || validatorMessage}`; 41 | 42 | if (typeof validatorMessage === 'object') { 43 | args = { ...validatorMessage.detail, property: startCase(validatorMessage.detail.property) }; 44 | } 45 | 46 | if (typeof validatorMessage === 'object' && validatorMessage.message.startsWith('each value in')) { 47 | translateMessage += trans('each value in', { lang: language }); 48 | translateMessage += ' '; 49 | validatorMessage.message = validatorMessage.message.replace('each value in ', ''); 50 | } 51 | 52 | translateMessage += trans(key, { 53 | lang: language, 54 | args 55 | }); 56 | return translateMessage; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Nest Logo 3 |

4 | 5 |

6 | This repository contains a set of custom exception filters and exceptions designed for use in a NestJS application. It enhances error handling for HTTP-based and microservice-based (gRPC, Kafka) applications by providing more meaningful error responses and localization support. 7 | 8 |

9 | 10 | ## Installation 🤖 11 | 12 | To begin using it, we first install the required dependencies. 13 | 14 | ``` 15 | npm install @hodfords/nestjs-exception 16 | ``` 17 | 18 | ## Exception Classes 19 | 20 | > **Note**: These exception classes only function when used alongside the `HttpExceptionFilter` or one of its child classes (`GrpcExceptionFilter`, `KafkaExceptionFilter`, etc.). Be sure to apply the appropriate filter in your application. 21 | 22 | **1\. UuidException** 23 | 24 | This exception is used to handle invalid UUID formats in requests. It returns a 400 `BAD_REQUEST` status. 25 | 26 | Parameters: 27 | 28 | - `field`: The name of the field that contains the invalid UUID. This value is passed to indicate which field caused the exception. 29 | 30 | **2\. ValidateException** 31 | 32 | Handles specific validation errors related to a particular field. Returns a 422 `UNPROCESSABLE_ENTITY` status. 33 | 34 | Parameters: 35 | 36 | - `property`: The field name that caused the validation error. 37 | - `message`: The detailed message for the validation error. 38 | - `constraint`: The validation constraint that was violated (e.g., notNull). 39 | - `detail`: Additional information for the validation error, if applicable. 40 | 41 | ## Exception Filters 42 | 43 | - **HttpExceptionFilter**: Handles various types of HTTP exceptions with localization support. 44 | 45 | - **GrpcExceptionFilter**: Handles exceptions for gRPC microservices, formatting errors for gRPC clients. 46 | 47 | - **KafkaExceptionFilter**: Manages exceptions in Kafka microservices, formatting errors for Kafka messaging. 48 | 49 | - **ValidatorExceptionFilter**: Catches validation errors (`ValidateException`), supporting nested object validation and localization. 50 | 51 | **Note on Translation**: These filters, especially `HttpExceptionFilter` and `ValidatorExceptionFilter`, rely on a translation service to provide localized error messages. Ensure that your application has translation support enabled (e.g., using `@hodfords/nestjs-cls-translation`). The filters use translation keys defined in your language files to dynamically translate error messages based on the request's language. 52 | 53 | ## Example of usage 54 | 55 | To use the exception classes and filters in your NestJS application, follow these steps: 56 | 57 | #### 1\. **Applying the `HttpExceptionFilter`** 58 | 59 | **Global Application:** 60 | 61 | ```typescript 62 | import { HttpExceptionFilter } from '@hodfords/nestjs-exception'; 63 | import { NestFactory } from '@nestjs/core'; 64 | import { AppModule } from './app.module'; 65 | 66 | async function bootstrap() { 67 | const app = await NestFactory.create(AppModule); 68 | app.useGlobalFilters(new HttpExceptionFilter()); 69 | await app.listen(3000); 70 | } 71 | bootstrap(); 72 | ``` 73 | 74 | **Controller-Level Application:** 75 | 76 | ```typescript 77 | import { Controller, UseFilters } from '@nestjs/common'; 78 | import { HttpExceptionFilter } from 'hodfords/nestjs-exception'; 79 | 80 | @Controller('users') 81 | @UseFilters(HttpExceptionFilter) 82 | export class UserController {} 83 | ``` 84 | 85 | #### 2\. **Throwing a Custom Exception** 86 | 87 | ```typescript 88 | import { UuidException } from '@hodfords/nestjs-exception'; 89 | 90 | @Controller('users') 91 | export class UserController { 92 | @Get(':id') 93 | getUser(@Param('id') id: string) { 94 | if (!isValidUUID(id)) { 95 | throw new UuidException('id'); // Translation key: 'error.field_malformed' 96 | } 97 | return { id }; 98 | } 99 | } 100 | ``` 101 | 102 | ## License 103 | 104 | This project is licensed under the MIT License 105 | -------------------------------------------------------------------------------- /lib/filters/http-exception.filter.ts: -------------------------------------------------------------------------------- 1 | import { trans } from '@hodfords/nestjs-cls-translation'; 2 | import { 3 | ArgumentsHost, 4 | Catch, 5 | ExceptionFilter, 6 | HttpException, 7 | HttpStatus, 8 | PayloadTooLargeException 9 | } from '@nestjs/common'; 10 | import { EntityNotFoundError } from 'typeorm'; 11 | import { UuidException } from '../exceptions/uuid.exception'; 12 | import { ValidateException } from '../exceptions/validate.exception'; 13 | import { BaseExceptionFilter } from './base-exception.filter'; 14 | import { ValidatorExceptionFilter } from './validator-exception.filter'; 15 | 16 | @Catch() 17 | export class HttpExceptionFilter extends BaseExceptionFilter implements ExceptionFilter { 18 | protected isMicroservice: boolean = false; 19 | 20 | catch(exception, host: ArgumentsHost): void { 21 | const language = this.getLanguage(host); 22 | if (exception instanceof EntityNotFoundError) { 23 | return this.catchEntityNotFound(exception, host); 24 | } else if (exception instanceof UuidException) { 25 | const args = { 26 | field: exception.field 27 | }; 28 | return this.catchBadRequestWithArgs(host, 'error.field_malformed', language, args); 29 | } else if (exception instanceof ValidateException) { 30 | return new ValidatorExceptionFilter(this.isMicroservice, this.responseError).catch(exception, host); 31 | } else if (exception instanceof PayloadTooLargeException) { 32 | return this.catchPayloadTooLargeException(host, 'error.multer.file_too_large', language); 33 | } else if (exception.type === 'entity.too.large') { 34 | return this.catchPayloadTooLargeException(host, 'error.payload_too_large', language); 35 | } else if (exception instanceof HttpException) { 36 | return this.catchHttpException(exception, host, language); 37 | } else if (['JsonWebTokenError', 'TokenExpiredError'].includes(exception.name)) { 38 | return this.responseError(host, HttpStatus.UNAUTHORIZED, exception.message); 39 | } else { 40 | return this.catchAnotherException(exception, host); 41 | } 42 | } 43 | 44 | catchAnotherException(exception, host: ArgumentsHost): void { 45 | console.error(exception); 46 | const language = this.getLanguage(host); 47 | const message = trans('error.an_error_occurred', { lang: language }); 48 | return this.responseError(host, HttpStatus.INTERNAL_SERVER_ERROR, message); 49 | } 50 | 51 | catchHttpException(exception, host: ArgumentsHost, language: string): void { 52 | const response = exception.getResponse(); 53 | if (response?.translate) { 54 | const message = trans(response.translate, { 55 | lang: language, 56 | args: response.args 57 | }); 58 | delete response.translate; 59 | delete response.args; 60 | return this.responseError(host, exception.getStatus(), message, response); 61 | } else { 62 | return this.responseError(host, exception.getStatus(), exception.message, exception.getResponse()); 63 | } 64 | } 65 | 66 | catchEntityNotFound(exception, host: ArgumentsHost): void { 67 | const messageRegex = /"[a-zA-Z]+"/.exec(exception.message); 68 | let message = exception.message; 69 | if (messageRegex) { 70 | message = messageRegex[0].replace('"', '').replace('"', ''); 71 | } 72 | return this.responseError( 73 | host, 74 | HttpStatus.NOT_FOUND, 75 | trans(`error.not_found.${message}`, { 76 | lang: host.switchToHttp().getRequest().i18nLang 77 | }) 78 | ); 79 | } 80 | 81 | catchBadRequestWithArgs(host: ArgumentsHost, messageKey: string, language: string, args: { field: string }): void { 82 | const message = trans(messageKey, { 83 | lang: language, 84 | args 85 | }); 86 | return this.responseError(host, HttpStatus.BAD_REQUEST, message); 87 | } 88 | 89 | catchPayloadTooLargeException(host: ArgumentsHost, messageKey: string, language: string): void { 90 | const message = trans(messageKey, { lang: language }); 91 | return this.responseError(host, HttpStatus.PAYLOAD_TOO_LARGE, message); 92 | } 93 | } 94 | --------------------------------------------------------------------------------