TYPESCRIPT

TypeScript Decorators: Syntax, Usage, and Examples

TypeScript decorators modify classes, methods, properties, or parameters at runtime. They add metadata and enhance functionality without changing the original code structure. Decorators follow the decorator pattern in TypeScript and enable powerful abstractions in object-oriented programming.

How to Use TypeScript Decorators

To use decorators, enable experimental support in your tsconfig.json file:

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

A decorator is a function that starts with @ and applies to a class, method, property, or parameter.

Basic Class Decorator

function Logger(constructor: Function) {
  console.log(`Class ${constructor.name} was created.`);
}

@Logger
class User {
  constructor(public name: string) {}
}

When you create a new User, the decorator logs the class creation.

const user = new User("Alice");
// Output: Class User was created.

When to Use TypeScript Decorators

Decorators provide a clean way to extend functionality. You can use them to:

  1. Log class instantiations, method calls, or property changes.
  2. Restrict access to certain methods based on user roles.
  3. Cache results with a cache decorator for better performance.

Examples of TypeScript Decorators

Class Decorator

A class decorator modifies an entire class. The example below adds a timestamp property to a class:

function Timestamp<T extends { new (...args: any[]): {} }>(constructor: T) {
  return class extends constructor {
    timestamp = new Date();
  };
}

@Timestamp
class Message {
  constructor(public content: string) {}
}

const msg = new Message("Hello");
console.log(msg.timestamp); // Outputs current timestamp

Method Decorator

A method decorator modifies a method's behavior. The example below logs every method call:

function Log(target: any, methodName: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`Calling ${methodName} with arguments:`, args);
    return originalMethod.apply(this, args);
  };
}

class Calculator {
  @Log
  add(a: number, b: number): number {
    return a + b;
  }
}

const calc = new Calculator();
calc.add(3, 7);
// Output: Calling add with arguments: [3, 7]

Property Decorator

A property decorator modifies properties. The example below ensures a property always stores a capitalized string:

function Capitalize(target: any, propertyKey: string) {
  let value: string;

  const getter = function () {
    return value;
  };

  const setter = function (newVal: string) {
    value = newVal.charAt(0).toUpperCase() + newVal.slice(1);
  };

  Object.defineProperty(target, propertyKey, { get: getter, set: setter });
}

class Person {
  @Capitalize
  name: string = "";

  constructor(name: string) {
    this.name = name;
  }
}

const p = new Person("john");
console.log(p.name); // Output: John

Parameter Decorator

A parameter decorator modifies function parameters. The example below logs method arguments:

function LogParam(target: any, methodName: string, paramIndex: number) {
  console.log(`Parameter at index ${paramIndex} in method ${methodName} is logged.`);
}

class UserService {
  createUser(@LogParam name: string) {
    console.log(`User ${name} created.`);
  }
}

const service = new UserService();
service.createUser("Alice");
// Output: Parameter at index 0 in method createUser is logged.

Learn More About TypeScript Decorators

How to Create a Decorator

A decorator is a function that takes parameters based on where you apply it:

  • Class decorators receive the class constructor.
  • Method decorators receive the target, method name, and descriptor.
  • Property decorators receive the target and property key.
  • Parameter decorators receive the target, method name, and parameter index.

This decorator factory accepts arguments:

function Prefix(prefix: string) {
  return function (target: any, propertyKey: string) {
    let value: string;

    const getter = () => value;
    const setter = (newValue: string) => {
      value = `${prefix} ${newValue}`;
    };

    Object.defineProperty(target, propertyKey, { get: getter, set: setter });
  };
}

class Book {
  @Prefix("Title:")
  name: string = "";

  constructor(name: string) {
    this.name = name;
  }
}

const b = new Book("TypeScript Guide");
console.log(b.name); // Output: Title: TypeScript Guide

Using Decorators Without a Class

Decorators usually work with classes, but you can apply them to standalone functions using higher-order functions:

function MeasureTime(fn: Function) {
  return function (...args: any[]) {
    console.time("Execution time");
    const result = fn(...args);
    console.timeEnd("Execution time");
    return result;
  };
}

const slowFunction = MeasureTime(() => {
  for (let i = 0; i < 1e6; i++) {}
});

slowFunction(); // Outputs execution time

Async Decorator

An async decorator handles asynchronous function calls. The example below retries an async operation on failure:

function Retry(retries: number) {
  return function (target: any, methodName: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    descriptor.value = async function (...args: any[]) {
      let attempts = 0;
      while (attempts < retries) {
        try {
          return await originalMethod.apply(this, args);
        } catch (error) {
          attempts++;
          console.log(`Retry ${attempts} for ${methodName}`);
        }
      }
      throw new Error(`${methodName} failed after ${retries} attempts`);
    };
  };
}

class API {
  @Retry(3)
  async fetchData() {
    throw new Error("Network error");
  }
}

const api = new API();
api.fetchData().catch(console.error);

Cache Decorator

A cache decorator stores function results to improve performance:

function Cache(target: any, methodName: string, descriptor: PropertyDescriptor) {
  const cache = new Map();
  const originalMethod = descriptor.value;

  descriptor.value = function (...args: any[]) {
    const key = JSON.stringify(args);
    if (!cache.has(key)) {
      cache.set(key, originalMethod.apply(this, args));
    }
    return cache.get(key);
  };
}

class MathOperations {
  @Cache
  factorial(n: number): number {
    return n <= 1 ? 1 : n * this.factorial(n - 1);
  }
}

const math = new MathOperations();
console.log(math.factorial(5)); // Cached result used for efficiency

Fastify Decorators

Fastify, a web framework, supports decorators to extend server functionality:

import Fastify from "fastify";

const fastify = Fastify();

fastify.decorate("greet", (name: string) => `Hello, ${name}!`);

fastify.get("/greet/:name", (request, reply) => {
  reply.send({ message: fastify.greet(request.params.name) });
});

fastify.listen(3000, () => console.log("Server running..."));

TypeScript decorators let you enhance class-based programming. You can use them for logging, validation, caching, and async operations without modifying core logic.

Learn TypeScript for Free
Start learning now
button icon
To advance beyond this tutorial and learn TypeScript by doing, try the interactive experience of Mimo. Whether you're starting from scratch or brushing up your coding skills, Mimo helps you take your coding journey above and beyond.

Sign up or download Mimo from the App Store or Google Play to enhance your programming skills and prepare for a career in tech.

You can code, too.

© 2025 Mimo GmbH