Suggest an editImprove this articleRefine the answer for “What are exception filters in NestJS?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Exception filters** in NestJS catch unhandled exceptions and shape the HTTP response before it reaches the client. Implement `ExceptionFilter`, use `@Catch()` to declare which exception types to handle, then register globally via `APP_FILTER`. ```typescript @Catch(HttpException) export class HttpExceptionFilter implements ExceptionFilter { catch(exception: HttpException, host: ArgumentsHost) { const ctx = host.switchToHttp(); const status = exception.getStatus(); ctx.getResponse().status(status).json({ statusCode: status, path: ctx.getRequest().url, }); } } ``` **Key point:** `@Catch()` with no args catches everything; `@Catch(HttpException)` only handles NestJS HTTP exceptions.Shown above the full answer for quick recall.Answer (EN)Image**Exception filters** in NestJS are classes that intercept unhandled exceptions before the response leaves the server, giving you full control over the status code, response body, and side effects like logging. ## Theory ### TL;DR - Think of a filter as a mailroom sorter: it catches bad envelopes (thrown errors) and decides what to send back instead of letting them bounce randomly - The built-in global filter handles `HttpException` automatically; custom filters let you target specific types, add logging, or reshape the response - `@Catch(HttpException)` targets only NestJS HTTP errors; `@Catch()` with no args catches everything including raw `Error` - Register globally via `APP_FILTER` in a module (supports DI) or `app.useGlobalFilters()` in `main.ts` (no DI) - Filters run after an exception is thrown but before the response is sent ### Quick example ```typescript // http-exception.filter.ts import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common'; import { Request, Response } from 'express'; @Catch(HttpException) // targets HttpException and all its subclasses 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(); response.status(status).json({ statusCode: status, timestamp: new Date().toISOString(), path: request.url, message: exception.getResponse(), }); } } ``` Any `HttpException` thrown anywhere in the app now returns a consistent JSON shape with timestamp and path. The built-in NestJS default no longer runs for matched types. ### Key difference NestJS ships with a global exception handler that formats a basic JSON response for `HttpException` subclasses. Custom filters override or extend this by implementing `ExceptionFilter` and declaring via `@Catch()` which exception types to handle. The main benefit is a single place for logging, Sentry calls, or custom fields without touching every controller. That beats `try/catch` blocks scattered across handlers. ### When to use - App-wide consistent error format: register a global filter via `APP_FILTER` - Route-specific behavior (for example, `/admin` errors go to Slack): use `@UseFilters()` on a controller or method - Only certain exception types need special handling: `@Catch(BadRequestException)` or `@Catch(ValidationError)` - Side effects like Sentry or Prometheus without changing business logic: add them inside `catch()` - Safety net for unhandled programmer errors: `@Catch()` with no args maps everything to 500 ### How NestJS processes filters When an exception bubbles up from a controller, NestJS checks `@Catch()` decorator metadata registered at bootstrap. The first filter whose type argument matches the thrown exception runs `catch()`. Inside, `ArgumentsHost` gives you the Express `Request` and `Response` objects. If no custom filter matches, `BaseExceptionFilter` handles standard `HttpException` instances. Filter order with multiple globals is reversed at runtime. The last provider registered for `APP_FILTER` fires first for matching exceptions. I discovered this after wondering why my logger filter was not running before the response went out. Register catch all filters last. ### Common mistakes 1. **Making `catch()` async.** NestJS filters are synchronous. An `async catch()` that awaits anything can leave the response hanging indefinitely. ```typescript // Wrong - response hangs async catch(exception: HttpException, host: ArgumentsHost) { await sendToSlack(exception.message); // response never sent } // Fix - fire and forget for async side effects catch(exception: HttpException, host: ArgumentsHost) { setImmediate(() => sendToSlack(exception.message)); // write response synchronously below } ``` 2. **Forgetting `multi: true` when chaining global filters.** Without it, a single `APP_FILTER` provider can replace the built-in handler, leaving some error types with no fallback. ```typescript // Wrong { provide: APP_FILTER, useClass: MyFilter } // Fix { provide: APP_FILTER, useClass: MyFilter, multi: true } ``` 3. **Using `@Catch()` with no args everywhere.** It grabs `TypeError` and `ReferenceError` too, masking real programmer bugs as generic 500s. ```typescript // Risky - TypeErrors disappear into 500 @Catch() // Safer for most filters @Catch(HttpException) ``` 4. **Expecting `@UseFilters()` to stack at controller and method level.** The method-level filter overrides the controller-level one for that endpoint. They do not stack. ### Real-world usage - NestJS + Prisma: catch `PrismaClientKnownRequestError` with code `P2002` and return `409 Conflict` with the duplicate field name - Sentry via `nestjs-sentry`: filter calls `Sentry.captureException()` before sending the response - Validation with `class-validator`: catch `BadRequestException`, extract field errors from the body, return a structured `errors` array - GraphQL APIs: `@Catch(ApolloError)` shapes GQL error responses independently from REST endpoints - Microservice gateway: logging and tracing filters at the gateway; domain-specific error details at individual services ### Follow-up questions **Q:** How do you apply a filter to just one method? **A:** Add `@UseFilters(MyFilter)` directly on the method. It overrides any controller-level or global filter for that specific endpoint. **Q:** What is the difference between `@Catch(HttpException)` and `@Catch()`? **A:** `@Catch(HttpException)` only intercepts NestJS HTTP exceptions, letting raw `Error` objects fall through to the built-in handler. `@Catch()` catches every throwable, including `TypeError` and `ReferenceError`. **Q:** Can a filter access data set by a guard or interceptor? **A:** No. Filters run in the error phase after the execution chain has failed. You can only access request data via `ArgumentsHost`. Guard or interceptor context is not available at that point. **Q:** How does filter order work when you register multiple globals? **A:** NestJS reverses the registration order at runtime. The last `APP_FILTER` provider registered fires first for a matching exception type. Register broad filters last or they will shadow typed ones. **Q:** In a microservice setup with a gateway, where should filters live? **A:** Domain-specific filters (like a `NotFoundException` that includes entity details) belong to individual services. Cross-cutting filters for logging, alerting, or tracing belong to the gateway. Services stay independent; the gateway handles observability. ## Examples ### Basic: global catch all filter A solid starting point for production apps. Catches every thrown value and returns a consistent JSON body with logging. ```typescript // all-exceptions.filter.ts import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus, Logger } from '@nestjs/common'; import { Request, Response } from 'express'; @Catch() export class AllExceptionsFilter implements ExceptionFilter { private readonly logger = new Logger(AllExceptionsFilter.name); 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.getResponse() : 'Internal server error'; this.logger.error(`${request.method} ${request.url}`, String(exception)); response.status(status).json({ statusCode: status, message, timestamp: new Date().toISOString(), path: request.url, }); } } // app.module.ts @Module({ providers: [{ provide: APP_FILTER, useClass: AllExceptionsFilter }], }) export class AppModule {} ``` Every unhandled exception now returns `{ statusCode, message, timestamp, path }`. Registering via `APP_FILTER` instead of `useGlobalFilters()` keeps dependency injection working inside the filter. ### Intermediate: validation filter on a signup endpoint Real scenario from auth flows. `class-validator` throws `BadRequestException` with an array of field errors inside the response body. This filter extracts them into a structured shape. ```typescript // validation.filter.ts import { Catch, ArgumentsHost, BadRequestException } from '@nestjs/common'; import { Response, Request } from 'express'; @Catch(BadRequestException) export class ValidationFilter implements ExceptionFilter { catch(exception: BadRequestException, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse<Response>(); const request = ctx.getRequest<Request>(); const body = exception.getResponse() as any; response.status(400).json({ statusCode: 400, message: 'Validation failed', errors: body.message, // array: ['email must be an email'] path: request.url, }); } } // auth.controller.ts @Controller('auth') @UseFilters(ValidationFilter) export class AuthController { @Post('signup') signup(@Body() dto: CreateUserDto) { return this.authService.signup(dto); } } ``` `POST /auth/signup` with an invalid email now returns `{ statusCode: 400, message: "Validation failed", errors: ["email must be an email"] }` instead of the default NestJS format. ### Advanced: Prisma error mapping Catching database-level constraint violations and translating them into proper HTTP responses. Common in any NestJS + Prisma codebase. ```typescript // prisma-exception.filter.ts import { ExceptionFilter, Catch, ArgumentsHost, HttpStatus } from '@nestjs/common'; import { Prisma } from '@prisma/client'; import { Response, Request } from 'express'; @Catch(Prisma.PrismaClientKnownRequestError) export class PrismaExceptionFilter implements ExceptionFilter { catch(exception: Prisma.PrismaClientKnownRequestError, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse<Response>(); const request = ctx.getRequest<Request>(); // P2002 = unique constraint violation const status = exception.code === 'P2002' ? HttpStatus.CONFLICT : HttpStatus.INTERNAL_SERVER_ERROR; const message = exception.code === 'P2002' ? `Duplicate value on: ${(exception.meta?.target as string[])?.join(', ')}` : 'Database error'; response.status(status).json({ statusCode: status, message, timestamp: new Date().toISOString(), path: request.url, }); } } ``` `POST /users` with a duplicate email no longer returns a raw Prisma stack trace or a generic 500. The client gets `{ statusCode: 409, message: "Duplicate value on: email" }`. Register this filter globally or per module depending on whether Prisma is used app-wide.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.