Что такое exception filters в NestJS?
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 контекстам без отдельной настройки


