Suggest an editImprove this articleRefine the answer for “What are decorators in TypeScript?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**TypeScript decorators** are functions applied with `@name` syntax to classes, methods, properties, or parameters. The compiler rewrites `@Logger class User {}` to `Logger(User)` at build time, before any code runs. ```typescript function Log(target: any) { console.log(target.name); } @Log class UserService {} // Logs "UserService" when the module loads ``` **Key:** requires `"experimentalDecorators": true` in `tsconfig.json`. Widely used in Angular, NestJS, and TypeORM.Shown above the full answer for quick recall.Answer (EN)Image**TypeScript decorators** are functions that wrap classes, methods, properties, or parameters using `@decorator` syntax, and run at the point where the class or member is defined, not when it gets called. ## Theory ### TL;DR - Think of a decorator like a stamp on an envelope: the letter inside stays the same, but the stamp changes how it gets handled. - The compiler transforms `@Logger class User {}` into `Logger(User)` before any code runs. - Requires `"experimentalDecorators": true` in `tsconfig.json`. - Use for cross-cutting concerns: logging, validation, routing metadata. - For simple one-off logic, a plain function is less magic and easier to test. ### Quick example ```typescript // tsconfig.json: "experimentalDecorators": true function LogCreation(target: any) { console.log(`Creating: ${target.name}`); } @LogCreation class User { name = "Alice"; } // Output: "Creating: User" // Fires when the file loads, not when new User() is called ``` When I first ran into this in a NestJS codebase, the "runs at module load, not at instantiation" behavior caught me completely off guard. It still trips up most developers the first time. ### How the compiler handles decorators TypeScript scans for `@decorator` during the emit phase and rewrites it to a direct function call. `@Logger class User {}` becomes roughly `User = Logger(User)`. No special runtime. Just a transformed AST. With `"emitDecoratorMetadata": true` enabled, the compiler also attaches `Reflect.metadata` symbols via `tslib`. This is how Angular and NestJS inject dependency types at runtime without you passing them explicitly. TypeScript 5.0+ has two modes: legacy (`experimentalDecorators`) and the newer TC39 stage 3 proposal mode. Most existing frameworks, including Angular and NestJS, still use legacy mode. ### When to use - Repeated boilerplate across many classes: logging, performance timing, error handling. A decorator keeps it DRY. - Framework contracts: Angular's `@Component`, NestJS's `@Get`. These are not optional. - DTO validation with class-validator: `@IsEmail()`, `@IsString()`. - Skip decorators for logic that only appears once. A plain wrapper function is simpler to read and test. - Avoid on performance-critical hot paths. Every decorator adds a function call layer. ### Common mistakes **Forgetting `experimentalDecorators` in tsconfig:** ```typescript @Log class User {} // Error: Decorators not enabled ``` Add `"experimentalDecorators": true` to `tsconfig.json`, or pass `--experimentalDecorators` to the CLI. **Not returning the descriptor in method decorators:** ```typescript function BadLog(target: any, key: string, desc: PropertyDescriptor) { desc.value = () => console.log("called"); // Modifies but never returns } ``` Bundlers like esbuild expect the decorator to return the modified `PropertyDescriptor`. Always `return desc`. **Applying a decorator to an arrow function class field:** ```typescript class SearchService { @Throttle(1000) search = () => { /* ... */ }; // Throttle checks desc.value - it's undefined here } ``` Arrow function fields don't have a `value` on their descriptor the same way regular methods do. The decorator runs but silently does nothing. Use regular methods if you need method decorators. **Relying on `Reflect.getMetadata` without both flags:** ```typescript const type = Reflect.getMetadata("design:type", target, key); // Returns undefined ``` You need `"emitDecoratorMetadata": true` in tsconfig AND `import "reflect-metadata"` in your entry file. Missing either one gives you `undefined` everywhere. **Decorating private fields with `#` syntax:** ```typescript class C { @Log #private = 1; // No runtime descriptor for private fields } ``` Private fields using `#` don't have accessible `PropertyDescriptor` objects at runtime. Use TypeScript's `private` keyword if the property needs to be decorated. ### Real-world usage - NestJS: `@Controller('/users')`, `@Get(':id')`, `@Body()` for routing and parameter extraction. - Angular: `@Component`, `@Injectable`, `@Input` for DI and component metadata. - TypeORM: `@Entity()`, `@Column()`, `@PrimaryGeneratedColumn()` for schema definition. - class-validator: `@IsEmail()`, `@MinLength(8)` for DTO validation in request pipelines. - class-transformer: `@Expose()`, `@Transform()` for serialization control. ### Follow-up questions **Q:** What arguments does a method decorator receive? **A:** Three: `target` (the class prototype for instance methods, the constructor for static methods), `propertyKey` (the method name as string or symbol), and `descriptor` (the `PropertyDescriptor` with `value`, `writable`, `enumerable`, `configurable`). **Q:** In what order do stacked decorators execute? **A:** Decorator factories run top-to-bottom. The actual decorator functions apply bottom-to-top. So `@A @B method()` calls `A()` then `B()` as factories, but applies `B` first, then `A` wraps the result. **Q:** Can you access constructor parameters from a class decorator? **A:** No. A class decorator receives only the constructor function. To intercept constructor parameters, return a new class that extends the original and overrides the constructor. **Q:** How do decorators behave with inheritance? **A:** Method decorators on a child class modify the child's prototype, not the parent's. If a parent has a decorated method and the child overrides it without a decorator, the child's version is undecorated. **Q:** Why use decorator factories like `@Throttle(1000)` instead of plain `@Throttle`? **A:** A factory is a function that returns the actual decorator. This pattern lets you pass configuration per use site. Without it you cannot parameterize behavior like debounce delay or log level. ## Examples ### Basic: class decorator for logging ```typescript function LogCreation(constructor: Function) { console.log(`Class defined: ${constructor.name}`); } @LogCreation class UserService { constructor(private name: string) {} greet() { return `Hello, ${this.name}`; } } // Output: "Class defined: UserService" - fires when the module loads const svc = new UserService("Alice"); // Nothing extra logged here console.log(svc.greet()); // "Hello, Alice" ``` The decorator runs once when the module is parsed, not on every `new UserService()`. This is the mental model correction most developers need on first contact. ### Intermediate: method decorator factory with throttle ```typescript function Throttle(ms: number) { return function (target: any, key: string, desc: PropertyDescriptor) { if (!desc.value || typeof desc.value !== "function") return desc; let lastCall = 0; const original = desc.value; desc.value = function (...args: any[]) { const now = Date.now(); if (now - lastCall < ms) return; lastCall = now; return original.apply(this, args); }; return desc; }; } class SearchService { @Throttle(1000) search(query: string) { console.log(`Searching: ${query}`); // In production: fetch(`/api/search?q=${query}`) } } const service = new SearchService(); service.search("typescript"); // Logs: "Searching: typescript" service.search("decorators"); // Ignored - less than 1000ms passed ``` The factory pattern (`Throttle(1000)`) lets you configure the delay per method. The `return desc` is not optional: bundlers expect the descriptor back, and skipping it produces fragile behavior depending on the toolchain. ### Advanced: route metadata collector (NestJS pattern) ```typescript import "reflect-metadata"; function Get(path: string) { return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { const routes = Reflect.getMetadata("routes", target.constructor) || []; routes.push({ path, method: "GET", handler: propertyKey }); Reflect.defineMetadata("routes", routes, target.constructor); return descriptor; }; } class UserController { @Get("/users") getUsers() { return [{ id: 1, name: "Alice" }]; } @Get("/users/:id") getUserById() { return { id: 1, name: "Alice" }; } } // Reading metadata to wire up routes: const routes = Reflect.getMetadata("routes", UserController); console.log(routes); // [ // { path: '/users', method: 'GET', handler: 'getUsers' }, // { path: '/users/:id', method: 'GET', handler: 'getUserById' } // ] ``` This is the actual pattern NestJS uses internally. Decorators store metadata on the class. A bootstrapper reads it later to register Express routes. The decorator itself never touches Express at all. That separation is what makes the pattern composable.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.