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. Theenvironment
field is validated to be one of'development'
,'production'
, or'test'
, and the database configuration is validated with specific rules (e.g., hostname format forhost
, port number range, and minimum lengths foruser
,password
, andname
). - Loading Environment Variables: The
config
object is populated with values from the environment variables usingprocess.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.