Что такое exception filters в NestJS?

MiddleNestJS · Backend·Обновлено 5 июля 2026
Коротко
Exception filters в NestJS — это механизм перехвата необработанных исключений, позволяющий централизованно формировать HTTP-ответ с нужным статусом и телом вместо стандартного поведения фреймворка.

Exception Filters в NestJS

Exception filters — слой обработки ошибок в NestJS, который перехватывает исключения, вылетевшие из обработчиков маршрутов, и преобразует их в структурированный HTTP-ответ. Без фильтров NestJS использует встроенный GlobalExceptionFilter, который возвращает { statusCode, message, error } для HttpException и 500 Internal Server Error для всего остального.

Как работает фильтр

Фильтр реализует интерфейс ExceptionFilter<T> и декорируется @Catch(). Метод catch(exception, host) получает выброшенное исключение и ArgumentsHost — контекст, из которого можно получить объекты Request и Response.

import { ExceptionFilter, Catch, ArgumentsHost, HttpException } 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 и path для удобства отладки
      timestamp: new Date().toISOString(),
      path: request.url,
      message:
        typeof exceptionResponse === 'string'
          ? exceptionResponse
          : (exceptionResponse as any).message,
    });
  }
}

Уровни применения

Фильтр можно подключить на трёх уровнях:

  • Метод/контроллер — через @UseFilters()
  • Глобальный — через app.useGlobalFilters() или провайдер APP_FILTER
// Глобально через DI (позволяет инжектировать зависимости)
@Module({
  providers: [
    {
      provide: APP_FILTER,
      useClass: AllExceptionsFilter,
    },
  ],
})
export class AppModule {}

Перехват всех исключений

@Catch() без аргументов перехватывает любое исключение — удобно для глобального логирования.

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  constructor(private readonly logger: Logger) {}

  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();

    // Определяем статус: HttpException даёт свой, остальное — 500
    const status =
      exception instanceof HttpException
        ? exception.getStatus()
        : 500;

    this.logger.error(exception);

    response.status(status).json({
      statusCode: status,
      message: 'Internal server error',
    });
  }
}

Порядок выполнения в pipeline

Exception filters стоят последними в цепочке: Middleware → Guards → Interceptors → Pipes → Controller → Interceptors (после) → Exception Filter. Если исключение выброшено в guard или pipe, фильтр всё равно его поймает.

Когда использовать

  • Единый формат ошибок во всём API
  • Логирование ошибок с контекстом запроса
  • Преобразование доменных исключений (например, Prisma NotFoundError) в HttpException
  • Скрытие внутренних деталей от клиента в production

Что хочет услышать интервьюер

Кандидат понимает назначение фильтров: перехват исключений и формирование HTTP-ответа

Знает интерфейс ExceptionFilter и декоратор @Catch(), умеет работать с ArgumentsHost

Понимает разницу между точечным (@UseFilters на методе) и глобальным применением (APP_FILTER)

Осознаёт место фильтров в execution pipeline NestJS и порядок их вызова

Может объяснить, зачем регистрировать фильтр через APP_FILTER вместо useGlobalFilters() — для поддержки DI

Пример: Кастомный фильтр для HttpException

import {
  ExceptionFilter,
  Catch,
  ArgumentsHost,
  HttpException,
} 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 body = exception.getResponse();

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

Пример: Глобальная регистрация через APP_FILTER (с поддержкой DI)

import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';
import { AllExceptionsFilter } from './all-exceptions.filter';

@Module({
  providers: [
    {
      provide: APP_FILTER,
      useClass: AllExceptionsFilter,
    },
  ],
})
export class AppModule {}

Пример: Фильтр, преобразующий Prisma-ошибки в HTTP-ответы

import { ExceptionFilter, Catch, ArgumentsHost } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { Response } 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>();

    // P2002 — нарушение уникального ограничения
    if (exception.code === 'P2002') {
      return response.status(409).json({
        statusCode: 409,
        message: 'Запись с такими данными уже существует',
      });
    }

    // P2025 — запись не найдена
    if (exception.code === 'P2025') {
      return response.status(404).json({
        statusCode: 404,
        message: 'Запись не найдена',
      });
    }

    response.status(500).json({
      statusCode: 500,
      message: 'Ошибка базы данных',
    });
  }
}

Типичные ошибки

Путают exception filters с middleware — middleware не перехватывает исключения из контроллеров

Регистрируют глобальный фильтр через app.useGlobalFilters() и удивляются, что инжекция зависимостей не работает

Забывают про @Catch() без аргументов для перехвата non-HttpException ошибок (например, ошибок БД)

Возвращают response.json() без явного вызова response.status(), теряя правильный HTTP-статус

Не учитывают, что фильтры не применяются к WebSocket и gRPC контекстам без отдельной настройки

Лучшие курсы по теме

изображение курса

Docker и Ansible

Антон Ларичев
AI-тренажерыAI-тренажеры
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Node.js с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Nest.js с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.6
3 999 ₽ 6 990 ₽
Подробнее