How to pass a #liaoliaots/nestjs-redis redis connection to global guard constructor - node.js

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

Related

Access custom logger in AppModule

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);

how can I do handling in nestjs when not caught

I am using nestjs.
I have created an AllExceptionFilter.
However, once I run the post request api on the httpService and then an error is returned
nestjs will not accept the RESTful API after that.
What should I do?
■ error log
/Users/username/Documents/workspace/project/nestjs/src/shared/filters/custom-exception.filter.ts:29
path: httpAdapter.getRequestUrl(ctx.getRequest()),
^
TypeError: Cannot read properties of undefined (reading 'getRequestUrl')
at AllExceptionsFilter.catch (/Users/username/Documents/workspace/project/nestjs/src/shared/filters/custom-exception.filter.ts:29:25)
at ExceptionsHandler.invokeCustomFilters (/Users/username/Documents/workspace/project/nestjs/node_modules/#nestjs/core/exceptions/exceptions-handler.js:33:26)
at ExceptionsHandler.next (/Users/username/Documents/workspace/project/nestjs/node_modules/#nestjs/core/exceptions/exceptions-handler.js:13:18)
at /Users/username/Documents/workspace/project/nestjs/node_modules/#nestjs/core/router/router-proxy.js:13:35
at processTicksAndRejections (node:internal/process/task_queues:96:5)
■ no try/catch point code
await firstValueFrom(this.httpService.post(
url,
{
id: 'id'
},
));
■ AllExceptionFilter
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
HttpStatus,
} from '#nestjs/common';
import { HttpAdapterHost } from '#nestjs/core';
#Catch()
export class AllExceptionsFilter implements ExceptionFilter {
constructor(private readonly httpAdapterHost: HttpAdapterHost) {}
catch(exception: unknown, host: ArgumentsHost): void {
// In certain situations `httpAdapter` might not be available in the
// constructor method, thus we should resolve it here.
const { httpAdapter } = this.httpAdapterHost;
const ctx = host.switchToHttp();
const httpStatus =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const responseBody = {
statusCode: httpStatus,
timestamp: new Date().toISOString(),
path: httpAdapter.getRequestUrl(ctx.getRequest()),
};
httpAdapter.reply(ctx.getResponse(), responseBody, httpStatus);
}
}
Looks like an error is thrown by your endpoint, and the exception filter has an error that is triggered when attempting to handle the first exception.
As specified in the stack trace, the error is coming from: custom-exception.filter.ts:29, which is this line: path: httpAdapter.getRequestUrl(ctx.getRequest())
The problem is that httpAdapter in that line is undefined. For some reason DI isn't injecting it.
If you're using this filter globally, note the following from the docs:
Global-scoped filters are used across the whole application, for every controller and every route handler. In terms of dependency injection, global filters registered from outside of any module (with useGlobalFilters() as in the example above) cannot inject dependencies since this is done outside the context of any module. In order to solve this issue, you can register a global-scoped filter directly from any module using the following construction:
import { Module } from '#nestjs/common';
import { APP_FILTER } from '#nestjs/core';
#Module({
providers: [
{
provide: APP_FILTER,
useClass: HttpExceptionFilter,
},
],
})
export class AppModule {}
If you use the above approach you won't need the useGlobalFilters() method.
If you want to bind this filter at the controller level, the following syntax enables DI:
#UseFilters(AllExceptionsFilter)
#Controller("app")
export class AppController {

NestJS Mocking Injected Connection Imported from Different Module

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! :)

Programatically declare RabbitMQ consumers in NestJS / Node.js?

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
}

handleInterval in NestJS not working with mongoose

My goal is to build a nest.js app using cronjobs to access the mongoDB each 10 seconds. In this case I want to read from mongo but in the future I was also thinking of saving to mongo.
I have no errors but the cron job is not executing
The project is here:
https://github.com/meyetchristian4/nestJS-with-mongo-and-taskScheduler
AppModule
// app.module.ts
#Module({
imports: [
ScheduleModule.forRoot(),
TaskModule,
MongooseModule.forRoot('mongodb://localhost:27017/nest'),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
You haven't imported the ScheduleModule and initialised the listeners, so although the TasksModule is registered, the listeners are only registered as plain methods.
TasksService
Optional
To enable you to obtain the allData as I believe you wanted (you had it commented out), you can change your method:
#Injectable()
export class TasksService {
private readonly logger = new Logger(TasksService.name);
constructor(#InjectModel(info.name) private infoModel: Model) {}
#Interval(10000)
async handleInterval() {
this.logger.debug('Called every 10 seconds');
let allData = await this.findAll();
console.log(`Mongo: ${allData}`); // or JSON.stringify(allData)
}
async findAll(): Promise {
return this.infoModel.find().exec();
}
}
Output:
[Nest] 11320 - 24/05/2021, 17:21:44 [TasksService] Called every 10 seconds
Learn More
https://docs.nestjs.com/techniques/task-scheduling#installation

Resources