We use cookies to enhance your experience on the site
CodeWorlds

Exception Filters - Crisis Management in Your Application

In this module, you will learn advanced exception handling techniques in a NestJS-based system on a matter of utmost importance. Our legion must be prepared for every crisis that may befall us in the dangerous provinces of our application. Today you will learn about Exception Filters - an advanced crisis response system that will keep the entire legion safe!

At a Roman fort, every crisis requires a swift and precise response. A storm in the provinces, an enemy legion attack, a standard failure - each situation has its own response protocol. Exception Filters in NestJS work similarly - they automatically intercept errors and respond according to pre-established procedures.

What Are Exception Filters?

Exception Filters are classes implementing the interface

ExceptionFilter
, that:

  • Intercept exceptions thrown in the application
  • Process errors in a unified way
  • Format error responses for clients
  • Log incidents for diagnostic purposes
  • Implement a recovery strategy when possible
1import {
2 ExceptionFilter,
3 Catch,
4 ArgumentsHost,
5 HttpException,
6 HttpStatus,
7} from '@nestjs/common';
8import { Request, Response } from 'express';
9
10@Catch(HttpException)
11export class LegionaryHttpExceptionFilter implements ExceptionFilter {
12 catch(exception: HttpException, host: ArgumentsHost) {
13 const ctx = host.switchToHttp();
14 const response = ctx.getResponse<Response>();
15 const request = ctx.getRequest<Request>();
16 const status = exception.getStatus();
17 const exceptionResponse = exception.getResponse();
18
19 console.log(` Crisis in the system! Status: ${status}`);
20 console.log(` Location: ${request.method} ${request.url}`);
21 console.log(` Details: ${exception.message}`);
22
23 const errorResponse = {
24 success: false,
25 statusCode: status,
26 timestamp: new Date().toISOString(),
27 path: request.url,
28 method: request.method,
29 message: this.getLegionaryErrorMessage(status),
30 details: typeof exceptionResponse === 'object' 
31 ? exceptionResponse 
32 : { message: exceptionResponse },
33 cohort: 'Legio Nigra',
34 centurion: 'Caesar.js'
35 };
36
37 response.status(status).json(errorResponse);
38 }
39
40 private getLegionaryErrorMessage(status: number): string {
41 const messages = {
42 400: 'Invalid payload! Check your tributes before submitting to the system! ',
43 401: 'Unauthorized. A valid JWT token is required. ',
44 403: 'No permissions for this resource. You need a higher rank! ',
45 404: 'Tribute not found! Check the map or choose another route! ',
46 409: 'Conflict in the treasury! Another legionary is already working with this tribute! ',
47 429: 'Too many requests. Rate limit exceeded. ',
48 500: 'Application failure! The mechanic is already working on the fix! '
49 };
50 
51 return messages[status] || 'Unknown crisis in the system! All hands to the front line! ';
52 }
53}

Specialized Exception Filters

Database Exception Filter

A special filter for database errors:

