I would like to specify the arguments for NestJs Controllers (and Post, Get, etc) dynamically (the particular use case is to make them configurable).
E.g.
#Controller(config.get('whatever'))
Is this possible? Can't find a mention in the docs, but it seems like a reasonable use case, e.g. when switching between environments.
A bit of a hack to do this, but you can create a factory provider, that all it does is set metadata for the #Controller(). It could look something like this:
{
provide: Symbol('CONTROLLER_HACK'),
useFactory: (config: ConfigService) => {
const controllerPrefix = config..get('whatever') ?? 'defaultValue';
Reflect.defineMetadata(
PATH_METADATA,
controllerPrefix,
ControllerToHaveTheAtControllerOverridden
);
},
inject: [ConfigService],
},
Related
I have module which implement JWT passport
const appGuard = {
provide: APP_GUARD,
useClass: JwtAuthGuard,
};
#Module({})
export class JwtAuthModule {
static forRoot(options: { jwks: string, excludePath: string[] }): DynamicModule {
const exportableProviders = [
{ provide: JWKS_URI, useValue: options.jwks },
{ provide: EXCLUDE_FROM_AUTH, useValue: options.excludePath },
appGuard,
];
return {
module: JwtAuthModule,
providers: [
...exportableProviders,
JwtStrategy,
JwtAuthGuard
],
exports: exportableProviders,
};
}
}
But I am getting an error:
Error: Nest cannot export a provider/module that is not a part of the
currently processed module (JwtAuthModule). Please verify whether the
exported APP_GUARD is available in this particular context.
Possible Solutions:
Is APP_GUARD part of the relevant providers/imports within JwtAuthModule?
What is interesting if I debug i see that APP_GUARD has name in providers like: APP_GUARD (UUID: 63cf5b8e-815e-4034-a4a8-c6196d98b612)
Simple work around is to register APP_GUARD in providers at each root module and import module, but i would love to be able just to import this module once if possible...
APP_GUARD, along with the other APP_* providers, are special provider tokens that Nest allows to be bound multiple times. As soon as it is used it is bound as a global enhancer of whatever type relating back to the token (pipe, guard, interceptor, filter). As it's bound, there's no need to export the enhancer as well, there's nothing else that can make use of it directly, it's bound globally so it's already fine as is. Just remove the APP_GUARD provider you have from the exports and you should be good to go
I'm making a NestJS wrapper for Typegoose because the existing one is complete deprecated and has one critical drawback that I want to fix in my implementation.
Problem: there is #EventTrackerFor(schema: AnyClass) that takes Typegoose class. It's implemented like this:
export const EventTrackerFor = (schema: AnyClass) =>
applyDecorators(Injectable, SetMetadata('tracker-for', schema.name));
Also, there are #Pre(eventName: PreEvents) and Post(eventName: PostEvents) decorators:
export const Post = (eventName: PreEvents) => SetMetadata('post', eventName);
export const Pre = (eventName: PostEvents) => SetMetadata('pre', eventName);
And as a result, library user will do it like that:
#EventTrackerFor(User)
class UserEventTracker {
constructor(private readonly anyService: AnyService) {}
#Pre(PreEvents.SAVE)
#Post(PostEvents.SAVE)
logOnAndAfterCreate() {
console.log('user created')
}
}
// ------------------------ Any module
#Module({
imports: [MyModule.forFeature([ {schema: User} ])],
providers: [UserEventTracker]
})
class AnyModule {}
I need to get value from #EventTrackerFor somehow and methods of the provider, which are decorated with #Pre() and #Post decorators, including values that passed inside them.
I was looking for a clue in different packages like #nestjs/bull, which has similar logics inside, but they have so much code, so I could not understand how do they do it.
Project repository: https://github.com/GrapeoffJS/kindagoose
I have a question regarding dynamic modules importing.
Let's say I have #Injectable service which depends on the parameter that came from the configuration:
#Injectable()
export class StatsService {
constructor(#Inject(SERVICE_NAME) private serviceName: string) {}
...
}
Let's say I expose this service through the Dynamic module:
#Module({})
export class CoreServicesModule {
static register(coreConfig: CoreServicesConfig): DynamicModule {
const { serviceName } = coreConfig;
return {
module: CoreServicesModule,
providers: [
{
provide: SERVICE_NAME,
useValue: serviceName
},
StatsService
],
exports: [
StatsService
]
};
}
}
Let's say my application is pretty big, and I have a lot of different modules.
But I need StatsService in every one of them.
So for example to be able to use StatsService in one of the modules i need to do something like:
#Module({
imports: [CoreServicesModule.register({ serviceName: 'test', ... })]
})
export class SomeModule { ... }
And I need to do it for each module...
Do we have a way to do it only once in NestJS?
Or how we can re-use the already registered module?
#Global decorator also not help me here cause I still need to import the module in each place...
#Global() decorator will help you if you are interested in using StatService in all other modules without the need to import that module!
BUT from your code it seems that each module that import CoreServicesModule would also make a small adjustment to it by dynamically specifying the providers it has (such as ValidationPipe).
in that case you should NOT make it #Global() since global modules should be registered only once
Described here https://docs.nestjs.com/microservices/basics#client
Another option is to use the #Client() property decorator.
#Client({ transport: Transport.TCP }) client: ClientProxy;
Where do i have to put it to make it work?
Compared to this
#Module({
imports: [
ClientsModule.register([
{ name: 'MATH_SERVICE', transport: Transport.TCP },
]),
]
...
})
You have two options with microservice client injection, as mentioned in the docs. You can either use #Inject('MATH_SERVICE') in the constructor and do constructor based dependency injection, or use #Client() on a class property. To do the latter you would do something like this:
#Injectable()
export class MicroserviceConsumerService {
#Client({ transport: Transport.TCP })
tcpService: ClientProxy;
// rest of code
}
Keep in mind that with this approach, unit testing does become more difficult as you can no longer mock the dependency being injected as you would with the #Inject('MATH_SERVICE') approach
I like to organise my project along feature lines with different modules for cross cutting concerns eg: configuration, authentication, etc. However when importing an Interceptor into a feature module for use with a Controller Nest doesn't seem to reuse the existing instance.
controllers.module.ts
#Module({
imports: [
// ConfigService is exported from this module.
ConfigModule
],
providers: [
// For debugging purposes I used a factory so I could place a breakpoint and see
// when the Interceptor is being created.
{
provide: MyInterceptor,
useFactory: (config: ConfigService) => new MyInterceptor(config),
inject: [
ConfigService
]
}
],
exports: [
MyInterceptor
]
})
export class ControllersModule {
}
customer.module.ts
#Module({
imports: [
ControllersModule
],
controllers: [
CustomerController
],
providers: [
CustomerService
]
})
export class CustomerModule {
}
customer.controller.ts
#Controller("/customers")
#UseInterceptors(MyInterceptor)
export class CustomerController {
constructor(private readonly customerService: CustomerService) {
}
#Get()
customers() {
return this.customerService.findAll();
}
}
When the application starts up, I can see the MyInterceptor provider factory being called, with an instance of ConfigService. However then I see the following error on the console
error: [ExceptionHandler] Nest can't resolve dependencies of the MyInterceptor (?). Please make sure that the argument ConfigService at index [0] is available in the CustomerModule context.
Potential solutions:
- If ConfigService is a provider, is it part of the current CustomerModule?
- If ConfigService is exported from a separate #Module, is that module imported within CustomerModule?
#Module({
imports: [ /* the Module containing ConfigService */ ]
})
Now maybe there's something about how Nest instantiates/uses Interceptors that I'm not understanding but I thought that given that MyInteceptor had been created, and the ControllersModule imported by CustomerModule that the bean would have been available and applied to CustomerController.
Is there something I'm missing here?
Interceptors (along with other request lifecycle classes) are kinda like pseudoproviders in that that are #Injectable() but they aren't added to a providers array for binding. You can bind then via the providers array, (APP_INTERCEPTOR) but that will cause it to be bound globally.
Because interceptors can't be added to a providers array the way you are trying, you need to instead add the ConfigModule to whatever module uses the interceptor.
and as a side-note, you shouldn't use #Res with interceptors in nestjs
why
look, when you are using an interceptor, you are handling (with using .handle()) the stream of response (observable) not a whole package of it, but using express #Res actually is somehow getting around the whole flow of response streaming.
this is also explicitly mentioned in nestjs official documents:
We already know that handle() returns an Observable. The stream
contains the value returned from the route handler, and thus we can
easily mutate it using RxJS's map() operator.
WARNING
The response mapping feature doesn't work with the
library-specific response strategy (using the #Res() object directly
is forbidden).