DEV Community

Vincent Jang
Vincent Jang

Posted on

How to make Useful Decorator in NestJS

Hello devs!
I'm backend Developer Phantola (pandora)!
I wanna share a how to make a decorator in NestJS.

First of all, In my opinion, NestJS is more close NodeJS(also TS) DI framework. As you can see a official document in NestJS, They insist agnosticism about framework.

So if you want to a software use DI, NestJS will be attractive option for you.

If you have a little bit of experience using NestJS, Decorator is very powerful and important feature for NestJS.

Personally, I worked in 3yr in web3 platform, I made and manage a few servers, I just want to share a experience about make a decorator more useful.

Let's do this.

This article provide a contents with few steps. and We make a provider decorator(Class decorator)

  1. Decide a Decorator name and Symbol.
  2. Make a module
  3. Implement OnModuleInit
  4. Apply Decorator in your code

Step 1. Decide a Decorator name and Symbol.

In this example, I will make a error catch decorator.
So, Decorator Name will be @CatchError and internal symbol will be CATCH_ERROR_DECORATOR.

// catch-error.decorator.ts import { SetMetadata } from '@nestjs/common'; export const CatchErrorDecoratorSymbol = Symbol('CATCH_ERROR_DECORATOR') export const CatchError = () => SetMetadata(CatchErrorDecoratorSymbol, true); 
Enter fullscreen mode Exit fullscreen mode

Then you can use @CatchError Decorator in your logics.

Step 2. Make a module for decorator

We make a module for CatchError decorator.
As you know, typescript decorator is work like function composition in math.

You can see a detail description in here

Decorator is more like wrapper function. So we need to define how to our decorator works.

Then, Let's Make a module

// catch-error.module.ts @Module({ imports: [DiscoveryModule], providers: [], exports: [], }) export class CatchErrorModule implements OnModuleInit { constructor() {} onModuleInit() {} } 
Enter fullscreen mode Exit fullscreen mode

After make a module, You can import a module which you want.

Step3. Implement OnModuleInit

After this step, Our decorator will be define when module init lifecycle

Before we implement a lifecycle function, we need to Inject few services.

// catch-error.module.ts @Module({ imports: [DiscoveryModule], providers: [], exports: [], }) export class CatchErrorModule implements OnModuleInit { constructor( private readonly discoveryService: DiscoveryService, private readonly metadataScanner: MetaDataScanner, private readonly reflector: Reflector ) {} onModuleInit() {} } 
Enter fullscreen mode Exit fullscreen mode

I talk a services briefly.

DiscoveryService, MetaDataScanner, Reflector is very important for useful metadata.

DiscoveryService can find a provider and controller instances.

MetaDataScanner provide a function for get list of class methods.

At Last, Reflector can use globally, It provides a get/set metadata

Then, start again.

// inside a CatchErrorModule onModuleInit() { return this.discoveryService .getProviders() // Find all providers .filter((wrapper) => wrapper.isDependencyTreeStatic()) .filter(({ instance }) => instance && Object.getPrototypeOf(instance)) .forEach(({ instance }) => { // Find and filtering decorator applied classes const isDecoratorApplied = this.reflector.get(CatchErrorDecoratorSymbol, instance.constructor) if(!isDecoratorApplied) return; const providerMetaKeys = Reflect.getOwnMetadataKeys( instance.constructor, ); const providerMetaDatas = providerMetaKeys.map((k) => [ k, Reflect.getMetadata(k, instance.constructor), ]); // Get all methods in provider which decorator applied this.metadataScanner.getAllMethodNames(instance).forEach((method) => { const methodRef = instance[method]; const methodMetaKeys = Reflect.getOwnMetadataKeys(methodRef); const methodMetaDatas = methodMetaKeys.map((k) => [ k, Reflect.getMetadata(k, methodRef), ]); // wrapping code here // Preserve exist metadatas for method methodMetaDatas.forEach(([k, v]) => Reflect.defineMetadata(k, v, instance[method]), ); }) // Preserve exist metadatas for provider providerMetaDatas.forEach(([k, v]) => Reflect.defineMetadata(k, v, instance.constructor), ); }) } 
Enter fullscreen mode Exit fullscreen mode

So, In this step, We implement a decorator initializing code use onModuleInit lifecycle. then, If your Nest Application start, and module initialized, your decorator will be decorate a methods.

4. Apply Decorator in your code.

 // your provider @Injectable() @CatchError() export class ExampleService { constructor() {} pong() { return { code: 200, message: 'pong', }; } async decoratorTest() { throw new Error('Error occurred') } } 
Enter fullscreen mode Exit fullscreen mode

Thanks for reading.

This decorator evaluation mechanism is very helpful if you want inject another services, or another dependencies.

I hope a your application more dynamically.
also, I hope this helps even a little bit.

Top comments (0)