1import { Catch, ExceptionFilter, ArgumentsHost } from '@nestjs/common';
2import { QueryFailedError, EntityNotFoundError, CannotCreateEntityIdMapError } from 'typeorm';
3
4@Catch(QueryFailedError, EntityNotFoundError, CannotCreateEntityIdMapError)
5export class DatabaseExceptionFilter implements ExceptionFilter {
6 catch(exception: any, host: ArgumentsHost) {
7 const ctx = host.switchToHttp();
8 const response = ctx.getResponse();
9 const request = ctx.getRequest();
10
11 let status = 500;
12 let message = 'Error in the data treasury!';
13 let code = 'DATABASE_ERROR';
14
15 if (exception instanceof QueryFailedError) {
16 status = 400;
17 message = this.handleQueryError(exception);
18 code = 'QUERY_FAILED';
19 } else if (exception instanceof EntityNotFoundError) {
20 status = 404;
21 message = 'Tribute not found in the treasury!';
22 code = 'ENTITY_NOT_FOUND';
23 } else if (exception instanceof CannotCreateEntityIdMapError) {
24 status = 400;
25 message = 'Invalid tribute identifier!';
26 code = 'INVALID_ENTITY_ID';
27 }
28
29 console.error(` Database error:`, {
30 error: exception.message,
31 query: exception.query,
32 parameters: exception.parameters,
33 url: request.url,
34 method: request.method
35 });
36
37 response.status(status).json({
38 success: false,
39 error: {
40 code,
41 message,
42 timestamp: new Date().toISOString(),
43 path: request.url
44 },
45 hint: this.getRecoveryHint(code)
46 });
47 }
48
49 private handleQueryError(exception: QueryFailedError): string {
50 const message = exception.message;
51 
52 if (message.includes('duplicate key')) {
53 return 'This tribute already exists in the treasury! Each tribute must have a unique identifier.';
54 }
55 
56 if (message.includes('foreign key constraint')) {
57 return 'Cannot delete this element - it is linked to other tributes!';
58 }
59 
60 if (message.includes('not null constraint')) {
61 return 'Missing required tribute information!';
62 }
63 
64 if (message.includes('check constraint')) {
65 return 'Data does not meet treasury rules (e.g., value must be positive)!';
66 }
67 
68 return 'Error in the data treasury operation!';
69 }
70
71 private getRecoveryHint(code: string): string {
72 const hints = {
73 'QUERY_FAILED': 'Verify that all data is correct and try again',
74 'ENTITY_NOT_FOUND': 'Make sure the tribute exists or create a new one',
75 'INVALID_ENTITY_ID': 'Check the identifier format (should be a number)'
76 };
77 
78 return hints[code] || 'Contact the centurion for problem details';
79 }
80}

Validation Exception Filter

A specialized filter for validation errors:

1import { Catch, ExceptionFilter, ArgumentsHost, BadRequestException } from '@nestjs/common';
2import { ValidationError } from 'class-validator';
3
4@Catch(BadRequestException)
5export class ValidationExceptionFilter implements ExceptionFilter {
6 catch(exception: BadRequestException, host: ArgumentsHost) {
7 const ctx = host.switchToHttp();
8 const response = ctx.getResponse();
9 const request = ctx.getRequest();
10 const exceptionResponse = exception.getResponse();
11
12 // Check if this is a validation error
13 if (this.isValidationError(exceptionResponse)) {
14 const validationErrors = this.extractValidationErrors(exceptionResponse);
15 
16 console.log(` Validation errors for ${request.url}:`, validationErrors);
17
18 response.status(400).json({
19 success: false,
20 error: 'VALIDATION_FAILED',
21 message: 'Your payload did not pass quality control!',
22 validationErrors,
23 timestamp: new Date().toISOString(),
24 path: request.url,
25 suggestions: this.generateSuggestions(validationErrors)
26 });
27 } else {
28 // Standard 400 error
29 response.status(400).json({
30 success: false,
31 error: 'BAD_REQUEST',
32 message: 'Invalid request!',
33 details: exceptionResponse,
34 timestamp: new Date().toISOString(),
35 path: request.url
36 });
37 }
38 }
39
40 private isValidationError(response: any): boolean {
41 return response && 
42 Array.isArray(response.message) && 
43 response.message.length > 0 &&
44 typeof response.message[0] === 'object';
45 }
46
47 private extractValidationErrors(response: any): any[] {
48 if (!Array.isArray(response.message)) {
49 return [];
50 }
51
52 return response.message.map(error => ({
53 field: error.property,
54 value: error.value,
55 constraints: Object.values(error.constraints || {}),
56 children: error.children || []
57 }));
58 }
59
60 private generateSuggestions(validationErrors: any[]): string[] {
61 const suggestions = [];
62 
63 validationErrors.forEach(error => {
64 if (error.field === 'email') {
65 suggestions.push('Check the email format (example@legionaries.com)');
66 }
67 
68 if (error.field === 'password') {
69 suggestions.push('Password must be at least 8 characters and contain numbers');
70 }
71 
72 if (error.field === 'resourceValue') {
73 suggestions.push('Tribute value must be a number greater than 0');
74 }
75 
76 if (error.field === 'coordinates') {
77 suggestions.push('Coordinates must be in format: latitude,longitude');
78 }
79 });
80 
81 if (suggestions.length === 0) {
82 suggestions.push('Check all fields and make sure the data is correct');
83 }
84 
85 return suggestions;
86 }
87}

All Exceptions Filter

A universal filter that catches all errors:

