Mohit Bhardwaj
Mohit Bhardwaj

Reputation: 10083

NestJS - How to use .env variables in main app module file for database connection

I am working on my first NestJS application, which was working fine with hardcoded database connecting string in app.module.ts.

But then as per our requirements, I had to pick the database config values from environment files. For that, I followed the configuration documentation on the nestjs documentation website - https://docs.nestjs.com/techniques/configuration

But the issue is that I need to use the .env variables inside the same file for database connection, which is failing.

Here is my original code that was working fine:

@Module({
  imports: [
    MongooseModule.forRoot(`mongodb+srv://myusername:[email protected]?retryWrites=true&w=majority&db=dbname`, { useNewUrlParser: true, dbName: 'dbname' }),
    ProductModule,
    CategoryModule,
  ],
  controllers: [
    AppController,
    HealthCheckController,
  ],
  providers: [AppService, CustomLogger],
})

Now, I wanted to pick those DB values from .env files which are like local.env, dev.env etc. depending on the environment. Now, my this code is not working:

@Module({
  imports: [
    ConfigModule.forRoot({ envFilePath: `${process.env.NODE_ENV}.env` }),
    MongooseModule.forRoot(`mongodb+srv://${ConfigModule.get('DB_USER')}:${ConfigModule.get('DB_PASS')}@myhost.net?retryWrites=true&w=majority&db=dbname`, { useNewUrlParser: true, dbName: 'dbname' }),
    ProductModule,
    CategoryModule,
  ],
  controllers: [
    AppController,
    HealthCheckController,
  ],
  providers: [AppService, CustomLogger],
})

Upvotes: 110

Views: 188428

Answers (11)

Iliyas
Iliyas

Reputation: 71

db.module.ts

import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { SnakeNamingStrategy } from 'typeorm-naming-strategies';

@Module({
  imports: [
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: () => ({
        type: 'postgres',
        host: process.env['DB_HOST'],
        port: +process.env['DB_PORT'],
        username: process.env['DB_USER'],
        password: process.env['DB_PASSWORD'],
        database: process.env['DB_DATABASE'],
        synchronize: true,
        logging: false,
        entities: [__dirname + '/../**/*.entity.{js,ts}'],
        autoLoadEntities: true,
        namingStrategy: new SnakeNamingStrategy(),
      }),
    }),
  ],
})
export class DatabaseModule {}

Ensure that the DatabaseModule, as well as any other module, is positioned below the config module. This allows environment variables to be loaded at the application level first. For the DatabaseModule, you can use the async functions provided by any ORM during configuration.

app.module.ts

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      cache: true,
      envFilePath: [`.env.${process.env.NODE_ENV}`, '.env'],
    }),
    CacheModule.register({
      isGlobal: true,
    }),
    DatabaseModule,
    BidsModule,
    UsersModule,
  ],})

Upvotes: 2

Torsten Barthel
Torsten Barthel

Reputation: 3458

If someone needs an answer on how to use .env vars inside a configuration.js file used when initializing ConfigModule here is a little workaround I found. Otherwise it was not possible to use this defined environment vars inside the configuration.js. The approach to handle this configuration in app.module.ts:

import { ConfigModule } from '@nestjs/config'
import { configuration } from '@testlib/config'

// ...
// hacky way to make it work
ConfigModule.forRoot(),
ConfigModule.forRoot({
    load: [configuration],
    isGlobal: true,
}),

The solution was to call ConfigModule.forRoot() twice: First to make env vars privatly available and second the real initialization with configuration.js this time with appropiatly filled env vars. Without this first step the env vars are not accessible.

Here is an example of used configuration.js:

import 'dotenv/config'

let configuration
const e = process.env

if (e.NODE_ENV && e.NODE_ENV === 'development') {
    // local dev config
    configuration = {
        test: e.TEST,
        uri: 'http://localhost:2001/api/'
    }
} else {
    // production config
    configuration = {
        test: e.TEST,
        uri: e.URI
    }
}

const func = () => configuration

export { func as configuration }

Upvotes: 0

Wisdom
Wisdom

Reputation: 11

One way to implement this is to generate a database module using the @nestjs/cli (you can create it manually if you like), then in the imports array of the module, add this line of code

imports: [
    MongooseModule.forRootAsync({
        imports: [ConfigModule],
        useFactory: (configService: ConfigService) => ({
            uri: configService.get('MONGODB_URI'),
        }),
        inject: [ConfigService],
    }),
],

