Savvi Studio

Configuration Management

Last Updated: December 7, 2024
Status: ✅ Production Ready
Related: Usage Guide | Testing Guide

Overview

All application configuration is centralized in src/config.ts. This provides a single source of truth for environment variables with type safety, validation, and ESLint enforcement.

Core Principles

  1. Single Source of Truth - Only src/config.ts accesses process.env
  2. Type Safety - All config values are properly typed
  3. Validation - Built-in validation for critical settings
  4. ESLint Enforcement - Direct process.env access blocked
  5. Config-Centric - Validation checks values, not env var names

Architecture

Configuration Module

The module exports config grouped by domain:

// Runtime
export const nodeEnv: string;
export const isDevelopment: boolean;
export const isProduction: boolean;

// Server
export const serverConfig: { port, vercelUrl, baseUrl };

// WorkOS/Auth
export const workosConfig: { apiKey, clientId, cookiePassword, ... };

// JWT & Encryption
export const authConfig: { jwtSecret, encryptionKey, ... };

// Database
export const databaseConfig: { host, port, user, password, database };

// Migrations
export const migrationConfig: { skipOnStartup, requireSuccess };

// Features
export const featureConfig: { enableSetupRouteRedirect, dsiBaseUrl };

// Security
export const securityConfig: { vpnWhitelist };

// CI/CD
export const ciConfig: { isCI, forceColor, useColors };

Validation

export interface ConfigValidationResult {
  valid: boolean;
  errors: string[];
  warnings: string[];
}

// Validate critical config (required for app)
export function validateCriticalConfig(): ConfigValidationResult;

// Validate important config (may limit features)
export function validateImportantConfig(): ConfigValidationResult;

// Validate all
export function validateConfig(): ConfigValidationResult;

Quick Start

Basic Usage

// ❌ WRONG - ESLint error
const apiKey = process.env.WORKOS_API_KEY;

// ✅ CORRECT
import { workosConfig } from '@/config';
const apiKey = workosConfig.apiKey;

Environment Checks

// ❌ WRONG
if (process.env.NODE_ENV === 'development') { }

// ✅ CORRECT
import { isDevelopment } from '@/config';
if (isDevelopment) { }

Validation

import { validateConfig } from '@/config';

const validation = validateConfig();
if (!validation.valid) {
  console.error('Configuration errors:', validation.errors);
}

ESLint Enforcement

Only these files can access process.env:

  • src/config.ts - the config module itself
  • next.config.ts - Next.js framework config
  • vitest.config.ts - test framework config
  • instrumentation.ts - Next.js instrumentation
  • src/lib/tool/tool.ts - passes env to child processes

All other files must import from @/config.

Adding New Configuration

Step 1: Add to Config

// In src/config.ts
export const myServiceConfig = {
  apiUrl: getEnv('MY_SERVICE_API_URL'),
  apiKey: getEnv('MY_SERVICE_API_KEY'),
  timeout: getEnvInt('MY_SERVICE_TIMEOUT', 5000),
  enabled: getEnvBool('MY_SERVICE_ENABLED', false),
} as const;

Step 2: Add Validation (if critical)

export function validateMyServiceConfig(): ConfigValidationResult {
  const errors: string[] = [];
  
  if (myServiceConfig.enabled && !myServiceConfig.apiKey) {
    errors.push('Missing API key when service enabled');
  }
  
  return { valid: errors.length === 0, errors, warnings: [] };
}

Step 3: Use in Code

import { myServiceConfig } from '@/config';

if (myServiceConfig.enabled) {
  await fetch(myServiceConfig.apiUrl!, {
    headers: { 'Authorization': `Bearer ${myServiceConfig.apiKey}` }
  });
}

Helper Functions

// Get required (throws if missing)
function getEnvRequired(key: string): string;

// Get optional with default
function getEnv(key: string, defaultValue?: string): string | undefined;

// Parse integer
function getEnvInt(key: string, defaultValue: number): number;

// Parse boolean
function getEnvBool(key: string, defaultValue?: boolean): boolean;

Benefits

Single Source - All config in one place
Type Safe - TypeScript types throughout
Testable - Mock one module vs many env vars
Validated - Catch errors at startup
Secure - Controlled access to sensitive values
Future-Proof - Config source can change
Enforced - ESLint prevents violations

Troubleshooting

ESLint Error: "Direct access to process.env not allowed"

Solution: Import from @/config:

// Before
const value = process.env.MY_VAR;

// After
import { myConfig } from '@/config';
const value = myConfig.myVar;

Need to Whitelist a File

Only whitelist if legitimately needed (framework config, spawning processes):

  1. Add to eslint.config.mjs files array
  2. Document why it needs the exception

Config Values Undefined

  1. Check .env.example for required variables
  2. Copy to .env.development and set values
  3. Restart dev server

Further Reading

References

  • Source: src/config.ts
  • ESLint: eslint.config.mjs
  • Validation: src/lib/db/migrations/startup.ts
  • Environment Variables: .env.example