Skip to content
Plurafy

Environment Variables and Configuration

Environment variables enable configuration of applications across different environments. Learn secure configuration management practices for Node.js applications.

Implement secure, flexible configuration management that adapts to different environments and protects sensitive information.

Environment Variables: Runtime configuration Configuration Files: Structured configuration Command Line Args: Runtime overrides Secrets Managers: Secure credential storage Configuration Servers: Centralized management

  • Never hardcode credentials
  • Use different configs per environment
  • Validate configuration on startup
  • Document all configuration options
  • Implement graceful defaults
require('dotenv').config();
module.exports = {
app: {
name: process.env.APP_NAME || 'MyApp',
env: process.env.NODE_ENV || 'development',
port: parseInt(process.env.PORT || '3000'),
debug: process.env.DEBUG === 'true',
},
database: {
url: process.env.DATABASE_URL,
pool: {
min: parseInt(process.env.DB_POOL_MIN || '2'),
max: parseInt(process.env.DB_POOL_MAX || '10'),
},
timeout: parseInt(process.env.DB_TIMEOUT || '5000'),
},
redis: {
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT || '6379'),
password: process.env.REDIS_PASSWORD,
db: parseInt(process.env.REDIS_DB || '0'),
},
jwt: {
secret: process.env.JWT_SECRET,
expiresIn: process.env.JWT_EXPIRES_IN || '24h',
},
email: {
provider: process.env.EMAIL_PROVIDER || 'sendgrid',
apiKey: process.env.EMAIL_API_KEY,
from: process.env.EMAIL_FROM,
},
};
const Joi = require('joi');
const envSchema = Joi.object({
// App
NODE_ENV: Joi.string()
.valid('development', 'test', 'production')
.default('development'),
APP_NAME: Joi.string().required(),
PORT: Joi.number().default(3000),
DEBUG: Joi.boolean().default(false),
// Database
DATABASE_URL: Joi.string().required(),
DB_POOL_MIN: Joi.number().default(2),
DB_POOL_MAX: Joi.number().default(10),
// Security
JWT_SECRET: Joi.string().required().min(32),
API_KEY: Joi.string().required(),
// Email
EMAIL_PROVIDER: Joi.string().valid('sendgrid', 'mailgun'),
EMAIL_API_KEY: Joi.string().required(),
// Optional services
REDIS_HOST: Joi.string(),
REDIS_PORT: Joi.number(),
}).unknown().required();
function validateConfig(env) {
const { error, value } = envSchema.validate(env, {
allowUnknown: true,
stripUnknown: true,
});
if (error) {
throw new Error(`Configuration validation error: ${error.message}`);
}
return value;
}
const validatedEnv = validateConfig(process.env);
module.exports = validatedEnv;
config/default.js
module.exports = {
app: {
name: 'MyApp',
debug: false,
},
database: {
pool: { min: 2, max: 10 },
},
};
// config/development.js
module.exports = {
app: {
debug: true,
},
database: {
url: 'postgresql://localhost/myapp_dev',
},
};
// config/production.js
module.exports = {
app: {
debug: false,
},
database: {
url: process.env.DATABASE_URL,
pool: { min: 5, max: 30 },
},
};
// config/index.js
const config = require(`./default`);
const envConfig = require(`./${process.env.NODE_ENV || 'development'}`);
module.exports = { ...config, ...envConfig };
const AWS = require('aws-sdk');
const client = new AWS.SecretsManager();
class SecretsManager {
constructor() {
this.cache = new Map();
}
async getSecret(secretName) {
// Check cache
if (this.cache.has(secretName)) {
return this.cache.get(secretName);
}
try {
const result = await client.getSecretValue({ SecretId: secretName }).promise();
let secret;
if ('SecretString' in result) {
secret = JSON.parse(result.SecretString);
} else {
secret = Buffer.from(result.SecretBinary, 'base64').toString('ascii');
}
// Cache for 1 hour
this.cache.set(secretName, secret);
setTimeout(() => this.cache.delete(secretName), 3600000);
return secret;
} catch (error) {
throw new Error(`Failed to retrieve secret ${secretName}: ${error.message}`);
}
}
clearCache(secretName) {
this.cache.delete(secretName);
}
}
module.exports = new SecretsManager();
class ConfigurationService {
constructor(baseConfig) {
this.config = { ...baseConfig };
this.watchers = [];
}
get(key) {
return this.getNestedValue(this.config, key);
}
set(key, value) {
this.setNestedValue(this.config, key, value);
this.notifyWatchers(key, value);
}
subscribe(key, callback) {
this.watchers.push({ key, callback });
}
notifyWatchers(key, value) {
this.watchers.forEach(watcher => {
if (key.startsWith(watcher.key)) {
watcher.callback(value, key);
}
});
}
getNestedValue(obj, path) {
return path.split('.').reduce((current, prop) => current?.[prop], obj);
}
setNestedValue(obj, path, value) {
const keys = path.split('.');
const lastKey = keys.pop();
const target = keys.reduce((current, prop) => {
current[prop] = current[prop] || {};
return current[prop];
}, obj);
target[lastKey] = value;
}
}
// Usage
const configService = new ConfigurationService(require('./config'));
configService.subscribe('database.url', (value) => {
console.log('Database URL changed:', value);
// Reconnect to new database
});
configService.set('database.url', 'postgresql://newhost/db');
class FeatureFlagManager {
constructor(config) {
this.flags = config.flags || {};
this.overrides = new Map();
}
isEnabled(flagName, context = {}) {
// Check overrides first
if (this.overrides.has(flagName)) {
return this.overrides.get(flagName);
}
const flag = this.flags[flagName];
if (!flag) return false;
// Check conditions
if (flag.enabled === false) return false;
if (flag.enabled === true) return true;
// Percentage rollout
if (flag.rolloutPercentage) {
const hash = this.hashContext(context);
return (hash % 100) < flag.rolloutPercentage;
}
// User-based
if (flag.enabledUsers && context.userId) {
return flag.enabledUsers.includes(context.userId);
}
return false;
}
override(flagName, value) {
this.overrides.set(flagName, value);
}
clearOverride(flagName) {
this.overrides.delete(flagName);
}
hashContext(context) {
const str = JSON.stringify(context);
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) - hash) + str.charCodeAt(i);
hash |= 0; // Convert to 32-bit integer
}
return Math.abs(hash);
}
}
// Usage
const featureFlags = new FeatureFlagManager({
flags: {
newPaymentFlow: {
enabled: true,
rolloutPercentage: 50,
},
betaFeature: {
enabled: true,
enabledUsers: ['user-1', 'user-2'],
},
},
});
if (featureFlags.isEnabled('newPaymentFlow', { userId: 'user-123' })) {
// Use new payment flow
}
  1. Security

    • Never commit secrets to version control
    • Use .gitignore for .env files
    • Rotate secrets regularly
    • Use encrypted storage for sensitive data
  2. Organization

    • Group related settings
    • Use consistent naming conventions
    • Document all configuration options
    • Provide sensible defaults
  3. Validation

    • Validate config on startup
    • Fail fast on invalid configuration
    • Type-check values
    • Test configuration changes
  4. Flexibility

    • Support multiple sources
    • Allow environment overrides
    • Enable feature flags
    • Support dynamic updates
  5. Monitoring

    • Log configuration changes
    • Alert on invalid values
    • Track configuration versions
    • Audit access to sensitive config
  • Multi-tenant SaaS: Per-tenant configuration
  • CI/CD Pipelines: Environment-specific builds
  • Microservices: Distributed configuration
  • Feature Rollout: Gradual feature enablement
  • Secrets Management: Centralized credential storage