Что такое custom decorators в NestJS?

SeniorNestJS · Backend·Обновлено 7 июля 2026
Коротко
Custom decorators в NestJS — это пользовательские декораторы, создаваемые с помощью createParamDecorator, SetMetadata или стандартных декораторов TypeScript, позволяющие инкапсулировать повторяющуюся логику извлечения данных из запроса, установки метаданных и аспектно-ориентированного поведения.

Custom Decorators в NestJS

Custom decorators — это механизм расширения стандартного поведения NestJS путём создания собственных декораторов для контроллеров, методов, параметров и классов. Они позволяют следовать принципу DRY и выносить сквозную логику в переиспользуемые абстракции.

Типы кастомных декораторов

NestJS поддерживает несколько сценариев создания декораторов:

1. Param decorators — извлекают данные из объекта запроса. Создаются через createParamDecorator:

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

// Извлекаем текущего пользователя из request.user
export const CurrentUser = createParamDecorator(
  (data: string | undefined, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    const user = request.user;
    // Если передано поле — возвращаем только его
    return data ? user?.[data] : user;
  },
);

2. Metadata decorators — устанавливают метаданные через SetMetadata, которые затем читаются в guards или interceptors:

import { SetMetadata } from '@nestjs/common';

export const ROLES_KEY = 'roles';
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);

3. Composition decorators — объединяют несколько декораторов в один с помощью applyDecorators:

import { applyDecorators, UseGuards, UseInterceptors } from '@nestjs/common';
import { AuthGuard } from './auth.guard';
import { LoggingInterceptor } from './logging.interceptor';

export function Auth(...roles: string[]) {
  return applyDecorators(
    Roles(...roles),
    UseGuards(AuthGuard, RolesGuard),
    UseInterceptors(LoggingInterceptor),
  );
}

4. Class/Method decorators на чистом TypeScript — для установки метаданных через Reflect.metadata:

import 'reflect-metadata';

// Декоратор для маркировки публичных роутов
export function Public() {
  return SetMetadata('isPublic', true);
}

Чтение метаданных в Guard

Метаданные, установленные через SetMetadata, читаются с помощью Reflector:

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

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    // getAllAndOverride — берёт метаданные метода, затем класса
    const roles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);
    if (!roles) return true;
    const { user } = context.switchToHttp().getRequest();
    return roles.some(role => user.roles?.includes(role));
  }
}

Декоратор с валидацией данных

Param-декораторы можно комбинировать с ValidationPipe для автоматической валидации:

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

// Извлекаем тело запроса и конкретное поле из него
export const Body = createParamDecorator(
  (data: string, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    return data ? request.body?.[data] : request.body;
  },
);

Ключевые принципы для senior-уровня

  • ExecutionContext даёт доступ к HTTP, WebSocket или gRPC контексту — декораторы должны правильно его переключать через switchToHttp(), switchToWs(), switchToRpc().
  • Reflector.getAllAndOverride vs getAllAndMerge — первый берёт ближайшее определение (метод > класс), второй объединяет массивы из обоих уровней.
  • Param-декораторы выполняются до guard-ов и interceptor-ов в пайплайне запроса.
  • При создании декораторов для микросервисов нужно использовать ctx.switchToRpc().getData() вместо HTTP-запроса.

Что хочет услышать интервьюер

Понимание разницы между param decorators, metadata decorators и composition decorators, и когда применять каждый из них

Знание `createParamDecorator` и `ExecutionContext` — как получить данные из HTTP/WS/RPC контекста

Умение работать с `Reflector` для чтения метаданных в Guards и Interceptors, включая разницу `getAllAndOverride` vs `getAllAndMerge`

Навык композиции декораторов через `applyDecorators` для создания переиспользуемых абстракций (например, `@Auth()`)

Понимание порядка выполнения декораторов в пайплайне NestJS и возможных побочных эффектов

Пример: Param-декоратор CurrentUser

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('profile')
getProfile(@CurrentUser() user: UserEntity) {
  return user;
}

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

Пример: Composition-декоратор Auth с ролями

import { applyDecorators, SetMetadata, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from './guards/jwt-auth.guard';
import { RolesGuard } from './guards/roles.guard';

export const ROLES_KEY = 'roles';

export const Roles = (...roles: string[]) =>
  SetMetadata(ROLES_KEY, roles);

// Один декоратор объединяет guard-ы и метаданные ролей
export function Auth(...roles: string[]) {
  return applyDecorators(
    Roles(...roles),
    UseGuards(JwtAuthGuard, RolesGuard),
  );
}

// Использование
@Auth('admin', 'manager')
@Delete(':id')
remove(@Param('id') id: string) {
  return this.service.remove(id);
}

Пример: RolesGuard с Reflector

import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from './auth.decorator';

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    // getAllAndOverride: сначала ищет на методе, затем на классе
    const requiredRoles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);

    // Если роли не указаны — доступ разрешён
    if (!requiredRoles || requiredRoles.length === 0) {
      return true;
    }

    const { user } = context.switchToHttp().getRequest();
    return requiredRoles.some(role => user?.roles?.includes(role));
  }
}

Типичные ошибки

Использование `createParamDecorator` для сайд-эффектов вместо Interceptor или Guard — param-декораторы должны только извлекать данные

Игнорирование типа контекста: попытка вызвать `switchToHttp()` в WebSocket- или gRPC-контроллере приводит к ошибке в рантайме

Прямое использование `Reflect.getMetadata` вместо `Reflector` из NestJS, что ломает совместимость с тестами и DI

Создание отдельных декораторов вместо compositing через `applyDecorators`, что ведёт к дублированию и рассинхронизации логики

Неправильное использование `getAllAndMerge` там, где нужен `getAllAndOverride`, что приводит к неожиданному объединению ролей из метода и класса

Лучшие курсы по теме

изображение курса

Docker и Ansible

Антон Ларичев
AI-тренажерыAI-тренажеры
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Node.js с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Nest.js с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.6
3 999 ₽ 6 990 ₽
Подробнее