Как работает цепочка обработки запроса в NestJS?

«Как работает цепочка обработки запроса в NestJS?» — вопрос из категории NestJS, который задают на 26% собеседований Node.js Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В NestJS запрос проходит через строго определенную цепочку (middleware -> guards -> interceptors -> pipes -> controller -> interceptors -> exception filters). Я настраивал эту цепочку для добавления логирования, авторизации и валидации.

Порядок выполнения:

  1. Глобальные и модульные Middleware: Идеально для логирования, CORS, сжатия данных. Они выполняются до маршрутизации.
  2. Guards: Проверяют права доступа на основе ролей или JWT-токена. Если guard возвращает false, цепочка прерывается.
  3. Interceptors (до обработчика): Могут трансформировать входящий запрос или добавлять дополнительную логику (например, логирование времени выполнения).
  4. Pipes: Преобразуют и валидируют данные для параметров обработчика (например, с помощью class-validator).
  5. Controller Handler: Выполняется целевой метод контроллера.
  6. Interceptors (после обработчика): Могут трансформировать исходящий ответ.
  7. Exception Filters: Перехватывают и обрабатывают исключения, возникшие на любом этапе.

Пример Guard и Pipe:

// Guard для проверки JWT
@Injectable()
export class JwtAuthGuard implements CanActivate {
  constructor(private jwtService: JwtService) {}

  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();
    const token = request.headers.authorization?.split(' ')[1];
    if (!token) throw new UnauthorizedException();

    try {
      const payload = this.jwtService.verify(token);
      request.user = payload; // Добавляем пользователя в запрос
      return true;
    } catch {
      throw new UnauthorizedException('Неверный токен');
    }
  }
}

// Pipe для валидации DTO
@Injectable()
export class ValidationPipe implements PipeTransform {
  async transform(value: any, metadata: ArgumentMetadata) {
    const object = plainToClass(metadata.metatype, value);
    const errors = await validate(object);
    if (errors.length > 0) {
      throw new BadRequestException('Валидация не пройдена');
    }
    return object;
  }
}

Практическое применение: В моем последнем проекте я использовал глобальный interceptor для оборачивания всех ответов в единый формат { data: ..., timestamp: ... } и глобальный guard для проверки API-ключей на публичных эндпоинтах.