Search code examples
javascriptnode.jstypescriptnestjs

How to read environment variables inside the app module import statement?


I have created a module called DmaModule, and expect the consumer of it to pass in some options into it during its instantiation. These options are dmaHost, superadminUsername, and superadminPassword. In order for me to get these options, I have to grab them from the environment variables.

I want to pass them as options, and I don't want to read them as environment variables inside the module.

Here is my AppModule:

import { Module } from '@nestjs/common';
import { DmaModule } from '@app/dma';
import { ConfigModule, ConfigService } from '@nestjs/config';
@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true, // Makes the ConfigModule globally available
      envFilePath: `.env.${process.env.NODE_ENV}`,
    }),
    DmaModule.forRootAsync({
      inject: [ConfigService],
      imports: [ConfigModule],
      useFactory: (config: ConfigService) => ({
        dmaHost: config.get<string>('DMA_HOST'),
        superadminUsername: config.get<string>('DMA_SUPERADMIN_USERNAME'),
        superadminPassword: config.get<string>('DMA_SUPERADMIN_PASSWORD'),
      }),
    }),
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

And here is my DmaModule:

import { DynamicModule, Module } from '@nestjs/common';
import { DmaClientProvider } from './providers/dma-client-provider/dma-client-provider';

export const DMA_PROPS_TOKEN = 'DMA_MODULE_OPTIONS';

export interface DmaModuleProps {
  dmaHost: string;
  superadminUsername: string;
  superadminPassword: string;
}

interface RootOptions {
  imports: any[];
  inject: any[];
  useFactory: (...args: any[]) => DmaModuleProps;
}

@Module({
  imports: [],
  providers: [DmaClientProvider], // << šŸ‘ˆ take a look here
  exports: [],
})
export class DmaModule {
  static forRootAsync(options: RootOptions): DynamicModule {
    return {
      module: DmaModule,
      providers: [
        {
          provide: DMA_PROPS_TOKEN,
          useFactory: options.useFactory,
          inject: options.inject,
        },
      ],
      exports: [DMA_PROPS_TOKEN],
    };
  }
}

I want now to start using these options passed to the DmaModule and be able to give them to the services and providers listed under the DmaModule.

So I have a provider called DmaClientProvider, and I want to simply console.log() the options passed from the AppModule down to the DmaModule inside of the DmaClientProvider.

Here is my DmaClientProvider code:

import { Inject, Injectable } from '@nestjs/common';
import { DMA_PROPS_TOKEN, DmaModuleProps } from '@app/dma/dma.module';

@Injectable()
export class DmaClientProvider {
  constructor(
    @Inject(DMA_PROPS_TOKEN) private readonly options: DmaModuleProps,
  ) {
    console.log(options);
  }
}

I thought it will work, but I am getting this error:

ā•°ā”€>$ npm run start:dev                                                                                                                                               20:02:11

> [email protected] start:dev
> cross-env NODE_ENV=development nest start --watch


 Info  Webpack is building your sources...

webpack 5.92.1 compiled successfully in 408 ms
Type-checking in progress...
[Nest] 290429  - 07/12/2024, 8:02:17 PM     LOG [NestFactory] Starting Nest application...
[Nest] 290429  - 07/12/2024, 8:02:17 PM     LOG [InjectorLogger] Nest encountered an undefined dependency. This may be due to a circular import or a missing dependency declaration.
[Nest] 290429  - 07/12/2024, 8:02:17 PM   ERROR [ExceptionHandler] Nest can't resolve dependencies of the DmaClientProvider (?). Please make sure that the argument dependency at index [0] is available in the DmaModule context.

Potential solutions:
- Is DmaModule a valid NestJS module?
- If dependency is a provider, is it part of the current DmaModule?
- If dependency is exported from a separate @Module, is that module imported within DmaModule?
  @Module({
    imports: [ /* the Module containing dependency */ ]
  })

Error: Nest can't resolve dependencies of the DmaClientProvider (?). Please make sure that the argument dependency at index [0] is available in the DmaModule context.

Potential solutions:
- Is DmaModule a valid NestJS module?
- If dependency is a provider, is it part of the current DmaModule?
- If dependency is exported from a separate @Module, is that module imported within DmaModule?
  @Module({
    imports: [ /* the Module containing dependency */ ]
  })

Here is my folder structure:

