# Configuration Management - UnifiedConfig Pattern Complete guide to managing configuration in backend microservices. ## Table of Contents - [UnifiedConfig Overview](#unifiedconfig-overview) - [NEVER Use process.env Directly](#never-use-processenv-directly) - [Configuration Structure](#configuration-structure) - [Environment-Specific Configs](#environment-specific-configs) - [Secrets Management](#secrets-management) - [Migration Guide](#migration-guide) --- ## UnifiedConfig Overview ### Why UnifiedConfig? **Problems with process.env:** - ❌ No type safety - ❌ No validation - ❌ Hard to test - ❌ Scattered throughout code - ❌ No default values - ❌ Runtime errors for typos **Benefits of unifiedConfig:** - ✅ Type-safe configuration - ✅ Single source of truth - ✅ Validated at startup - ✅ Easy to test with mocks - ✅ Clear structure - ✅ Fallback to environment variables --- ## NEVER Use process.env Directly ### The Rule ```typescript // ❌ NEVER DO THIS const timeout = parseInt(process.env.TIMEOUT_MS || '5000'); const dbHost = process.env.DB_HOST || 'localhost'; // ✅ ALWAYS DO THIS import { config } from './config/unifiedConfig'; const timeout = config.timeouts.default; const dbHost = config.database.host; ``` ### Why This Matters **Example of problems:** ```typescript // Typo in environment variable name const host = process.env.DB_HSOT; // undefined! No error! // Type safety const port = process.env.PORT; // string! Need parseInt const timeout = parseInt(process.env.TIMEOUT); // NaN if not set! ``` **With unifiedConfig:** ```typescript const port = config.server.port; // number, guaranteed const timeout = config.timeouts.default; // number, with fallback ``` --- ## Configuration Structure ### UnifiedConfig Interface ```typescript export interface UnifiedConfig { database: { host: string; port: number; username: string; password: string; database: string; }; server: { port: number; sessionSecret: string; }; tokens: { jwt: string; inactivity: string; internal: string; }; keycloak: { realm: string; client: string; baseUrl: string; secret: string; }; aws: { region: string; emailQueueUrl: string; accessKeyId: string; secretAccessKey: string; }; sentry: { dsn: string; environment: string; tracesSampleRate: number; }; // ... more sections } ``` ### Implementation Pattern **File:** `/blog-api/src/config/unifiedConfig.ts` ```typescript import * as fs from 'fs'; import * as path from 'path'; import * as ini from 'ini'; const configPath = path.join(__dirname, '../../config.ini'); const iniConfig = ini.parse(fs.readFileSync(configPath, 'utf-8')); export const config: UnifiedConfig = { database: { host: iniConfig.database?.host || process.env.DB_HOST || 'localhost', port: parseInt(iniConfig.database?.port || process.env.DB_PORT || '3306'), username: iniConfig.database?.username || process.env.DB_USER || 'root', password: iniConfig.database?.password || process.env.DB_PASSWORD || '', database: iniConfig.database?.database || process.env.DB_NAME || 'blog_dev', }, server: { port: parseInt(iniConfig.server?.port || process.env.PORT || '3002'), sessionSecret: iniConfig.server?.sessionSecret || process.env.SESSION_SECRET || 'dev-secret', }, // ... more configuration }; // Validate critical config if (!config.tokens.jwt) { throw new Error('JWT secret not configured!'); } ``` **Key Points:** - Read from config.ini first - Fallback to process.env - Default values for development - Validation at startup - Type-safe access --- ## Environment-Specific Configs ### config.ini Structure ```ini [database] host = localhost port = 3306 username = root password = password1 database = blog_dev [server] port = 3002 sessionSecret = your-secret-here [tokens] jwt = your-jwt-secret inactivity = 30m internal = internal-api-token [keycloak] realm = myapp client = myapp-client baseUrl = http://localhost:8080 secret = keycloak-client-secret [sentry] dsn = https://your-sentry-dsn environment = development tracesSampleRate = 0.1 ``` ### Environment Overrides ```bash # .env file (optional overrides) DB_HOST=production-db.example.com DB_PASSWORD=secure-password PORT=80 ``` **Precedence:** 1. config.ini (highest priority) 2. process.env variables 3. Hard-coded defaults (lowest priority) --- ## Secrets Management ### DO NOT Commit Secrets ```gitignore # .gitignore config.ini .env sentry.ini *.pem *.key ``` ### Use Environment Variables in Production ```typescript // Development: config.ini // Production: Environment variables export const config: UnifiedConfig = { database: { password: process.env.DB_PASSWORD || iniConfig.database?.password || '', }, tokens: { jwt: process.env.JWT_SECRET || iniConfig.tokens?.jwt || '', }, }; ``` --- ## Migration Guide ### Find All process.env Usage ```bash grep -r "process.env" blog-api/src/ --include="*.ts" | wc -l ``` ### Migration Example **Before:** ```typescript // Scattered throughout code const timeout = parseInt(process.env.OPENID_HTTP_TIMEOUT_MS || '15000'); const keycloakUrl = process.env.KEYCLOAK_BASE_URL; const jwtSecret = process.env.JWT_SECRET; ``` **After:** ```typescript import { config } from './config/unifiedConfig'; const timeout = config.keycloak.timeout; const keycloakUrl = config.keycloak.baseUrl; const jwtSecret = config.tokens.jwt; ``` **Benefits:** - Type-safe - Centralized - Easy to test - Validated at startup --- **Related Files:** - SKILL.md - [testing-guide.md](testing-guide.md)