Chapter 35: TypeScript Decorators
TypeScript Decorators very calmly, step by step, like we’re sitting together with VS Code open, slowly typing examples, and really understanding what they are, why they exist, how they changed in TypeScript 5.0, and what realistic patterns people actually use in 2025–2026.
Decorators are one of the most loved (and sometimes most controversial) features in modern TypeScript — especially in frameworks like NestJS, Angular, tRPC procedures, class-based React components, MobX, Zod-like schema builders, etc.
1. What is a decorator? (the simplest honest explanation)
A decorator is a special kind of function that you place above a class, method, property, accessor (getter/setter), or parameter — using the @ symbol.
It receives information about what it’s decorating (the target, name, descriptor, etc.) and can:
- modify the behavior
- add metadata
- replace the thing entirely
- log / track usage
- enforce rules at design time
Think of it like annotations in Java / Python / C#, but more powerful because JavaScript is dynamic.
2. Very important history — two eras of decorators
| Era | Syntax / Proposal stage | Supported in TS version | Status in Feb 2026 | Most code you see today? |
|---|---|---|---|---|
| Legacy / experimental | @decorator | TS 1.5 – 4.9 | Still works (with experimentalDecorators) | Old AngularJS / old NestJS projects |
| Modern / stage-3 | @decorator | TS 5.0+ (stable) | Recommended — aligned with TC39 proposal | Almost all new code in 2026 |
In February 2026 → you should almost always use the modern stage-3 decorators (unless maintaining very old code).
3. Modern decorator syntax (TS 5.0+ — this is what you should learn)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
// 1. Method decorator example function logged<This, Args extends any[], Ret>( target: (this: This, ...args: Args) => Ret, context: ClassMethodDecoratorContext ) { const methodName = String(context.name); return function (this: This, ...args: Args): Ret { console.log(`Calling ${methodName} with args:`, args); const result = target.call(this, ...args); console.log(`${methodName} returned:`, result); return result; }; } class UserService { @logged getUser(id: number) { return { id, name: "Sara" }; } } new UserService().getUser(42); // Console: // Calling getUser with args: [42] // getUser returned: { id: 42, name: 'Sara' } |
4. The five places you can put decorators (2026 reality)
| Location | Decorator context type | Most common real use-case in 2026 |
|---|---|---|
| Class | ClassDecoratorContext | Dependency injection (NestJS @Injectable()), MobX |
| Method | ClassMethodDecoratorContext | Logging, timing, authorization, caching |
| Property | ClassFieldDecoratorContext | Auto-observable (MobX), validation metadata |
| Getter / Setter | ClassGetterDecoratorContext / Setter | Lazy loading, memoization |
| Parameter | ClassParameterDecoratorContext | Dependency injection (NestJS @Body(), @Query()) |
5. Realistic, production-grade examples (what you actually see in 2026)
Example 1: Simple timing decorator (very common utility)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
function timed<This, Args extends any[], Ret>( target: (this: This, ...args: Args) => Ret, context: ClassMethodDecoratorContext ) { const name = String(context.name); return function (this: This, ...args: Args): Ret { console.time(name); const result = target.call(this, ...args); console.timeEnd(name); return result; }; } class ReportService { @timed generateLargeReport() { // long operation... let sum = 0; for (let i = 0; i < 1e8; i++) sum += i; return sum; } } |
Example 2: Authorization / role guard (very frequent in NestJS-like code)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
function requireRole(role: string) { return function <This, Args extends any[], Ret>( target: (this: This, ...args: Args) => Ret, context: ClassMethodDecoratorContext ) { return function (this: This & { user?: { roles: string[] } }, ...args: Args): Ret { if (!this.user?.roles.includes(role)) { throw new Error(`Required role: {role}`); } return target.call(this, ...args); }; }; } class AdminController { @requireRole("admin") deleteUser(id: number) { console.log(`Deleting user ${id}`); } } |
Example 3: Property decorator — auto-validation metadata (Zod-like pattern)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
function minLength(length: number) { return function (target: any, context: ClassFieldDecoratorContext) { // In real code you'd store metadata in a WeakMap or Reflect.metadata console.log(`Field ${String(context.name)} must be >= ${length} chars`); }; } class UserDto { @minLength(3) username: string = ""; @minLength(8) password: string = ""; } |
6. Quick cheat-sheet — decorator kinds & context
| Decorator on | Context type | this inside decorator function | Most common libraries using it |
|---|---|---|---|
| class | ClassDecoratorContext | undefined | @Injectable(), @Controller() |
| method | ClassMethodDecoratorContext | the instance | logging, auth, caching |
| field/property | ClassFieldDecoratorContext | undefined | MobX @observable, validation |
| getter / setter | ClassGetterDecoratorContext | undefined | memoization, lazy |
| parameter | ClassParameterDecoratorContext | undefined | DI — @Inject, @Body() |
7. tsconfig.json settings needed in 2026
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
{ "compilerOptions": { "experimentalDecorators": false, // ← turn OFF legacy mode "emitDecoratorMetadata": false, // ← usually off unless using reflect-metadata "target": "ES2022", "module": "ESNext" } } |
Note: experimentalDecorators = legacy mode — you should not enable it for new code.
8. Summary — 2026 honest verdict
| Scenario | Should you use decorators? | Recommended style |
|---|---|---|
| New NestJS / Angular / tRPC project | Yes — heavily | Modern stage-3 decorators |
| Plain React function components | Rarely | Prefer hooks / HOCs |
| Small script / utility | No | Plain functions |
| Maintaining old AngularJS / legacy | Yes (legacy mode) | Keep experimentalDecorators: true |
| Learning TypeScript | Yes — understand both | Focus on modern syntax first |
Want to go deeper into one area?
- Full NestJS controller + service decorator example
- How MobX / class-transformer / class-validator use decorators
- Writing a real metadata-based validation system
- Differences between legacy & stage-3 decorators (emitted JS)
- Common gotchas & performance considerations
Just tell me — we’ll zoom right in there! 😄
