typed-environment
A zero-dependency, TypeScript-native library to load and validate environment variables using a custom schema with advanced validation capabilities.
Overview
typed-environment
provides a type-safe way to access environment variables in your TypeScript projects. It allows you to define a schema for your environment variables, with support for validation, type conversion, and default values.
Features
- 🔒 Type Safety: Full TypeScript support with typed environment variables
- ✅ Validation: Validate environment variables against your schema
- 🔄 Type Conversion: Automatically convert string values to the appropriate type (string, number, boolean)
- 📝 Documentation: Self-documenting environment requirements
- 🏷️ Default Values: Specify default values for optional variables
- 🔍 Error Handling: Clear error messages for missing or invalid environment variables
- 🎯 Advanced Validations: String patterns, length limits, number ranges, and custom validators
Installation
npm install typed-environment
yarn add typed-environment
Basic Usage
import TypedEnv from 'typed-environment';
const schema = {
DATABASE_URL: { type: 'string', required: true },
PORT: { type: 'number', default: 3000 },
DEBUG: { type: 'boolean', default: false },
NODE_ENV: {
type: 'string',
required: true,
choices: ['development', 'production', 'test']
},
} as const;
const env = new TypedEnv(schema);
const config = env.init();
console.log(config.DATABASE_URL);
console.log(config.PORT);
console.log(config.DEBUG);
console.log(config.NODE_ENV);
Advanced Validation Features
String Validation
const schema = {
USERNAME: {
type: 'string',
required: true,
minLength: 3,
maxLength: 20
},
EMAIL: {
type: 'string',
required: true,
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
},
API_KEY: {
type: 'string',
required: true,
pattern: '^[a-zA-Z0-9]{32}$'
},
CUSTOM_FIELD: {
type: 'string',
required: true,
customValidator: (value: string) => value.startsWith('prefix_')
}
} as const;
Number Validation
const schema = {
PORT: {
type: 'number',
required: true,
min: 1000,
max: 65535
},
WORKER_COUNT: {
type: 'number',
required: true,
min: 1,
customValidator: (value: number) => value <= require('os').cpus().length
}
} as const;
Boolean Validation
const schema = {
ENABLE_FEATURE: {
type: 'boolean',
required: true,
customValidator: (value: boolean) => {
return process.env.NODE_ENV !== 'production' || value === true;
}
}
} as const;
Real-World Example
import TypedEnv from 'typed-environment';
const schema = {
DATABASE_URL: {
type: 'string',
required: true,
pattern: /^postgresql:\/\/.+/,
},
PORT: {
type: 'number',
default: 3000,
min: 1000,
max: 65535,
},
JWT_SECRET: {
type: 'string',
required: true,
minLength: 32,
pattern: /^[a-zA-Z0-9+/=]+$/,
},
ENABLE_LOGGING: {
type: 'boolean',
default: true,
},
NODE_ENV: {
type: 'string',
required: true,
choices: ['development', 'staging', 'production'],
},
RATE_LIMIT: {
type: 'number',
default: 100,
min: 1,
max: 10000,
customValidator: (value: number) => value % 10 === 0,
},
} as const;
const env = new TypedEnv(schema);
const config = env.init();
Validation Types
required | all | Field must be present |
default | all | Default value if not provided |
choices | all | Enum validation |
minLength | string | Minimum string length |
maxLength | string | Maximum string length |
pattern | string | Regex pattern matching |
min | number | Minimum numeric value |
max | number | Maximum numeric value |
customValidator | all | Custom validation function |
Error Handling
The library provides specific error types for different validation issues. When your .env
file contains invalid values, the library will throw descriptive errors to help you identify and fix the problems.
Error Types
MissingRequiredFieldError
: When a required field is missing
InvalidTypeError
: When a value can't be converted to the expected type
InvalidBooleanError
: When a value can't be parsed as a boolean
InvalidEnumError
: When a value is not one of the allowed choices
InvalidStringLengthError
: When a string doesn't meet length requirements
InvalidPatternError
: When a string doesn't match the required pattern
InvalidNumberRangeError
: When a number is outside the allowed range
CustomValidationError
: When custom validation fails
Comprehensive Error Handling Examples
Basic Error Handling Pattern
import TypedEnv from 'typed-environment';
import {
MissingRequiredFieldError,
InvalidPatternError,
InvalidStringLengthError,
InvalidNumberRangeError,
CustomValidationError,
InvalidEnumError,
InvalidTypeError,
InvalidBooleanError
} from 'typed-environment';
const schema = {
DATABASE_URL: {
type: 'string',
required: true,
pattern: /^postgresql:\/\/.+/
},
API_KEY: {
type: 'string',
required: true,
minLength: 32,
maxLength: 64
},
PORT: {
type: 'number',
required: true,
min: 1000,
max: 65535
},
NODE_ENV: {
type: 'string',
required: true,
choices: ['development', 'staging', 'production']
}
} as const;
const env = new TypedEnv(schema);
try {
const config = env.init();
console.log('Configuration loaded successfully!');
} catch (error) {
if (error instanceof MissingRequiredFieldError) {
console.error(`❌ Missing required environment variable: ${error.message}`);
process.exit(1);
} else if (error instanceof InvalidPatternError) {
console.error(`❌ Invalid format: ${error.message}`);
console.error('💡 Hint: DATABASE_URL should start with "postgresql://"');
} else if (error instanceof InvalidStringLengthError) {
console.error(`❌ Invalid length: ${error.message}`);
console.error('💡 Hint: API_KEY should be between 32-64 characters');
} else if (error instanceof InvalidNumberRangeError) {
console.error(`❌ Invalid port: ${error.message}`);
console.error('💡 Hint: PORT should be between 1000-65535');
} else if (error instanceof InvalidEnumError) {
console.error(`❌ Invalid environment: ${error.message}`);
console.error('💡 Hint: NODE_ENV must be development, staging, or production');
} else {
console.error(`❌ Configuration error: ${error.message}`);
}
}
Advanced Validation Error Scenarios
Here are real-world examples of what happens when your .env
file contains incorrect values:
Scenario 1: Invalid Database URL Pattern
DATABASE_URL=mysql://localhost:3306/myapp
API_KEY=abc123def456ghi789jkl012mno345pqr678stu901
PORT=8080
NODE_ENV=development
try {
const config = env.init();
} catch (error) {
if (error instanceof InvalidPatternError) {
console.error('Database URL must be a PostgreSQL connection string');
console.error('Expected format: postgresql://user:password@host:port/database');
}
}
Scenario 2: API Key Too Short
DATABASE_URL=postgresql://localhost:5432/myapp
API_KEY=short_key
PORT=8080
NODE_ENV=development
try {
const config = env.init();
} catch (error) {
if (error instanceof InvalidStringLengthError) {
console.error('API key must be between 32 and 64 characters');
console.error(`Current length: ${error.message.match(/\((\d+) characters\)/)?.[1]} characters`);
}
}
Scenario 3: Port Out of Range
DATABASE_URL=postgresql://localhost:5432/myapp
API_KEY=abc123def456ghi789jkl012mno345pqr678stu901
PORT=80
NODE_ENV=development
try {
const config = env.init();
} catch (error) {
if (error instanceof InvalidNumberRangeError) {
console.error('Port must be in the valid range for application servers');
console.error('Use ports 1000-65535 to avoid conflicts with system services');
}
}
Scenario 4: Invalid Environment Value
DATABASE_URL=postgresql://localhost:5432/myapp
API_KEY=abc123def456ghi789jkl012mno345pqr678stu901
PORT=8080
NODE_ENV=prod
try {
const config = env.init();
} catch (error) {
if (error instanceof InvalidEnumError) {
console.error('Invalid environment name');
console.error('Valid options: development, staging, production');
}
}
Custom Validation Error Handling
const schema = {
WORKER_COUNT: {
type: 'number',
required: true,
min: 1,
customValidator: (value: number) => {
const maxWorkers = require('os').cpus().length;
return value <= maxWorkers;
}
},
SECURE_TOKEN: {
type: 'string',
required: true,
customValidator: (value: string) => {
return /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/.test(value);
}
}
} as const;
try {
const config = env.init();
} catch (error) {
if (error instanceof CustomValidationError) {
if (error.message.includes('WORKER_COUNT')) {
console.error('Worker count exceeds available CPU cores');
console.error(`Maximum allowed workers: ${require('os').cpus().length}`);
} else if (error.message.includes('SECURE_TOKEN')) {
console.error('Token must contain uppercase, lowercase, number, and special character');
console.error('Example: MySecure123!');
}
}
}
Production-Ready Error Handling
function loadConfiguration() {
const env = new TypedEnv(schema);
try {
return env.init();
} catch (error) {
console.error('Configuration validation failed:', error);
if (error instanceof MissingRequiredFieldError) {
console.error('\n🔧 Setup Required:');
console.error('Please ensure all required environment variables are set in your .env file');
console.error('Run: cp .env.example .env');
} else if (error instanceof InvalidPatternError ||
error instanceof InvalidStringLengthError ||
error instanceof InvalidNumberRangeError ||
error instanceof InvalidEnumError) {
console.error('\n📋 Configuration Guide:');
console.error('Please check your .env file values against the requirements:');
console.error('- DATABASE_URL: postgresql://user:pass@host:port/db');
console.error('- API_KEY: 32-64 character string');
console.error('- PORT: number between 1000-65535');
console.error('- NODE_ENV: development|staging|production');
} else if (error instanceof CustomValidationError) {
console.error('\n⚙️ Custom Validation Failed:');
console.error('Please review the custom validation requirements in your schema');
}
if (process.env.NODE_ENV === 'production') {
process.exit(1);
}
throw error;
}
}
const config = loadConfiguration();
Error Prevention Tips
- Create an
.env.example
file with sample values to guide developers
- Use descriptive validation rules that match your application's requirements
- Implement graceful startup that validates configuration before starting services
- Log validation errors clearly to help with debugging during deployment
- Test your schema with invalid values to ensure proper error handling
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is licensed under the MIT License - see the LICENSE file for details.