Home > Blockchain >  process.env's are undefined - NestJS
process.env's are undefined - NestJS

Time:10-03

I've decided to write here because I've ran out of ideas. I have a NestJS app in which I use env's - nothing unusual. But something strange happens when I want to use them. I also have my own parser of these values which returns them in a convenient object - that's the first file:

env.ts

const parseStringEnv = (name: string) => {
  const value: string = process.env[name];

  if (!value) {
    throw new Error(`Invalid env ${name}`);
  }

  return value;
};

const parseIntEnv = (name: string) => {
  const value: string = process.env[name];

  const int: number = parseInt(value);

  if (isNaN(int)) {
    throw new Error(`Invalid env ${name}`);
  }

  return int;
};

const parseBoolEnv = (name: string) => {
  const value: string = process.env[name];

  if (value === "false") {
    return false;
  }

  if (value === "true") {
    return true;
  }

  throw new Error(`Invalid env ${name}`);
};

const parseMongoString = (): string => {
  const host = parseStringEnv("DATABASE_HOST");
  const port = parseStringEnv("DATABASE_PORT");
  const user = parseStringEnv("DATABASE_USER");
  const pwd = parseStringEnv("DATABASE_PWD");
  const dbname = parseStringEnv("DATABASE_NAME");

  return `mongodb://${user}:${pwd}@${host}:${port}/${dbname}?authSource=admin&ssl=false`;
};

export const env = {
  JWT_SECRET: parseStringEnv("JWT_SECRET"),
  PORT_BACKEND: parseIntEnv("PORT_BACKEND"),
  CLIENT_HOST: parseStringEnv("CLIENT_HOST"),
  ENABLE_CORS: parseBoolEnv("ENABLE_CORS"),
  MONGO_URI: parseMongoString(),
};

export type Env = typeof env;

I want to use it for setting port on which the app runs on and also the connection parameters for Mongoose:

In main.ts:

<rest of the code>
await app.listen(env.PORT_BACKEND || 8080);
<rest of the code>

Now, the magic starts here - the app starts just fine when ONLY ConfigModule is being imported. It will also start without ConfigModule and with require('doting').config() added. When I add MongooseModule, the app crashes because it can't parse env - and the best thing is that exception thrown has nothing to do with env's that are used to create MONGO_URI!! I'm getting "Invalid env JWT_SECRET" from my parser.

In app.module.ts

import { Module } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";
import { MongooseModule } from "@nestjs/mongoose";

import { AppController } from "./app.controller";
import { env } from "./common/env";

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
    }),
    MongooseModule.forRoot(env.MONGO_URI), //WTF?
  ],
  controllers: [AppController],
})
export class AppModule {}

I've honestly just ran out of ideas what could be wrong. The parser worked just fine in my last project (but I haven't used Mongoose so maybe that's what causes issues). Below is my .env file template.

JWT_SECRET=
ENABLE_CORS=
PORT_BACKEND=
DATABASE_HOST=
DATABASE_PORT=
DATABASE_USER=
DATABASE_PWD
DATABASE_NAME=
CLIENT_HOST=

Thanks for everyone who has spent their time trying to help me ;)

CodePudding user response:

What's happening is you're importing env.ts before the ConfigModule has imported and set the variables in your .env file.

This is why calling require('dotenv').config() works. Under the hood, that's what the ConfigModule is doing for you. However, your call to ConfigModule.forRoot is happening after you import env.ts, so the .env file hasn't been imported yet and those variables don't yet exist.

I would highly recommend you take a look at custom configuration files, which handles this for you the "Nest way":

From the Nest docs, but note that you could also use the env.ts file you already have:

// env.ts
export default () => ({
  // Add your own properties here however you'd like
  port: parseInt(process.env.PORT, 10) || 3000,
  database: {
    host: process.env.DATABASE_HOST,
    port: parseInt(process.env.DATABASE_PORT, 10) || 5432
  }
});

And then modify your AppModule to the following. Note that we're using the forRootAsync so that we can get a handle to the ConfigService and grab the variable from that.

// app.module.ts
import configuration from './common/env';

@Module({
  imports: [
    ConfigModule.forRoot({
      load: [configuration],
    }),
    //  
    MongooseModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: async (configService: ConfigService) => ({
        uri: configService.get<string>('MONGO_URI'),
      }),
      inject: [ConfigService],
    });
  ],
})
export class AppModule {}

As an alternative, you could also just call require('dotenv').config() inside your env.ts file at the top, but you'll miss out on all the ConfigModule helpers like dev/prod .env files.

  • Related