1import {
2 ExceptionFilter,
3 Catch,
4 ArgumentsHost,
5 HttpException,
6 HttpStatus,
7 Logger
8} from '@nestjs/common';
9
10@Catch()
11export class AllExceptionsFilter implements ExceptionFilter {
12 private readonly logger = new Logger(AllExceptionsFilter.name);
13
14 catch(exception: unknown, host: ArgumentsHost): void {
15 const ctx = host.switchToHttp();
16 const response = ctx.getResponse();
17 const request = ctx.getRequest();
18
19 const httpStatus = exception instanceof HttpException
20 ? exception.getStatus()
21 : HttpStatus.INTERNAL_SERVER_ERROR;
22
23 const timestamp = new Date().toISOString();
24 const path = request.url;
25 const method = request.method;
26 const userAgent = request.headers['user-agent'] || 'Unknown';
27 const ip = request.ip;
28 const legionaries = request.user || { name: 'Unknown', rank: 'Guest' };
29
30 // Detailed logging of different error types
31 if (exception instanceof HttpException) {
32 this.logger.warn(`HTTP Exception: ${exception.message}`, {
33 status: httpStatus,
34 path,
35 method,
36 legionaries: legionaries.name
37 });
38 } else if (exception instanceof Error) {
39 this.logger.error(`Unhandled Error: ${exception.message}`, {
40 stack: exception.stack,
41 path,
42 method,
43 legionaries: legionaries.name
44 });
45 } else {
46 this.logger.error('Unknown Exception', {
47 exception,
48 path,
49 method,
50 legionaries: legionaries.name
51 });
52 }
53
54 const errorResponse = {
55 success: false,
56 error: {
57 statusCode: httpStatus,
58 message: this.getErrorMessage(exception, httpStatus),
59 timestamp,
60 path,
61 method,
62 requestId: this.generateRequestId(),
63 },
64 context: {
65 cohort: 'Legio Nigra',
66 centurion: 'Caesar.js',
67 legionaries: legionaries.name,
68 rank: legionaries.rank,
69 userAgent,
70 ip: this.maskIpAddress(ip)
71 },
72 support: {
73 message: 'If the problem persists, contact the centurion',
74 reportBug: '/api/support/report-bug',
75 documentation: '/api/docs'
76 }
77 };
78
79 // In development environment, add stack trace
80 if (process.env.NODE_ENV === 'development' && exception instanceof Error) {
81 errorResponse.debug = {
82 stack: exception.stack,
83 cause: exception.cause
84 };
85 }
86
87 response.status(httpStatus).json(errorResponse);
88 }
89
90 private getErrorMessage(exception: unknown, status: number): string {
91 if (exception instanceof HttpException) {
92 return exception.message;
93 }
94
95 const messages = {
96 400: 'Invalid request - check input data',
97 401: 'Authorization required - please log in again',
98 403: 'No permissions - contact the centurion',
99 404: 'Resource not found',
100 409: 'Conflict - tribute is currently in use',
101 422: 'Cannot process the request',
102 429: 'Too many requests - try again later',
103 500: 'Internal server error',
104 502: 'Gateway error - problem with external service',
105 503: 'Service unavailable - maintenance break',
106 504: 'Request timeout exceeded'
107 };
108
109 return messages[status] || 'An unexpected error occurred';
110 }
111
112 private generateRequestId(): string {
113 return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
114 }
115
116 private maskIpAddress(ip: string): string {
117 if (!ip) return 'unknown';
118 
119 const parts = ip.split('.');
120 if (parts.length === 4) {
121 return `${parts[0]}.${parts[1]}.xxx.xxx`;
122 }
123 
124 return 'masked';
125 }
126}

Business Logic Exception Filter

A filter for specific business logic errors:

