5

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,

Sean
  • 508
  • 6
  • 20
  • 1
    try to use `JwtModule.registerAsync` instead of `JwtModule.register` to avoid using `process.env.*` just like https://docs.nestjs.com/techniques/http-module#async-configuration – Micael Levi Jun 03 '21 at 23:22

1 Answers1

14

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

The Geek
  • 1,185
  • 9
  • 12