Access custom logger in AppModule - nestjs

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

Related

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

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

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

How to dynamically inject providers in NestJs

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().

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