1export class LegionaryBusinessException extends Error {
2 constructor(
3 message: string,
4 public readonly code: string,
5 public readonly context?: any
6 ) {
7 super(message);
8 this.name = 'LegionaryBusinessException';
9 }
10}
11
12@Catch(LegionaryBusinessException)
13export class BusinessExceptionFilter implements ExceptionFilter {
14 catch(exception: LegionaryBusinessException, host: ArgumentsHost) {
15 const ctx = host.switchToHttp();
16 const response = ctx.getResponse();
17 const request = ctx.getRequest();
18
19 console.log(` Business error:`, {
20 code: exception.code,
21 message: exception.message,
22 context: exception.context,
23 url: request.url
24 });
25
26 const errorMap = {
27 'INSUFFICIENT_LEGION': {
28 status: 422,
29 message: 'Not enough legionaries in the system to perform this operation!',
30 solution: 'Recruit more legion members'
31 },
32 'TRIBUTE_LOCKED': {
33 status: 423,
34 message: 'Tribute is locked by another legionary!',
35 solution: 'Wait until the operation is completed'
36 },
37 'COHORT_CAPACITY_EXCEEDED': {
38 status: 413,
39 message: 'The cohort is overloaded! Cannot add more tributes!',
40 solution: 'Transfer some tributes to another fort'
41 },
42 'CURSED_TRIBUTE_WARNING': {
43 status: 406,
44 message: 'You are trying to move a cursed tribute!',
45 solution: 'You need special permissions or a purification ritual'
46 },
47 'WEATHER_TOO_DANGEROUS': {
48 status: 503,
49 message: 'The weather is too dangerous for operations!',
50 solution: 'Wait for better weather conditions'
51 }
52 };
53
54 const errorInfo = errorMap[exception.code] || {
55 status: 400,
56 message: exception.message,
57 solution: 'Contact the centurion for details'
58 };
59
60 response.status(errorInfo.status).json({
61 success: false,
62 error: {
63 code: exception.code,
64 message: errorInfo.message,
65 context: exception.context,
66 solution: errorInfo.solution,
67 timestamp: new Date().toISOString(),
68 path: request.url
69 },
70 legionaries: {
71 advice: this.getLegionaryAdvice(exception.code),
72 nextSteps: this.getNextSteps(exception.code)
73 }
74 });
75 }
76
77 private getLegionaryAdvice(code: string): string {
78 const advice = {
79 'INSUFFICIENT_LEGION': 'Remember: One legionary is not a legion, three is the start of a legion!',
80 'TRIBUTE_LOCKED': 'Patience is a virtue of every experienced legionary',
81 'COHORT_CAPACITY_EXCEEDED': 'Even the largest fort has its limits',
82 'CURSED_TRIBUTE_WARNING': 'Cursed tributes require wisdom, not just courage',
83 'WEATHER_TOO_DANGEROUS': 'A wise centurion knows the difference between bravery and madness'
84 };
85 
86 return advice[code] || 'Every error is a lesson for the future';
87 }
88
89 private getNextSteps(code: string): string[] {
90 const steps = {
91 'INSUFFICIENT_LEGION': [
92 'Visit the recruitment port',
93 'Check legionary availability',
94 'Consider temporarily hiring a legion'
95 ],
96 'TRIBUTE_LOCKED': [
97 'Check the operation status',
98 'Set a completion notification',
99 'Check alternative tributes'
100 ],
101 'COHORT_CAPACITY_EXCEEDED': [
102 'Check availability of other cohorts',
103 'Consider selling less valuable tributes',
104 'Upgrade the current fort capacity'
105 ]
106 };
107 
108 return steps[code] || ['Contact the centurion', 'Check the documentation'];
109 }
110}

Rate Limiting Exception Filter

A special filter for rate limiting errors:

