NestJS Decorators
Leveraging the Power of Decorators in NestJS for Enhanced Functionality and Readability
Decorators in NestJS play a crucial role in enhancing the functionality and readability of the code. They are a powerful feature that allows you to modify the behaviour of classes, methods, and properties in a declarative way.
Origin of Decorators
Decorators are a design pattern that originated in the world of object-oriented programming (OOP). They allow behavior to be added to individual objects, without modifying the class code. In JavaScript, decorators were proposed and moved to stage 2 as of 2021. NestJS leverages TypeScript, which supports decorators as an experimental feature, to provide this powerful functionality.
How Decorators Work in NestJS
In NestJS, decorators are used extensively to define and configure its components e.g. routes, inject dependencies, apply guards, interceptors, and much more. They are functions that add metadata to the class and its members, which NestJS uses to create the desired behaviour.
Built-in Decorators in NestJS
NestJS comes with a set of built-in decorators that cover a wide range of functionalities:
@Controller: Defines a controller class.
@Get, @Post, @Put, @Delete: Define route handlers for various HTTP methods.
@Injectable: Marks a class as a provider that can be injected into other classes.
@Param, @Query, @Body, @Headers: Extract parameters from the request.
@UseGuards: Apply guards to a route or controller.
@UseInterceptors: Apply interceptors to a route or controller.
@UsePipes: Apply pipes for data transformation and validation.
Example: Using Built-in Decorators
import { Controller, Get, Param } from '@nestjs/common';
@Controller('users') // Define a controller with the route 'users'
export class UsersController {
@Get(':id') // Define a GET route that takes an 'id' parameter
findOne(@Param('id') id: string) { // Extract the 'id' parameter from the request
return `This action returns user #${id}`; // Return a response with the user ID
}
}
Creating Custom Decorators
Custom decorators in NestJS are simple to create and can be very powerful. They allow you to encapsulate repetitive logic and reuse it across your application.
Creating a Custom Parameter Decorator
Custom parameter decorators can be used to extract specific data from the request.
Example: Current User Decorator
Let's create a custom decorator to extract the current user from the request.
- Define the Decorator:
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
// Create a custom parameter decorator named 'CurrentUser'
export const CurrentUser = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest(); // Get the request object
return request.user; // Return the user object from the request
},
);
- Use the Decorator:
import { Controller, Get } from '@nestjs/common';
import { CurrentUser } from './current-user.decorator';
@Controller('profile') // Define a controller with the route 'profile'
export class ProfileController {
@Get() // Define a GET route
getProfile(@CurrentUser() user: any) { // Use the custom 'CurrentUser' decorator to get the user object
return user; // Return the user object
}
}
Creating a Custom Class Decorator
Custom class decorators can be used to add metadata to classes.
Example: Roles Decorator
Let's create a custom decorator to specify roles for a controller.
- Define the Decorator:
import { SetMetadata } from '@nestjs/common';
// Create a custom class decorator named 'Roles' that sets metadata 'roles'
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
- Use the Decorator:
import { Controller, Get } from '@nestjs/common';
import { Roles } from './roles.decorator';
@Controller('admin') // Define a controller with the route 'admin'
@Roles('admin') // Use the custom 'Roles' decorator to set the role to 'admin'
export class AdminController {
@Get() // Define a GET route
getAdminData() {
return 'Admin data'; // Return admin data
}
}
- Create a Guard to Use the Metadata:
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {} // Inject the Reflector service
canActivate(context: ExecutionContext): boolean {
const roles = this.reflector.get<string[]>('roles', context.getHandler()); // Get the roles metadata
if (!roles) {
return true; // If no roles are defined, allow access
}
const request = context.switchToHttp().getRequest(); // Get the request object
const user = request.user; // Get the user object from the request
return roles.includes(user.role); // Check if the user's role is included in the defined roles
}
}
- Apply the Guard:
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { RolesGuard } from './roles.guard';
import { AdminController } from './admin.controller';
@Module({
controllers: [AdminController],
providers: [
{
provide: APP_GUARD,
useClass: RolesGuard, // Apply the RolesGuard globally
},
],
})
export class AdminModule {}
Creating a Custom Method Decorator
Custom method decorators can modify the behaviour of methods.
Example: Log Execution Time Decorator
Let's create a custom decorator to log the execution time of a method.
- Define the Decorator:
import { performance } from 'perf_hooks';
// Create a custom method decorator named 'LogExecutionTime'
export function LogExecutionTime(): MethodDecorator {
return (
target: Object,
propertyKey: string | symbol,
descriptor: TypedPropertyDescriptor<any>,
) => {
const originalMethod = descriptor.value; // Store the original method
descriptor.value = async function (...args: any[]) {
const start = performance.now(); // Start timing
const result = await originalMethod.apply(this, args); // Execute the original method
const end = performance.now(); // End timing
console.log(
`${String(propertyKey)} executed in ${end - start} milliseconds`, // Log the execution time
);
return result; // Return the result of the original method
};
return descriptor; // Return the modified descriptor
};
}
- Use the Decorator:
import { Controller, Get } from '@nestjs/common';
import { LogExecutionTime } from './log-execution-time.decorator';
@Controller('analytics') // Define a controller with the route 'analytics'
export class AnalyticsController {
@Get() // Define a GET route
@LogExecutionTime() // Use the custom 'LogExecutionTime' decorator
getAnalytics() {
// Simulate a long-running process
return new Promise((resolve) => setTimeout(() => resolve('Analytics data'), 2000)); // Return a promise that resolves after 2 seconds
}
}
Decorators in NestJS are a powerful feature that enhances the readability and functionality of your code. Understanding built-in decorators and creating custom ones can help you build more modular, reusable, and maintainable applications. Whether extracting parameters from requests, adding metadata to classes, or modifying method behaviour, decorators provide a flexible and declarative way to extend your application's capabilities.