Обработка ошибок в Nest.js: фильтры исключений

Что делает фильтр исключений в Nest.js
Фильтр исключений (Exception Filter) перехватывает ошибки, брошенные в обработчиках маршрутов, и формирует ответ клиенту. Он может работать на уровне контроллера или глобально для всего приложения.
Ключевая идея: фильтр отделяет бизнес-логику от логики отображения ошибок. Это упрощает поддержку, тестирование и согласованность ответов API.
Важно: фильтры не заменяют проверку входных данных — используйте Pipes и DTO для валидации.
Стандартное поведение Nest.js при ошибках
По умолчанию Nest.js перехватывает необработанные исключения и возвращает ответ 500 Internal Server Error с простым JSON:
{
"statusCode": 500,
"message": "Internal server error"
}Если вы кидаете объект ошибки, у которого есть statusCode и message, Nest вернёт их вместо ответа по умолчанию. Но лучше явно контролировать ответы через фильтры и встроенные исключения.
Создание собственного фильтра исключений
Ниже — пример фильтра, который обрабатывает HttpException и формирует детализированный JSON для клиента.
Создайте файл http.exception.ts и добавьте импорты:
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
} from '@nestjs/common';
import { Request, Response } from 'express';Назначение импортов кратко:
- ExceptionFilter — интерфейс для фильтра исключений.
- Catch — декоратор, отмечающий класс как фильтр для указанных типов исключений.
- ArgumentsHost — даёт доступ к аргументам обработчика и контекстам (HTTP, RPC, WebSockets).
- HttpException — базовый класс HTTP-исключений Nest.
- Request/Response — интерфейсы Express.
Создайте класс, реализующий ExceptionFilter, и пометьте его декоратором Catch:
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {}Заполните класс логикой:
catch(exception: HttpException, host: ArgumentsHost) {
// Получаем HTTP-контекст и объекты запроса/ответа
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
// Получаем статус из исключения
const status = exception.getStatus();
// Формируем структурированный JSON-ответ
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message:
exception.message
|| (exception.getResponse() as any)['message']
|| 'Internal Server Error',
});
} Примечание: exception.getResponse() может вернуть объект или строку; в реальном приложении добавьте проверки типов.
Привязка фильтров: глобально и на контроллер
Глобальная привязка в main.ts:
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { HttpExceptionFilter } from './exception/http.exception';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Привязываем фильтр глобально
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(4050);
}
bootstrap();Привязка к контроллеру:
@Controller()
@UseFilters(new HttpExceptionFilter())
export class AppController {}Где привязывать фильтр зависит от области покрытия: контроллерный фильтр влияет только на этот контроллер; глобальный — на всё приложение.
Важно: порядок подключений влияет — глобальные фильтры применяются к запросам, если контроллеры/роуты не переопределяют поведение локальными фильтрами.
Встроенные исключения Nest.js и как их использовать
Nest предоставляет готовые классы исключений для распространённых HTTP-статусов. Их удобно бросать в коде, чтобы вернуть корректный ответ клиенту:
Пример: NotFoundException для 404
getUserById(id: number) {
const user = users.find((user) => user.id === id);
if (!user) {
throw new NotFoundException({
message: `User with id ${id} not found`,
});
}
}Общие встроенные классы:
- BadRequestException — 400
- UnauthorizedException — 401
- ForbiddenException — 403
- NotFoundException — 404
- RequestTimeoutException — 408
- ConflictException — 409
- InternalServerErrorException — 500
Эти классы помогают делать ответы понятными и соответствующими REST-практикам.
Когда фильтры не решают всех задач — ограничения и контрпримеры
- Валидация входных данных: за неё отвечают Pipes и DTO. Фильтр лишь обрабатывает уже возникшую ошибку.
- Асинхронные фоновые задачи: ошибки внутри задач cron/queue могут не попадать в HTTP-контекст; нужны отдельные обработчики и логирование.
- Ошибки на уровне инфраструктуры (подключение к БД, депозитории): лучше оборачивать в понятные исключения, прежде чем отдавать клиенту технические детали.
Контрпример: если в контроллере вы ловите ошибку try/catch и возвращаете кастомный ответ, фильтр не сработает — это ожидаемое поведение.
Альтернативы и дополнения к фильтрам
- Interceptors (перехватчики): можно оборачивать цепочку обработки запроса, модифицировать ответ и ловить ошибки через RxJS catchError. Полезны для общего трансформирования ответов и метрик.
- Middleware: выполняется до маршрута, хорош для аутентификации, логирования и раннего прерывания запроса.
- Guard: для авторизации/аутентификации, возвращает 401/403 ещё до выполнения обработчика.
- Pipes: валидация и трансформация входных данных.
Пример паттерна: валидация через Pipe -> Guard для доступа -> контроллер кидает исключение -> Exception Filter формирует ответ -> Interceptor добавляет стандартную обёртку и метрики.
Интеграция логирования и внешних сервисов ошибок
Фильтры — отличное место для единого логирования и интеграции с APM/отслеживанием ошибок (Sentry, Datadog и т.п.). Общая рекомендация:
- В фильтре логируйте минимум: тип исключения, статус, путь, идентификатор запроса.
- Включайте stack trace только для внутренних сред (стадия разработки или staging), а продуктивным клиентам отдавайте обобщённые сообщения.
- Не отправляйте в ответ чувствительные данные (пароли, токены).
Важно для соответствия GDPR: не логируйте персональные данные без маскировки и правовой основы.
Шаблон расширенного фильтра с логированием (пример)
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
const status = exception.getStatus();
// Простой лог
console.error({
status,
path: request.url,
message: exception.message,
// stack: exception.stack // включать по необходимости
});
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message: exception.message || 'Internal Server Error',
});
}
} Пошаговая мини-методология внедрения фильтров в проект
- Проанализируйте существующие ошибки и форматы ответов API.
- Определите стандартную схему ошибки (statusCode, message, timestamp, path, code).
- Напишите глобальный фильтр, который формирует эту схему и логирует инциденты.
- Покройте тестами сценарии — юнит и интеграционные (см. раздел Критерии приёмки).
- Для специфичных контроллеров добавьте локальные фильтры при необходимости.
- Интегрируйте с системой мониторинга и настройте оповещения.
Критерии приёмки
- Все контроллеры возвращают ошибки в едином формате.
- В логах присутствуют: timestamp, путь, статус, краткое сообщение.
- Stack trace не доступен клиентам в продакшене.
- Валидация входных данных происходит через Pipes и возвращает 4xx ошибки.
- Интеграция с системой оповещений настроена для ошибок 5xx.
Ролевые чек-листы
Developer:
- Использовать встроенные исключения для предсказуемых ошибок.
- Не раскрывать чувствительные данные в ошибках.
- Покрыть новые фильтры unit-тестами.
Code reviewer:
- Проверить, что фильтр не меняет бизнес-логику.
- Проверить корректность логирования и отсутствие PII в логах.
DevOps / SRE:
- Настроить централизованное логирование и оповещения для 5xx.
- Проверить, что скорость обработки ошибок не деградирует приложение.
Читабельная шпаргалка: какие исключения использовать
- 400 — BadRequestException (ошибка клиента, неверные данные)
- 401 — UnauthorizedException (неавторизован)
- 403 — ForbiddenException (нет прав)
- 404 — NotFoundException (ресурс не найден)
- 409 — ConflictException (конфликт состояния)
- 408 — RequestTimeoutException (тайм-аут)
- 500 — InternalServerErrorException (внутренняя ошибка)
Пример decision flow (Mermaid)
flowchart TD
A[Начало запроса] --> B{Валидация DTO}
B -- Ошибка --> C[Возврат 400 через Pipe]
B -- ОК --> D{Guard: авторизован?}
D -- Нет --> E[Возврат 401]
D -- Да --> F[Выполнение контроллера]
F -- Ошибка 'брошено исключение' --> G[Exception Filter]
G --> H[Логирование + форматированный ответ]
F -- Успех --> I[Стандартный ответ]Тест-кейсы и приёмка
- Юнит: фильтр возвращает корректный statusCode и структуру JSON для HttpException.
- Интеграция: при NotFoundException контроллер должен вернуть 404 с ожидаемым сообщением.
- Негатив: убедиться, что приватные поля не попадают в ответ при 500.
Безопасность и приватность
- Фильтры не должны раскрывать внутренние сообщения ошибок клиентам в продакшене.
- Маскируйте или удаляйте персональные данные из логов.
- При интеграции с внешними APM соблюдайте требования локального законодательства о передаче данных.
Примеры реального использования
- Перехват ошибок в REST API и возвращение консистентной схемы ошибки для фронтенда.
- Формирование удобных сообщений для мобильных приложений (короткие message + код для локализации).
- Отправка критичных ошибок в Sentry с контекстом: путь, тело запроса (обрезанное), id пользователя.
Часто задаваемые вопросы
Какой уровень покрытия фильтрами предпочтителен: глобальный или локальный?
Выберите глобальный фильтр как базовый уровень (единый формат ошибок). Добавляйте локальные фильтры для специфичных контроллеров или модулей, если нужна особая логика.
Можно ли через фильтр возвращать HTML-страницу вместо JSON?
Да. Фильтр получает объект Response и может вернуть HTML, редирект или любой другой контент тип. Но для REST API рекомендуется использовать JSON.
Нужно ли логировать стек-трейс в продакшене?
Как правило, нет. Логируйте стек-трейс во внутренние мониторинговые системы, но не включайте его в ответы клиентам.
Итог
Фильтры исключений в Nest.js — мощный инструмент для централизации обработки ошибок. Комбинируйте их со встроенными исключениями, Pipes, Guards и Interceptors чтобы получить предсказуемые, безопасные и удобные для потребителей API ошибки. Начните с глобального фильтра, определите формат ответа и расширяйте решение по мере роста приложения.
Похожие материалы
RDP: полный гид по настройке и безопасности
Android как клавиатура и трекпад для Windows
Советы и приёмы для работы с PDF
Calibration в Lightroom Classic: как и когда использовать
Отключить Siri Suggestions на iPhone