My team is trying to identify a way to dynamically DI such that there can a bulk insertion point without a need to spell out + import each Module.
#Module({
...
providers: [
WorkerService,
WorkerResolver,
Worker2Service,
Worker2Resolver........
]
})
want to achieve
var allModules = ... // logic here to include all my resolvers, or all my services
#Module({
...
providers: [
...allModules
]
})
You can use glob package to dynamically find modules then use NestJs dynamic module feature to load them dynamically.
Suppose all of your worker files are stored in a directory named workers and with extention .worker.ts:
#Module({})
export class WorkerModule {
static forRootAsync(): DynamicModule {
return {
module: WorkerModule ,
imports: [WorkerCoreModule.forRootAsync()],
};
}
}
export class WorkerCoreModule {
static async forRootAsync(): Promise<DynamicModule> {
// Feel free to change path if your structure is different
const workersPath = glob.sync('src/**/workers/*.worker.ts');
const workersRelativePathWithoutExt = modelsPath
// Replace src, because you are probably running the code
// from dist folder
.map((path) => path.replace('src/', './../'))
.map((path) => path.replace('.ts', ''));
const workerProviders: Provider<any>[] = [];
const importedModules = await Promise.all(
workersRelativePathWithoutExt.map((path) => import(path)),
);
importedModules.forEach((modules) => {
// Might be different if you are using default export instead
const worker = modules[Object.keys(modules)[0]];
workerProviders.push({
provide: worker.name,
useValue: worker,
});
});
return {
module: WorkerCoreModule,
providers: [...workerProviders],
// You can omit exports if providers are meant to be used
// only in this module
exports: [...workerProviders],
};
}
}
Now suppose you have a simple worker class with path src/anyModule/workers/simple-worker.ts, you can use it like this:
class WrokersService {
constructor(#Inject('SimpleWorker') simpleWroker: SimpleWorker) {}
.
.
.
}
If you want to omit #Inject('SimpleWorker') and automatically inject modules like NestJs services then you need to make these changes to WorkerCoreModule:
workerProviders.push({
provide: worker,
});
However in order for this to work you need to be sure that your workers classes are decorated with #injectable().
Related
I replaced the nestjs logger with a custom for having it also for bootstrap messages.
This is the code i have used to instantiate it
const logger = CustomLoggerModule.createLogger();
try {
const app = await NestFactory.create(AppModule, {
logger,
});
} catch (e) {
logger.error(e)
}
createLogger is a static method of the CustomLoggerModule class that returns an instance of the CustomLoggerModule class, a class that implements Nest's LoggerService interface.
I now want to have access to the Logger inside the AppModule and this is what I've tried:
#Module({
imports: [
ConfigModule.forRoot(),
ESModule,
ElasticSearchConfigModule,
KafkaConfigModule,
IndexModule,
AppConfigModule,
ReadyModule,
],
providers: [
{
provide: APP_FILTER, //you have to use this custom provider
useClass: ErrorFilter, //this is your custom exception filter
},
Logger,
],
})
export class AppModule implements NestModule {
constructor(private moduleRef: ModuleRef) {}
configure(consumer: MiddlewareConsumer) {
const logger = this.moduleRef.get(Logger) as CustomLoggerModule;
const logger.initMiddleware(consumer);
}
}
But in this way, I get an exception at the line logger.initMiddleware, because the logger returned by moduleRef is of type Logger
Obviously by trying to access CustomLogger inside modules other than the AppModule by dependency injection works as expected
How can i access my custom logger instance inside the appModule ?
From Nest: "Because application instantiation (NestFactory.create()) happens outside the context of any module, it doesn't participate in the normal Dependency Injection phase of initialization."
So, you must ensure that at least one application module imports a CustomloggerModule to trigger Nest to instantiate a singleton instance of the custom logger class.
One of your application modules needs to have the following:
providers: [Logger]
exports: [Logger]
Also, your not setting the value of the optional logger property in NestFactory.create() to an object that fulfills the LoggerService interface. Try:
{
logger: logger
}
Then, you need to instruct Nest to use the logger singleton instance in your bootstrap:
const app = await NestFactory.create(AppModule, {
logger,
});
app.useLogger(logger);
I've been getting this error all day long:
Nest can't resolve dependencies of the ClubsService (ClubsApiService, AuthApiService, ClubFollowersRepo, ClubsRepo, ClubPrivacyRepo, ?). Please make sure that the argument DatabaseConnection at index [5] is available in the ClubsModule context.
Potential solutions:
- If DatabaseConnection is a provider, is it part of the current ClubsModule?
- If DatabaseConnection is exported from a separate #Module, is that module imported within ClubsModule?
#Module({
imports: [ /* the Module containing DatabaseConnection */ ]
})
I figured that the problem is, that I have not mocked the Mongo DB connection. The error is quite clear, the #InjectConnection in ClubsService should be mocked (see below).
ClubsService:
#Injectable()
export class ClubsService {
constructor(
private readonly clubsApiService: ClubsApiService,
private readonly authApiService: AuthApiService,
private readonly clubFollowersRepo: ClubFollowersRepo,
private readonly clubsRepo: ClubsRepo,
private readonly clubPrivacyRepo: ClubPrivacyRepo,
#InjectConnection() private readonly connection: Connection, // <--- THIS GUY
) {}
// ...
}
The problem is that the test file I am executing is in a different module than where ClubsService is. And so in the different module (let's call it YModule), I have this piece of code:
YModule:
import { getConnectionToken } from '#nestjs/mongoose';
import { MongoMemoryServer } from 'mongodb-memory-server';
import { Connection, connect } from 'mongoose';
describe('YService.spec.ts in YModule', () => {
beforeAll(async () => {
mongod = await MongoMemoryServer.create();
const uri = mongod.getUri();
mongoConnection = (await connect(uri)).connection;
});
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
// ...
],
imports: [ClubsModule], // <--- ClubsModule is not provider, but imported module
})
.overrideProvider(getConnectionToken())
.useValue(mongoConnection)
.compile();
});
});
This approach with getConnectionToken() won't work as I have to mock a connection coming from the imported ClubsModule, not a provider.
How would you mock a connection injected in a different module that you imported?
Thanks a lot! :)
As Jay McDoniel mentioned in the post comment, you should not import modules in your unit testing file but mock the needed dependencies instead. Why is that? Consider the example from the question above:
ClubsModule has the connection dependency that can be replaced with an in-memory database server, that's true, but this should be done within the ClubsModule itself (clubs folder), not outside in different modules.
What you really want to do outside ClubsModule, let's say, in the YModule (y folder), is to mock every provider ClubsModule exports that you use within the test file of YModule.
This makes sense as you should test ClubsModule specific dependencies only within its module and everywhere else just mock it.
I originally imported ClubsModule because I wanted to use the repository (a provider) that ClubsModule exports. But then I realized that I don't want to test the functionality of the repository's function, I already test them inside the ClubsModule, so there is no need to do that twice. Instead, it is a good idea to mock the repository instead.
Code Example:
y.service.spec.ts:
import { YService } from './y.service'; // <--- For illustration; Provider within YModule
import { ClubsRepo } from '../clubs/clubs.repo'; // <--- Import the real Repository Provider from different Module (ClubsModule)
describe('y.service.spec.ts in YModule', () => {
const clubsRepo = { // <--- Mocking ClubRepo's functions used within this test file
insertMany: () => Promise.resolve([]),
deleteMany: () => Promise.resolve(),
}
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
ClubsRepo, // <--- The real Repository Provider from the import statement above
],
})
.overrideProvider(ClubsRepo) // <--- Overriding the Repository Provider from imports
.useValue(clubsRepo) // <--- Overriding to the mock 'clubsRepo' (const above)
.compile();
service = module.get<YService>(YService); // <--- For illustration; unlike ClubsRepo, this provider resides within this module
});
it('example', () => {
// ...
jest.spyOn(clubsRepo, 'insertMany'); // <--- Using "insertMany" from the mocked clubsRepo (the const) defined at the beginning
// ...
});
});
The reason for importing ClubsRepo to the test file y.service.spec.ts is because y.service.ts (the actual Provider in YModule) uses the functions of the ClubsRepo. In this case, don't forget importing ClubsModule in y.module.ts too.
y.module.ts:
import { ClubsModule } from '../clubs/clubs.module';
#Module({
imports: [
// ...
ClubsModule, // <--- Don't forget this line
// ...
],
providers: [
// ...
],
})
export class YModule {}
That's it, happy testing! :)
i'm new in NestJS and have some misunderstands with #liaoliaots/nestjs-redis(https://github.com/liaoliaots/nestjs-redis) package. For example, i have a guard with following constructor:
import { InjectRedis } from '#liaoliaots/nestjs-redis';
import { Redis } from 'ioredis';
#Injectable()
export class SomeGuard implements CanActivate {
constructor(#InjectRedis() redis: Redis) {}
...
}
and then i want that guard to be global:
//main.ts
...
app.useGlobalGuards(new SomeGuard(/* what??? */));
...
so thats a problem: what i need to pass? #InjectRedis makes weird things:)
thx for responding
Instead of app.useGlobalGuards, use this another way:
// ...
import { Module } from '#nestjs/common'
import { APP_GUARD } from '#nestjs/core'
#Module({
// ...
providers: [
{
provide: APP_GUARD,
useClass: SomeGuard,
},
],
})
export class AppModule {}
is cleaner and helps you avoid polluting your boostrap funcion. Also, it lets Nest resolves that Redis dependency. Otherwise you'll need to get this dependency and pass it to new SomeGuard using
const redis = app.get(getRedisToken())
https://docs.nestjs.com/guards#binding-guards
I am using a NestJS application to consume a RabbitMQ queue.
Each message can be processed no matter the order, so I'm wondering what would be the best practise to declare new consumers for the same queue.
Expected behaviour: The queue is processed by this service, which is using several consumers
Queue: [1,2,3,4,5,6, ...N];
In nestJS you can use the #RabbitSubscribe decorator to assign a function to process the data. What I want to do could be achieved by simply duplicating (and renaming) the function with the decorator, so this function will also be called to process data from the queue
#RabbitSubscribe({
...
queue: 'my-queue',
})
async firstSubscriber(data){
// 1, 3, 5...
}
#RabbitSubscribe({
...
queue: 'my-queue',
})
async secondSubscriber(data){
// 2, 4, 6...
}
I am aware that I could duplicate the project and scale horizontally, but I'd prefer doing this on the same process.
How could I declare subscribers to get this same behaviour programatically, so I could process the data with more concurrent processing?
You will benefit if you use #golevelup/nestjs-rabbitmq package as its supports different messages queque consumption and more if your app is hybrid.
First install
npm i #golevelup/nestjs-rabbitmq
then your nestjs app structure should look like this
src --
|
app.module.ts
main.ts
app.module.ts
someHttpModule1 --
|
someHttpModule1.controller.ts
someHttpModule1.module.ts
someHttpModule1.service.ts
...
someHttpModule2 --
|
someHttpModule2.controller.ts
someHttpModule2.module.ts
someHttpModule2.service.ts
...
...
// Events module is designed for consuming messages from rabbitmq
events --
|
events.module.ts
someEventConsumerModule1 --
|
someEventConsumerModule1.module.ts
someEventConsumerModule1.service.ts
someEventConsumerModule2 --
|
someEventConsumerModule2.module.ts
someEventConsumerModule2.service.ts
...
In the src/app.module.ts file
// module imports
import { SomeHttpModule1 } from './someHttpModule1/someHttpModule1.module'
import { SomeHttpModule2 } from './someHttpModule2/someHttpModule.module'
import { EventsModule } from './events/events.module'
// and other necessery modules
#Module(
imports: [
SomeHttpModule1,
SomeHttpModule2,
EventsModule,
// and other dependent modules
],
controller: [],
providers: []
})
export class AppModule{}
And in your events.module.ts file
// imports
import { RabbitMQModule } from '#golevelup/nestjs-rabbitmq'
import { SomeEventConsumerModule1 } from './someEventConsumerModule1/someEventConsumerModule1.module'
import { SomeEventConsumerModule2 } from './someEventConsumerModule2/someEventConsumerModule2.module'
// and so on
#Module({
imports: [
RabbitMQModule.forRoot(RabbitMQModule, {
exchanges: [{
name: 'amq.direct',
type: 'direct' // check out docs for more information on exchange types
}],
uri: 'amqp://guest:guest#localhost:5672', // default login and password is guest, and listens locally to 5672 port in amqp protocol
connectionInitOptions: { wait: false }
}),
SomeEventConsumerModule1,
SomeEventConsumerModule2,
// ... and other dependent consumer modules
]
})
export class EventsModule {}
And below is example for one consumer module (someEventConsumerModule1.module.ts)
// imports
import { SomeEventConsumerModule1Service } from './someEventConsumerModule1.service'
// ...
#Module({
imports: [
SomeEventConsumerModule1,
// other modules if injected
],
providers: [
SomeEventConsumerModule1Service
]
})
export class SomeEventConsumerModule1 {}
And in your service file put your business logic how to handle messages
// imports
import { RabbitSubscribe } from '#golevelup/nestjs-rabbitmq'
import { Injectable } from '#nestjs/common'
import { ConsumeMessage, Channel } from 'amqplib' // for type safety you will need to install package first
// ... so on
#Injectable()
export class SomeEventConsumerModule1Service {
constructor(
// other module services if needs to be injected
) {}
#RabbitSubscribe({
exchange: 'amq.direct',
routingKey: 'direct-route-key', // up to you
queue: 'queueNameToBeConsumed',
errorHandler: (channel: Channel, msg: ConsumeMessage, error: Error) => {
console.log(error)
channel.reject(msg, false) // use error handler, or otherwise app will crush in not intended way
}
})
public async onQueueConsumption(msg: {}, amqpMsg: ConsumeMessage) {
const eventData = JSON.parse(amqpMsg.content.toString())
// do something with eventData
console.log(`EventData: ${eventData}, successfully consumed!`)
}
// ... and in the same way
}
Is it possible, given a loaded module, to get its file path?
const MyModule = require('./MyModule');
const MyOtherModule = require('../otherfolder/MyOtherModule');
function print(){
console.log(thisIsThePathTo(MyModule)); <--- Should print the absolute path of the loaded module
console.log(thisIsThePathTo(MyOtherModule)); <--- Should print the absolute path of the loaded module
}
I saw require.resolve but I need the opposite lookup...
Any ideas?
Thanks!
The documentation for require.main describes the module object.
The module has an id and a path, however those are not exported. You can add those properties to the module.exports object to export them. Then, in a separate module, you can access them via MyOtherModule.id or MyOtherModule.path
For example,
In MyOtherModule/index.js:
myOtherModuleFunction = function() {
console.log('This is module 2')
}
module.exports = {
// spread all properties in module.exports
...module,
// then add the exports
exports: myOtherModuleFunction
}
and in MyModule/MyModule.js,
module.exports = {
...module,
exports: { someFunction: () => console.log('MyModule') }
}
and in MyModule/index.js:
const MyModule = require('./MyModule');
const MyOtherModule = require('../../MyOtherModule/');
function thisIsThePathTo(module) {
return module.path
}
function print(){
console.log(thisIsThePathTo(MyModule))
console.log(thisIsThePathTo(MyOtherModule))
}
print()
Running node src/MyModule/index.js outputs:
/.../stackoverflow/62043302/src/MyModule/
/.../stackoverflow/62043302/src/MyOtherModule
And if you print module.id instead of module.path, you'll get:
/.../stackoverflow/62043302/src/MyModule/index.js
/.../stackoverflow/62043302/src/MyOtherModule/index.js
However, spreading all properties includes module.children and module.parent, and you'll also have to use module.exports when accessing the so you probably only want to include id or path, like so:
myOtherModuleFunction = function() {
console.log('This is module 2')
}
const { id, path } = module
module.exports = {
id,
path,
myOtherModuleFunction,
}```
and require like so:
```js
const {id: otherModuleId, myOtherModuleFunction } = require('MyOtherModule')
This can get messy. If you're importing modules you did not author, you will not have the option to lookup the id or path (unless the authors added it to module.exports).