NestJSJuniorTechnical

Что такое декораторы (decorators) в NestJS? Приведите примеры часто используемых декораторов.

Декораторы в NestJS — TypeScript-аннотации на основе reflect-metadata. Используются для маршрутизации (@Get, @Post), DI (@Injectable, @Inject), параметров (@Body, @Param, @Query) и кросс-срезовых задач (@UseGuards, @UsePipes).

Декораторы в NestJS

Декораторы — синтаксический сахар TypeScript над функциями-обёртками, которые применяются к классам, методам, параметрам и свойствам. NestJS строит на них весь метаданных-ориентированный DI и маршрутизацию. Под капотом используется reflect-metadata для хранения метаданных при компиляции.

Декораторы классов

@Module({ imports: [], controllers: [], providers: [] })
export class AppModule {}

@Controller('users')     // базовый путь /users
export class UsersController {}

@Injectable()            // провайдер, управляемый DI
export class UsersService {}

@Global()                // модуль доступен везде без импорта
@Module({ providers: [ConfigService], exports: [ConfigService] })
export class ConfigModule {}

Декораторы методов контроллера (роуты)

@Get('profile')          // GET /users/profile
@Post()                  // POST /users
@Put(':id')              // PUT /users/:id
@Patch(':id')            // PATCH /users/:id
@Delete(':id')           // DELETE /users/:id
@All('*')                // любой метод

@HttpCode(201)           // явный статус ответа
@Header('Cache-Control', 'no-cache')  // заголовок ответа
@Redirect('https://nestjs.com', 301) // редирект

Декораторы параметров метода

@Get(':id')
findOne(
  @Param('id', ParseIntPipe) id: number,   // route param
  @Query('sort') sort: string,              // query string
  @Body() dto: CreateUserDto,              // request body
  @Req() req: Request,                     // весь объект запроса
  @Res({ passthrough: true }) res: Response, // объект ответа
  @Headers('authorization') auth: string,  // заголовок
  @Ip() ip: string,                        // IP клиента
  @HostParam('account') account: string,   // поддомен
) {}

Декораторы области видимости и жизненного цикла

// Scope.REQUEST — новый экземпляр на каждый запрос
@Injectable({ scope: Scope.REQUEST })
export class RequestScopedService {}

// Scope.TRANSIENT — новый экземпляр на каждый inject
@Injectable({ scope: Scope.TRANSIENT })
export class TransientService {}

Декораторы guards, interceptors, pipes, filters

@UseGuards(JwtAuthGuard)           // Guard — авторизация
@UseInterceptors(LoggingInterceptor) // Interceptor — до/после
@UsePipes(ValidationPipe)          // Pipe — трансформация/валидация
@UseFilters(HttpExceptionFilter)   // Filter — обработка ошибок

Пользовательский декоратор параметра

// decorators/current-user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const CurrentUser = createParamDecorator(
  (data: string | undefined, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    const user = request.user;
    return data ? user?.[data] : user;
  },
);

// использование
@Get('me')
getProfile(@CurrentUser() user: User) {
  return user;
}

@Get('me/email')
getEmail(@CurrentUser('email') email: string) {
  return { email };
}

Декоратор SetMetadata и Reflector

// decorators/roles.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const Roles = (...roles: string[]) =>
  SetMetadata('roles', roles);

// controllers/admin.controller.ts
@Roles('admin')
@UseGuards(RolesGuard)
@Get('settings')
getSettings() {}

// guards/roles.guard.ts
@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(ctx: ExecutionContext): boolean {
    const roles = this.reflector.getAllAndOverride<string[]>('roles', [
      ctx.getHandler(),
      ctx.getClass(),
    ]);
    if (!roles) return true;
    const { user } = ctx.switchToHttp().getRequest();
    return roles.some(role => user.roles.includes(role));
  }
}

Подводные камни

  • emitDecoratorMetadata в tsconfig — без "emitDecoratorMetadata": true и "experimentalDecorators": true DI вообще не работает; тип параметра не сохраняется в метаданных.
  • @Res() без passthrough — если инжектируете @Res() без { passthrough: true }, NestJS перестаёт управлять ответом: интерцепторы не сработают, JSON не сериализуется автоматически.
  • Порядок декораторов — декораторы применяются снизу вверх; @UseGuards над @UseInterceptors означает, что guard сработает первым — это может удивить.
  • @Global() злоупотребление — глобальные модули упрощают доступ, но скрывают зависимости и усложняют тестирование отдельных модулей.
  • createParamDecorator и DI — внутри createParamDecorator нельзя инжектировать сервисы напрямую; для этого нужен Pipe или guard, а не декоратор параметра.
  • SetMetadata и наследование — метаданные контроллера не наследуются автоматически в дочерних классах; нужно явно применять декоратор в каждом классе.

Common mistakes

  • Дает общий ответ про Node.js и не называет конкретные API NestJS.
  • Не объясняет, где в lifecycle находится декораторы NestJS.
  • Не разделяет validation, authorization, business logic и persistence.
  • Игнорирует ошибки, лимиты входных данных, observability и тестирование.

What the interviewer is testing

  • Может объяснить декораторы NestJS на примере кода.
  • Называет ключевые API: @Get(), @Body(), @UseGuards().
  • Использует точные API NestJS, а не вымышленные hooks/decorators/methods.
  • Видит production-риски: безопасность, отказоустойчивость, логирование и тесты.

Sources

Related topics