Что такое interceptors в NestJS?
Interceptors в NestJS
Interceptor (перехватчик) — это механизм AOP (Aspect-Oriented Programming) в NestJS. Он оборачивает выполнение метода контроллера и позволяет выполнять дополнительную логику до и после его вызова, используя RxJS-поток.
Как устроен интерцептор
Каждый интерцептор реализует интерфейс NestInterceptor с единственным методом intercept. Этот метод получает два аргумента:
context: ExecutionContext— контекст выполнения, дающий доступ к объектам запроса и ответаnext: CallHandler— обработчик, вызовnext.handle()которого запускает сам метод контроллера и возвращаетObservable
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const now = Date.now();
console.log('Запрос получен...');
return next
.handle()
.pipe(
// Код после выполнения метода контроллера
tap(() => console.log(`Запрос выполнен за ${Date.now() - now}мс`)),
);
}
}
Основные сценарии использования
Трансформация ответа — оборачивание данных в единый формат:
import { map } from 'rxjs/operators';
@Injectable()
export class TransformInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
// Оборачиваем любой ответ в объект { data: ... }
map((value) => ({ data: value })),
);
}
}
Кеширование — возврат кешированного значения без вызова контроллера:
import { of } from 'rxjs';
@Injectable()
export class CacheInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const cachedValue = this.cache.get(key);
if (cachedValue) {
// Возвращаем данные, не вызывая метод контроллера
return of(cachedValue);
}
return next.handle();
}
}
Привязка интерцептора
Интерцептор можно применить на трёх уровнях:
// На уровне метода
@Get()
@UseInterceptors(LoggingInterceptor)
findAll() {}
// На уровне контроллера
@UseInterceptors(LoggingInterceptor)
@Controller('users')
export class UsersController {}
// Глобально через модуль
// app.module.ts
{
providers: [
{ provide: APP_INTERCEPTOR, useClass: LoggingInterceptor }
]
}
Отличие от других механизмов
| Механизм | Когда работает | Доступ к response |
|---|---|---|
| Middleware | До маршрутизации | Да (Express) |
| Guard | После middleware | Нет |
| Interceptor | До и после handler | Через RxJS |
| Pipe | Трансформация аргументов | Нет |
| Filter | Только при исключении | Частично |
Важная особенность: интерцепторы работают с RxJS Observable, что позволяет использовать всю мощь реактивных операторов: catchError, timeout, retry и другие.
Что хочет услышать интервьюер
Кандидат объясняет, что интерцептор реализует интерфейс NestInterceptor с методом intercept, принимающим ExecutionContext и CallHandler
Понимание разделения логики ДО вызова (код до next.handle()) и ПОСЛЕ вызова (операторы RxJS в pipe)
Знание типичных кейсов: логирование, трансформация ответа, кеширование, обработка ошибок
Понимание уровней применения: метод, контроллер, глобально через APP_INTERCEPTOR
Осознание отличий от Guards, Pipes и Middleware — в каком порядке они вызываются в lifecycle
Пример: Интерцептор логирования времени выполнения
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const { method, url } = request;
const start = Date.now();
console.log(`[${method}] ${url} — начало обработки`);
return next.handle().pipe(
tap(() => {
console.log(`[${method}] ${url} — выполнено за ${Date.now() - start}мс`);
}),
);
}
}
Пример: Трансформация ответа в единый формат
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
export interface Response<T> {
data: T;
success: boolean;
}
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
intercept(
context: ExecutionContext,
next: CallHandler,
): Observable<Response<T>> {
return next.handle().pipe(
// Все ответы оборачиваем в { data, success }
map((data) => ({ data, success: true })),
);
}
}
Пример: Регистрация интерцептора глобально через APP_INTERCEPTOR
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { TransformInterceptor } from './transform.interceptor';
@Module({
providers: [
{
provide: APP_INTERCEPTOR,
useClass: TransformInterceptor,
},
],
})
export class AppModule {}
Типичные ошибки
Путают interceptors с middleware — middleware не имеет доступа к результату handler, интерцептор имеет
Забывают вызвать next.handle() — это приводит к тому, что метод контроллера не выполняется вообще
Не понимают, что next.handle() возвращает Observable, и пытаются работать с ним как с Promise
Применяют интерцептор через @UseInterceptors вместо APP_INTERCEPTOR, удивляясь что DI не работает корректно
Смешивают логику до и после выполнения в одном месте, не разделяя код до pipe() и код внутри pipe()


