
Decorators in JavaScript
A simple guide to decorators in JavaScript with practical examples
Decorators (JavaScript)
Decorators in JavaScript are an experimental feature that lets you modify the behavior of classes, methods, properties, and parameters declaratively. Introduced in the ECMAScript proposals, decorators provide an elegant way to apply extra functionality without directly modifying the class or function code. They’re especially useful in modern frameworks like Angular and NestJS, which use them to define components, services, and middleware.
For example, in NestJS it’s common to find code like this:
import { Controller, Get } from '@nestjs/common';
@Controller('cats')
export class CatsController {
@Get()
findAll(): string {
return 'This action returns all cats';
}
}
In this case decorators are used to add characteristics and functionality to a class, turning it into a controller, and to define a route that here will be used for HTTP GET calls.
How Decorators Work
Decorators work as functions applied to a class or an element of a class (for example, a method) and can modify its behavior. They rely on reflection and metaprogramming, letting you add validation, caching logic, error handling, or other functionality without touching the original code. Since they’re still being standardized, using them in projects often requires enabling specific options in the TypeScript compiler
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}
or using tools like Babel.
Use Cases for Decorators
Decorators are used in several contexts:
- Logging and Monitoring Can be used to log method calls, collect metrics, or trace code execution without modifying its content.
- Input Validation Applicable to methods to ensure arguments meet certain rules before the function runs.
- Authentication and Authorization Useful for controlling access to methods and properties, for example checking a user’s role.
- Error Handling Can intercept exceptions and apply retry strategies or automatic logging.
- Lazy Loading and Optimization Can improve performance by delaying method execution or implementing caching mechanisms.
A Decorator Example
Let’s look at a simple example of how to create and use a decorator for a JavaScript class. Links to learn more are at the bottom of the article.
Creating a Decorator
A decorator is a function that takes as an argument the function or class to decorate and returns a new function or class with extra functionality. In this example, we’ll create a decorator that logs the arguments a method is called with.
function logTheCall(target, name, descriptor) {
const original = descriptor.value;
descriptor.value = function (...args) {
console.log(`You called ${name}, arguments: ${args}`);
return original.apply(this, args);
};
return descriptor;
}
Using the Decorator
Now we can use the logTheCall decorator to decorate a class method. This lets us see the arguments the method was called with in the console log.
class Example {
@logTheCall // <--- add the decorator like this, with "@" as a prefix
exampleMethod(arg1, arg2) {
console.log("Running the method");
return arg1 + arg2;
}
}
const exampleInstance = new Example();
exampleInstance.exampleMethod(1, 2);
When we call exampleMethod, we’ll see in the console log:
You called exampleMethod, arguments: 1,2
Running the method
In other words, before actually running exampleMethod, the decorator logs the call to the console.
Class Decorators
Decorators can also be used on classes. Let’s see an example of how to seal a class using a decorator.
Creating a Class Decorator
The following decorator seals a class, preventing new properties or methods from being added.
function sealed(constructor) {
Object.seal(constructor);
return constructor;
}
@sealed
class ExampleClass {
constructor(name) {
this.name = name;
}
}
const instance = new ExampleClass("hello");
console.log(Object.isSealed(ExampleClass)); // true
Testing Decorators
To test decorators, it’s important to adopt strategies like unit testing with frameworks such as Jest or Mocha. The test should verify that the decorator correctly modifies the behavior of the element it’s applied to, without introducing unwanted side effects. For example, you can check whether a logging decorator actually logs method calls, or whether a validation decorator blocks invalid values. A good approach is to isolate the decorator and test it separately, simulating various scenarios.
Using Jest we could test our decorator like this
describe("logTheCall Decorator", () => {
it("should log the method call with its arguments", () => {
console.log = jest.fn();
const instance = new Example();
const result = instance.exampleMethod(1, 2);
expect(console.log).toHaveBeenCalledWith("You called exampleMethod, arguments: 1,2");
expect(result).toBe(3);
});
});
Conclusion
Decorators are a powerful JavaScript feature that lets you extend the behavior of classes and methods in a modular, reusable way. They’re especially useful in advanced development contexts and can significantly improve code maintainability.
Useful Links
- Official JavaScript documentation
- JavaScript Decorators proposal
- Decorators in TypeScript
- Proxies in JavaScript, another advanced feature for intercepting and customizing object behavior
Happy coding! 😃