envolution
v1.4.1
Published
TypeScript-first environment configuration with Zod validation, .env support, hot reloading, and startup validation. Zero-config setup - just install and use! Perfect for Node.js, AWS Lambda, and modern TypeScript applications.
Maintainers
Readme
envolution
A TypeScript-first environment configuration package with Zod validation, type safety, extensible schemas, .env file support, hot reloading, and startup validation. Perfect for Node.js, AWS Lambda, and modern TypeScript applications.
📋 Table of Contents
- Features
- Installation
- Quick Start
- Available Schemas
- API Reference
- Environment Variables
- Error Handling
- Testing
- Contributing
- License
Features
- 🔒 Type Safety: Full TypeScript support with inferred types
- ✅ Validation: Runtime validation with helpful error messages using Zod
- 📝 .env File Support: Loads environment variables from .env files automatically
- 🔄 Hot Reloading: Watch for file changes and reload configuration automatically
- 🚦 Startup Validation: Validate all required config at startup
- 🔧 Extensibility: Easy to extend with custom schemas
- 📋 Template Generation: Generate .env templates and TypeScript types from schemas
- 🚀 Singleton Pattern: Thread-safe singleton instance
- ☁️ AWS Optimized: Built-in AWS-specific schemas
- 📦 Zero Dependencies: Includes all dependencies - no setup required
- 🌳 Tree Shakeable: Modular exports for optimal bundle size
- 🔄 Dual Module Support: Works with both CommonJS and ES Modules
- 🧪 Well Tested: Comprehensive test coverage
Installation
npm install envolution✨ Zero-config setup! envolution includes all its dependencies (zod and dotenv) - no need to install anything else.
Module Compatibility
envolution supports both CommonJS and ES Modules:
CommonJS
const { EnvironmentConfig, BaseEnvironmentSchema } = require('envolution');ES Modules
import { EnvironmentConfig, BaseEnvironmentSchema } from 'envolution';The package automatically detects your project's module system and provides the appropriate format.
Why envolution?
| Feature | envolution | dotenv | config | convict | |---------|------------|--------|--------|---------| | TypeScript Support | ✅ Full type safety | ❌ No types | ❌ Basic types | ❌ No types | | Runtime Validation | ✅ Zod schemas | ❌ No validation | ❌ No validation | ✅ Basic validation | | Hot Reloading | ✅ Built-in | ❌ No | ❌ No | ❌ No | | Template Generation | ✅ Auto-generate .env templates | ❌ Manual | ❌ Manual | ❌ Manual | | AWS Optimized | ✅ Built-in schemas | ❌ No | ❌ No | ❌ No | | Startup Validation | ✅ Built-in | ❌ No | ❌ No | ❌ No | | Bundle Size | ✅ Tree-shakeable | ✅ Small | ❌ Large | ❌ Large | | Zero Dependencies | ✅ Standalone package | ✅ Yes | ❌ Many deps | ❌ Many deps |
Quick Start
Basic Usage
import { EnvironmentConfig, BaseEnvironmentSchema } from 'envolution';
// Get environment config with base schema (loads .env by default)
const env = EnvironmentConfig.getInstance(BaseEnvironmentSchema);
// Get values
const stage = env.get('STAGE'); // 'dev' | 'prod' | 'test'
const logLevel = env.get('LOG_LEVEL'); // 'debug' | 'info' | 'warn' | 'error'
// Get required values (throws if missing)
const serviceName = env.getRequired('SERVICE_NAME', 'MyService');
// Convenience getters
console.log(`Running in ${env.stage} mode`);
console.log(`Is production: ${env.isProduction}`);
console.log(`Is development: ${env.isDevelopment}`);.env File Support
By default, .env and .env.local files are loaded automatically. You can customize which files to load:
const env = EnvironmentConfig.getInstance(BaseEnvironmentSchema, {
envFiles: ['.env', '.env.production'], // custom list
loadEnvFiles: true // (default)
});Hot Reloading
Enable hot-reloading to automatically reload configuration when files change:
const env = EnvironmentConfig.getInstance(MySchema, {
hotReload: true,
watchFiles: true,
watchInterval: 5000, // Check every 5 seconds
});
// Listen for configuration changes
env.on('configChanged', (change) => {
console.log(`Variable ${change.variable} changed from ${change.oldValue} to ${change.newValue}`);
});
// Listen for specific variable changes
env.on('configChanged:PORT', (change) => {
console.log(`Port changed to ${change.newValue}`);
});
// Manual reload
env.reload();
// Stop watching (cleanup)
env.stopWatching();Template Generation
Generate .env templates and TypeScript types from your schemas:
import { generateEnvTemplate, generateTypes, generateAllTemplates } from 'envolution';
// Generate .env template
const envTemplate = generateEnvTemplate(MySchema, {
includeDefaults: true,
includeComments: true,
outputFile: '.env.template'
});
// Generate TypeScript types
const typesTemplate = generateTypes(MySchema, {
outputFile: 'env.types.ts'
});
// Generate all templates at once
const allTemplates = generateAllTemplates(MySchema, {
envFile: '.env.template',
typesFile: 'env.types.ts'
});Startup Validation
You can validate all required config at startup (throws if any required variable is missing or invalid):
const env = EnvironmentConfig.getInstance(BaseEnvironmentSchema, {
validateStartup: true
});
// If any required variable is missing/invalid, an error is thrown immediatelyOr call manually:
env.validateStartup();Custom Schema
import { z } from 'zod';
import { createEnvironmentConfig, BaseEnvironmentSchema, mergeSchemas } from 'envolution';
// Define custom schema
const DatabaseSchema = z.object({
DATABASE_URL: z.string().url(),
DATABASE_POOL_SIZE: z.string().transform(Number).pipe(z.number().positive()).default('10'),
DATABASE_TIMEOUT: z.string().transform(Number).pipe(z.number().positive()).default('5000'),
});
// Merge with base schema
const FullSchema = mergeSchemas(BaseEnvironmentSchema, DatabaseSchema);
// Create environment config with custom schema (no type casting needed!)
const env = createEnvironmentConfig(FullSchema);
// Now you have type safety for database variables
const dbUrl = env.getRequired('DATABASE_URL', 'Database connection');
const poolSize = env.get('DATABASE_POOL_SIZE'); // number
const timeout = env.get('DATABASE_TIMEOUT'); // numberAWS Lambda Usage
import { EnvironmentConfig, mergeSchemas, BaseEnvironmentSchema, AWSEnvironmentSchema } from 'envolution';
// Create schema for AWS Lambda
const LambdaSchema = mergeSchemas(BaseEnvironmentSchema, AWSEnvironmentSchema);
// Get environment config
const env = EnvironmentConfig.getInstance(LambdaSchema);
// Lambda handler
export const handler = async (event: Record<string, unknown>) => {
// Get AWS-specific environment variables
const stage = env.stage;
const region = env.get('AWS_REGION');
const stateMachineArn = env.getRequired('STATE_MACHINE_ARN', 'Step Function');
const jobTableName = env.getRequired('JOB_TABLE_NAME', 'Job table');
// Your Lambda logic here...
return {
statusCode: 200,
body: JSON.stringify({ message: 'Success' }),
};
};Available Schemas
Base Schema
Includes common configuration options:
STAGE: Environment stage ('dev', 'prod', 'test')NODE_ENV: Node environmentLOG_LEVEL: Logging levelSERVICE_NAME: Service nameVERSION: Application versionENABLE_DEBUG: Debug mode flagENABLE_METRICS: Metrics collection flagREQUEST_TIMEOUT: Request timeoutMAX_RETRIES: Maximum retry attempts
AWS Schema
Includes AWS-specific variables:
AWS_REGION: AWS regionAWS_ACCOUNT_ID: AWS account IDSTATE_MACHINE_ARN: Step Function ARNQUOTA_TABLE: DynamoDB quota tableJOB_TABLE_NAME: DynamoDB job tableQUEUE_URL: SQS queue URLMODEL_ID: Bedrock model IDLAMBDA_TIMEOUT: Lambda timeoutLAMBDA_MEMORY_SIZE: Lambda memory size
Security Schema
Includes security-related variables:
JWT_SECRET: JWT signing secretJWT_EXPIRES_IN: JWT expiration timeAPI_KEY: API keyENCRYPTION_KEY: Encryption keyCORS_ORIGIN: CORS originRATE_LIMIT_WINDOW: Rate limiting windowRATE_LIMIT_MAX_REQUESTS: Rate limiting max requests
API Reference
EnvironmentConfig
getInstance<T>(schema?, options?)
Get singleton instance of EnvironmentConfig. Note: You must call this method explicitly to configure options like suppressWarnings. There is no automatic env export to prevent unwanted initialization.
Options:
envFiles: string[] — Which .env files to load (default: ['.env', '.env.local'])loadEnvFiles: boolean — Whether to load .env files (default: true)validateStartup: boolean — Whether to validate all config at startup (default: false)hotReload: boolean — Whether to enable hot-reloading (default: false)watchFiles: boolean — Whether to watch for file changes (default: false)watchInterval: number — Interval in milliseconds to check for changes (default: 5000)suppressWarnings: boolean — Whether to suppress warning logs for missing/invalid env files (default: false)
const env = EnvironmentConfig.getInstance(MySchema, { validateStartup: true });CLI Applications: Use suppressWarnings: true to prevent warning logs from appearing to end users:
const env = EnvironmentConfig.getInstance(MySchema, {
suppressWarnings: true, // Hide warnings from end users
validateStartup: true
});get<K>(key)
Get a configuration value.
const value = env.get('MY_VARIABLE');getRequired<K>(key, context?)
Get a required configuration value (throws if undefined/null).
const value = env.getRequired('MY_VARIABLE', 'MyService');getAll()
Get all configuration values.
const allConfig = env.getAll();validateStartup()
Validate all required config at startup (throws if any required variable is missing or invalid).
env.validateStartup();reload()
Manually trigger configuration reload.
env.reload();stopWatching()
Stop watching for file changes and cleanup resources.
env.stopWatching();Event Listeners
Listen for configuration changes:
// General configuration change
env.on('configChanged', (change) => {
console.log(`${change.variable} changed to ${change.newValue}`);
});
// Specific variable change
env.on('configChanged:PORT', (change) => {
console.log(`Port changed to ${change.newValue}`);
});
// Configuration reloaded
env.on('configReloaded', (event) => {
console.log('Configuration reloaded successfully');
});
// Reload error
env.on('configReloadError', (error) => {
console.error('Failed to reload configuration:', error);
});stage, isProduction, isDevelopment, isTest
Convenience getters for environment stage.
const stage = env.stage; // 'dev' | 'prod' | 'test'
const isProd = env.isProduction; // boolean
const isDev = env.isDevelopment; // boolean
const isTest = env.isTest; // booleanSchema Utilities
mergeSchemas(schema1, schema2)
Merge two schemas together.
const FullSchema = mergeSchemas(BaseSchema, CustomSchema);mergeMultipleSchemas(...schemas)
Merge multiple schemas together.
const FullSchema = mergeMultipleSchemas(Schema1, Schema2, Schema3);createCustomSchema(schema)
Create a custom schema with proper typing.
const CustomSchema = createCustomSchema(z.object({...}));createLooseSchema(schema)
Create a schema that allows unknown properties.
const LooseSchema = createLooseSchema(MySchema);createStrictSchema(schema)
Create a schema with strict validation.
const StrictSchema = createStrictSchema(MySchema);Template Generation
generateEnvTemplate(schema, options?)
Generate .env template from schema.
const template = generateEnvTemplate(MySchema, {
includeDefaults: true,
includeComments: true,
outputFile: '.env.template'
});generateTypes(schema, options?)
Generate TypeScript types from schema.
const types = generateTypes(MySchema, {
outputFile: 'env.types.ts'
});generateAllTemplates(schema, options?)
Generate all templates at once.
const allTemplates = generateAllTemplates(MySchema, {
envFile: '.env.template',
typesFile: 'env.types.ts'
});Environment Variables
Set these environment variables in your application (or in a .env file):
# Base configuration
STAGE=dev
LOG_LEVEL=info
SERVICE_NAME=my-service
ENABLE_DEBUG=false
ENABLE_METRICS=true
# AWS configuration
AWS_REGION=us-east-1
STATE_MACHINE_ARN=arn:aws:states:...
JOB_TABLE_NAME=my-jobs-table
QUEUE_URL=https://sqs.us-east-1.amazonaws.com/...
# Security configuration
JWT_SECRET=your-super-secret-jwt-key-here
API_KEY=your-api-keyError Handling
When validation fails, you get detailed error messages:
try {
const env = EnvironmentConfig.getInstance(MySchema, { validateStartup: true });
} catch (error) {
if (error instanceof EnvironmentConfig.EnvironmentValidationError) {
console.error(error.message);
// Example:
// Environment validation failed at startup.
// Missing required variables: DATABASE_URL, API_KEY
// Invalid variables: PORT (expected valid value, got 'abc')
console.error('Missing:', error.missingVars);
console.error('Invalid:', error.invalidVars);
} else {
throw error;
}
}Testing
For testing, you can reset the singleton instance:
import { EnvironmentConfig } from 'envolution';
beforeEach(() => {
EnvironmentConfig.reset();
});Contributing
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests
- Submit a pull request
License
MIT License - see LICENSE file for details.