1import { ThrottlerException } from '@nestjs/throttler';
2
3@Catch(ThrottlerException)
4export class RateLimitExceptionFilter implements ExceptionFilter {
5 catch(exception: ThrottlerException, host: ArgumentsHost) {
6 const ctx = host.switchToHttp();
7 const response = ctx.getResponse();
8 const request = ctx.getRequest();
9
10 const retryAfter = this.calculateRetryAfter(request);
11 const legionaries = request.user || { name: 'Unknown' };
12
13 console.log(` Rate limit exceeded:`, {
14 legionaries: legionaries.name,
15 ip: request.ip,
16 url: request.url,
17 retryAfter
18 });
19
20 response
21 .status(429)
22 .header('Retry-After', retryAfter.toString())
23 .json({
24 success: false,
25 error: {
26 code: 'RATE_LIMIT_EXCEEDED',
27 message: 'Too many requests! The cohort cannot handle such an assault!',
28 retryAfter,
29 timestamp: new Date().toISOString(),
30 path: request.url
31 },
32 legionaries: {
33 name: legionaries.name,
34 advice: 'Even the fastest fort needs time to maneuver',
35 suggestion: `Try again in ${retryAfter} seconds`
36 },
37 limits: {
38 current: 'Exceeded',
39 window: '1 minuta',
40 maxRequests: this.getMaxRequests(request.url)
41 }
42 });
43 }
44
45 private calculateRetryAfter(request: any): number {
46 // Different limits for different endpoints
47 const endpoint = request.url;
48 
49 if (endpoint.includes('/auth/')) {
50 return 60; // 1 minute for authorization
51 }
52 
53 if (endpoint.includes('/tributes/')) {
54 return 30; // 30 seconds for tributes
55 }
56 
57 return 15; // 15 seconds by default
58 }
59
60 private getMaxRequests(url: string): number {
61 if (url.includes('/auth/')) return 5;
62 if (url.includes('/tributes/')) return 10;
63 return 20;
64 }
65}

Registering Exception Filters

1// main.ts - Global registration
2import { NestFactory } from '@nestjs/core';
3import { AppModule } from './app.module';
4
5async function bootstrap() {
6 const app = await NestFactory.create(AppModule);
7 
8 // Global exception filters in order from most specific
9 app.useGlobalFilters(
10 new DatabaseExceptionFilter(),
11 new ValidationExceptionFilter(),
12 new BusinessExceptionFilter(),
13 new RateLimitExceptionFilter(),
14 new AllExceptionsFilter(),
15 );
16 
17 await app.listen(3000);
18}
19bootstrap();
20
21// Or in a module
22@Module({
23 providers: [
24 {
25 provide: APP_FILTER,
26 useClass: AllExceptionsFilter,
27 },
28 ],
29})
30export class AppModule {}
31
32// On a controller
33@Controller('tributes')
34@UseFilters(DatabaseExceptionFilter, ValidationExceptionFilter)
35export class TributeController {
36 // On a specific method
37 @Post('transfer')
38 @UseFilters(BusinessExceptionFilter)
39 transferTribute(@Body() transferDto: TransferTributeDto) {
40 // Implementation
41 }
42}

Integration with Monitoring Systems

1@Injectable()
2export class MonitoringExceptionFilter implements ExceptionFilter {
3 constructor(
4 private readonly loggingService: LoggingService,
5 private readonly metricsService: MetricsService,
6 private readonly alertingService: AlertingService
7 ) {}
8
9 catch(exception: any, host: ArgumentsHost) {
10 const ctx = host.switchToHttp();
11 const request = ctx.getRequest();
12 const response = ctx.getResponse();
13 
14 const status = exception instanceof HttpException 
15 ? exception.getStatus() 
16 : 500;
17
18 // Structured logging
19 this.loggingService.logError({
20 timestamp: new Date().toISOString(),
21 level: 'ERROR',
22 message: exception.message,
23 stack: exception.stack,
24 context: {
25 method: request.method,
26 url: request.url,
27 userAgent: request.headers['user-agent'],
28 ip: request.ip,
29 user: request.user?.id
30 },
31 tags: ['exception', 'http', `status-${status}`]
32 });
33
34 // Metrics
35 this.metricsService.incrementCounter('http_errors_total', {
36 method: request.method,
37 status: status.toString(),
38 endpoint: request.route?.path || request.url
39 });
40
41 // Alerts for critical errors
42 if (status >= 500) {
43 this.alertingService.sendAlert({
44 severity: 'HIGH',
45 title: `Server Error ${status}`,
46 description: exception.message,
47 context: {
48 url: request.url,
49 method: request.method,
50 timestamp: new Date().toISOString()
51 }
52 });
53 }
54
55 // Standard response
56 response.status(status).json({
57 success: false,
58 error: exception.message,
59 timestamp: new Date().toISOString(),
60 path: request.url
61 });
62 }
63}

Exception Filters are your last line of defense against chaos in the application. Well-configured filters can turn a catastrophe into a controlled situation, providing the legion and passengers with safety and clear information about what is happening.

Remember: a good centurion is prepared for every crisis, and a good Exception Filter for every error!

Go to CodeWorlds