diff --git a/lib/config.module.ts b/lib/config.module.ts index 5d5d489b..07ca2589 100644 --- a/lib/config.module.ts +++ b/lib/config.module.ts @@ -53,8 +53,8 @@ export class ConfigModule { * Also, registers custom configurations globally. * @param options */ - static async forRoot( - options: ConfigModuleOptions = {}, + static async forRoot>( + options: ConfigModuleOptions = {}, ): Promise { const envFilePaths = Array.isArray(options.envFilePath) ? options.envFilePath @@ -65,12 +65,13 @@ export class ConfigModule { ? {} : this.loadEnvFile(envFilePaths, options); - if (!options.ignoreEnvVars) { + if (!options.ignoreEnvVars || options.validatePredefined) { config = { ...config, ...process.env, }; } + if (options.validate) { const validatedConfig = options.validate(config); validatedEnvConfig = validatedConfig; @@ -101,9 +102,14 @@ export class ConfigModule { const configServiceProvider = { provide: ConfigService, useFactory: (configService: ConfigService) => { + const untypedConfigService = configService as any; if (options.cache) { - (configService as any).isCacheEnabled = true; + untypedConfigService.isCacheEnabled = true; + } + if (options.skipPredefined) { + untypedConfigService.skipPredefined = true; } + configService.setEnvFilePaths(envFilePaths); return configService; }, diff --git a/lib/config.service.ts b/lib/config.service.ts index f7e280c6..188ef51b 100644 --- a/lib/config.service.ts +++ b/lib/config.service.ts @@ -54,8 +54,17 @@ export class ConfigService< return this._isCacheEnabled; } + private set skipPredefined(value: boolean) { + this._skipPredefined = value; + } + + private get skipPredefined(): boolean { + return this._skipPredefined; + } + private readonly cache: Partial = {} as any; private readonly _changes$ = new Subject(); + private _skipPredefined = false; private _isCacheEnabled = false; private envFilePaths: string[] = []; @@ -122,6 +131,11 @@ export class ConfigService< defaultValueOrOptions?: T | ConfigGetOptions, options?: ConfigGetOptions, ): T | undefined { + const internalValue = this.getFromInternalConfig(propertyPath); + if (!isUndefined(internalValue)) { + return internalValue; + } + const validatedEnvValue = this.getFromValidatedEnv(propertyPath); if (!isUndefined(validatedEnvValue)) { return validatedEnvValue; @@ -132,14 +146,14 @@ export class ConfigService< ? undefined : defaultValueOrOptions; - const processEnvValue = this.getFromProcessEnv(propertyPath, defaultValue); - if (!isUndefined(processEnvValue)) { - return processEnvValue; - } - - const internalValue = this.getFromInternalConfig(propertyPath); - if (!isUndefined(internalValue)) { - return internalValue; + if (!this.skipPredefined) { + const processEnvValue = this.getFromProcessEnv( + propertyPath, + defaultValue, + ); + if (!isUndefined(processEnvValue)) { + return processEnvValue; + } } return defaultValue as T; diff --git a/lib/interfaces/config-module-options.interface.ts b/lib/interfaces/config-module-options.interface.ts index 49aaa9d8..0c968f40 100644 --- a/lib/interfaces/config-module-options.interface.ts +++ b/lib/interfaces/config-module-options.interface.ts @@ -4,7 +4,9 @@ import { ConfigFactory } from './config-factory.interface'; /** * @publicApi */ -export interface ConfigModuleOptions { +export interface ConfigModuleOptions< + ValidationOptions extends Record = Record, +> { /** * If "true", values from the process.env object will be cached in the memory. * This improves the overall application performance. @@ -25,6 +27,7 @@ export interface ConfigModuleOptions { /** * If "true", predefined environment variables will not be validated. + * @deprecated Use `validatePredefined` instead. */ ignoreEnvVars?: boolean; @@ -42,6 +45,19 @@ export interface ConfigModuleOptions { */ validate?: (config: Record) => Record; + /** + * If "true", predefined environment variables will be validated. + * Predefined environment variables are process variables that were set before the module was imported. + * @default true + */ + validatePredefined?: boolean; + + /** + * If "true", predefined environment variables will be ignored and not picked up by the `ConfigService#get` method. + * @default false + */ + skipPredefined?: boolean; + /** * Environment variables validation schema (Joi). */ @@ -51,7 +67,7 @@ export interface ConfigModuleOptions { * Schema validation options. * See: https://joi.dev/api/?v=17.3.0#anyvalidatevalue-options */ - validationOptions?: Record; + validationOptions?: ValidationOptions; /** * Array of custom configuration files to be loaded. diff --git a/tests/e2e/load-priority.spec.ts b/tests/e2e/load-priority.spec.ts index 50b59f56..36a36980 100644 --- a/tests/e2e/load-priority.spec.ts +++ b/tests/e2e/load-priority.spec.ts @@ -80,9 +80,9 @@ describe('Environment variables and .env files', () => { await app.init(); }); - it('should choose .env file vars over load configuration', () => { + it('should choose the load configuration over .env file vars', () => { const configService = app.get(ConfigService); - expect(configService.get('PORT')).toEqual('4000'); + expect(configService.get('PORT')).toEqual('8000'); }); }); diff --git a/tests/e2e/skip-predefined.spec.ts b/tests/e2e/skip-predefined.spec.ts new file mode 100644 index 00000000..e160e3b7 --- /dev/null +++ b/tests/e2e/skip-predefined.spec.ts @@ -0,0 +1,29 @@ +import { INestApplication } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import { ConfigModule, ConfigService } from '../../lib'; +import { AppModule } from '../src/app.module'; + +describe('Environment variables (skip predefined)', () => { + let app: INestApplication; + + beforeEach(async () => { + const module = await Test.createTestingModule({ + imports: [AppModule.withSkipPredefined()], + }).compile(); + + app = module.createNestApplication(); + await app.init(); + }); + + it(`should ignore predefined environment variables`, async () => { + process.env.RANDOM_PREDEFINED = 'test'; + await ConfigModule.envVariablesLoaded; + + const configService = app.get(ConfigService); + expect(configService.get('RANDOM_PREDEFINED')).toBeUndefined(); + }); + + afterEach(async () => { + await app.close(); + }); +}); diff --git a/tests/src/app.module.ts b/tests/src/app.module.ts index 5d39b1de..b0570c9a 100644 --- a/tests/src/app.module.ts +++ b/tests/src/app.module.ts @@ -68,6 +68,19 @@ export class AppModule { }; } + static withSkipPredefined(): DynamicModule { + return { + module: AppModule, + imports: [ + ConfigModule.forRoot({ + envFilePath: join(__dirname, '.env'), + load: [() => ({ obj: { test: 'true', test2: undefined } })], + skipPredefined: true, + }), + ], + }; + } + static withEnvVars(): DynamicModule { return { module: AppModule,