Skip to content

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

typescript
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

typescript
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:

json
{
  "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.

typescript
loadConfig(): { success: true; data: AppConfig } | { success: false; error: string }

Returns: A Result object containing either the validated configuration or an error message.

Example:

typescript
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.

typescript
getConfig(): AppConfig | null

Example:

typescript
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.

typescript
getServerConfig(): ServerConfig

Throws: Error if configuration is not loaded.

Example:

typescript
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.

typescript
getLoggingConfig(): LoggingConfig

Example:

typescript
const loggingConfig = configManager.getLoggingConfig();
const logger = createLoggerFromConfig(loggingConfig);

getAppConfig()

Returns the application configuration section.

typescript
getAppConfig(): AppConfigSection

Example:

typescript
const appConfig = configManager.getAppConfig();
console.log(`Environment: ${appConfig.environment}`);

isFeatureEnabled()

Checks if a specific feature flag is enabled.

typescript
isFeatureEnabled(featureName: string): boolean

Parameters:

  • featureName - The name of the feature to check

Example:

typescript
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.

typescript
static validateConfigFile(configPath?: string): { success: true; data: AppConfig } | { success: false; error: string }

Parameters:

  • configPath - Optional path to config file (defaults to 'config.json')

Example:

typescript
// 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

typescript
const result = configManager.loadConfig();
if (!result.success) {
  // Error: "Configuration file not found: config.json"
  console.error(result.error);
}

Invalid JSON

typescript
// 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

typescript
// 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

typescript
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

typescript
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

typescript
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

typescript
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

typescript
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

typescript
// 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

Next Steps

Released under the MIT License.