NestJS Exception Filters

An exception filter catches errors thrown anywhere in your application and transforms them into structured HTTP responses. When a service throws an unexpected error or a guard blocks a request, NestJS routes that exception to the appropriate filter. The filter decides what status code and response body the client receives.

The Default Exception Filter

NestJS includes a built-in global exception filter that handles all unhandled exceptions. When your code throws a NestJS HTTP exception, the default filter produces a clean JSON error response automatically:

throw new NotFoundException('User not found');

→ Response:
{
  "statusCode": 404,
  "message": "User not found",
  "error": "Not Found"
}

When your code throws a generic JavaScript Error that is not an HTTP exception, the default filter returns a 500 Internal Server Error to avoid leaking implementation details to the client.

Creating a Custom Exception Filter

A filter is a class decorated with @Catch() that implements ExceptionFilter:

import {
  ExceptionFilter, Catch, ArgumentsHost,
  HttpException, HttpStatus
} from '@nestjs/common';
import { Request, Response } from 'express';

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();
    const status = exception.getStatus();
    const exceptionResponse = exception.getResponse();

    response.status(status).json({
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
      message:
        typeof exceptionResponse === 'string'
          ? exceptionResponse
          : (exceptionResponse as any).message,
    });
  }
}

Custom Exception Filter Response

Before (default filter):
{
  "statusCode": 404,
  "message": "User not found",
  "error": "Not Found"
}

After (custom filter):
{
  "statusCode": 404,
  "timestamp": "2025-01-15T10:30:00.000Z",
  "path": "/users/999",
  "message": "User not found"
}

Applying Exception Filters

// On a single route
@Get(':id')
@UseFilters(HttpExceptionFilter)
findOne(@Param('id', ParseIntPipe) id: number) { ... }

// On an entire controller
@Controller('users')
@UseFilters(HttpExceptionFilter)
export class UsersController { ... }

// Globally — catches every exception in the application
app.useGlobalFilters(new HttpExceptionFilter());

Catching All Exceptions

Pass no argument to @Catch() to catch every exception, regardless of type:

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();

    const status =
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;

    const message =
      exception instanceof HttpException
        ? exception.message
        : 'Internal server error';

    response.status(status).json({
      statusCode: status,
      message,
      path: request.url,
      timestamp: new Date().toISOString(),
    });
  }
}

Creating Custom Exception Classes

Define custom exceptions for specific business errors. This keeps error semantics close to the domain rather than relying on generic HTTP status codes:

import { HttpException, HttpStatus } from '@nestjs/common';

export class InsufficientStockException extends HttpException {
  constructor(productId: number, available: number) {
    super(
      {
        statusCode: HttpStatus.CONFLICT,
        error: 'Insufficient Stock',
        message: `Product ${productId} has only ${available} units available`,
      },
      HttpStatus.CONFLICT,
    );
  }
}

// Usage in service
if (product.stock < requestedQuantity) {
  throw new InsufficientStockException(product.id, product.stock);
}

Exception Filter Pipeline Position

HTTP Request
     |
     v
Middleware → Guards → Interceptors → Pipes → Controller
                                                 |
                                          Exception thrown
                                                 |
                                                 v
                                       Exception Filter
                                                 |
                                                 v
                                        Error Response sent

Logging Exceptions

Exception filters are the correct place to log errors centrally. Every unhandled error passes through the filter, giving you one place to record stack traces, alert monitoring services, or write to a log file:

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  private readonly logger = new Logger(AllExceptionsFilter.name);

  catch(exception: unknown, host: ArgumentsHost) {
    this.logger.error(
      exception instanceof Error ? exception.message : 'Unknown error',
      exception instanceof Error ? exception.stack : '',
    );
    // ... send response
  }
}

Exception filters give you complete control over error presentation. Rather than letting raw errors bubble up to clients with framework-generated messages, you decide exactly what error format every consumer of your API receives — consistent, informative, and safe from implementation leaks.

Leave a Comment

Your email address will not be published. Required fields are marked *