Skip to content

NestJS Exception Filters

Exception filters are the final layer that turns a thrown error into an HTTP response. This lesson covers Nest's built-in exception layer and how to write a custom filter.

How Nest Handles Thrown Exceptions

By default, NestJS ships a built-in global exception filter that catches any unhandled exception, formats known HttpException instances into their corresponding status code and JSON body, and falls back to a generic 500 Internal Server Error for anything else.

throw new NotFoundException('Cat not found');

// Automatically becomes:
// HTTP 404
// { "statusCode": 404, "message": "Cat not found", "error": "Not Found" }

You never have to manually catch and format HttpException subclasses yourself, Nest's default exception filter already does this for the entire application.

Writing a Custom Exception Filter

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const status = exception.getStatus();

    response.status(status).json({
      statusCode: status,
      message: exception.message,
      timestamp: new Date().toISOString(),
    });
  }
}
  • @Catch() declares which exception type(s) a filter handles; omit arguments to catch everything.
  • ArgumentsHost (a simpler relative of ExecutionContext) gives access to the underlying request/response.
  • Filters can be applied per-route, per-controller, or globally with app.useGlobalFilters().
  • A custom global filter is the standard place to enforce one consistent error response shape.

Exception Filter Cheatsheet

How to declare and apply exception filters.

Scope Syntax
Route-level @UseFilters(HttpExceptionFilter) above a method
Controller-level @UseFilters(HttpExceptionFilter) above a class
Global (no DI) app.useGlobalFilters(new HttpExceptionFilter())
Global (DI-enabled) { provide: APP_FILTER, useClass: HttpExceptionFilter }

Catching Every Exception Type

Omitting a type from @Catch() makes a filter catch everything, including non-HttpException errors thrown from anywhere in the application, useful for a top-level safety net that logs unexpected errors and returns a generic 500 without leaking internal details.

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    const status =
      exception instanceof HttpException ? exception.getStatus() : 500;
    // log full details server-side, return a generic message to the client
  }
}

How Multiple Filters Interact

A more specific filter (catching HttpException) registered at the controller level takes precedence over a catch-all global filter for exceptions of that specific type, letting you layer general and specific error handling together.

Common Mistakes

  • Writing a custom filter for HttpException but forgetting to also handle unexpected non-HTTP errors.
  • Leaking internal stack traces or database error details directly to API clients.
  • Registering a global filter with useGlobalFilters() when it needs injected dependencies (use APP_FILTER instead).
  • Duplicating Nest's default formatting logic instead of extending or reusing it where possible.

Key Takeaways

  • Nest's default exception filter already formats HttpException instances into proper HTTP responses.
  • Custom filters implement ExceptionFilter and are declared with @Catch().
  • A catch-all filter (@Catch() with no arguments) is a useful top-level safety net.
  • Global filters needing dependency injection should be registered via the APP_FILTER token.

Pro Tip

Always pair a catch-all global exception filter with structured server-side logging (including the original error, not just its message), it's the difference between debugging a production incident in minutes versus guessing blindly from a generic 500 response.