express-env-validator
v0.1.0
Published
Type-safe environment variable validation for Express apps with Zod
Maintainers
Readme
express-env-validator
Type-safe, fail-fast environment configuration validation for Express apps with first-class secret handling.
Features
✅ Type-Safe - Full TypeScript support with automatic type inference from Zod schemas ✅ Fail-Fast - Validate environment at startup, not at runtime ✅ Secret Masking - Automatic masking of sensitive values in errors and logs ✅ Async Validation - Test database connections and external services during startup ✅ Express Integration - Built-in middleware with AsyncLocalStorage context ✅ Zero Dependencies - Only peer dependency: Zod ✅ Tiny Bundle - ~6KB minified, tree-shakeable ESM and CJS builds
Installation
npm install express-env-validator zodQuick Start
import express from 'express';
import { z } from 'zod';
import { validateEnvOrThrow, envMiddleware, getEnv, secret } from 'express-env-validator';
// 1. Define your schema
const envSchema = z.object({
PORT: z.string().transform(Number),
NODE_ENV: z.enum(['development', 'production', 'test']),
DATABASE_URL: secret(z.string().url()),
API_KEY: secret(z.string().min(32)),
});
// 2. Validate at startup (fail-fast if invalid)
const config = validateEnvOrThrow(envSchema);
// 3. Create Express app
const app = express();
// 4. Inject config into request context
app.use(envMiddleware(config));
// 5. Access config anywhere in your handlers
app.get('/api/info', (req, res) => {
const env = getEnv<typeof config>();
res.json({
environment: env.NODE_ENV,
port: env.PORT,
});
});
app.listen(config.PORT);Core Concepts
1. Fail-Fast Validation
Validate your environment before starting your application. If validation fails, your app won't start:
// Throws with clear error messages if validation fails
const config = validateEnvOrThrow(envSchema);
// Or handle errors manually
const result = validateEnv(envSchema);
if (!result.success) {
console.error('Environment validation failed:');
result.errors.forEach(err => {
console.error(` - ${err.key}: ${err.message}`);
});
process.exit(1);
}2. Secret Marking & Masking
Mark sensitive values with secret() to automatically mask them in error messages:
const envSchema = z.object({
API_KEY: secret(z.string()),
DB_PASSWORD: secret(z.string()),
PUBLIC_URL: z.string(), // Not a secret
});
// If validation fails, secrets are automatically masked
// Error: "API_KEY must be at least 32 characters"
// Value: "ab***yz" (instead of showing the full key)3. Async Validation
Test connections to databases, Redis, APIs during startup:
const config = await validateEnvAsyncOrThrow(envSchema, {
validators: {
DATABASE_URL: async (url) => {
const client = new pg.Client({ connectionString: url });
await client.connect();
await client.query('SELECT 1');
await client.end();
},
REDIS_URL: async (url) => {
const client = redis.createClient({ url });
await client.connect();
await client.ping();
await client.quit();
},
},
});
// Server only starts if all connections succeed!4. AsyncLocalStorage Context
Access your config anywhere without prop drilling:
app.use(envMiddleware(config));
// In any route handler
app.get('/route', (req, res) => {
const env = getEnv();
// Full type safety!
});
// In middleware
app.use((req, res, next) => {
const env = getEnv();
req.context = { nodeEnv: env.NODE_ENV };
next();
});
// In utility functions
function logDatabaseQuery(query: string) {
const env = getEnv();
if (env.LOG_QUERIES) {
console.log(query);
}
}API Reference
See API.md for complete API documentation.
Core Functions
validateEnv(schema, options?)- Validate environment variablesvalidateEnvOrThrow(schema, options?)- Validate and throw on errorvalidateEnvAsync(schema, options?)- Async validation with connection checksvalidateEnvAsyncOrThrow(schema, options?)- Async validate and throw on error
Secret Handling
secret(schema)- Mark a Zod schema as containing secret dataisSecret(schema)- Check if a schema is marked as secretmaskSecret(value)- Mask a secret value for display
Context Management
withEnvContext(env, fn)- Run a function with env in AsyncLocalStoragegetEnv<T>()- Get env from context (throws if not found)tryGetEnv<T>()- Get env from context (returns undefined if not found)
Express Integration
envMiddleware(env)- Express middleware to inject env into context
Examples
Basic Express App
import { validateEnvOrThrow, envMiddleware, getEnv } from 'express-env-validator';
const config = validateEnvOrThrow(z.object({
PORT: z.string().transform(Number),
NODE_ENV: z.enum(['development', 'production']),
}));
app.use(envMiddleware(config));
app.get('/health', (req, res) => {
const env = getEnv();
res.json({ status: 'healthy', env: env.NODE_ENV });
});With Async Validation
const config = await validateEnvAsyncOrThrow(envSchema, {
validators: {
DATABASE_URL: async (url) => {
// Returns string error message on failure
try {
await testConnection(url);
} catch (error) {
return `Database connection failed: ${error.message}`;
}
},
},
});Error Handling
const result = validateEnv(envSchema, {
onError: (errors) => {
errors.forEach(err => {
console.error(`❌ ${err.key}: ${err.message}`);
if (err.isSecret && err.value) {
console.error(` Value: ${err.value} (masked)`);
}
});
},
onSuccess: (config) => {
console.log('✅ Environment validated successfully');
},
});Custom Environment Source
// Useful for testing
const config = validateEnv(schema, {
env: {
PORT: '3000',
NODE_ENV: 'test',
},
});Type Safety
Full TypeScript inference from your Zod schema:
const schema = z.object({
PORT: z.string().transform(Number),
DEBUG: z.enum(['true', 'false']).transform(v => v === 'true'),
ITEMS: z.string().transform(s => s.split(',')),
});
const config = validateEnvOrThrow(schema);
config.PORT; // Type: number
config.DEBUG; // Type: boolean
config.ITEMS; // Type: string[]Error Messages
Clear, actionable error messages:
Environment validation failed:
- PORT: Required
- NODE_ENV: Invalid enum value. Expected 'development' | 'production' | 'test', received 'prod'
- API_KEY: String must contain at least 32 character(s)
- DATABASE_URL: Invalid urlSecrets are automatically masked:
Environment validation failed:
- API_KEY: String must contain at least 32 character(s)
Value: ap***ey (masked)Best Practices
- Validate at Startup: Call
validateEnvOrThrow()before creating your Express app - Mark Secrets: Always use
secret()for sensitive values (API keys, passwords, tokens) - Use Async Validators: Test external service connections during startup
- Centralize Config: Create a single
config.tsmodule that exports validated config - Type Your Schema: Let TypeScript infer types from your Zod schema
Real-World Example
See the examples directory for complete working examples:
- Basic Express App - Simple setup with sync validation
- Async Validation - Connection checks for PostgreSQL and Redis
- Error Handling - Custom error handling and logging patterns
Why express-env-validator?
- Better than dotenv alone: Dotenv loads
.envfiles but doesn't validate or type them - Better than process.env: No runtime surprises, full type safety, fail-fast
- Better than config libraries: Simpler, smaller, focused on Express with Zod
- Production-Ready: Built for fintech-grade rigor with secret handling
Requirements
- Node.js >= 18.0.0
- Express ^4.18.0 or ^5.0.0
- Zod ^3.23.0
License
MIT © [Your Name]
Contributing
See CONTRIBUTING.md for contribution guidelines.
Changelog
See CHANGELOG.md for release history.
