Create Custom Decorators in NestJS Easily
Creating custom decorators in NestJS is a powerful way to enhance your application's functionality and maintain clean code. NestJS, a progressive Node.js framework, heavily relies on decorators from TypeScript. These decorators make the code more succinct and readable by allowing you to attach metadata to classes, methods, or properties. In this article, we'll walk you through the process of creating custom decorators and provide real-world examples of their use in NestJS applications.
Understanding Decorators in NestJS
Before you start creating custom decorators, it's crucial to understand what decorators are. In NestJS, decorators are special functions prefixed with the @
symbol, which allow you to define behaviors without changing the existing code. Common decorators include @Controller
, @Get
, @Post
, among others.
Why Create Custom Decorators?
Custom decorators can:
- Promote Code Reusability: Encapsulate common operations and use them across multiple components.
- Enhance Readability: Abstract complex tasks and improve code readability.
- Ensure Consistency: Standardize behavior across various parts of your application.
Setting Up a NestJS Project
To illustrate how to create custom decorators, first ensure you have a NestJS project set up. If not, create one using the following command:
npm i -g @nestjs/cli
nest new my-nestjs-app
Navigate to the project directory:
cd my-nestjs-app
Creating a Custom Decorator
Custom decorators are functions that accept a target (like a class or method), property name, and descriptor. Let’s create a basic example to log method execution time.
Step 1: Define the Decorator
Create a new file named log-execution-time.decorator.ts
in your project:
import { Logger } from '@nestjs/common';
export function LogExecutionTime(): MethodDecorator {
return (target, propertyKey, descriptor: PropertyDescriptor) => {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: any[]) {
const start = Date.now();
const result = await originalMethod.apply(this, args);
const end = Date.now();
Logger.log(`${String(propertyKey)} executed in ${end - start}ms`);
return result;
};
return descriptor;
};
}
Step 2: Apply the Decorator
Now, apply the decorator to a method inside a service or controller. Here is an example using a service method:
import { Injectable } from '@nestjs/common';
import { LogExecutionTime } from './log-execution-time.decorator';
@Injectable()
export class AppService {
@LogExecutionTime()
async fetchData(): Promise<string> {
// Simulate a time-consuming task
await new Promise(resolve => setTimeout(resolve, 2000));
return 'Data fetched';
}
}
Real-World Example
Let's consider a real-world scenario where you might need a custom decorator. Suppose you want to control access to certain routes based on user roles.
Step 1: Create a Role-based Decorator
Create a new decorator roles.decorator.ts
:
import { SetMetadata } from '@nestjs/common';
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
Step 2: Use the Roles Decorator
Attach the Roles
decorator to your route handlers:
import { Controller, Get, UseGuards } from '@nestjs/common';
import { Roles } from './roles.decorator';
import { RolesGuard } from './roles.guard';
@Controller('users')
export class UserController {
@Get()
@Roles('admin')
@UseGuards(RolesGuard)
getUsers() {
return 'This route is restricted to admin users';
}
}
Implementing Guards
To make the Roles
decorator functional, create a guard roles.guard.ts
:
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const roles = this.reflector.get<string[]>('roles', context.getHandler());
if (!roles) {
return true;
}
const request = context.switchToHttp().getRequest();
const user = request.user;
return roles.some(role => user.roles?.includes(role));
}
}
Conclusion
Creating custom decorators in NestJS is a straightforward process that can greatly simplify and streamline your application’s codebase. By using custom decorators like LogExecutionTime
and Roles
, you can ensure your code remains clean, reusable, and maintainable. As you become more familiar with NestJS and TypeScript decorators, you'll find myriad ways to apply these patterns to your projects.
For further reading on NestJS decorators and their applications, you might visit NestJS Official Documentation. Engaging with the broader developer community on platforms like GitHub can also offer insights and advanced examples.