Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Перехватчики

Перехватчик (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":[...]}. Контроллеры при этом не меняются.

Порядок обработки запроса

ШагИнструментТипичная задача
1MiddlewareЖурнал запросов, заголовки
2ГардыПроверка доступа
3Перехватчики (до метода)Замер времени, кеш
4ПайпыПроверка и преобразование аргументов
5Метод контроллераСама работа
6Перехватчики (после метода)Изменение ответа
7Фильтры исключенийОтвет при ошибке на любом шаге

Частая ошибка

Перехватчик, который не вызвал next.handle() или не вернул его результат, обрывает запрос: метод контроллера не выполнится, а клиент не получит ответа. Метод intercept() всегда должен возвращать next.handle() — с операторами или без.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.