While working on a TypeScript project, I needed to add extra information (metadata) to classes and their properties, such as logging when a method runs or validating input, without modifying their actual implementation.
That’s when I discovered TypeScript decorators. Decorators allow you to add metadata, modify, or extend the behavior of classes, methods, and properties in a clean and reusable way.
In this article, I’ll cover everything you need to know about TypeScript decorators, from basic usage to advanced techniques. So let’s dive in!
What are TypeScript Decorators?
Decorators are a special kind of declaration that can be attached to a class declaration, method, accessor, property, or parameter. They use the form @expression, where expression must evaluate to a function that will be called at runtime with information about the decorated declaration.
Decorators allow you to add metadata, modify, or extend the behavior of classes, methods, and properties in a clean and reusable way. Instead of adding repetitive code everywhere, we could use decorators to handle logging, validation, or adding metadata in a single place while keeping the main logic clean.
Decorators are an experimental feature in TypeScript, so you’ll need to enable them in your tsconfig.json file:
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}Types of Decorators in TypeScript
Class Decorators in TypeScript
Class decorators are applied to the constructor of the class and can be used to observe, modify, or replace a class definition.
Here’s a simple example of a class decorator:
function Logger(target: Function) {
console.log(`Creating instance of: ${target.name}`);
}
@Logger
class User {
constructor(public name: string) {}
}
// When you create a new User instance, you'll see the log
const user = new User("John");
Method Decorators in TypeScript
Method decorators are declared just before a method declaration and can be used to observe, modify, or replace a method definition.
function Log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`Calling ${propertyKey} with`, args);
return original.apply(this, args);
};
return descriptor;
}
class Calculator {
@Log
add(a: number, b: number) {
return a + b;
}
}
const calc = new Calculator();
calc.add(5, 3); // Logs: "Calling add with [5, 3]" and returns 8
Property Decorators in TypeScript
Property decorators are declared just before a property declaration and can be used to observe, modify, or replace a property definition.
function Format(formatString: string) {
return function (target: any, propertyKey: string) {
let value: string;
const getter = function () {
return value;
};
const setter = function (newVal: string) {
value = formatString.replace("%s", newVal);
};
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
};
}
class Greeting {
@Format("Hello, %s!")
message!: string;
}
const greeting = new Greeting();
greeting.message = "John";
console.log(greeting.message); // Outputs: "Hello, John!"
Real-World Use Cases for Decorators in TypeScript
Creating an API Route Decorator in TypeScript
Here’s how you might create a decorator for defining API routes in a Node.js application with Express:
import express, { Request, Response } from 'express';
const app = express();
// Decorator factory
function Route(path: string) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
app.get(path, async (req: Request, res: Response) => {
try {
// Check if the method expects parameters
const result = original.length >= 1
? await original.call(target, req, res)
: await original.call(target);
res.json(result);
} catch (error) {
if (error instanceof Error) {
res.status(500).json({ error: error.message });
} else {
res.status(500).json({ error: "An unknown error occurred." });
}
}
});
return descriptor;
};
}
class UserController {
@Route('/api/users')
async getUsers() {
// Fetch users from database (mock)
return [{ id: 1, name: 'John Doe' }, { id: 2, name: 'Jane Smith' }];
}
@Route('/api/users/:id')
async getUser(req: Request) {
const userId = req.params.id;
// Fetch user from database (mock)
return { id: userId, name: 'John Doe' };
}
}
new UserController();
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});

Creating a Property Validation Decorator in TypeScript
Here’s a practical example of property validation with decorators:
function MinLength(min: number) {
return function (target: any, propertyKey: string) {
let value: string;
const getter = function () {
return value;
};
const setter = function (newVal: string) {
if (newVal.length < min) {
throw new Error(`${propertyKey} should be at least ${min} characters`);
}
value = newVal;
};
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
};
}
class Registration {
@MinLength(8)
password!: string;
constructor(password: string) {
this.password = password;
}
}
try {
const registration = new Registration("pass");
} catch (error) {
if (error instanceof Error) {
console.error(error.message); // Outputs: "password should be at least 8 characters"
} else {
console.error("An unknown error occurred.");
}
}
Decorator Composition in TypeScript
You can apply multiple decorators to a declaration. They are evaluated in the order they appear in the code (from bottom to top):
function First() {
console.log("First decorator");
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("First decorator called");
};
}
function Second() {
console.log("Second decorator");
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("Second decorator called");
};
}
class Example {
@First()
@Second()
method() {}
}
// Output:
// "First decorator"
// "Second decorator"
// "Second decorator called"
// "First decorator called"
Conclusion
I hope you found this article helpful. TypeScript decorators are an incredibly powerful feature that can help you write cleaner, more maintainable code. They’re especially useful for cross-cutting concerns, such as logging, validation, and authorization.
Although they’re still technically an experimental feature, they’ve been around for a long time and are widely used in popular frameworks such as Angular, NestJS, and TypeORM.
Other TypeScript articles you may also like:
- TypeScript Program to Add Two Numbers
- Understanding TypeScript’s ‘satisfies’ Operator
- Understanding satisfies vs as Operator in TypeScript
- Call REST APIs Using TypeScript

I am Bijay Kumar, a Microsoft MVP in SharePoint. Apart from SharePoint, I started working on Python, Machine learning, and artificial intelligence for the last 5 years. During this time I got expertise in various Python libraries also like Tkinter, Pandas, NumPy, Turtle, Django, Matplotlib, Tensorflow, Scipy, Scikit-Learn, etc… for various clients in the United States, Canada, the United Kingdom, Australia, New Zealand, etc. Check out my profile.