I have one module (auth module) where I register JwtModule:
JwtModule.register({
secret: process.env.SECRET_KEY,
signOptions: { expiresIn: '60m' },
}),
Register function looks like this: static register(options: JwtModuleOptions): DynamicModule;
JwtModule is defined in node_modules (jwt.module.js)
let JwtModule = JwtModule_1 = class JwtModule {
static register(options) {
console.log(options);
console.log(process.env.SECRET_KEY);
return {
module: JwtModule_1,
providers: jwt_providers_1.createJwtProvider(options)
};
}
In app.module.ts I have
ConfigModule.forRoot({
isGlobal: true,
}),
process.env.SECRET_KEY is undefined in node_modules file (console.log(process.env.SECRET_KEY)), but signOptions: { expiresIn: '60m' } is defined.
If I try to write to console SECRET_KEY from .env everywhere else is defined, but in node_modules is undefined.
The ConfigModule's ConfigService runs dotenv's config() method, but all decorators run at the same time, so the config() method is not called yet. You can use the async registration instead and use the ConfigService like so
JwtModule.registerAsync({
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
secret: config.get('SECRET_KEY')
})
})
Be sure that you Call the ConfigService in your App Module constructor :
export class AppModule {
constructor(private configService: ConfigService) {}
}
Related
I started building a simple telegram bot using nestjs-telegraf package.
Following the docs, I created the basic AppUpdate service:
#Update()
#Injectable()
export class AppUpdate {
#Command('random')
async onRandom(#Ctx() ctx: Context) {
const resp = await asyncRequestToGetData(ctx.message.text);
await ctx.reply(resp);
}
}
And provided it like this in app.module
#Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
TelegrafModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
token: configService.get<string>('TELEGRAM_BOT_TOKEN'),
}),
inject: [ConfigService],
}),
HttpModule,
],
controllers: [AppController],
providers: [AppUpdate, ...],
})
export class AppModule {}
The asyncRequestToGetData is an http call that sometimes takes 10 seconds or more.
And for some reason if another user tries to call the same command on the bot while request is processing, the onRandom is not called until the processing is finished.
Basically, not able to handle concurrent requests.
Was not able to find the reason/solution so far.
I am trying to implement custom Telegraf middleware with nestjs-telegraf library and connection to DB using Prisma.
My AppModule is:
#Module({
imports: [
TelegrafModule.forRootAsync({
imports: [ConfigModule, LoggerModule],
useFactory: (configService: ConfigService, logger: LoggerMiddleware) => {
return {
token: configService.get<string>("TELEGRAM_TOKEN")!,
middlewares: [sessionMiddleware, logger.use]
};
},
inject: [ConfigService, LoggerMiddleware]
}),
PrismaModule
],
controllers: [],
providers: [...someProviders]
})
export class AppModule {}
LoggerMiddleware:
#Injectable()
export class LoggerMiddleware {
constructor(private readonly prisma: PrismaService) {}
async use(ctx: Context, next: NextFunction) {
const listUser = await this.prisma.user.findMany()
console.log('listUser = ', listUser)
next()
}
}
LoggerModule:
#Module({
imports: [PrismaModule],
providers: [LoggerMiddleware, PrismaService],
exports: [LoggerMiddleware]
})
export class LoggerModule {}
It starts without errors and code reaches my logger middleware but then I am getting an error:
TypeError: Cannot read properties of undefined (reading 'prisma')
I have access to Prisma service from another provider and a connection to DB works.
At the start, nest initializes all dependencies successfully but I don't understand why during execution it fell with this error. What did I do wrong?
The answer was given by Alexander Bukhalo on github
this is my first question here, so i'd like to apologize in advance if i miss something important.
So this is pretty basic, but i didn't find an answer anywhere, as the title states, my NestJs application running with Fastify, simply does not execute any code after the app.listen('port') line, this doesn't happen with Express. OBS: I'm referring to the main.ts file.
relevant code below:
async function bootstrap() {
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter({ logger: { level: 'warn' } }),
);
const configService = app.get(ConfigService);
await app.listen(configService.get<number>(ENV_KEYS.PORT), '0.0.0.0');
console.log(
`Application is running on: ${await app.getUrl()}\nApplication Version: ${version}`,
);
}
bootstrap();
The console.log() after await app.listen never executes, even if the app is working normally, and as far as my tests show, no code after await app.listen() ever executes.
I'd like to know how to overcome this issue, because i need to run some code after the app is already bootstrapped.
So thanks to #Micael Levi pointing me to an issue on github (github.com/nestjs/nest/issues/7572) i started to look into problems within my controllers, and the reason to freeze the application on app.listen() was because of "an double instance" of my AuthController, let me explain
I had my AuthController defined on AuthModule:
// auth.module.ts
#Module({
imports: [
PassportModule,
JwtModule.register({
secret: JWT_CONSTANTS.SECRET,
}),
],
controllers: [AuthController],
providers: [AuthService, LocalStrategy, JwtStrategy],
exports: [AuthService, LocalStrategy, JwtStrategy],
})
And in the AppModule, i was importing AuthModule while also declaring AuthController AGAIN:
// app.module.ts
#Module({
imports: [
ConfigModule.forRoot(getConfigServiceConfiguration()),
TypeOrmModule.forRootAsync({
useFactory: async (configService: ConfigService) =>
getDatabaseModuleOptionsAsync(configService),
inject: [ConfigService],
}),
AuthModule, // <-AuthController is declared within AuthModule scope
UsuarioModule,
ClienteModule,
],
controllers: [AppController, AuthController] // <- AuthController here again,
providers: [AppService],
})
Removing AuthController from controllers:[] in AppModule solved my problem. Rookie mistake, but for some reason this isn't a problem with express, and doesn't raise any compilation errors with Fastify either!
I am trying to load the "process.env.AUTH_SECRET" in AuthModule, but it is giving me the undefined error "Error: secretOrPrivateKey must have a value".
I did setup the "isGlobal: true" in AppModule and it was able to read "process.env.MONGO_URL" there fine.
I have also installed dotenv, which reads fine if I do:
import * as dotenv from 'dotenv';
dotenv.config();
export const jwtConstants = {
secret: process.env.AUTH_SECRET,
};
But I would rather do it the "NestJs" way as the doc says adding isGlobal should make the env available to all other modules.
auth.module.ts
import { Module } from '#nestjs/common';
import { AuthService } from './auth.service';
import { UserModule } from '../user/user.module';
import { PassportModule } from '#nestjs/passport';
import { LocalStrategy } from './local.strategy';
import { JwtModule } from '#nestjs/jwt';
#Module({
imports: [
UserModule,
PassportModule,
JwtModule.register({
secret: process.env.AUTH_SECRET, //Cannot read this.
signOptions: { expiresIn: '60s' },
}),
],
providers: [AuthService, LocalStrategy],
exports: [AuthService, JwtModule],
})
export class AuthModule {}
app.module.ts
import { Module } from '#nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './user/user.module';
import { MongooseModule } from '#nestjs/mongoose';
import { ConfigModule } from '#nestjs/config';
import { AuthModule } from './auth/auth.module';
#Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
}),
UserModule,
MongooseModule.forRoot(process.env.MONGO_URL), // Can read this fine
AuthModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
What am I missing or doing wrong? Just for reference, I am trying to follow this authentication tutorial.
Thank you,
Your MongooseModule and AuthModule are dependent on your ConfigModule, but they don't know it.
Your modules are busy being loaded up, however your ConfigModule has to preform an async i/o process (reading your .env) before it can be ready. Meanwhile, Nest carries on loading without waiting for ConfigModule to finish it's job unless another it finds another module is dependent on one of it's exports.
This is where a modules *Async flavoured methods come in to play. They give you control over the modules instantiation. In this context, they allow us to inject the ConfigService from the ConfigModule which will not happen until the ConfigService is ready.
So changing your JWTModule configuration from using .register to .registerAsync to inject the ConfigService is what you are after:
JWTModule.registerAsync({
inject: [ConfigService],
useFactory: (config: ConfigService) => {
secret: config.get<string>('AUTH_SECRET'),
signOptions: { expiresIn: '60s' }
}
})
Now, JWTModule will not load until ConfigService is ready and available in scope. You will most likely need to do that for your MongooseModule too.
That is the "NestJS" way.
Saying that, if all you really needed to do was have your .env loaded into process.env, put:
import * as dotenv from 'dotenv';
import { resolve } from 'path';
dotenv.config({ path: resolve(__dirname, '../.env') });
At the start of your main.ts file. That would lead to dotenv synchronously loading it before anything else happened
I'am new using Node and Nest framework, and I have a problem when testing modules that import a Custom Config Module that uses Joi to validate env vars:
yarn test
Test suite failed to run
Config validation error: "APP_PORT" is required
app-config.module.ts
#Module({
imports: [
ConfigModule.forRoot({
expandVariables: true,
load: [configuration],
validationSchema: Joi.object({
APP_PORT: Joi.number().required()
}),
}),
],
providers: [AppConfigService],
exports: [AppConfigService],
})
export class AppConfigModule { }
app.config.ts
import { registerAs } from '#nestjs/config';
export default registerAs('app', () => ({
env: process.env.NODE_ENV,
port: process.env.APP_PORT || 3000
...
}));
invoice.service.spec.ts
describe('InvoiceService', () => {
let service: InvoiceService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({
load: [configuration],
ignoreEnvFile: true,
}),
AppConfigModule
],
providers: [
InvoiceService,
....
],
}).compile();
service = module.get<InvoiceService>(InvoiceService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
I use env file to deploy locally, and I only set up ignoreEnvFile: true in test class because env file is ignore from github repo, and project integrate github actions that run unit test.
How is the best way to solved this problem? I would not like add env file to repo.
Exist any way to disable/fake/mock Joi validation method. I saw some examples using setupFiles but I'm not sure if it's a good practice.
May you please show the package.json file?
you must have installed npm i --save #nestjs/config then create .env file inside root directory. Moreover you should import that into you app module.
import { Module } from '#nestjs/common';
import { ConfigModule } from '#nestjs/config';
#Module({
imports: [
ConfigModule.forRoot({
envFilePath: ".development.env",
}),
],
})
export class AppModule {}
Ref: https://docs.nestjs.com/techniques/configuration