Delving Developer

Create Custom Decorators in NestJS Easily

Eddie Cunningham
Eddie Cunningham
3 min readNestJS
Cover Image for 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.