config icon indicating copy to clipboard operation
config copied to clipboard

Using ConfigModule.forFeature with the same property names in different feature configurations results in properties being overridden

Open IRCraziestTaxi opened this issue 2 years ago • 0 comments

Is there an existing issue for this?

  • [X] I have searched the existing issues

Current behavior

Suppose we have the following .env:

APPLE_API_URL = 'http://apples.com/api/v1'
ORANGE_API_URL = 'http://oranges.com/api/v1'

There are two generated library modules:

AppleModule

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { AppleService } from './apple.service';
import { appleConfig } from './config';

@Module({
  imports: [ConfigModule.forFeature(appleConfig)],
  providers: [AppleService],
  exports: [AppleService],
})
export class AppleModule {}

appleConfig

import { AppleConfig } from './interfaces';

export const appleConfig = (): AppleConfig => ({
  apiUrl: process.env.APPLE_API_URL,
});

OrangeModule

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { orangeConfig } from './config';
import { OrangeService } from './orange.service';

@Module({
  imports: [ConfigModule.forFeature(orangeConfig)],
  providers: [OrangeService],
  exports: [OrangeService],
})
export class OrangeModule {}

orangeConfig

import { OrangeConfig } from './interfaces';

export const orangeConfig = (): OrangeConfig => ({
  apiUrl: process.env.ORANGE_API_URL,
});

The idea here is that I want to shorten ConfigService property names from things like appleApiUrl and orangeApiUrl to simple apiUrl in each feature's ConfigModule registration.

In this minimal reproduction, the services are simply logging what each module's ConfigService is getting for apiUrl.

apple.service.ts

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

@Injectable()
export class AppleService {
  public constructor(
    private readonly configService: ConfigService<AppleConfig>,
  ) {}

  public doAppleThing(): void {
    const apiUrl = this.configService.get('apiUrl');
    console.log(apiUrl);
  }
}

orange.service.ts

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

@Injectable()
export class OrangeService {
  public constructor(
    private readonly configService: ConfigService<OrangeConfig>,
  ) {}

  public doOrangeThing(): void {
    const apiUrl = this.configService.get('apiUrl');
    console.log(apiUrl);
  }
}

The app's controller has GET routes apple and orange that simply call the respective service's method.

Making a request to http://localhost:3000/orange logs the expected value:

'http://oranges.com/api/v1'

However, making a request to http://localhost:3000/apple also logs the "orange API URL" rather than the "apple API URL" as expected:

'http://oranges.com/api/v1'

This tells me that, since OrangeModule is imported after AppleModule in AppModule, OrangeModule's ConfigModule.forFeature registration is overriding the value of apiUrl in AppleModule's configuration even though they should be separate registrations.

Minimum reproduction code

https://github.com/IRCraziestTaxi/nestjs-config-same-prop-name-undefined

Steps to reproduce

See "Current behavior" section above and minimal reproduction.

Expected behavior

Since the two feature modules' ConfigModule.forFeature registrations are for separate modules using a forFeature call, each module's configuration for apiUrl should be unique per the config factory passed into forFeature.

Package version

2.2.0

NestJS version

9.1.4

Node.js version

18.3.0

In which operating systems have you tested?

  • [X] macOS
  • [ ] Windows
  • [ ] Linux

Other

No response

IRCraziestTaxi avatar Oct 08 '22 03:10 IRCraziestTaxi