Environment Configuration - Complete Guide
Published: September 25, 2024 | Reading time: 19 minutes
Environment Configuration Overview
Proper environment configuration ensures secure and flexible deployments:
Configuration Benefits
# Configuration Benefits
- Environment separation
- Security management
- Flexible deployments
- Configuration validation
- Type safety
- Documentation
- Team collaboration
Environment Variables
Basic Environment Variable Usage
Environment Variables
# Environment Variables
# 1. Basic Environment Variables
const port = process.env.PORT || 3000;
const nodeEnv = process.env.NODE_ENV || 'development';
const dbUrl = process.env.DATABASE_URL;
console.log(`Server running on port ${port}`);
console.log(`Environment: ${nodeEnv}`);
console.log(`Database URL: ${dbUrl}`);
# 2. Environment Variable Validation
function validateEnvVars() {
const required = ['DATABASE_URL', 'JWT_SECRET', 'API_KEY'];
const missing = required.filter(key => !process.env[key]);
if (missing.length > 0) {
throw new Error(`Missing required environment variables: ${missing.join(', ')}`);
}
}
validateEnvVars();
# 3. Environment Configuration Object
const config = {
port: parseInt(process.env.PORT) || 3000,
nodeEnv: process.env.NODE_ENV || 'development',
database: {
url: process.env.DATABASE_URL,
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT) || 5432,
name: process.env.DB_NAME || 'myapp',
username: process.env.DB_USERNAME || 'postgres',
password: process.env.DB_PASSWORD
},
jwt: {
secret: process.env.JWT_SECRET,
expiresIn: process.env.JWT_EXPIRES_IN || '24h'
},
api: {
key: process.env.API_KEY,
baseUrl: process.env.API_BASE_URL || 'https://api.example.com'
}
};
# 4. Environment-Specific Configuration
const config = {
development: {
port: 3000,
database: {
host: 'localhost',
port: 5432,
name: 'myapp_dev'
},
logging: {
level: 'debug'
}
},
production: {
port: process.env.PORT || 80,
database: {
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT),
name: process.env.DB_NAME
},
logging: {
level: 'error'
}
}
};
const env = process.env.NODE_ENV || 'development';
const currentConfig = config[env];
# 5. Environment Variable Types
const config = {
// String
apiKey: process.env.API_KEY,
// Number
port: parseInt(process.env.PORT) || 3000,
timeout: parseInt(process.env.TIMEOUT) || 5000,
// Boolean
enableLogging: process.env.ENABLE_LOGGING === 'true',
debugMode: process.env.DEBUG_MODE === '1',
// Array
allowedOrigins: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'],
// Object
redis: {
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT) || 6379,
password: process.env.REDIS_PASSWORD
}
};
# 6. Environment Variable Validation with Joi
const Joi = require('joi');
const envSchema = Joi.object({
NODE_ENV: Joi.string().valid('development', 'production', 'test').default('development'),
PORT: Joi.number().default(3000),
DATABASE_URL: Joi.string().required(),
JWT_SECRET: Joi.string().required(),
API_KEY: Joi.string().required(),
DB_HOST: Joi.string().default('localhost'),
DB_PORT: Joi.number().default(5432),
DB_NAME: Joi.string().required(),
DB_USERNAME: Joi.string().required(),
DB_PASSWORD: Joi.string().required()
});
const { error, value } = envSchema.validate(process.env);
if (error) {
throw new Error(`Environment validation error: ${error.message}`);
}
const config = value;
# 7. Environment Variable Loading
require('dotenv').config();
// Load environment-specific files
if (process.env.NODE_ENV === 'development') {
require('dotenv').config({ path: '.env.development' });
} else if (process.env.NODE_ENV === 'production') {
require('dotenv').config({ path: '.env.production' });
}
# 8. Environment Variable Encryption
const crypto = require('crypto');
function encrypt(text, key) {
const cipher = crypto.createCipher('aes192', key);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
return encrypted;
}
function decrypt(encrypted, key) {
const decipher = crypto.createDecipher('aes192', key);
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
// Encrypt sensitive environment variables
const encryptedDbPassword = encrypt(process.env.DB_PASSWORD, process.env.ENCRYPTION_KEY);
const decryptedDbPassword = decrypt(encryptedDbPassword, process.env.ENCRYPTION_KEY);
# 9. Environment Variable Documentation
/**
* Environment Variables Documentation
*
* Required Variables:
* - DATABASE_URL: Database connection string
* - JWT_SECRET: Secret key for JWT tokens
* - API_KEY: API key for external services
*
* Optional Variables:
* - PORT: Server port (default: 3000)
* - NODE_ENV: Environment (development, production, test)
* - DB_HOST: Database host (default: localhost)
* - DB_PORT: Database port (default: 5432)
* - DB_NAME: Database name
* - DB_USERNAME: Database username
* - DB_PASSWORD: Database password
*
* Development Variables:
* - DEBUG_MODE: Enable debug mode (default: false)
* - ENABLE_LOGGING: Enable logging (default: true)
*
* Production Variables:
* - REDIS_HOST: Redis host
* - REDIS_PORT: Redis port
* - REDIS_PASSWORD: Redis password
*/
# 10. Environment Variable Testing
const config = {
port: parseInt(process.env.PORT) || 3000,
nodeEnv: process.env.NODE_ENV || 'development',
database: {
url: process.env.DATABASE_URL,
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT) || 5432,
name: process.env.DB_NAME || 'myapp',
username: process.env.DB_USERNAME || 'postgres',
password: process.env.DB_PASSWORD
}
};
// Test configuration
if (process.env.NODE_ENV === 'test') {
config.database.name = 'myapp_test';
config.port = 3001;
}
module.exports = config;
Configuration Management
Advanced Configuration Patterns
Configuration Management
# Configuration Management
# 1. Configuration Class
class Config {
constructor() {
this.loadConfig();
}
loadConfig() {
this.port = parseInt(process.env.PORT) || 3000;
this.nodeEnv = process.env.NODE_ENV || 'development';
this.database = {
url: process.env.DATABASE_URL,
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT) || 5432,
name: process.env.DB_NAME || 'myapp',
username: process.env.DB_USERNAME || 'postgres',
password: process.env.DB_PASSWORD
};
this.jwt = {
secret: process.env.JWT_SECRET,
expiresIn: process.env.JWT_EXPIRES_IN || '24h'
};
this.api = {
key: process.env.API_KEY,
baseUrl: process.env.API_BASE_URL || 'https://api.example.com'
};
}
validate() {
const required = ['DATABASE_URL', 'JWT_SECRET', 'API_KEY'];
const missing = required.filter(key => !process.env[key]);
if (missing.length > 0) {
throw new Error(`Missing required environment variables: ${missing.join(', ')}`);
}
}
isDevelopment() {
return this.nodeEnv === 'development';
}
isProduction() {
return this.nodeEnv === 'production';
}
isTest() {
return this.nodeEnv === 'test';
}
}
const config = new Config();
config.validate();
# 2. Configuration Factory
class ConfigFactory {
static create(env = process.env.NODE_ENV || 'development') {
const configs = {
development: {
port: 3000,
database: {
host: 'localhost',
port: 5432,
name: 'myapp_dev'
},
logging: {
level: 'debug'
}
},
production: {
port: process.env.PORT || 80,
database: {
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT),
name: process.env.DB_NAME
},
logging: {
level: 'error'
}
},
test: {
port: 3001,
database: {
host: 'localhost',
port: 5432,
name: 'myapp_test'
},
logging: {
level: 'silent'
}
}
};
return configs[env];
}
}
const config = ConfigFactory.create();
# 3. Configuration Builder
class ConfigBuilder {
constructor() {
this.config = {};
}
setPort(port) {
this.config.port = port;
return this;
}
setNodeEnv(env) {
this.config.nodeEnv = env;
return this;
}
setDatabase(dbConfig) {
this.config.database = dbConfig;
return this;
}
setJwt(jwtConfig) {
this.config.jwt = jwtConfig;
return this;
}
setApi(apiConfig) {
this.config.api = apiConfig;
return this;
}
build() {
return this.config;
}
}
const config = new ConfigBuilder()
.setPort(parseInt(process.env.PORT) || 3000)
.setNodeEnv(process.env.NODE_ENV || 'development')
.setDatabase({
url: process.env.DATABASE_URL,
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT) || 5432,
name: process.env.DB_NAME || 'myapp',
username: process.env.DB_USERNAME || 'postgres',
password: process.env.DB_PASSWORD
})
.setJwt({
secret: process.env.JWT_SECRET,
expiresIn: process.env.JWT_EXPIRES_IN || '24h'
})
.setApi({
key: process.env.API_KEY,
baseUrl: process.env.API_BASE_URL || 'https://api.example.com'
})
.build();
# 4. Configuration with Defaults
const defaults = {
port: 3000,
nodeEnv: 'development',
database: {
host: 'localhost',
port: 5432,
name: 'myapp',
username: 'postgres',
password: ''
},
jwt: {
secret: 'default-secret',
expiresIn: '24h'
},
api: {
baseUrl: 'https://api.example.com'
}
};
function mergeConfig(envConfig, defaults) {
return {
...defaults,
...envConfig,
database: {
...defaults.database,
...envConfig.database
},
jwt: {
...defaults.jwt,
...envConfig.jwt
},
api: {
...defaults.api,
...envConfig.api
}
};
}
const envConfig = {
port: parseInt(process.env.PORT),
nodeEnv: process.env.NODE_ENV,
database: {
url: process.env.DATABASE_URL,
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT),
name: process.env.DB_NAME,
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD
},
jwt: {
secret: process.env.JWT_SECRET,
expiresIn: process.env.JWT_EXPIRES_IN
},
api: {
key: process.env.API_KEY,
baseUrl: process.env.API_BASE_URL
}
};
const config = mergeConfig(envConfig, defaults);
# 5. Configuration with Validation
const Joi = require('joi');
const configSchema = Joi.object({
port: Joi.number().min(1).max(65535).default(3000),
nodeEnv: Joi.string().valid('development', 'production', 'test').default('development'),
database: Joi.object({
url: Joi.string().required(),
host: Joi.string().default('localhost'),
port: Joi.number().min(1).max(65535).default(5432),
name: Joi.string().required(),
username: Joi.string().required(),
password: Joi.string().required()
}),
jwt: Joi.object({
secret: Joi.string().required(),
expiresIn: Joi.string().default('24h')
}),
api: Joi.object({
key: Joi.string().required(),
baseUrl: Joi.string().uri().default('https://api.example.com')
})
});
function validateConfig(config) {
const { error, value } = configSchema.validate(config);
if (error) {
throw new Error(`Configuration validation error: ${error.message}`);
}
return value;
}
const config = validateConfig({
port: parseInt(process.env.PORT) || 3000,
nodeEnv: process.env.NODE_ENV || 'development',
database: {
url: process.env.DATABASE_URL,
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT) || 5432,
name: process.env.DB_NAME || 'myapp',
username: process.env.DB_USERNAME || 'postgres',
password: process.env.DB_PASSWORD
},
jwt: {
secret: process.env.JWT_SECRET,
expiresIn: process.env.JWT_EXPIRES_IN || '24h'
},
api: {
key: process.env.API_KEY,
baseUrl: process.env.API_BASE_URL || 'https://api.example.com'
}
});
# 6. Configuration with Hot Reloading
const fs = require('fs');
const path = require('path');
class HotReloadConfig {
constructor(configPath) {
this.configPath = configPath;
this.config = this.loadConfig();
this.watchers = [];
}
loadConfig() {
try {
const configFile = fs.readFileSync(this.configPath, 'utf8');
return JSON.parse(configFile);
} catch (error) {
console.error('Error loading config:', error);
return {};
}
}
watch() {
const watcher = fs.watch(this.configPath, (eventType) => {
if (eventType === 'change') {
console.log('Config file changed, reloading...');
this.config = this.loadConfig();
this.notifyWatchers();
}
});
this.watchers.push(watcher);
}
addWatcher(callback) {
this.watchers.push(callback);
}
notifyWatchers() {
this.watchers.forEach(watcher => {
if (typeof watcher === 'function') {
watcher(this.config);
}
});
}
get(key) {
return this.config[key];
}
set(key, value) {
this.config[key] = value;
}
}
const hotConfig = new HotReloadConfig('./config.json');
hotConfig.watch();
# 7. Configuration with Encryption
const crypto = require('crypto');
class EncryptedConfig {
constructor(encryptionKey) {
this.encryptionKey = encryptionKey;
this.config = {};
}
encrypt(text) {
const cipher = crypto.createCipher('aes192', this.encryptionKey);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
return encrypted;
}
decrypt(encrypted) {
const decipher = crypto.createDecipher('aes192', this.encryptionKey);
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
set(key, value) {
this.config[key] = value;
}
setEncrypted(key, value) {
this.config[key] = this.encrypt(value);
}
get(key) {
return this.config[key];
}
getDecrypted(key) {
const encrypted = this.config[key];
return encrypted ? this.decrypt(encrypted) : undefined;
}
}
const encryptedConfig = new EncryptedConfig(process.env.ENCRYPTION_KEY);
# 8. Configuration with Environment Overrides
class EnvironmentConfig {
constructor() {
this.config = this.loadConfig();
}
loadConfig() {
const baseConfig = {
port: 3000,
nodeEnv: 'development',
database: {
host: 'localhost',
port: 5432,
name: 'myapp',
username: 'postgres',
password: ''
},
jwt: {
secret: 'default-secret',
expiresIn: '24h'
},
api: {
baseUrl: 'https://api.example.com'
}
};
// Override with environment variables
const envOverrides = {
port: parseInt(process.env.PORT),
nodeEnv: process.env.NODE_ENV,
database: {
url: process.env.DATABASE_URL,
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT),
name: process.env.DB_NAME,
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD
},
jwt: {
secret: process.env.JWT_SECRET,
expiresIn: process.env.JWT_EXPIRES_IN
},
api: {
key: process.env.API_KEY,
baseUrl: process.env.API_BASE_URL
}
};
return this.mergeConfig(baseConfig, envOverrides);
}
mergeConfig(base, overrides) {
const result = { ...base };
for (const key in overrides) {
if (overrides[key] !== undefined) {
if (typeof overrides[key] === 'object' && !Array.isArray(overrides[key])) {
result[key] = { ...result[key], ...overrides[key] };
} else {
result[key] = overrides[key];
}
}
}
return result;
}
get(key) {
return this.config[key];
}
}
const envConfig = new EnvironmentConfig();
# 9. Configuration with Type Safety
class TypedConfig {
constructor() {
this.config = this.loadConfig();
}
loadConfig() {
return {
port: parseInt(process.env.PORT) || 3000,
nodeEnv: process.env.NODE_ENV || 'development',
database: {
url: process.env.DATABASE_URL,
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT) || 5432,
name: process.env.DB_NAME || 'myapp',
username: process.env.DB_USERNAME || 'postgres',
password: process.env.DB_PASSWORD
},
jwt: {
secret: process.env.JWT_SECRET,
expiresIn: process.env.JWT_EXPIRES_IN || '24h'
},
api: {
key: process.env.API_KEY,
baseUrl: process.env.API_BASE_URL || 'https://api.example.com'
}
};
}
getPort() {
return this.config.port;
}
getNodeEnv() {
return this.config.nodeEnv;
}
getDatabase() {
return this.config.database;
}
getJwt() {
return this.config.jwt;
}
getApi() {
return this.config.api;
}
isDevelopment() {
return this.config.nodeEnv === 'development';
}
isProduction() {
return this.config.nodeEnv === 'production';
}
isTest() {
return this.config.nodeEnv === 'test';
}
}
const typedConfig = new TypedConfig();
# 10. Configuration with Validation and Defaults
const Joi = require('joi');
class ValidatedConfig {
constructor() {
this.schema = this.createSchema();
this.config = this.loadAndValidateConfig();
}
createSchema() {
return Joi.object({
port: Joi.number().min(1).max(65535).default(3000),
nodeEnv: Joi.string().valid('development', 'production', 'test').default('development'),
database: Joi.object({
url: Joi.string().required(),
host: Joi.string().default('localhost'),
port: Joi.number().min(1).max(65535).default(5432),
name: Joi.string().required(),
username: Joi.string().required(),
password: Joi.string().required()
}),
jwt: Joi.object({
secret: Joi.string().required(),
expiresIn: Joi.string().default('24h')
}),
api: Joi.object({
key: Joi.string().required(),
baseUrl: Joi.string().uri().default('https://api.example.com')
})
});
}
loadAndValidateConfig() {
const rawConfig = {
port: parseInt(process.env.PORT),
nodeEnv: process.env.NODE_ENV,
database: {
url: process.env.DATABASE_URL,
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT),
name: process.env.DB_NAME,
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD
},
jwt: {
secret: process.env.JWT_SECRET,
expiresIn: process.env.JWT_EXPIRES_IN
},
api: {
key: process.env.API_KEY,
baseUrl: process.env.API_BASE_URL
}
};
const { error, value } = this.schema.validate(rawConfig);
if (error) {
throw new Error(`Configuration validation error: ${error.message}`);
}
return value;
}
get(key) {
return this.config[key];
}
getPort() {
return this.config.port;
}
getNodeEnv() {
return this.config.nodeEnv;
}
getDatabase() {
return this.config.database;
}
getJwt() {
return this.config.jwt;
}
getApi() {
return this.config.api;
}
}
const validatedConfig = new ValidatedConfig();
Deployment Strategies
Environment-Specific Deployments
Deployment Strategies
- Environment-specific configs
- Configuration validation
- Secret management
- Configuration encryption
- Hot reloading
- Type safety
- Documentation
Best Practices
- Use environment variables
- Validate configurations
- Separate secrets
- Use defaults
- Document variables
- Test configurations
- Monitor changes
Summary
Environment configuration involves several key components:
- Environment Variables: Basic usage, validation, and type handling
- Configuration Management: Advanced patterns and validation
- Deployment Strategies: Environment-specific configurations and best practices
- Security: Secret management and encryption
Need More Help?
Struggling with environment configuration or need help implementing secure configuration management? Our Node.js experts can help you set up robust configuration systems.
Get Configuration Help