NestJS Custom Decorators

Custom decorators let you create your own shorthand annotations for common patterns in your NestJS application. Instead of repeating the same combination of built-in decorators on every route, or manually extracting the same data from the request object in every controller, a custom decorator encapsulates that logic and lets you apply it with a single, readable label.

Types of Custom Decorators

Type                     | What It Does
-------------------------|--------------------------------------------
Parameter decorator      | Extracts specific data from req/context
Method/Class decorator   | Applies a combination of existing decorators
Metadata decorator       | Attaches data for guards/interceptors to read

Custom Parameter Decorator

The most useful custom decorator extracts data from the request and injects it directly into a controller method parameter. The classic example is a @CurrentUser() decorator:

// 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;

    // If data is provided, return that specific field
    return data ? user?.[data] : user;
  },
);

Usage in a controller:

@Get('profile')
@UseGuards(JwtAuthGuard)
getProfile(@CurrentUser() user: User) {
  return user;   // the full req.user object
}

@Get('my-email')
@UseGuards(JwtAuthGuard)
getEmail(@CurrentUser('email') email: string) {
  return { email };   // only the email field from req.user
}

Without this decorator, every controller method would need @Req() req: Request and then manually access req.user. The custom decorator encapsulates that access pattern into a clean, self-documenting annotation.

Extracting Client IP

export const ClientIp = createParamDecorator(
  (data: unknown, ctx: ExecutionContext): string => {
    const request = ctx.switchToHttp().getRequest();
    return request.ip || request.headers['x-forwarded-for'];
  },
);

// Usage
@Post('login')
login(@Body() dto: LoginDto, @ClientIp() ip: string) {
  this.logger.log(`Login attempt from ${ip}`);
  return this.authService.login(dto);
}

Composing Multiple Decorators

You often apply the same combination of decorators — for example, @UseGuards(JwtAuthGuard) and @ApiBearerAuth() — on every protected route. Use applyDecorators() to combine them into one:

// auth.decorator.ts
import { applyDecorators, UseGuards } from '@nestjs/common';
import { ApiBearerAuth, ApiUnauthorizedResponse } from '@nestjs/swagger';

export function Auth(...roles: Role[]) {
  return applyDecorators(
    Roles(...roles),
    UseGuards(JwtAuthGuard, RolesGuard),
    ApiBearerAuth(),
    ApiUnauthorizedResponse({ description: 'Unauthorized' }),
  );
}

Usage — one decorator replaces four:

// Before custom decorator:
@Get('reports')
@Roles(Role.Admin)
@UseGuards(JwtAuthGuard, RolesGuard)
@ApiBearerAuth()
@ApiUnauthorizedResponse({ description: 'Unauthorized' })
getReports() { ... }

// After custom decorator:
@Get('reports')
@Auth(Role.Admin)
getReports() { ... }

Metadata Decorator

Metadata decorators attach data that guards or interceptors read using the Reflector. The @Roles() decorator from the role-based access topic is a metadata decorator:

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

// A guard reads it back:
const requiredRoles = this.reflector.get<Role[]>('roles', context.getHandler());

Class Decorator Shorthand

// A decorator that marks an entire controller as admin-only
export function AdminController(prefix: string) {
  return applyDecorators(
    Controller(prefix),
    UseGuards(JwtAuthGuard, RolesGuard),
    Roles(Role.Admin),
  );
}

// Usage
@AdminController('admin/users')
export class AdminUsersController { ... }

Using Decorators With Validation

Parameter decorators created with createParamDecorator also work with pipes. Pass a pipe as the second argument to validate or transform the extracted value:

@Get('profile')
@UseGuards(JwtAuthGuard)
getProfile(
  @CurrentUser(new ValidationPipe({ validateCustomDecorators: true }))
  user: User,
) {
  return user;
}

When to Create a Custom Decorator

Create a custom decorator when:
- You access the same req property in 3 or more controllers
- You apply the same 2+ decorators together on multiple routes
- You want to give a complex pattern a clear, descriptive name
- You need to attach metadata for guards/interceptors to consume

Do not create one when:
- The built-in decorator works perfectly
- It would be used only once

Custom decorators are a readability investment. They turn repetitive, verbose decorator stacks into single, self-explanatory annotations. The controller code becomes shorter, the intent becomes clearer, and new team members understand the codebase faster because the annotations speak the domain language of the application — @Auth(Role.Admin) — rather than the framework's internal plumbing language.

Leave a Comment

Your email address will not be published. Required fields are marked *