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.
