Special annotations that attach metadata or behaviour to classes, methods, and properties — the foundation of Angular and NestJS.
Decorators are functions that wrap other code. They start with @ and are placed directly above a class, method, property, or parameter. When TypeScript sees a decorator, it calls that function and passes the decorated target to it.
⚠️ Enable decorators in tsconfig.json: set "experimentalDecorators": true and "emitDecoratorMetadata": true.
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"target": "ES2021"
}
}
A class decorator is applied to the constructor of a class. It can add properties, replace the constructor, or just log information.
// A simple class decorator function Sealed(constructor: Function) { Object.seal(constructor); Object.seal(constructor.prototype); } @Sealed class BankAccount { owner: string; constructor(owner: string) { this.owner = owner; } } // Decorator with arguments (decorator factory) function Controller(path: string) { return function(constructor: Function) { Reflect.defineMetadata('path', path, constructor); }; } @Controller('/users') class UserController { // handles routes starting with /users }
Method decorators can intercept, wrap, or modify a method's behavior — perfect for logging, authorization checks, and caching.
// Log every method call automatically function Log( target: any, key: string, descriptor: PropertyDescriptor ) { const original = descriptor.value; descriptor.value = function(...args: any[]) { console.log(`Calling ${key} with:`, args); const result = original.apply(this, args); console.log(`${key} returned:`, result); return result; }; return descriptor; } class Calculator { @Log add(a: number, b: number): number { return a + b; } } const calc = new Calculator(); calc.add(3, 4); // → Calling add with: [3, 4] // → add returned: 7
Property decorators are placed above class fields to add metadata or validation.
function MinLength(min: number) { return function(target: any, propertyKey: string) { let value: string; Object.defineProperty(target, propertyKey, { get() { return value; }, set(newVal: string) { if (newVal.length < min) { throw new Error(`${propertyKey} must be at least ${min} chars`); } value = newVal; } }); }; } class User { @MinLength(3) name: string = ""; } const user = new User(); user.name = "Alice"; // ✅ // user.name = "Al"; ❌ Error: name must be at least 3 chars
NestJS uses decorators as its entire API. Here's how a real NestJS controller looks:
import { Controller, Get, Post, Body, Param } from '@nestjs/common'; @Controller('users') // base route: /users export class UsersController { @Get() // GET /users findAll() { return ['alice', 'bob']; } @Get(':id') // GET /users/:id findOne(@Param('id') id: string) { return `User #${id}`; } @Post() // POST /users create(@Body() body: CreateUserDto) { return body; } }
🏛️ Every @Get(), @Post(), @Body() you see in NestJS is a decorator. That's the power of this feature.