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)
- Decide a Decorator name and Symbol.
- Make a module
- Implement
OnModuleInit
- 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);
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() {} }
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() {} }
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), ); }) }
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') } }
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)