ā•°ā”€>$ tree -I node_modules/                                                                                                                                           21:16:25
.
ā”œā”€ā”€ dist
ā”‚Ā Ā  ā””ā”€ā”€ main.js
ā”œā”€ā”€ libs
ā”‚Ā Ā  ā””ā”€ā”€ dma
ā”‚Ā Ā      ā”œā”€ā”€ src
ā”‚Ā Ā      ā”‚Ā Ā  ā”œā”€ā”€ dma.module.ts
ā”‚Ā Ā      ā”‚Ā Ā  ā”œā”€ā”€ dma.service.spec.ts
ā”‚Ā Ā      ā”‚Ā Ā  ā”œā”€ā”€ dma.service.ts
ā”‚Ā Ā      ā”‚Ā Ā  ā”œā”€ā”€ index.ts
ā”‚Ā Ā      ā”‚Ā Ā  ā”œā”€ā”€ modules
ā”‚Ā Ā      ā”‚Ā Ā  ā”‚Ā Ā  ā”œā”€ā”€ dma-user-groups
ā”‚Ā Ā      ā”‚Ā Ā  ā”‚Ā Ā  ā”‚Ā Ā  ā”œā”€ā”€ dma-user-groups.module.ts
ā”‚Ā Ā      ā”‚Ā Ā  ā”‚Ā Ā  ā”‚Ā Ā  ā”œā”€ā”€ dma-user-groups.service.spec.ts
ā”‚Ā Ā      ā”‚Ā Ā  ā”‚Ā Ā  ā”‚Ā Ā  ā””ā”€ā”€ dma-user-groups.service.ts
ā”‚Ā Ā      ā”‚Ā Ā  ā”‚Ā Ā  ā””ā”€ā”€ dma-users
ā”‚Ā Ā      ā”‚Ā Ā  ā”‚Ā Ā      ā”œā”€ā”€ dma-users.module.ts
ā”‚Ā Ā      ā”‚Ā Ā  ā”‚Ā Ā      ā”œā”€ā”€ dma-users.service.spec.ts
ā”‚Ā Ā      ā”‚Ā Ā  ā”‚Ā Ā      ā””ā”€ā”€ dma-users.service.ts
ā”‚Ā Ā      ā”‚Ā Ā  ā””ā”€ā”€ providers
ā”‚Ā Ā      ā”‚Ā Ā      ā””ā”€ā”€ dma-client-provider
ā”‚Ā Ā      ā”‚Ā Ā          ā”œā”€ā”€ dma-client-provider.spec.ts
ā”‚Ā Ā      ā”‚Ā Ā          ā”œā”€ā”€ dma-client-provider.ts
ā”‚Ā Ā      ā”‚Ā Ā          ā””ā”€ā”€ dmaEndpoints.ts
ā”‚Ā Ā      ā””ā”€ā”€ tsconfig.lib.json
ā”œā”€ā”€ nest-cli.json
ā”œā”€ā”€ package.json
ā”œā”€ā”€ package-lock.json
ā”œā”€ā”€ README.md
ā”œā”€ā”€ src
ā”‚Ā Ā  ā”œā”€ā”€ app.controller.spec.ts
ā”‚Ā Ā  ā”œā”€ā”€ app.controller.ts
ā”‚Ā Ā  ā”œā”€ā”€ app.module.ts
ā”‚Ā Ā  ā”œā”€ā”€ app.service.ts
ā”‚Ā Ā  ā””ā”€ā”€ main.ts
ā”œā”€ā”€ test
ā”‚Ā Ā  ā”œā”€ā”€ app.e2e-spec.ts
ā”‚Ā Ā  ā””ā”€ā”€ jest-e2e.json
ā”œā”€ā”€ tsconfig.build.json
ā””ā”€ā”€ tsconfig.json

11 directories, 28 files


Solution

  • The solution was to simply use the name of the token litrally, and not to import it, I don't know why, if someone knows please let me know in the comments so I can update my answer.

    I needed to change the

    import { DMA_PROPS_TOKEN, DmaModuleProps } from '@app/dma/dma.module
    // ...
    @Inject(DMA_PROPS_TOKEN) private readonly options: DmaModuleProps
    

    to

    import { DMA_PROPS_TOKEN, DmaModuleProps } from '@app/dma/dma.module
    // ...
    @Inject("DMA_PROPS_TOKEN") private readonly options: DmaModuleProps