Skip to content
TypeScript ts nestjs 3 min read

Decorators in NestJS

Decorators are the syntax that makes NestJS feel declarative: you annotate a class with @Controller(), a method with @Get(), and a parameter with @Body(), and the framework wires everything up at runtime. Under the hood these rely on the legacy (experimental) decorator implementation plus reflect-metadata, configured through two tsconfig flags. This page surveys the built-in decorator families, explains the compiler setup they require, and shows how to author your own custom parameter decorators with createParamDecorator.

The tsconfig setup decorators need

NestJS uses the pre-Stage-3 decorator flavor so it can emit parameter type metadata — the feature that powers automatic dependency injection. Both flags below are mandatory.

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

experimentalDecorators enables the legacy decorator syntax; emitDecoratorMetadata makes the compiler record each parameter’s type (via reflect-metadata) so Nest can resolve constructor dependencies. The Nest CLI sets both for you, but hand-rolled configs must include them or DI silently breaks.

TypeScript 5.0 shipped standard (Stage 3) decorators, which do not support emitDecoratorMetadata and have a different signature. NestJS still requires the legacy flavor — do not turn experimentalDecorators off expecting Stage 3 to work here.

Built-in decorators by target

Nest groups decorators by what they annotate. Knowing the family helps you predict where each applies.

TargetExamplesPurpose
Class@Controller(), @Injectable(), @Module()register with the framework
Method@Get(), @Post(), @UseGuards()declare routes & behavior
Parameter@Body(), @Param(), @Query(), @Headers()extract request data
Property@Inject(), plus class-validator rules on DTOsinject & validate

The class and method decorators define structure and routing; parameter decorators pull pieces out of the incoming request so your handler receives plain, typed values.

A typical decorated controller

The decorators compose top to bottom: the class decorator sets the route prefix, the method decorator sets the verb and sub-path, and the parameter decorators bind arguments.

import { Body, Controller, Get, Param, Post, Query } from "@nestjs/common";

@Controller("users")
export class UsersController {
  @Get()
  list(@Query("page") page: string) {
    return { page };
  }

  @Post()
  create(@Body() dto: CreateUserDto) {
    return dto;
  }

  @Get(":id")
  findOne(@Param("id") id: string) {
    return { id };
  }
}

Each parameter decorator reads exactly one slice of the request. Note that @Param/@Query values arrive as strings — combine them with a parse pipe (e.g. ParseIntPipe) to coerce types.

Custom parameter decorators

When you find yourself pulling the same value out of the request in many handlers — the authenticated user, a tenant id, a header — wrap it in a custom decorator with createParamDecorator. The factory receives the decorator’s argument and an ExecutionContext.

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

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

Now any handler can write @CurrentUser() user: User instead of reaching into request.user by hand. The logic lives in one place, and the handler signature documents the dependency.

Passing data and composing decorators

The first factory argument (data) lets a single decorator be parameterised — for instance, extracting one property of the user. You can also bundle a decorator with guards or interceptors using applyDecorators.

import { applyDecorators, UseGuards } from "@nestjs/common";

export const User = createParamDecorator(
  (field: keyof UserEntity | undefined, ctx: ExecutionContext) => {
    const user = ctx.switchToHttp().getRequest().user as UserEntity;
    return field ? user?.[field] : user;
  },
);

// @User() → whole user; @User("email") → just the email
export const Auth = () => applyDecorators(UseGuards(JwtAuthGuard));

@User("email") returns a single typed field thanks to keyof UserEntity, while @Auth() is a reusable composite that applies the JWT guard. Custom decorators also work with pipes: @User("id", ParseIntPipe) validates the extracted value.

Best Practices

  • Keep experimentalDecorators and emitDecoratorMetadata enabled; Nest’s DI depends on the metadata.
  • Do not switch to Stage 3 decorators for Nest code — the framework targets the legacy flavor.
  • Reach for createParamDecorator whenever the same request extraction repeats across handlers.
  • Type the data argument (e.g. keyof Entity) so parameterised decorators stay type-safe.
  • Use applyDecorators to bundle related guards/interceptors into one expressive decorator.
  • Combine custom param decorators with parse pipes to coerce and validate extracted values.
  • Keep decorator factories thin — pull data out, but leave business logic to services.
Last updated June 29, 2026
Was this helpful?