[NestJS]- Referring .env variables returning 'undefined'

123 views Asked by At

I have been developing a rest API using NestJS with typescript. I have followed nestjs documentation on configuration and tried all sorts of tricks to inject env vars from .env.${NODE_ENV} files. But each time i am hit with undefined as the result. I have exhausted all ideas. Now I am looking towards the community for help. Please help me to identify where am i making a mistake.

tsconfig.ts

{
  "compilerOptions": {
    "module": "commonjs",
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "noUnusedLocals": true,
    "target": "es6",
    "sourceMap": true,
    "outDir": "./dist",
    "baseUrl": "./"
  },
  "exclude": ["node_modules", "dist"]
}

config.ts

import { Logger } from '@nestjs/common';
import { config } from 'dotenv';
import path = require('path');

const envpath = path.join(__dirname, 'envs', `.env.${process.env.NODE_ENV}`)
Logger.log(`using profile: ${process.env.NODE_ENV} & config path ${envpath}`);
config({ path: envpath });

const APPCONFIG = {
  ENABLE_CORS: process.env.ENABLE_CORS === 'true',
  SWAGGER_SCHEME: (process.env.SWAGGER_SCHEME as 'http' | 'https') || 'https',
  APP_VERSION: process.env.APP_VERSION,
  DEPENDENT_SERVICE_BASE_URL: process.env.DEPENDENT_SERVICE_BASE_URL,
};

export { APPCONFIG };

app.module.ts

import { Logger, Module } from '@nestjs/common';
import { AuthModule } from './abcd/authentication/auth.module';
import { HealthModule } from './health-api/health.module';
import { ConfigModule } from '@nestjs/config';
import path = require('path');

const envpath = path.join(__dirname, 'envs', `.env.${process.env.NODE_ENV}`)
Logger.log(`using path for configService : ${envpath}`);

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true, envFilePath: `${envpath}` }),
    AuthModule,
    HealthModule
  ]
})
export class AppModule { }

auth.module.ts

import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';

@Module({
  imports: [],
  controllers: [AuthController],
  providers: [AuthService],
  exports: [AuthService]
})
export class AuthModule { }

auth.service.ts

import { ConfigService } from '@nestjs/config';

@Injectable()
export class AuthService {
constructor(private readonly configService: ConfigService)

async timestamp(timestampDto: TimestampDto, timeStampUrl: string, authorization: string): Promise<any> {
    try {

      const token = this.extractBearerToken(authorization);
      this.logger.log(token);

      const downstreamAPIUrl = this.ConfigService.get(TIMESTAMP_URL);
      console.log('API URL: ' +downstreamAPIUrl);   #returning undefined

      const headers = {
        Authorization: `Bearer ${token}`
      };

      this.logger.log(timestampDto.payload);
      this.logger.log(timestampDto.publicCert);

      const formData = new FormData();
      formData.append('payload', timestampDto.payload);
      formData.append('publicCert', timestampDto.publicCert);

      headers['timestampUrl'] = timeStampUrl;

      const response = await axios.post(downstreamAPIUrl, formData, { headers });

      return response.data;
    } ...

I have .env.{NODE_ENV} files in envs folder. the below is my project structure.

this is my directory structure...

├───dependencies
│   └───interface
├───abcd
│   └───authentication
│       └───dto
│           └───interface
├───envs
├───exceptions
│   ├───filters
│   └───persistors
├───health-api
│   └───interface
├───launchdarklyConfig
│   └───__mocks__
├───logger

this is inside src/production/abcd/app path.

My .env.${NODE_ENV} file contains

NODE_ENV=prod
TIMESTAMP_URL=https://abcde.com/api/timestamp

I have also tried not to use dotenv as @nestjs/config is wrapper around dotenv package. As per their documentation, ConfigModule and ConfigService must be leveraged to fetch the values.

My regards to everyone in advance.

1

There are 1 answers

2
Tal Rofe On

I created this minimal reproduce which works for me. You might want to compare your code to mine, it seems quite identical though (assuming you have typos in your code). Note that I just used nest new . to create this project from 0. Then just added the ConfigModule to the code.

package.json file:

{
  "name": "playground-nest",
  "version": "0.0.1",
  "description": "",
  "author": "",
  "private": true,
  "license": "UNLICENSED",
  "scripts": {
    "build": "nest build",
    "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
    "start": "nest start",
    "start:dev": "nest start --watch"
  },
  "dependencies": {
    "@nestjs/common": "^10.0.0",
    "@nestjs/config": "^3.1.1",
    "@nestjs/core": "^10.0.0",
    "@nestjs/platform-express": "^10.0.0",
    "reflect-metadata": "^0.1.13",
    "rxjs": "^7.8.1"
  },
  "devDependencies": {
    "@nestjs/cli": "^10.0.0",
    "@nestjs/schematics": "^10.0.0",
    "@nestjs/testing": "^10.0.0",
    "@types/express": "^4.17.17",
    "@types/jest": "^29.5.2",
    "@types/node": "^20.3.1",
    "@types/supertest": "^2.0.12",
    "@typescript-eslint/eslint-plugin": "^6.0.0",
    "@typescript-eslint/parser": "^6.0.0",
    "eslint": "^8.42.0",
    "eslint-config-prettier": "^9.0.0",
    "eslint-plugin-prettier": "^5.0.0",
    "jest": "^29.5.0",
    "prettier": "^3.0.0",
    "source-map-support": "^0.5.21",
    "supertest": "^6.3.3",
    "ts-jest": "^29.1.0",
    "ts-loader": "^9.4.3",
    "ts-node": "^10.9.1",
    "tsconfig-paths": "^4.2.0",
    "typescript": "^5.1.3"
  }
}

tsconfig.json file:

{
  "compilerOptions": {
    "module": "commonjs",
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "target": "ES2021",
    "sourceMap": true,
    "outDir": "./dist",
    "baseUrl": "./",
    "incremental": true,
    "skipLibCheck": true,
    "strictNullChecks": false,
    "noImplicitAny": false,
    "strictBindCallApply": false,
    "forceConsistentCasingInFileNames": false,
    "noFallthroughCasesInSwitch": false,
    "esModuleInterop": true
  }
}

Some dummy environment variable file, .yazif.env:

TIMESTAMP_URL=https://abcde.com/api/timestamp

main.ts file:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

app.module.ts file:

import path from 'node:path';

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';

import { AppController } from './app.controller';
import { AppService } from './app.service';

const envFilePath = path.join(path.dirname(__dirname), '.yazif.env');

console.log(envFilePath);

@Module({
  imports: [ConfigModule.forRoot({ isGlobal: true, envFilePath})],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

app.service.ts file:

import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';

@Injectable()
export class AppService {
  constructor(private readonly configService: ConfigService) {}

  getHello(): string {
    console.log(this.configService.get('TIMESTAMP_URL'));

    return 'Hello World!';
  }
}

app.controller.ts file:

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}

Then, after doing on terminal curl http://localhost:3000 I can see on the terminal log the environment variable indeed set: https://abcde.com/api/timestamp