Что такое 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`, что приводит к неожиданному объединению ролей из метода и класса