where ConfigService is imported from @nestjs/config (You can install it if you don't have it in your project).

Ensure you add the MONGODB_URI in your .env file and you should be good to go.

Then you can use the DatabaseModule in your other modules by adding it to the imports array like this:

imports: [
    DatabaseModule,
    DatabaseModule.forFeature([
        { name: AnyDocumentYouWantToRegister.name, schema: AnySchemaYouWantToRegisterToMongoose },
    ]),
]

I hope this helps someone.

Upvotes: 0

smoke22catches
smoke22catches

Reputation: 11

There is much simpler way, that described in documentation. In main.ts file you need to access config service:

const configService = app.get(ConfigService);

And after that get access to environment variable:

const port = configService.get('PORT');

Upvotes: 0

Mohini Mishra
Mohini Mishra

Reputation: 21

MongooseModule.forRootAsync({
  useFactory: () => ({
    uri: process.env.DB_CONNECTION
  }),
}),
ConfigModule.forRoot({isGlobal: true,}),

Upvotes: 2

Sarnava
Sarnava

Reputation: 75

To use NestJS configuration (dot-env) in the app.module.ts itself, use MongooseModule.forRootAsync({}). Here is my code.

  @Module({
  imports: [
    MongooseModule.forRootAsync({
      useFactory: () => ({
        uri: process.env.CONNECTION_STRING,
      }),
    }),
    ConfigModule.forRoot(
      {
        isGlobal: true
      }
    )
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule { }

Upvotes: 4

andsilver
andsilver

Reputation: 5972

1. Keeping using ConfigModule

You need to set NODE_ENV in npm scripts so that it can be used to load an env file based on the env.

"scripts": {
  "start:local": "NODE_ENV=local npm run start"
  "start:dev": "NODE_ENV=dev npm run start"
}

Now you can just use the ConfigModule:

@Module({
  imports: [
    ConfigModule.forRoot({ envFilePath: `${process.env.NODE_ENV}.env` }), 
MongooseModule.forRoot(`mongodb+srv://${process.env.DB_USER}:${process.env.DB_PASS}@myhost.net?retryWrites=true&w=majority&db=dbname`, { useNewUrlParser: true, dbName: 'dbname' })
    ...
})

2. Using dotenv

npm install dotenv

Add some scripts to your package.json to set what env you are in.

"scripts": {
  ...
  "start:local": "NODE_ENV=local npm run start"
  "start:dev": "NODE_ENV=dev npm run start"
}

Import dotenv in main.ts file. Make sure you do it at the top of the file.

require('dotenv').config({ path: `../${process.env.NODE_ENV}.env` });

3. Using env-cmd

You can use env-cmd npm package.

npm install env-cmd

And add some commands for different envs in package.json, for example:

"scripts": {
  ...
  "start:local": "env-cmd -f local.env npm run start"
  "start:dev": "env-cmd -f dev.env npm run start"
}
...

Now you can use the env variables, for example:

MongooseModule.forRoot(`mongodb+srv://${process.env.DB_USER}:${process.env.DB_PASS}@myhost.net?retryWrites=true&w=majority&db=dbname`, { useNewUrlParser: true, dbName: 'dbname' })

process.env.MONGO_CONNECTION_STRING

Update:

To overcome the env set command problem in different platforms, you can install cross-env package.

npm install -D cross-env

And update the scripts:

"scripts": {
  "start:local": "cross-env NODE_ENV=local npm run start"
  "start:dev": "cross-env NODE_ENV=dev npm run start"
}

Upvotes: 89

swedge218
swedge218

Reputation: 4113

From Nestjs docs here - https://docs.nestjs.com/techniques/configuration

These steps worked for me with MySQL and TypeORM.

  1. Install Nestjs config module - npm i --save @nestjs/config. It relies on dotenv

  2. Create a .env file in your root folder and add your key/value pairs e.g. DATABASE_USER=myusername

  3. Open app.module.ts and import the config module

    import { ConfigModule } from '@nestjs/config';
  1. Add below line to the imports section of app.module.ts. I added it a the first import. It will load the contents of the .env file automatically.
    ConfigModule.forRoot(),
  1. Then you can begin to use the env variables as per the usual process.env.<variable_name> in the database config section e.g.
    process.env.DATABASE_USER

For more configuration of the ConfigModule, see the link above. You can use a custom file/path and set the module visible globally.

Upvotes: 127

kosiakMD
kosiakMD

Reputation: 1062

MongooseModule.forRootAsync({
  imports: [ConfigModule],
  useFactory: async (configService: ConfigService) => ({
    uri: configService.get<string>('MONGODB_URI'),
  }),
  inject: [ConfigService],
});

Upvotes: 38

Raiyad Raad
Raiyad Raad

Reputation: 537

By using nestjs/config package:

npm install @nestjs/config

After installing the package, in the app module (app.module.ts file):

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

@Module({
  imports: [ConfigModule.forRoot()],
})

export class AppModule {}

After that .env files can be accessed all over the app. Suppose your .env file looks like this.

DB_USER=mohit

to access DB_USER variable use process.env.DB_USER

Upvotes: 24

Daniel
Daniel

Reputation: 2531

You need to use the MongooseModule.forRootAsync(() => {...}) instead of MongooseModule.forRoot(...)

This makes MongooseModule wait for its IOC dependencies.

See: https://docs.nestjs.com/techniques/mongodb#async-configuration

Upvotes: 12

Related Questions