DEV Community

Vincent Jang
Vincent Jang

Posted on

3 1

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.

Image of Timescale

Timescale – the developer's data platform for modern apps, built on PostgreSQL

Timescale Cloud is PostgreSQL optimized for speed, scale, and performance. Over 3 million IoT, AI, crypto, and dev tool apps are powered by Timescale. Try it free today! No credit card required.

Try free

Top comments (0)

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more