ConfigManager
The ConfigManager
class provides type-safe configuration loading and validation for your TypeScript applications.
Overview
The ConfigManager integrates with the validation system to ensure your application configuration is valid before use. It provides methods for loading configuration files, accessing configuration sections, and checking feature flags.
Basic Usage
Creating a ConfigManager Instance
import { ConfigManager } from './config.js';
// Create with default config path (config.json)
const configManager = new ConfigManager();
// Create with custom config path
const configManager = new ConfigManager('config/production.json');
Loading Configuration
const configManager = new ConfigManager();
const result = configManager.loadConfig();
if (result.success) {
console.log('Configuration loaded successfully');
const config = result.data;
} else {
console.error('Failed to load configuration:', result.error);
process.exit(1);
}
Configuration File Structure
The ConfigManager expects a JSON configuration file with the following structure:
{
"app": {
"name": "my-application",
"version": "1.0.0",
"environment": "development"
},
"server": {
"port": 3000,
"host": "localhost",
"timeout": 30000
},
"logging": {
"level": "info",
"outputFile": "logs/app.log",
"includeTimestamp": true,
"useColors": true
},
"features": {
"enableMetrics": false,
"enableHealthCheck": true,
"enableCors": true
}
}
API Reference
loadConfig()
Loads and validates the configuration file.
loadConfig(): { success: true; data: AppConfig } | { success: false; error: string }
Returns: A Result object containing either the validated configuration or an error message.
Example:
const result = configManager.loadConfig();
if (result.success) {
// Use result.data (typed as AppConfig)
console.log(`Starting ${result.data.app.name}`);
} else {
console.error('Configuration error:', result.error);
}
getConfig()
Returns the currently loaded configuration or null if not loaded.
getConfig(): AppConfig | null
Example:
const config = configManager.getConfig();
if (config) {
console.log(`App: ${config.app.name} v${config.app.version}`);
} else {
console.log('Configuration not loaded');
}
getServerConfig()
Returns the server configuration section.
getServerConfig(): ServerConfig
Throws: Error if configuration is not loaded.
Example:
try {
const serverConfig = configManager.getServerConfig();
console.log(`Server will run on ${serverConfig.host}:${serverConfig.port}`);
} catch (error) {
console.error('Server config not available:', error.message);
}
getLoggingConfig()
Returns the logging configuration section.
getLoggingConfig(): LoggingConfig
Example:
const loggingConfig = configManager.getLoggingConfig();
const logger = createLoggerFromConfig(loggingConfig);
getAppConfig()
Returns the application configuration section.
getAppConfig(): AppConfigSection
Example:
const appConfig = configManager.getAppConfig();
console.log(`Environment: ${appConfig.environment}`);
isFeatureEnabled()
Checks if a specific feature flag is enabled.
isFeatureEnabled(featureName: string): boolean
Parameters:
featureName
- The name of the feature to check
Example:
if (configManager.isFeatureEnabled('enableMetrics')) {
// Initialize metrics collection
initializeMetrics();
}
if (configManager.isFeatureEnabled('enableCors')) {
// Configure CORS middleware
app.use(cors());
}
Static Methods
validateConfigFile()
Validates a configuration file without creating a ConfigManager instance.
static validateConfigFile(configPath?: string): { success: true; data: AppConfig } | { success: false; error: string }
Parameters:
configPath
- Optional path to config file (defaults to 'config.json')
Example:
// Validate before starting application
const validation = ConfigManager.validateConfigFile('./config/production.json');
if (!validation.success) {
console.error('Invalid configuration:', validation.error);
process.exit(1);
}
console.log('Configuration is valid');
Error Handling
The ConfigManager provides detailed error messages for common issues:
File Not Found
const result = configManager.loadConfig();
if (!result.success) {
// Error: "Configuration file not found: config.json"
console.error(result.error);
}
Invalid JSON
// If config.json contains invalid JSON
const result = configManager.loadConfig();
if (!result.success) {
// Error: "Invalid JSON in configuration file: Unexpected token..."
console.error(result.error);
}
Validation Errors
// If config.json doesn't match the expected schema
const result = configManager.loadConfig();
if (!result.success) {
// Error: "Configuration validation failed: app.name: String must contain at least 1 character(s)"
console.error(result.error);
}
Environment-Specific Configuration
Loading Different Configs
function loadEnvironmentConfig() {
const env = process.env.NODE_ENV || 'development';
const configPath = `config/config.${env}.json`;
const configManager = new ConfigManager(configPath);
const result = configManager.loadConfig();
if (!result.success) {
console.error(`Failed to load ${env} configuration:`, result.error);
process.exit(1);
}
return configManager;
}
// Usage
const configManager = loadEnvironmentConfig();
Configuration Hierarchy
function loadConfigWithFallbacks() {
const env = process.env.NODE_ENV || 'development';
// Try environment-specific config first
let configManager = new ConfigManager(`config.${env}.json`);
let result = configManager.loadConfig();
if (result.success) {
console.log(`Loaded ${env} configuration`);
return configManager;
}
// Fall back to default config
console.warn(`${env} config failed, trying default config`);
configManager = new ConfigManager('config.json');
result = configManager.loadConfig();
if (result.success) {
console.log('Loaded default configuration');
return configManager;
}
throw new Error('No valid configuration found');
}
Integration Examples
Application Startup
import { ConfigManager } from './config.js';
import { createLoggerFromConfig } from './logger.js';
import { createServer } from './server.js';
async function startApplication() {
// Load configuration
const configManager = new ConfigManager();
const configResult = configManager.loadConfig();
if (!configResult.success) {
console.error('Configuration error:', configResult.error);
process.exit(1);
}
// Create logger from config
const logger = createLoggerFromConfig(configManager.getLoggingConfig());
logger.info('Configuration loaded successfully');
// Start server with config
const serverConfig = configManager.getServerConfig();
const server = createServer(serverConfig, logger);
server.listen(serverConfig.port, serverConfig.host, () => {
logger.info('Server started', {
host: serverConfig.host,
port: serverConfig.port,
environment: configManager.getAppConfig().environment
});
});
// Configure features
if (configManager.isFeatureEnabled('enableHealthCheck')) {
setupHealthCheck(server, logger);
}
if (configManager.isFeatureEnabled('enableMetrics')) {
setupMetrics(server, logger);
}
}
startApplication().catch(console.error);
Express.js Integration
import express from 'express';
import { ConfigManager } from './config.js';
const app = express();
const configManager = new ConfigManager();
// Load config and make it available to routes
const configResult = configManager.loadConfig();
if (!configResult.success) {
throw new Error(`Configuration error: ${configResult.error}`);
}
// Add config to app locals
app.locals.config = configManager;
// Use config in middleware
if (configManager.isFeatureEnabled('enableCors')) {
app.use(cors());
}
// Use config in routes
app.get('/api/info', (req, res) => {
const appConfig = configManager.getAppConfig();
res.json({
name: appConfig.name,
version: appConfig.version,
environment: appConfig.environment
});
});
const serverConfig = configManager.getServerConfig();
app.listen(serverConfig.port, () => {
console.log(`Server running on port ${serverConfig.port}`);
});
Testing
Mocking ConfigManager
import { jest } from '@jest/globals';
// Mock the ConfigManager
const mockConfigManager = {
loadConfig: jest.fn(),
getConfig: jest.fn(),
getServerConfig: jest.fn(),
getLoggingConfig: jest.fn(),
getAppConfig: jest.fn(),
isFeatureEnabled: jest.fn()
};
// Test with mock
describe('Application Startup', () => {
it('should start with valid configuration', () => {
mockConfigManager.loadConfig.mockReturnValue({
success: true,
data: {
app: { name: 'test-app', version: '1.0.0', environment: 'test' },
server: { port: 3000, host: 'localhost', timeout: 30000 },
logging: { level: 'error', outputFile: null, includeTimestamp: false, useColors: false },
features: { enableMetrics: false, enableHealthCheck: true, enableCors: false }
}
});
mockConfigManager.getServerConfig.mockReturnValue({
port: 3000,
host: 'localhost',
timeout: 30000
});
// Test your application startup logic
const result = startApplication(mockConfigManager);
expect(result).toBeDefined();
});
});
Test Configuration Files
// test-config.json
{
"app": {
"name": "test-app",
"version": "1.0.0",
"environment": "test"
},
"server": {
"port": 0,
"host": "localhost",
"timeout": 5000
},
"logging": {
"level": "error",
"outputFile": null,
"includeTimestamp": false,
"useColors": false
},
"features": {
"enableMetrics": false,
"enableHealthCheck": false,
"enableCors": false
}
}
Best Practices
✅ Do
- Validate configuration early - Load and validate config at application startup
- Use environment-specific configs - Different configs for dev/staging/production
- Handle errors gracefully - Always check the result of loadConfig()
- Use feature flags - Control features through configuration
- Document your schema - Keep configuration structure documented
❌ Don't
- Skip validation - Always validate configuration before use
- Hardcode configuration - Use the ConfigManager instead of hardcoded values
- Ignore load errors - Always handle configuration loading failures
- Mix configuration sources - Use ConfigManager consistently throughout your app
- Store secrets in config files - Use environment variables for sensitive data