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)