I am trying to initialize a ConfigurationService using the onModuleInit hook. Referring to the NestJS Lifecycle Hooks Page I expect the hook to be called on module creation. This is not the case, however. Basically, I have this:
ConfigurationService:
import { Injectable, OnModuleInit } from '#nestjs/common';
import { ApplicationConfiguration } from './interfaces/application-configuration.interface';
import { ServerConfiguration } from './interfaces/server-configuration.interface';
import * as path from 'path';
#Injectable()
export class ConfigurationService implements OnModuleInit {
private _configuration: ApplicationConfiguration;
public async getServerConfigurationAsync(): Promise<ServerConfiguration> {
await this.ensureConfigurationIsLoadedAsync();
return new Promise<ServerConfiguration>((resolve, reject) => {
const serverConfiguration = this._configuration.server;
if (!serverConfiguration) {
reject(new Error('Missing server configuration.'));
}
return resolve(serverConfiguration);
});
}
private async ensureConfigurationIsLoadedAsync(): Promise<void> {
if (!this._configuration) {
this._configuration = await this.fetchAppConfigurationAsync();
}
}
private fetchAppConfigurationAsync(): Promise<ApplicationConfiguration> {
const configDir = process.env.CONFIG_DIR;
let configurationPath: string;
if (configDir) {
configurationPath = path.resolve(`configuration/${configDir}/config.json`);
}
else {
configurationPath = path.resolve('configuration/config.json');
}
return new Promise<ApplicationConfiguration>((resolve, reject) => {
try {
const configuration = require(configurationPath);
const mappedConfig: ApplicationConfiguration = {
server: configuration.server
};
resolve(mappedConfig);
}
catch (error) {
console.log(`Can't fetch configuration at ${configurationPath}.`);
reject(error);
}
});
}
// public async onModuleInit(): Promise<void> {
// console.log('ConfigurationService onModuleInit called.');
// await this.ensureConfigurationIsLoadedAsync();
// }
public onModuleInit(): void {
console.log('ConfigurationService onModuleInit called.');
}
}
Specific Async Provider:
export const SERVER_CONFIGURATION_PROVIDER: Provider = {
provide: CONFIGURATION_TOKENS.SERVER_CONFIGURATION,
useFactory: async (configurationService: ConfigurationService): Promise<ServerConfiguration> => {
console.log('SERVER_CONFIGURATION_PROVIDER useFactory called.');
return await configurationService.getServerConfigurationAsync();
},
inject: [ConfigurationService]
}
Configuration Module:
#Module({
providers: [
ConfigurationService,
SERVER_CONFIGURATION_PROVIDER
],
exports: [
SERVER_CONFIGURATION_PROVIDER
]
})
export class ConfigurationModule {
// public async onModuleInit(): Promise<void> {
// console.log('ConfigurationModule onModuleInit called.');
// }
public onModuleInit(): void {
console.log('ConfigurationModule onModuleInit called.');
}
}
And this is how I am trying to consume it:
#Controller('app')
export class AppController {
private _serverConfig: ServerConfiguration;
constructor(#Inject(CONFIGURATION_TOKENS.SERVER_CONFIGURATION) serverConfig: ServerConfiguration) {
this._serverConfig = serverConfig;
}
#Get()
public async getResult(): Promise<any> {
return this._serverConfig;
}
}
Now, the thing is I want to scrap the await this.ensureConfigurationIsLoadedAsync(); call in the getServerConfigurationAsync() method and initialize the Configuration through the onModuleInit hook. However, when running the application, the hook gets called long after the ConfigurationService gets consumed by the async provider. Am I missing something? Shouldn't the hook be invoked before the service gets consumed? The documentation states:
Called once the host module's dependencies have been resolved.
And
For each module, after module initialization:
await child controller & provider onModuleInit() methods
await module onModuleInit() method
However, I also found this in the source code:
nest/packages/core/nest-application.ts
public async init(): Promise<this> {
this.applyOptions();
await this.httpAdapter?.init();
const useBodyParser =
this.appOptions && this.appOptions.bodyParser !== false;
useBodyParser && this.registerParserMiddleware();
await this.registerModules();
await this.registerRouter();
await this.callInitHook();
await this.registerRouterHooks();
await this.callBootstrapHook();
this.isInitialized = true;
this.logger.log(MESSAGES.APPLICATION_READY);
return this;
}
So I am not sure what to expect and if I get the lifecycle right. Here is my log when I start the Application:
Before app create
[Nest] 28132 - 10/05/2020, 4:12:38 PM [NestFactory] Starting Nest application...
SERVER_CONFIGURATION_PROVIDER useFactory called.
[Nest] 28132 - 10/05/2020, 4:12:38 PM [InstanceLoader] ConfigurationModule dependencies initialized +10ms
[Nest] 28132 - 10/05/2020, 4:12:38 PM [InstanceLoader] AppModule dependencies initialized +1ms
After app create
before listen
[Nest] 28132 - 10/05/2020, 4:12:38 PM [RoutesResolver] AppController {/api/app}: +5ms
[Nest] 28132 - 10/05/2020, 4:12:38 PM [RouterExplorer] Mapped {/api/app, GET} route +4ms
ConfigurationService onModuleInit called.
ConfigurationModule onModuleInit called.
[Nest] 28132 - 10/05/2020, 4:12:38 PM [NestApplication] Nest application successfully started +2ms
after listen
Listening at port 5000.
Related
by example:
[https://stackoverflow.com/questions/72549668/how-to-do-custom-repository-using-typeorm-mongodb-in-nestjs][1]
Created custom repository в yandex-ndd-api-client.module:
import { DataSource, Repository } from 'typeorm';
import { Injectable } from '#nestjs/common';
// import {Team} from '#Domain/Team/Models/team.entity';
import { TestRepositoryTypeorm } from '../entity/testRepositoryTypeorm.entity';
#Injectable()
export class TestRepository extends Repository<TestRepositoryTypeorm> {
constructor(private dataSource: DataSource) {
super(TestRepositoryTypeorm, dataSource.createEntityManager());
}
async findTest(): Promise<any> { //TestRepositoryTypeorm | undefined
const findTests = await this.dataSource
.getRepository(TestRepositoryTypeorm)
.createQueryBuilder('test')
.getMany();
return await findTests;
}
}
Connected in the module::
providers: [YandexDeliveryService, YandexNddApiClientService, ConfigService, SyncService, TestRepository],
Connected it to the service yandex-ndd-api-client.service:
import { TestRepository } from './repository/testRepositoryTypeorm.retository';
#Injectable()
export class YandexNddApiClientService {
constructor(
// private yandexDeliveryApiService: YandexDeliveryApiService,
private httpService: HttpService,
private dataSource: DataSource,
private configService: ConfigService,
#Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
// #InjectRepository(TestRepository)
private testRepository: TestRepository,
) {}
Called in service:
//testRepositoryTypeorm
async testRepositoryTypeorm(): Promise<any> {
try {
console.log('testRepositoryTypeorm');
// return 'testRepositoryTypeorm';
return await this.testRepository.findTest();
} catch (e) {
console.log('ERROR testRepositoryTypeorm:', e);
}
}
As a result:
ERROR [ExceptionHandler] Nest can't resolve dependencies of the YandexNddApiClientService (HttpService, DataSource, ConfigService, winston, ?, SchedulerRegistry). Please make sure that the argument TestRepository at index [4] is available in the DetmirApiClientModule context.
Potential solutions:
- If TestRepository is a provider, is it part of the current DetmirApiClientModule?
- If TestRepository is exported from a separate #Module, is that module imported within DetmirApiClientModule?
#Module({
imports: [ /* the Module containing TestRepository */ ]
})
[1]: https://stackoverflow.com/questions/72549668/how-to-do-custom-repository-using-typeorm-mongodb-in-nestjs
DetmirApiClientModule.ts:
import { Module } from '#nestjs/common';
import { DetmirApiClientService } from './detmir-api-client.service';
import { DetmirApiClientController } from './detmir-api-client.controller';
import { SyncService } from 'src/sync.service';
import { YandexNddApiClientService } from 'src/yandex-ndd-api-client/yandex-ndd-api-client.service';
import { HttpModule, HttpService } from '#nestjs/axios';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { YandexNddApiClientModule } from 'src/yandex-ndd-api-client/yandex-ndd-api-client.module';
#Module({
providers: [DetmirApiClientService, SyncService, YandexNddApiClientService],
controllers: [DetmirApiClientController],
imports: [HttpModule, YandexNddApiClientModule], //TestRepository
})
export class DetmirApiClientModule {}
Most likely your YandexNddApiClientModule does not add the YandexNddApiClientService to the exports array. Add YandexNddApiClientService to the exports if it is not already there and remove YandexNddApiClientService from the providers array of DetmirApiClientModule. The error is being raised because you have YandexNddApiClientService declared in the providers of DetmirApiClientModule so Nest is trying to create the provider in the new module rather than re-use the module from its original context
I have created a guard in a separate module for checking feature flags as below
#Injectable()
export class FeatureFlagGuard implements CanActivate {
constructor(
private reflector: Reflector,
private featureFlagService: FeatureFlagService
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const featureKey = this.reflector.get<string>(
FEATURE_FLAG_DECORATOR_KEY,
context.getHandler()
);
if (!featureKey) {
return true;
}
return await this.featureFlagService.isFeatureEnabled(featureKey);
}
}
and here is my decorator
import { SetMetadata } from '#nestjs/common';
export const FEATURE_FLAG_DECORATOR_KEY = 'FEATURE_FLAG';
export const FeatureEnabled = (featureName: string) =>
SetMetadata(FEATURE_FLAG_DECORATOR_KEY, featureName);
Then in appModule I provided the FeatureFlagGuard as below
providers: [
{
provide: APP_GUARD,
useClass: FeatureFlagGuard
}
]
Then in my controller
#FeatureEnabled('FEATURE1')
#Get('/check-feature-flag')
checkFeatureFlag() {
return {
date: new Date().toISOString()
};
}
When I run the code I get this error, since the reflector is injected as null into my service
[error] [ExceptionsHandler] Cannot read properties of undefined (reading 'get')
Not sure what I missed
Thanks to #jayMcDoniel to give me a clue
The issue was the FeatureFlagService was not exported from the module. When I exported it the issue is resolved
I use Telegraf and cron from #nestjs/schedule in my Nestjs app.
Below you can see my app.module:
import { Module } from '#nestjs/common';
import { BotModule } from 'src/bot/bot.module';
import { TypeOrmModule } from '#nestjs/typeorm';
import { getConnectionOptions } from 'typeorm';
import { ConfigModule } from '#nestjs/config';
import { ScheduleModule } from '#nestjs/schedule';
#Module({
imports: [
BotModule,
ScheduleModule.forRoot(),
ConfigModule.forRoot({
isGlobal: true
}),
TypeOrmModule.forRootAsync({
useFactory: async () =>
Object.assign(await getConnectionOptions(), {
autoLoadEntities: true
})
})
]
})
export class AppModule {}
bot.module:
import { Module } from '#nestjs/common';
import { BotService } from 'src/bot/bot.service';
import { TelegrafModule } from 'nestjs-telegraf';
import { TypeOrmModule } from '#nestjs/typeorm';
import { TelegramBot } from './entities/bot.entity';
#Module({
imports: [
TypeOrmModule.forFeature([TelegramBot]),
TelegrafModule.forRootAsync({
useFactory: () => ({
token: process.env.BOT_TELEGRAM_TOKEN
})
})
],
providers: [BotService]
})
export class BotModule {}
bot.service:
import { Cron } from '#nestjs/schedule';
import { InjectRepository } from '#nestjs/typeorm';
import { On, Update } from 'nestjs-telegraf';
import { Context } from 'telegraf';
import { Repository } from 'typeorm';
import { TelegramBot } from './entities/bot.entity';
#Update()
export class BotService {
constructor(
#InjectRepository(TelegramBot)
private telegramRepo: Repository<TelegramBot>
) {}
#On('message')
#Cron('*/30 * * * * *')
async message(ctx: Context): Promise<void> {
await ctx.reply('Hello there');
}
}
My main goal is to receive every 30 seconds message from bot into chat: "Hello there",
but instead I receive nothing and messages in terminal:
[Nest] 47039 - 15/09/2021, 15:39:30 [Scheduler] TypeError: Cannot read property 'reply' of undefined +30005ms
[Nest] 47039 - 15/09/2021, 15:40:00 [Scheduler] TypeError: Cannot read property 'reply' of undefined +29997ms
[Nest] 47039 - 15/09/2021, 15:40:30 [Scheduler] TypeError: Cannot read property 'reply' of undefined +29995ms
[Nest] 47039 - 15/09/2021, 15:41:00 [Scheduler] TypeError: Cannot read property 'reply' of undefined +30003ms
[Nest] 47039 - 15/09/2021, 15:41:30 [Scheduler] TypeError: Cannot read property 'reply' of undefined +29999ms
[Nest] 47039 - 15/09/2021, 15:42:00 [Scheduler] TypeError: Cannot read property 'reply' of undefined +30002ms
So how can I use Cron for working correctly with Telegram bot?
The main problem into your solution, it's you don't able to use context in that scenario. This is an example how you can do that.
import { Injectable } from '#nestjs/common';
import { Cron, CronExpression } from "#nestjs/schedule";
import { Telegram } from 'telegraf';
#Injectable()
export class CronService {
private readonly bot: Telegram = new Telegram(process.env.BOT_TOKEN);
constructor() {}
#Cron(CronExpression.EVERY_30_SECONDS)
async runCronEvery30Seconds() {
await this.bot.sendMessage('chat ID','Hello there');
}
}
// location.entity.ts
import {Entity, Column, PrimaryGeneratedColumn, CreateDateColumn} from 'typeorm'
#Entity('location')
export class Location {
#PrimaryGeneratedColumn()
id: number;
#Column({length: 500})
name: string;
#Column('text')
desc: string;
#CreateDateColumn({type: 'timestamp'})
date: number;
}
// black.middleware.ts
import {Injectable, NestMiddleware} from '#nestjs/common'
import {Response, Request} from 'express'
import {InjectRepository} from '#nestjs/typeorm'
import {Location} from 'src/location/location.entity'
import {Repository} from 'typeorm'
#Injectable()
export class BlackMiddleware implements NestMiddleware {
constructor(#InjectRepository(Location) private locationRepository: Repository<Location>) {
}
use(req: Request, res: Response, next: () => void): any {
console.log(this.locationRepository, 'aaa');
return true;
}
}
In middleware, I want to use some other dependency injection, either entity, so how do I refer to it correctly?
// app.module.ts
...
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer): any {
consumer.apply(BlackMiddleware).forRoutes('*')
}
}
Expect typeorm to be used in the middleware, but I don't know how to resolve the dependency. Is the middleware being used globally
If used in this way, an error will be reported:
[Nest] 56559 - 2020/12/25 上午11:43:08 [ExceptionHandler] Nest can't resolve dependencies of the BlackMiddleware (?). Please make sure that the argument LocationRepository at index [0] is available in the BlackMiddleware context.
Potential solutions:
- If LocationRepository is a provider, is it part of the current BlackMiddleware?
- If LocationRepository is exported from a separate #Module, is that module imported within BlackMiddleware?
#Module({
imports: [ /* the Module containing LocationRepository */ ]
})
+0ms
Error: Nest can't resolve dependencies of the BlackMiddleware (?). Please make sure that the argument LocationRepository at index [0] is available in the BlackMiddleware context.
Potential solutions:
- If LocationRepository is a provider, is it part of the current BlackMiddleware?
- If LocationRepository is exported from a separate #Module, is that module imported within BlackMiddleware?
#Module({
imports: [ /* the Module containing LocationRepository */ ]
})
at Injector.lookupComponentInParentModules (/Users/brian/code/node/nest/learn-nest/node_modules/#nestjs/core/injector/injector.js:192:19)
at async Injector.resolveComponentInstance (/Users/brian/code/node/nest/learn-nest/node_modules/#nestjs/core/injector/injector.js:148:33)
at async resolveParam (/Users/brian/code/node/nest/learn-nest/node_modules/#nestjs/core/injector/injector.js:102:38)
at async Promise.all (index 0)
at async Injector.resolveConstructorParams (/Users/brian/code/node/nest/learn-nest/node_modules/#nestjs/core/injector/injector.js:117:27)
at async Injector.loadInstance (/Users/brian/code/node/nest/learn-nest/node_modules/#nestjs/core/injector/injector.js:81:9)
at async Injector.loadProvider (/Users/brian/code/node/nest/learn-nest/node_modules/#nestjs/core/injector/injector.js:38:9)
at async Promise.all (index 0)
at async InstanceLoader.createInstancesOfProviders (/Users/brian/code/node/nest/learn-nest/node_modules/#nestjs/core/injector/instance-loader.js:43:9)
at async /Users/brian/code/node/nest/learn-nest/node_modules/#nestjs/core/injector/instance-loader.js:28:13
at async Promise.all (index 26)
at async InstanceLoader.createInstances (/Users/brian/code/node/nest/learn-nest/node_modules/#nestjs/core/injector/instance-loader.js:27:9)
at async InstanceLoader.createInstancesOfDependencies (/Users/brian/code/node/nest/learn-nest/node_modules/#nestjs/core/injector/instance-loader.js:17:9)
at async /Users/brian/code/node/nest/learn-nest/node_modules/#nestjs/core/nest-factory.js:90:17
at async Function.asyncRun (/Users/brian/code/node/nest/learn-nest/node_modules/#nestjs/core/errors/exceptions-zone.js:18:13)
at async NestFactoryStatic.initialize (/Users/brian/code/node/nest/learn-nest/node_modules/#nestjs/core/nest-factory.js:88:13)
How do I properly use other dependency injection (such as database) in middleware?
Maybe you could try service just like controller. Let the logic be handled at the service layer.
for example:
import { Injectable, Logger, NestMiddleware } from '#nestjs/common';
import { NextFunction, Request, Response } from 'express';
import * as requestIp from 'request-ip';
import { LoggerService } from 'src/logger/logger.service';
#Injectable()
export class HttpLoggerMiddleware implements NestMiddleware {
constructor(private readonly loggerService: LoggerService) {}
private logger = new Logger();
use(request: Request, response: Response, next: NextFunction): void {
const { ip, method, originalUrl } = request;
const clientIp = requestIp.getClientIp(request);
const msg = `${method} ${originalUrl} ${clientIp} ${ip} `;
this.logger.log(msg);
this.loggerService.create({
ip: clientIp,
originalUrl,
});
next();
}
}
I created basic AuthGuard, but can't inject TokenService. I am getting this error:
Error: Nest can't resolve dependencies of the AuthGuard (?). Please verify whether [0] argument is available in the current context.
app.module.ts:
#Module({
modules: [
WorkModule,
],
components: [TokenService],
})
export class ApplicationModule { }
auth.guard.ts:
#Guard()
export class AuthGuard implements CanActivate {
constructor(
private readonly tokenService: TokenService,
) { }
public canActivate(dataOrRequest, context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
return true;
}
}
work.module.ts:
#Module({
controllers: [WorkController],
components: [WorkService],
})
export class WorkModule { }
Update, work.service.ts:
import { Component, Inject, HttpStatus, HttpException } from '#nestjs/common';
const dataStore = require('nedb');
const workDB = new dataStore({ filename: '../db/work.db', autoload: true });
import * as moment from 'moment';
import { WorkDay, WorkDayDTO } from './work.model';
import { WorkHelpers } from './work.helpers';
#Component()
export class WorkService {
public async getWorkGraphic(month: number, year: number) {
return new Promise((resolve, reject) => {
// logic here
});
}
public async addOrUpdateWorkDay(day: WorkDayDTO) {
return new Promise((resolve, reject) => {
// logic here
});
}
public async removeWorkDay(workDayId: string) {
return new Promise((resolve, reject) => {
// logic here
});
}
}
But with this configuration everything is working:
#Module({
controllers: [
WorkController,
],
components: [TokenService, WorkService],
})
export class ApplicationModule { }
What exactly is causing this error and how can I get it work with 1st solution (Modules) ?
Is possible to show your TokenService and WorkerService?
You should register both always in your components to use inside all of application scope.
If you are using inside a specific module and trying to use in another module, probably you will not be able.
Another scenario. Imagine if you have A component registered in A module, B component registered in B module and imagine if ure trying to use A component inside of B module, you cant do that unless you register in Application Module or register inside A component inside B module(dont do that, only shared services should be used in all of the scopes, is just an architecture overview).