In any API, error handling is an essential aspect of ensuring smooth communication between the backend and frontend. In NestJS, error handling is made simple and scalable with the use of Exception Filters. These filters catch and handle exceptions thrown by your application, allowing you to format and customize error responses globally or at the controller level.
What is an Exception Filter in NestJS?
An Exception Filter in NestJS allows you to catch and process errors thrown during the execution of requests. It provides a centralized way to manage and respond to errors consistently across your application. Exception filters can be applied globally or at the controller level, giving you fine-grained control over error handling.
Key Features of Exception Filters:
- Intercept errors thrown during request handling.
- Format error responses uniformly.
- Customize error responses, including adding detailed messages or error codes.
- Apply globally or to specific controllers.
Creating the GlobalExceptionFilter
Let’s dive into the implementation of a GlobalExceptionFilter in NestJS. This filter will catch all errors, format them in a consistent structure, and send them back to the client.
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
HttpStatus,
} from '@nestjs/common';
import { deleteFiles } from 'src/utils/file.utils';
interface ErrorMessage {
path: string;
message: string;
}
@Catch()
export class GlobalExceptionFilter implements ExceptionFilter {
catch(exception: any, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const request = ctx.getRequest();
const response = ctx.getResponse();
// Delete uploaded files if they exist
if (request.file) {
deleteFiles(request.file as Express.Multer.File);
}
if (request.files) {
deleteFiles(request.files as Express.Multer.File[]);
}
// Determine the status code based on exception type
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
let errorMessages: ErrorMessage[] = [];
// Handle different types of errors
if (exception?.message?.includes('Argument')) {
console.log('prisma');
errorMessages = this.formatPrismaError(exception);
} else if (exception?.response?.message) {
console.log('dto');
errorMessages = this.formatDtoValidationError(exception.response.message);
} else if (exception instanceof HttpException) {
console.log('http');
errorMessages = this.formatHttpError(exception);
} else {
console.log('other');
errorMessages = this.formatOtherError(exception);
}
// Send the response with a proper error structure
response.status(status).json({
success: false,
message: 'Validation error',
errorMessages,
});
}
formatPrismaError(exception: any): ErrorMessage[] {
const errorMessages: ErrorMessage[] = [];
if (exception.message.includes('Argument')) {
const match = exception.message.match(/Argument `(\w+)` is missing/);
if (match) {
errorMessages.push({
path: match[1],
message: `${match[1]} is missing`,
});
}
} else {
if (exception.meta?.target) {
errorMessages.push({
path: exception.meta.target[0], // Assuming the target is a list of fields
message: exception.message || 'Invalid value',
});
}
}
return errorMessages;
}
formatDtoValidationError(errors: any[]): ErrorMessage[] {
return errors.map((error) => {
if (typeof error === 'string') {
return {
path: error.split(' ')[0],
message: error,
};
}
return {
path: error.property,
message: error.constraints
? Object.values(error.constraints).join(', ')
: 'Validation error',
};
});
}
formatHttpError(exception: HttpException): ErrorMessage[] {
let errorMessages: ErrorMessage[] = [];
const response = exception.getResponse() as any;
if (typeof response === 'string') {
errorMessages.push({
path: 'general',
message: response || 'An error occurred',
});
} else if (typeof response === 'object' && response?.message) {
if (Array.isArray(response.message)) {
errorMessages = response.message.map((error: any) => ({
path: error.property || 'unknown_field',
message: error.constraints
? Object.values(error.constraints).join(', ')
: error.message || 'Validation error',
}));
} else {
errorMessages = [
{
path: 'general',
message: response.message || 'An error occurred',
},
];
}
}
return errorMessages;
}
formatOtherError(exception: any): ErrorMessage[] {
const errorMessages: ErrorMessage[] = [];
if (exception?.path && exception?.message) {
errorMessages.push({
path: exception.path || 'unknown_field',
message: exception.message || 'An unexpected error occurred',
});
} else {
errorMessages.push({
path: 'unknown',
message: exception.message || 'An unexpected error occurred',
});
}
return errorMessages;
}
}
Applying the GlobalExceptionFilter
To ensure the GlobalExceptionFilter is used across the entire application, we need to register it in the main.ts file of the NestJS application:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { GlobalExceptionFilter } from './filters/global-exception.filter';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Apply the Global Exception Filter
app.useGlobalFilters(new GlobalExceptionFilter());
await app.listen(3000);
}
bootstrap();
By using app.useGlobalFilters(), we ensure that any unhandled errors across the application are caught and formatted by the GlobalExceptionFilter.
Benefits of Using a GlobalExceptionFilter
Consistency in Error Handling:
By using a global exception filter, we ensure that all errors are handled and structured in the same way, making it easier for frontend developers to parse and display errors.Centralized Error Management:
The filter helps keep error-handling logic centralized, making it easier to manage and modify. Changes to error formats or behavior only need to be made in one place.Customizable Responses:
You can further customize the GlobalExceptionFilter to add more information, such as error codes, user-friendly messages, or even logging for debugging.Improved Developer Experience:
Developers working on the API can focus on business logic while relying on the global exception filter to handle any unexpected errors in a standardized way.
Regards,
N I Rimon
Top comments (0)