Custom Decorators
Custom Decorators in NestJS allow you to encapsulate repetitive logic, extract custom data from incoming requests, or attach metadata to your classes and methods for reflection.
Overview
Decorators are a core feature of TypeScript and the foundation of NestJS (@Controller, @Get, @Injectable).
NestJS makes it incredibly easy to build your own decorators. There are two primary use cases for Custom Decorators in NestJS:
- Param Decorators: Extracting specific data from the request object (e.g.,
@User()). - Metadata Decorators: Attaching custom settings to a route that Guards or Interceptors can read later (e.g.,
@Roles('admin')).
Key Concepts
createParamDecorator: A factory function provided by NestJS to build decorators that inject values directly into your route handler arguments.SetMetadata: A built-in decorator used to attach key-value pairs to a class or method.- Reflection (
Reflector): The mechanism used by Guards and Interceptors to read the metadata you attached via your custom decorators.
Code Examples
1. Creating a Parameter Decorator
Imagine you have an AuthGuard that validates a JWT and attaches the decoded user to request.user. Instead of injecting @Req() req into every controller and calling req.user, you can build a @User() decorator.
// user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const User = createParamDecorator(
(data: string | undefined, ctx: ExecutionContext) => {
// 1. Get the HTTP request
const request = ctx.switchToHttp().getRequest();
// 2. Extract the user object (set by your AuthGuard)
const user = request.user;
// 3. If the user passed a specific string (e.g. @User('email')),
// return only that property. Otherwise, return the whole object.
return data ? user?.[data] : user;
},
);
Using it in a controller:
@Get('profile')
getProfile(@User() user: UserEntity, @User('email') email: string) {
console.log(email); // 'test@test.com'
return user;
}
2. Creating a Metadata Decorator
You want to restrict certain routes to specific user roles. Instead of using raw @SetMetadata('roles', ['admin']), you should create a strongly-typed custom decorator.
// roles.decorator.ts
import { SetMetadata } from '@nestjs/common';
// This is the key we will use to look up the metadata later
export const ROLES_KEY = 'roles';
// The decorator takes an array of strings and attaches it to the ROLES_KEY
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
Using it in a controller:
@Post('users')
@Roles('admin', 'super-admin') // Attaching the metadata!
create() {
return 'This action adds a new user';
}
3. Reading the Metadata (The other half of the puzzle)
Attaching metadata does nothing on its own. You need a Guard to read it and enforce it using the Reflector class.
// roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from './roles.decorator';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
// Read the metadata attached by our @Roles() decorator
const requiredRoles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
context.getHandler(),
context.getClass(),
]);
if (!requiredRoles) return true; // No @Roles decorator? Allow access.
const { user } = context.switchToHttp().getRequest();
// Check if the user has the required roles
return requiredRoles.some((role) => user.roles?.includes(role));
}
}
Best Practices
- Hide
reqfrom Controllers: Your route handlers should almost never use@Req() req: Request. It ties your code to the underlying HTTP platform (Express) and makes unit testing much harder. Extract exactly what you need using custom Param Decorators (@User(),@IpAddress(),@UserAgent()) so your controllers remain pure and testable. - Compose Multiple Decorators: If you find yourself always writing
@UseGuards(JwtAuthGuard) @Roles('admin') @ApiBearerAuth(), you can combine them into one custom decorator using NestJS’sapplyDecoratorsutility:export const Auth = (...roles) => applyDecorators(UseGuards(JwtAuthGuard), Roles(...roles), ApiBearerAuth());