Introducing EnvGuard - a zero-dependency, type-safe environment variable validator with features like secret masking, warning mode, and .env.example generation
We've all been there. Your application crashes in production with a cryptic error, and after hours of debugging, you discover it's because someone forgot to set an environment variable. Or worse, they set PORT=three-thousand instead of PORT=3000.
Today, I'm excited to introduce EnvGuard - a zero-dependency, type-safe environment variable validator for Node.js that catches these issues at startup, not at 3 AM.
The Problem with Environment Variables
Environment variables are the standard way to configure applications, but they come with challenges:
// The old way - error-prone and repetitive
const port = process.env.PORT ? parseInt(process.env.PORT) : 3000;
const apiKey = process.env.API_KEY; // undefined? empty string? who knows!
const debug = process.env.DEBUG === 'true'; // what about 'yes', '1', 'on'?
This approach has several problems:
- No validation until the value is actually used
- Manual type coercion everywhere
- No TypeScript type inference
- Sensitive values can leak into logs
- No documentation of required variables
Enter EnvGuard
EnvGuard solves all of these problems with a clean, declarative API:
import { cleanEnv, str, num, bool, url } from '@opensourcesforge/envguard';
const env = cleanEnv({
PORT: num({ default: 3000 }),
DATABASE_URL: url(),
API_KEY: str({ secret: true }),
DEBUG: bool({ default: false }),
});
// Full TypeScript inference!
console.log(env.PORT); // number
console.log(env.DATABASE_URL); // string (validated URL)
console.log(env.API_KEY); // string (masked in errors)
console.log(env.DEBUG); // boolean
// Built-in environment helpers
if (env.isProduction) {
enableCaching();
}
What Makes EnvGuard Different?
I built EnvGuard because existing solutions were missing features I needed. Here's what sets EnvGuard apart:
1. Zero Dependencies
EnvGuard has no runtime dependencies. Your node_modules stays lean, and you don't inherit security vulnerabilities from transitive dependencies.
2. Secret Masking
When validation fails, sensitive values are automatically masked:
const env = cleanEnv({
API_KEY: str({ secret: true }),
});
// If API_KEY is invalid, error shows:
// ERROR: API_KEY - Invalid value (received: "sk_l****_xxx")
No more accidentally exposing secrets in CI logs!
3. Test-Specific Defaults
Unlike other libraries that only have devDefault, EnvGuard supports separate defaults for test environments:
const env = cleanEnv({
DATABASE_URL: url({
testDefault: 'postgres://localhost/test_db', // NODE_ENV=test
devDefault: 'postgres://localhost/dev_db', // NODE_ENV=development
// Required in production
}),
});
4. Warning Mode
Not every missing variable should crash your app. Use warnOnly for optional features:
const env = cleanEnv({
// Critical - will fail if missing
DATABASE_URL: url(),
// Optional - logs warning but continues
ANALYTICS_ID: str({
warnOnly: true,
desc: 'Google Analytics ID (optional)',
}),
});
5. Extra Variable Detection
Catch typos before they cause problems:
const env = cleanEnv(
{ PORT: num() },
{ warnOnExtra: true }
);
// If PROT=3000 is set (typo), you'll see:
// WARNING: PROT - Unknown environment variable
6. Conditional Requirements
Sometimes a variable is only required based on other configuration:
const env = cleanEnv({
USE_SMTP: bool({ default: false }),
SMTP_HOST: str({
requiredWhen: (env) => env.USE_SMTP === true,
}),
SMTP_PASSWORD: str({
requiredWhen: (env) => env.USE_SMTP === true,
secret: true,
}),
});
7. Rich Validator Library
EnvGuard includes validators you won't find elsewhere:
import {
str, num, bool, // Basics
url, email, host, port, // Network
json, array, uuid, // Data
duration, bytes, // Special
enums, regex, // Validation
makeValidator, // Custom
} from '@opensourcesforge/envguard';
const env = cleanEnv({
// Parse duration strings
CACHE_TTL: duration({ default: 300000 }), // '5m' -> 300000ms
// Parse byte sizes
MAX_UPLOAD: bytes({ unit: 'MB' }), // '50MB' -> 50
// Comma-separated arrays
ALLOWED_ORIGINS: array({ default: ['localhost'] }),
// Type-safe enums
LOG_LEVEL: enums({
values: ['debug', 'info', 'warn', 'error'] as const,
default: 'info',
}),
});
8. Auto-Generate .env.example
Document your environment variables automatically:
import { writeEnvExample, str, num, url } from '@opensourcesforge/envguard';
const spec = {
PORT: num({ default: 3000, desc: 'Server port' }),
DATABASE_URL: url({ desc: 'PostgreSQL connection URL' }),
API_KEY: str({ desc: 'API authentication key', secret: true }),
};
writeEnvExample(spec);
Generates:
# Server port
PORT=3000
# PostgreSQL connection URL
DATABASE_URL=
# API authentication key
API_KEY=
Real-World Example
Here's how I use EnvGuard in a typical Express application:
// src/env.ts
import { cleanEnv, str, num, bool, url, email, duration } from '@opensourcesforge/envguard';
export const env = cleanEnv({
// Server
PORT: num({ default: 3000, desc: 'HTTP server port' }),
HOST: str({ default: '0.0.0.0' }),
// Database
DATABASE_URL: url({ secret: true }),
DB_POOL_SIZE: num({ default: 10 }),
// Redis
REDIS_URL: url({ devDefault: 'redis://localhost:6379' }),
REDIS_TTL: duration({ default: 3600000 }), // 1 hour
// Auth
JWT_SECRET: str({ secret: true }),
JWT_EXPIRES_IN: str({ default: '7d' }),
// Email (optional)
SMTP_HOST: str({ warnOnly: true }),
SMTP_PORT: num({ default: 587 }),
FROM_EMAIL: email({ default: 'noreply@example.com' }),
// Features
ENABLE_SWAGGER: bool({ default: true }),
LOG_LEVEL: str({
choices: ['debug', 'info', 'warn', 'error'],
default: 'info',
}),
});
// src/app.ts
import express from 'express';
import { env } from './env';
const app = express();
if (env.ENABLE_SWAGGER && !env.isProduction) {
// Setup Swagger
}
app.listen(env.PORT, env.HOST, () => {
console.log(`Server running on ${env.HOST}:${env.PORT}`);
});
Getting Started
Install EnvGuard:
npm install @opensourcesforge/envguard
Create your environment configuration:
// src/env.ts
import { cleanEnv, str, num, bool } from '@opensourcesforge/envguard';
export const env = cleanEnv({
NODE_ENV: str({ choices: ['development', 'test', 'production'] }),
PORT: num({ default: 3000 }),
DEBUG: bool({ default: false }),
});
That's it! Your environment variables are now validated at startup with full TypeScript support.
Migration from envalid
If you're using envalid, migration is straightforward:
- import { cleanEnv, str, num } from 'envalid';
+ import { cleanEnv, str, num } from '@opensourcesforge/envguard';
- const env = cleanEnv(process.env, {
+ const env = cleanEnv({
PORT: num({ default: 3000 }),
API_KEY: str(),
});
Links
- GitHub: https://github.com/satinath-nit/envguard
- npm: @opensourcesforge/envguard
- Documentation: Full docs
Conclusion
Environment variable validation shouldn't be an afterthought. With EnvGuard, you get:
- Fail-fast validation at startup
- Full TypeScript type inference
- Secret masking for sensitive values
- Flexible defaults for different environments
- Zero dependencies
Give it a try and let me know what you think! I'd love to hear your feedback and feature requests.
EnvGuard is open source under the MIT license. Contributions are welcome!
Top comments (0)