NestJS: Configuration and Environment Variables

Updated: January 1, 2024 By: Guest Contributor Post a comment

Introduction

Mastering configuration management is critical for building scalable, maintainable, and adaptable web applications. This guide delves into NestJS, a progressive Node.js framework, outlining vital practices to handle configuration and environment variables effectively, thus laying the groundwork for robust and secure applications.

Setting up Configuration

Begin your journey with the installation of the configuration package:

$ npm install @nestjs/config

Then, import the ConfigModule in your app module:

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

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

This instantiates the ConfigModule globally, affording you access to environment variables via injection throughout your application.

Utilizing Dotenv Files

Introduce a .env file at your project root:

DB_HOST=localhost
DB_PORT=5432

Environment-specific variables can be managed with multiple dotenv files, e.g. .env.development, .env.test, and so on.

The ConfigModule automatically loads variables based on your NODE_ENV:

ConfigModule.forRoot({
  envFilePath: ".env." + process.env.NODE_ENV,
})

Typed Configuration Service

Create a typed configuration service to ensure the safe use of environment variables:

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

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

  get databaseHost(): string {
    return this.configService.get<string>('DB_HOST');
  }

  // Other configuration methods
}

This service can now be injected wherever it’s needed, offering safe access to the variables with the correct type.

Validation Schema

Add Joi or any other validation library to aid in the creation of validation schemas for your environment configurations:

import * as Joi from 'joi';

ConfigModule.forRoot({
  validationSchema: Joi.object({
    DB_HOST: Joi.string().required(),
    DB_PORT: Joi.number().default(5432),
  }),
})

The schema ensures all required variables are set and optionally assigns defaults.

Configuration Namespaces

Isolate configurations into namespaces to maintain organization:

ConfigModule.forRoot({
  load: [databaseConfig],
})

export const databaseConfig = () => ({
  database: {
    host: process.env.DB_HOST,
    port: process.env.DB_PORT,
  },
});

Access these namespaced settings within services using the get() method with the path to the configuration key:

const dbConfig = this.configService.get('database');

Async Configuration

For asynchronous operations, the ConfigModule supports async methods:

import { ConfigService } from '@nestjs/config';
import { DatabaseModule } from './database/database.module';

@Module({
  imports: [
    DatabaseModule,
    ConfigModule.forRootAsync({
      useClass: AppConfigService,
    }),
  ],
})
export class AppModule {}

This technique is beneficial if configs need to be fetched from a service, such as a database or API.

Environment Configuration for Modules

It’s often useful to have module-specific configurations:

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

@Module({
  imports: [
    ConfigModule.forFeature(databaseConfig),
  ],
})
export class DatabaseModule {}

Now databaseConfig is accessible solely within the DatabaseModule, helping to encapsulate configuration details.

Using Config in Other Services

Here’s an example of a service using the AppConfigService to access its configuration:

import { Injectable } from '@nestjs/common';
import { AppConfigService } from './app-config.service';

@Injectable()
export class SomeService {
  constructor(private config: AppConfigService) {
    const dbHost = this.config.databaseHost;
    // Use dbHost
  }
}

Custom Configuration Files

For complex setups, create custom configuration files and import them selectively:

import { ConfigModule } from '@nestjs/config';
import customConfig from './config/custom.config';

@Module({
  imports: [
    ConfigModule.forRoot({
      load: [customConfig],
    }),
  ],
})
export class CustomModule {}

Best Practices

  • Security: Never commit sensitive environment variables to version control. Use secrets management systems like Vault or environment management tools.
  • Validation: Always validate environment variables to prevent runtime errors.
  • Documentation: Clearly document all environment variables for ease of maintenance and collaboration.

Conclusion

Effective configuration management within NestJS applications is paramount to developing secure, efficient, and manageable web services and apps. By applying the best practices covered in this guide, such as segregating configuration, validating environment variables, and leveraging the power of ConfigModule, developers are well-equipped to build superior NestJS applications.