Config centralization
Skill JimmyBlanquet/project-forge/skills/core/config-centralization
Type-safe, environment-aware configuration management for Next.js/TypeScript applications using a singleton pattern with runtime validation.From its SKILL.md
npx -y skills add JimmyBlanquet/project-forge --skill config-centralizationAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
9.9 KB, ~2.2k tokens by cl100k_base, as published. Nobody here has run it
Config Centralization - Complete Documentation
Overview
The Config Centralization skill provides type-safe, environment-aware configuration management for Next.js/TypeScript applications using a singleton pattern with runtime validation.
Features
- Type-Safe Configuration: Full TypeScript generics support for compile-time safety
- Environment Management: Development, staging, production, test environments
- Runtime Validation: Required field validation with clear error messages at startup
- Singleton Pattern: Consistent configuration access across your application
- Default Values: Sensible defaults with environment variable override support
- Zero Dependencies: Portable implementation with no external dependencies
Installation
Prerequisites
- TypeScript 5+
- Node.js 18+
- (Optional) Next.js 14+ or any TypeScript project
Install Steps
-
Run the install script from the skill directory:
bash install.sh -
The script will copy files to your project:
config-manager.ts- Core ConfigManager classtypes.ts- TypeScript type definitionsindex.ts- Public API exports
Usage
Basic Usage
import { ConfigManager } from '@/lib/config'
// Get singleton instance
const config = ConfigManager.getInstance()
// Access configuration values
const apiUrl = config.get('API_URL')
const dbUrl = config.get('DATABASE_URL')
const port = config.get('PORT')
// Check if key exists
if (config.has('API_KEY')) {
const apiKey = config.get('API_KEY')
}
// Get current environment
const env = config.getEnv() // 'development' | 'production' | 'staging' | 'test'
// Get all configuration
const allConfig = config.getAll()
Type-Safe Configuration
import { ConfigManager, createConfigManager } from '@/lib/config'
import type { AppConfig } from '@/lib/config'
// Option 1: Use generic type parameter
const config = ConfigManager.getInstance<AppConfig>()
const apiUrl: string = config.get('API_URL')! // Type-safe access
// Option 2: Use createConfigManager helper
interface MyConfig {
API_URL: string
PORT: number
DATABASE_URL: string
}
const schema: MyConfig = {
API_URL: '',
PORT: 0,
DATABASE_URL: ''
}
const config = createConfigManager(schema)
const url = config.get('API_URL') // Fully typed
Environment-Aware Configuration
// Configuration loads from process.env automatically
// and detects environment from NODE_ENV
const config = ConfigManager.getInstance({
env: 'production', // Override environment
defaults: {
PORT: 3000,
LOG_LEVEL: 'info',
API_TIMEOUT: 5000
}
})
// Environment variables take precedence over defaults
// If PORT is set in process.env, it overrides the default
Required Field Validation
// Validate required fields at application startup
try {
const config = ConfigManager.getInstance({
required: ['DATABASE_URL', 'API_URL', 'API_KEY'],
defaults: {
PORT: 3000,
LOG_LEVEL: 'info'
}
})
// Application starts successfully - all required fields present
} catch (error) {
// Throws: "Missing required configuration: DATABASE_URL, API_URL
// Environment: production"
console.error(error.message)
process.exit(1)
}
Runtime Configuration Updates
const config = ConfigManager.getInstance()
// Set configuration value (use sparingly - prefer environment variables)
config.set('FEATURE_FLAG_NEW_UI', true)
// Get updated value
const isNewUI = config.get('FEATURE_FLAG_NEW_UI')
Integration Example
// lib/config/index.ts
import { ConfigManager } from './config-manager'
import type { AppConfig } from './types'
// Initialize configuration at app startup
export function initializeConfig() {
return ConfigManager.getInstance<AppConfig>({
env: process.env.NODE_ENV as any || 'development',
required: [
'DATABASE_URL',
'API_URL'
],
defaults: {
PORT: 3000,
HOST: '0.0.0.0',
LOG_LEVEL: 'info',
DB_POOL_MIN: 2,
DB_POOL_MAX: 10,
API_TIMEOUT: 5000
}
})
}
// app/page.tsx or pages/_app.tsx
import { initializeConfig } from '@/lib/config'
const config = initializeConfig()
export default function App() {
// Use configuration throughout your app
const apiUrl = config.get('API_URL')
// ...
}
Configuration Types
The skill provides predefined TypeScript interfaces:
// BaseConfig - Common configuration
interface BaseConfig {
NODE_ENV: 'development' | 'production' | 'staging' | 'test'
PORT?: number
HOST?: string
LOG_LEVEL?: 'debug' | 'info' | 'warn' | 'error'
}
// DatabaseConfig - Database settings
interface DatabaseConfig extends BaseConfig {
DATABASE_URL: string
DB_POOL_MIN?: number
DB_POOL_MAX?: number
DB_SSL?: boolean
}
// APIConfig - API settings
interface APIConfig extends BaseConfig {
API_URL: string
API_KEY?: string
API_TIMEOUT?: number
API_RETRY_ATTEMPTS?: number
}
// AppConfig - Full application configuration
interface AppConfig extends BaseConfig, DatabaseConfig, APIConfig {
// Extend with your custom fields
}
Environment Variables
This skill loads configuration from process.env. Set environment variables in:
.env.local- Local development (not committed).env.development- Development defaults.env.production- Production defaults.env.test- Test environment
Example .env.local:
NODE_ENV=development
DATABASE_URL=postgresql://localhost:5432/myapp
API_URL=https://api.example.com
API_KEY=your_api_key_here
PORT=3000
LOG_LEVEL=debug
Note: process.env values are always strings. The ConfigManager preserves types from defaults but returns strings for environment variables.
Best Practices
- Initialize Early: Create ConfigManager instance at app startup to catch validation errors early
- Use Required Fields: Validate critical configuration with
requiredoption to fail fast - Provide Defaults: Supply sensible defaults for optional configuration
- Type Your Config: Define TypeScript interfaces for full type safety
- Never Commit Secrets: Use
.env.localfor sensitive data (add to.gitignore) - Prefer Environment Variables: Use
config.set()sparingly - environment variables are more maintainable - Singleton Reset: Use
ConfigManager.reset()in tests to ensure clean state between test runs
Troubleshooting
Error: "Missing required configuration"
Cause: Required environment variables are not set.
Solution:
- Check
.env.localfile exists and contains required variables - Verify variable names match exactly (case-sensitive)
- Check the error message for specific missing fields
- Ensure
.env.localis loaded (Next.js loads it automatically)
Type Errors with get()
Cause: TypeScript cannot infer the return type.
Solution:
// Add type parameter to getInstance
const config = ConfigManager.getInstance<AppConfig>()
// Or use non-null assertion if you know it exists
const url: string = config.get('API_URL')!
Configuration Values are Strings
Cause: process.env always returns strings, even for numbers.
Solution: Use defaults to set proper types, or parse values:
const config = ConfigManager.getInstance({
defaults: {
PORT: 3000 // Number type from default
}
})
// Or parse manually
const port = parseInt(config.get('PORT') || '3000', 10)
Singleton State in Tests
Cause: Singleton instance persists between tests.
Solution: Reset instance in test setup:
import { beforeEach } from 'vitest'
import { ConfigManager } from '@/lib/config'
beforeEach(() => {
ConfigManager.reset()
// Clear test environment variables
delete process.env.TEST_VAR
})
API Reference
ConfigManager Class
class ConfigManager<T extends ConfigSchema = ConfigSchema> {
/**
* Get singleton instance with optional configuration
*/
static getInstance<T extends ConfigSchema = ConfigSchema>(
options?: ConfigOptions
): ConfigManager<T>
/**
* Reset singleton instance (useful for testing)
*/
static reset(): void
/**
* Get configuration value by key
*/
get<K extends keyof T>(key: K): T[K]
/**
* Get all configuration as immutable copy
*/
getAll(): Readonly<T>
/**
* Set configuration value (use sparingly)
*/
set<K extends keyof T>(key: K, value: T[K]): void
/**
* Check if configuration key exists and is defined
*/
has(key: keyof T): boolean
/**
* Get current environment
*/
getEnv(): string
}
ConfigOptions Interface
interface ConfigOptions {
// Environment name (default: process.env.NODE_ENV || 'development')
env?: 'development' | 'production' | 'staging' | 'test'
// Required configuration fields (validates at startup)
required?: string[]
// Default values (applied if not in process.env)
defaults?: ConfigSchema
}
createConfigManager Helper
function createConfigManager<T extends ConfigSchema>(
schema: T,
options?: ConfigOptions
): ConfigManager<T>
Testing
The skill includes comprehensive tests (26 tests, >70% coverage). Run tests:
npm test
Example test patterns:
import { describe, it, expect, beforeEach } from 'vitest'
import { ConfigManager } from './config-manager'
describe('My App Config', () => {
beforeEach(() => {
ConfigManager.reset()
delete process.env.TEST_VAR
})
it('should load configuration with required fields', () => {
process.env.DATABASE_URL = 'postgresql://localhost/test'
const config = ConfigManager.getInstance({
required: ['DATABASE_URL']
})
expect(config.get('DATABASE_URL')).toBe('postgresql://localhost/test')
})
})
License
MIT
What ships with it: 7 files
21.0 KB alongside SKILL.md, 5 of them executable
files/
- config-manager.tsruns3.1 KB
- index.tsruns490 B
- types.tsruns1.0 KB
tests/
- config-centralization.test.tsruns9.5 KB
- install.shruns3.7 KB
- package.json184 B
- README.md3.0 KB