NestJS Logo

Custom route decorators

Nest is built around a language feature called decorators. Decorators are well established in many programming languages but are still relatively new to JavaScript. For a deeper look at how decorators work, see this article. Here's a simple definition:

An ES2016 decorator is an expression which returns a function and can take a target, name and property descriptor as arguments. You apply it by prefixing the decorator with an @ character and placing this at the very top of what you are trying to decorate. Decorators can be defined for either a class, a method or a property.

Param decorators#

Nest provides a set of param decorators that you can use in HTTP route handlers. The following table lists them along with the plain Express (or Fastify) objects they represent:

@Request(), @Req()req
@Response(), @Res()res
@Next()next
@Session()req.session
@Param(param?: string)req.params / req.params[param]
@Body(param?: string)req.body / req.body[param]
@Query(param?: string)req.query / req.query[param]
@Headers(param?: string)req.headers / req.headers[param]
@Ip()req.ip
@HostParam(param?: string)req.hosts / req.hosts[param]

You can also create your own custom decorators. To see why this is useful, consider a common pattern in Node.js applications: properties are attached to the request object and then extracted manually in each route handler, with code like the following:


const user = req.user;

To make your code more readable and transparent, you can create a @User() decorator and reuse it across all of your controllers:

user.decorator.ts
JS TS

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

export const User = createParamDecorator(
  (data: unknown, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    return request.user;
  },
);

You can then use it wherever you need it:

JS TS

@Get()
async findOne(@User() user: UserEntity) {
  console.log(user);
}

@Get()
@Bind(User())
async findOne(user) {
  console.log(user);
}

Passing data#

When the behavior of your decorator depends on some condition, use the data parameter to pass an argument to the decorator's factory function. One use case is a custom decorator that extracts a property from the request object by key. Suppose, for example, that your authentication layer validates requests and attaches a user entity to the request object. The user entity for an authenticated request might look like this:


{
  "id": 101,
  "firstName": "Alan",
  "lastName": "Turing",
  "email": "alan@email.com",
  "roles": ["admin"]
}

Let's define a decorator that takes a property name as a key and returns the associated value if it exists (or undefined if it doesn't, or if the user object has not been created):

user.decorator.ts
JS TS

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

export const User = createParamDecorator(
  (data: string, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    const user = request.user;

    return data ? user?.[data] : user;
  },
);

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

export const User = createParamDecorator((data, ctx) => {
  const request = ctx.switchToHttp().getRequest();
  const user = request.user;

  return data ? user && user[data] : user;
});

You can then access a particular property through the @User() decorator in the controller:

JS TS

@Get()
async findOne(@User('firstName') firstName: string) {
  console.log(`Hello ${firstName}`);
}

@Get()
@Bind(User('firstName'))
async findOne(firstName) {
  console.log(`Hello ${firstName}`);
}

You can use the same decorator with different keys to access different properties. If the user object is deep or complex, this keeps route handler implementations simpler and more readable.

HintcreateParamDecorator<T>() is generic, so you can enforce type safety explicitly, e.g., createParamDecorator<string>((data, ctx) => ...). Alternatively, specify a parameter type in the factory function, e.g., createParamDecorator((data: string, ctx) => ...). If you omit both, data is typed as any.

Working with pipes#

Nest treats custom param decorators the same way as the built-in ones (@Body(), @Param(), and @Query()). This means pipes also run for parameters annotated with custom decorators (in our examples, the user argument). You can also apply a pipe directly to the custom decorator:

JS TS

@Get()
async findOne(
  @User(new ValidationPipe({ validateCustomDecorators: true }))
  user: UserEntity,
) {
  console.log(user);
}

@Get()
@Bind(User(new ValidationPipe({ validateCustomDecorators: true })))
async findOne(user) {
  console.log(user);
}
Hint By default, ValidationPipe does not validate arguments annotated with custom decorators. That's why the example above sets the validateCustomDecorators option to true.

Custom decorators also accept the schema option of the built-in parameter decorators. Pass a Standard Schema compatible schema, such as a Zod schema, and a StandardSchemaValidationPipe with the validateCustomDecorators option enabled validates the decorator's value against it:


@Get()
async findOne(@User('email', { schema: z.email() }) email: string) {
  console.log(email);
}

See Validating custom decorators for details.

Decorator composition#

Nest provides the applyDecorators() helper to compose multiple decorators. For example, suppose you want to combine all authentication-related decorators into a single decorator:

auth.decorator.ts
JS TS

import { applyDecorators, SetMetadata, UseGuards } from '@nestjs/common';
import { ApiBearerAuth, ApiUnauthorizedResponse } from '@nestjs/swagger';

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

import { applyDecorators, SetMetadata, UseGuards } from '@nestjs/common';
import { ApiBearerAuth, ApiUnauthorizedResponse } from '@nestjs/swagger';

export function Auth(...roles) {
  return applyDecorators(
    SetMetadata('roles', roles),
    UseGuards(AuthGuard, RolesGuard),
    ApiBearerAuth(),
    ApiUnauthorizedResponse({ description: 'Unauthorized' }),
  );
}

You can then use this custom @Auth() decorator as follows:


@Get('users')
@Auth('admin')
findAllUsers() {}

This applies all four decorators with a single declaration.

Warning The @ApiHideProperty() decorator from the @nestjs/swagger package is not composable and does not work correctly with the applyDecorators() function.
Edit on GitHub

Support us

Nest is an MIT-licensed open source project. It can grow thanks to the support of these awesome people. If you'd like to join them, please read more here.

Principal Sponsors

SerpApi LogoTrilon LogoMojam Logo

Sponsors / Partners

Become a sponsor