NestJS Environment Variables: Best Practices for Validating, and Structuring Configs

Porosh
5 min readFeb 10, 2025

--

NestJS Environment Variables: Best Practices for Validating, and Structuring Configs

When developing applications with NestJS, managing configuration settings across different environments is crucial. Environment variables play a key role in storing sensitive information and configuration settings for your app. However, simply loading them is not enough — validating these variables and centralizing their access can improve the maintainability and scalability of your project.

In this post, we’ll walk through how to read environment variables in a NestJS application, validate them for correctness, and centralize configuration for easy management and access throughout the app.

1. Installing Dependencies

First, let's install the necessary dependencies. We'll need the @nestjs/config module to manage configurations and @hapi/joi for validation.

2. Creating the .env File

Now, create a .env file in the root of the project and add your environment variables. Here's an example:

NODE_ENV = development
DB_HOST = localhost
DB_PORT = 1236
DB_USER = admin
DB_PASSWORD = yourpassword
DB_NAME = yourdatabase

Don’t forget to add the .env file to your .gitignore to avoid publishing it to version control:

.env

3. Creating a Configuration Folder and app.config.ts File

Next, create a config folder in the root of your project to organize your configuration files. Inside this folder, create a file named app.config.ts (or any name you prefer). This file will be responsible for loading and managing your environment variables in a centralized location.

Here’s an example of what the app.config.ts file might look like:

import * as Joi from '@hapi/joi';

const configSchema = Joi.object({
environment: Joi.string().valid('development', 'production', 'test').default('development'),
database: Joi.object({
host: Joi.string().hostname().default('localhost'),
port: Joi.number().integer().min(1).max(65535).default(5432),
user: Joi.string().min(3).required(),
password: Joi.string().min(6).required(),
name: Joi.string().min(3).required()
}).required()
});

export default () => {
const config = {
environment: process.env.NODE_ENV || 'development',
database: {
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT as string, 10) || 5432,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
name: process.env.DB_NAME
}
};

// Validate the configuration against the defined schema
const { error, value } = configSchema.validate(config, { abortEarly: false });

// If validation fails, throw an error with the validation messages
if (error) {
throw new Error(`Config validation error: ${error.message}`);
}

return value;
};

Breakdown of the Code:

  • Joi Schema Definition: We define a schema using Joi to validate our configuration. This ensures that our environment variables follow the expected structure, providing consistency and security. The environment field is validated to be one of 'development', 'production', or 'test', and the database configuration is validated with specific rules (e.g., hostname format for host, port number range, and minimum lengths for user, password, and name).
  • Loading Environment Variables: The config object is populated with values from the environment variables using process.env. Default values are provided for any variable that is not set in the environment.
  • Validation: The configuration object is validated using the configSchema. If any of the variables fail validation, an error is thrown with the validation message. If validation passes, the validated configuration object is returned.

4. Integrating the Configuration into app.module.ts

Now that we’ve created and validated our configuration file (app.config.ts), the next step is to integrate it into your NestJS application. This allows you to load and access the configuration values throughout your entire application in a centralized and organized way.

Now, open your app.module.ts file and configure the ConfigModule to load the settings from the app.config.ts file you created earlier.

Here’s an example of how to do this:

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '@nestjs/config';
import appConfig from 'config/app.config';

@Module({
imports: [
ConfigModule.forRoot({
load:[appConfig]
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

5. Using Configuration in Other Modules and Services

Now that we have integrated the configuration into the AppModule, it’s time to use it in other modules and services across your NestJS application. This allows you to centralize your configuration and make it accessible throughout your app.

To use the configuration in other modules, we need to import the ConfigModule in those modules.

Let’s say we have a UserModule where we want to access the database configuration. Here’s how to do it:

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config'; // Import ConfigModule
import { UserService } from './user.service';

@Module({
imports: [ConfigModule], // Import ConfigModule here if needed (optional, as it's global)
providers: [UserService],
})
export class UserModule {}

Once the ConfigModule is imported into your module, you can inject the ConfigService into your services to access the configuration values.

Let’s say we want to access the database configuration inside a UserService. Here’s how you can do that:

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

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

getDatabaseConfig() {
const dbConfig = this.configService.get('database'); // Access the 'database' config
console.log('Database Configuration:', dbConfig);
return dbConfig;
}
}

If you want to explicitly get the port and host, you can do something like this: const databaseHost = this.configService.get<string>('database.host');. Additionally, if you want to use the config file in your app service file, simply import the ConfigService into your app service file and use it as follows...

Conclusion

In this guide, we’ve explored how to effectively manage and use configuration in a NestJS application. By leveraging the @nestjs/config module, we centralized environment variables and validated them to ensure correctness and security. We also demonstrated how to access configuration values globally across different modules and services, promoting maintainability and scalability in your app.

Centralized and validated configuration management is crucial for building reliable, flexible, and scalable applications. It reduces the chances of errors caused by missing or incorrect environment variables and simplifies the process of changing configurations for different environments (e.g., development, production).

By following this approach, you’ll be able to manage sensitive information, database settings, API keys, and more, in a secure and organized manner, ensuring that your NestJS application is both robust and maintainable as it grows.

— — — — — — — —

👋 Hey there! If you have any burning questions or just want to say hi, don’t be shy — I’m only a message away. 💬 You can reach me at contact.porosh@aol.com | LinkedIn | Github.

🎨✨ Feel free to DM me if you’d like to collaborate on creating something amazing! Let’s make something incredible together! 💥

--

--

Porosh
Porosh

Written by Porosh

Full Stack Developer | Scalable Systems Architect | React.js, Node.js & Microservices Expert

No responses yet