← Назад к вопросам

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

2.0 Middle🔥 171 комментариев
#Архитектура и паттерны#Фреймворки и библиотеки

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

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

Цепочка (pipeline) в NestJS — это один из ключевых механизмов обработки запросов. Это последовательность операций, через которые проходит каждый запрос перед тем, как попасть в обработчик маршрута. Я активно работал с этим паттерном в production приложениях.

Архитектура обработки запроса

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

Запрос → Middleware → Guards → Interceptors (before) → Pipes → Controller → Service → Interceptors (after) → Response

Middleware

Самый первый уровень обработки, работает со всеми запросами:

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
    
    // Заполнить резервное копирование для логирования
    res.on('finish', () => {
      console.log(`Response status: ${res.statusCode}`);
    });
    
    next();
  }
}

// Зарегистрировать в модуле
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';

@Module({...})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('*'); // Применить ко всем маршрутам
  }
}

Guards (Охранники)

Определяют, имеет ли запрос доступ к маршруту:

import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();
    const token = request.headers.authorization?.split(' ')[1];
    
    if (!token) {
      return false;
    }
    
    try {
      const payload = jwt.verify(token, process.env.JWT_SECRET);
      request.user = payload;
      return true;
    } catch (error) {
      return false;
    }
  }
}

// Использование
@Controller('users')
export class UsersController {
  @Get('profile')
  @UseGuards(AuthGuard)
  getProfile(@Request() req) {
    return req.user;
  }
}

Interceptors (Перехватчики)

Обрабатывают запрос до и после обработчика:

import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class TimingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const start = Date.now();
    
    return next.handle().pipe(
      tap(() => {
        const duration = Date.now() - start;
        console.log(`Request took ${duration}ms`);
      })
    );
  }
}

// Глобальная регистрация
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useClass: TimingInterceptor,
    },
  ],
})
export class AppModule {}

Transformation Interceptor

Нормализация ответов:

@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, any> {
  intercept(context: ExecutionContext, next: CallHandler<T>): Observable<any> {
    return next.handle().pipe(
      map(data => ({
        success: true,
        data,
        timestamp: new Date().toISOString(),
      }))
    );
  }
}

Pipes (Трубы)

Валидация и трансформация данных:

import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';

@Injectable()
export class ParseIntPipe implements PipeTransform<string, number> {
  transform(value: string, metadata: ArgumentMetadata): number {
    const val = parseInt(value, 10);
    if (isNaN(val)) {
      throw new BadRequestException('Validation failed');
    }
    return val;
  }
}

// Использование
@Get(':id')
getUser(@Param('id', ParseIntPipe) id: number) {
  return this.usersService.findById(id);
}

Validation Pipe с class-validator

import { IsEmail, IsString, MinLength } from 'class-validator';

export class CreateUserDto {
  @IsEmail()
  email: string;

  @IsString()
  @MinLength(6)
  password: string;
}

@Post()
createUser(@Body(new ValidationPipe()) createUserDto: CreateUserDto) {
  return this.usersService.create(createUserDto);
}

Exception Filter (Обработка ошибок)

import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const status = exception.getStatus();

    response.status(status).json({
      statusCode: status,
      message: exception.getResponse(),
      timestamp: new Date().toISOString(),
    });
  }
}

// Глобальная регистрация
@Module({
  providers: [
    {
      provide: APP_FILTER,
      useClass: HttpExceptionFilter,
    },
  ],
})
export class AppModule {}

Полный пример цепочки

@Controller('posts')
@UseInterceptors(TransformInterceptor)
export class PostsController {
  constructor(private postsService: PostsService) {}

  @Get(':id')
  @UseGuards(AuthGuard)
  @UseInterceptors(CacheInterceptor)
  getPost(
    @Param('id', ParseIntPipe) id: number,
    @Request() req
  ) {
    console.log('User:', req.user);
    return this.postsService.findById(id);
  }

  @Post()
  @UseGuards(AuthGuard)
  createPost(
    @Body(ValidationPipe) createPostDto: CreatePostDto,
    @Request() req
  ) {
    return this.postsService.create(createPostDto, req.user.id);
  }
}

Порядок выполнения (важно!)

  1. Middleware — выполняется первым
  2. Guards — проверка доступа
  3. Interceptors (до) — перехват перед обработчиком
  4. Pipes — валидация параметров
  5. Controller Handler — основная логика
  6. Interceptors (после) — трансформация ответа
  7. Exception Filter — обработка ошибок

Практический пример: Аутентификация + Логирование

@Injectable()
export class RequestLoggerInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const { method, url, user } = context.switchToHttp().getRequest();
    const start = Date.now();

    return next.handle().pipe(
      tap(() => {
        const duration = Date.now() - start;
        console.log(`${user?.id} ${method} ${url} ${duration}ms`);
      }),
      catchError(error => {
        console.error(`Error in ${method} ${url}:`, error.message);
        throw error;
      })
    );
  }
}

Best Practices

  • Используй Guards для аутентификации — это их основное предназначение
  • Interceptors для кросс-кар проблем — логирование, кеширование, трансформация
  • Pipes для валидации — используй class-validator для структурированности
  • Exception Filters для единообразной обработки ошибок
  • Регистрируй глобально через APP_ tokens* для применения ко всему приложению
  • Помни про порядок выполнения — это критично для правильной работы

Полное понимание цепочки обработки позволяет создавать чистый, масштабируемый код с правильным разделением ответственности.