Перехватчики
Перехватчик (interceptor) — класс, который оборачивает вызов метода контроллера. Он может выполнить код до метода, после него и изменить возвращённое значение. Так удобно замерять время ответа или приводить все ответы к одному формату.
Замер времени
nest g interceptor common/interceptors/timing --flat// src/common/interceptors/timing.interceptor.ts
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable, tap } from 'rxjs';
@Injectable()
export class TimingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
const started = Date.now();
const handler = context.getHandler().name;
return next.handle().pipe(
tap(() => console.log(`${handler}: ${Date.now() - started} мс`)),
);
}
}next.handle() вызывает метод контроллера и возвращает поток RxJS (Observable) — объект, похожий на промис, через который NestJS передаёт результат. Код до next.handle() выполняется перед методом, а оператор tap() — после, когда ответ готов. Библиотека rxjs уже установлена вместе с NestJS.
Подключение
// src/posts/posts.controller.ts
import { Controller, Get, UseInterceptors } from '@nestjs/common';
import { TimingInterceptor } from '../common/interceptors/timing.interceptor';
import { PostsService } from './posts.service';
@Controller('posts')
@UseInterceptors(TimingInterceptor)
export class PostsController {
constructor(private readonly postsService: PostsService) {}
@Get()
findAll() {
return this.postsService.findAll();
}
}Откройте /posts — в терминале сервера появится строка вроде findAll: 2 мс. Для всего приложения перехватчик включают в src/main.ts: app.useGlobalInterceptors(new TimingInterceptor()).
Изменение ответа
Оператор map() заменяет результат метода. Обернём каждый ответ в объект с полем data:
// src/common/interceptors/wrap-data.interceptor.ts
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { map, Observable } from 'rxjs';
@Injectable()
export class WrapDataInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
return next.handle().pipe(map((data) => ({ data })));
}
}С этим перехватчиком /posts вернёт не массив, а {"data":[...]}. Контроллеры при этом не меняются.
Порядок обработки запроса
| Шаг | Инструмент | Типичная задача |
|---|---|---|
| 1 | Middleware | Журнал запросов, заголовки |
| 2 | Гарды | Проверка доступа |
| 3 | Перехватчики (до метода) | Замер времени, кеш |
| 4 | Пайпы | Проверка и преобразование аргументов |
| 5 | Метод контроллера | Сама работа |
| 6 | Перехватчики (после метода) | Изменение ответа |
| 7 | Фильтры исключений | Ответ при ошибке на любом шаге |
Частая ошибка
Перехватчик, который не вызвал next.handle() или не вернул его результат, обрывает запрос: метод контроллера не выполнится, а клиент не получит ответа. Метод intercept() всегда должен возвращать next.handle() — с операторами или без.