envproof
v1.4.0
Published
TypeScript-first environment variable validation with schema-driven type safety
Downloads
485
Maintainers
Readme
EnvProof
TypeScript-first environment variable validation with schema-driven type safety
EnvProof validates environment variables at application startup and fails fast with human-readable errors. No more runtime crashes from missing or invalid configuration.
Features
- 🔒 Type-safe - Full TypeScript inference, no
string | undefined - ⚡ Fail-fast - Validation at startup, not runtime
- 📝 Self-documenting - Schema is the single source of truth
- 🎯 Explicit coercion - Clear rules for string → number/boolean/etc
- 🔐 Secret masking - Automatic redaction in error output
- 📄 .env.example generation - Auto-generate documentation
- 🧪 Strict mode - Fail on unknown environment variables
- 🔗 Cross-field validation - Validate constraints across multiple vars
- 📚 Layered dotenv support -
.env+ environment-specific overrides - 🚀 Zero dependencies - Lightweight and fast
- 🌐 Framework-agnostic - Works everywhere Node.js runs
Why EnvProof?
| Feature | EnvProof | Zod | t3-env | envalid | | ----------------------- | :------: | :---: | :----: | :-----: | | Zero dependencies | ✅ | ❌ | ❌ | ❌ | | Built-in CLI | ✅ | ❌ | ❌ | ✅ | | .env.example generation | ✅ | ❌ | ❌ | ❌ | | Bundle size | ~5KB | ~60KB | ~65KB | ~15KB | | First-class env focus | ✅ | ❌ | ✅ | ✅ | | Type inference | ✅ | ✅ | ✅ | ⚠️ | | Human-readable errors | ✅ | ⚠️ | ⚠️ | ✅ |
Installation
npm install envproof
# or
pnpm add envproof
# or
bun add envproofQuick Start
import { createEnv, e } from "envproof";
// Define your schema (single source of truth)
const env = createEnv({
DATABASE_URL: e.url().description("PostgreSQL connection string"),
PORT: e.number().port().default(3000),
NODE_ENV: e.enum(["development", "staging", "production"] as const),
DEBUG: e.boolean().optional(),
API_KEY: e.string().secret(),
});
// env is fully typed!
console.log(env.DATABASE_URL); // URL object
console.log(env.PORT); // number (3000 if not set)
console.log(env.NODE_ENV); // 'development' | 'staging' | 'production'
console.log(env.DEBUG); // boolean | undefined
console.log(env.API_KEY); // stringSchema Types
String
e.string()
.minLength(1) // Minimum length
.maxLength(255) // Maximum length
.length(10) // Exact length
.pattern(/^[A-Z]+$/) // Regex validation
.email() // Email format
.uuid() // UUID format
.nonEmpty() // Must not be empty/whitespace
.startsWith("sk_") // Must start with prefix
.endsWith(".json") // Must end with suffix
.ip() // IP address (IPv4 or IPv6)
.ip({ version: "v4" }) // IPv4 only
.secret() // Mask in error output
.optional() // Allow undefined
.default("fallback") // Default value
.description("...") // Documentation
.example("example_value"); // Example for .env.exampleNumber
e.number()
.min(0) // Minimum value
.max(100) // Maximum value
.integer() // Must be integer
.positive() // Must be > 0
.nonNegative() // Must be >= 0
.port() // Valid port (1-65535)
.between(1, 10); // Range shorthandBoolean
Accepts: true, false, 1, 0, yes, no, on, off (case-insensitive)
e.boolean().optional().default(false);Enum
e.enum(["development", "staging", "production"] as const).default(
"development"
);Note: Use
as constfor proper type inference.
URL
Returns a native URL object for easy manipulation.
e.url()
.protocols(["http", "https"]) // Restrict protocols
.http() // Shorthand for http/https
.withPath() // Require a path
.host("api.example.com"); // Require specific hostJSON
Parse and validate JSON configuration.
e.json<{ host: string; port: number }>()
.object() // Must be an object
.array() // Must be an array
.validate((v) => v.port > 0, "Port must be positive");Array
Parse comma-separated values into arrays.
e.array(e.string()); // "a,b,c" -> ["a", "b", "c"]
e.array(e.number()).separator(";"); // "1;2;3" -> [1, 2, 3]
e.array(e.string().email())
.minLength(1) // Minimum array length
.maxLength(10); // Maximum array lengthDuration
Parse human-readable duration strings into milliseconds.
e.duration(); // "10m" -> 600000
e.duration().default("24h"); // Default: 86400000 (string supported!)
e.duration().default(5000); // Or use milliseconds directly
e.duration().min("1s").max("1h"); // "30s" -> 30000Supports: ms, s, m, h, d, w (and long forms like seconds).
Path
Validate file system paths.
e.path()
.exists() // Must exist on disk
.isFile() // Must be a file
.isDirectory() // Must be a directory
.absolute() // Must be absolute path
.extension(".json") // specific extension
.readable() // Must be readable
.writable(); // Must be writableIP Address
Validate IP addresses.
e.string().ip(); // IPv4 or IPv6
e.string().ip({ version: "v4" }); // IPv4 only
e.string().ip({ version: "v6" }); // IPv6 onlyAdvanced Examples
Check out the /examples folder for complete working examples:
- basic.ts - Simple usage
- express.ts - Express.js integration
- nextjs.ts - Next.js configuration
- docker.ts - Docker/container environments
- monorepo.ts - Monorepo shared configuration
Configuration Options
const env = createEnv(schema, {
// Custom source (default: process.env)
source: process.env,
// Prefix filtering
prefix: "APP_", // Only read APP_* variables
stripPrefix: true, // APP_PORT -> PORT in output
// Error handling
onError: "throw", // 'throw' | 'exit' | 'return'
exitCode: 1, // Exit code when onError: 'exit'
// Output format
reporter: "pretty", // 'pretty' | 'json' | 'minimal'
// Strict mode
strict: false, // Fail on unknown vars
strictIgnore: ["HOME", "PATH"], // Optional allow-list when strict=true
// Dotenv Loading
dotenv: true, // Load .env files automatically
dotenvPath: [".env", ".env.local"], // Custom layered paths
dotenvExpand: true, // Expand ${VAR} references
// Multi-Environment
environment: process.env.NODE_ENV, // Current environment
requireInProduction: ["API_KEY"], // Make optional vars required in prod
optionalInDevelopment: ["SENTRY_DSN"], // Make required vars optional in dev
// Cross-field validation
crossValidate: (env) => {
if (env.NODE_ENV === "production" && !env.API_KEY) {
return {
variable: "API_KEY",
message: "API_KEY is required in production",
};
}
},
});onError: "return" Mode
Use onError: "return" when you want createEnv to return a ValidationResult
instead of throwing:
const result = createEnv(schema, { onError: "return" });
if (!result.success) {
console.error(result.errors);
} else {
console.log(result.data.PORT);
}Transforms & Custom Validators
Chain transformations and custom rules:
e.string()
.transform((s) => s.trim()) // Trim whitespace
.transform((s) => s.toLowerCase()) // Lowercase
.custom((val) => val.startsWith("sk_"), "Must start with sk_"); // Custom ruleSchema Composition
Compose and reuse schemas with built-in utilities:
import { mergeSchemas, extendSchema, pickSchema, omitSchema, prefixSchema } from "envproof";
// Merge multiple schemas
const baseSchema = {
NODE_ENV: e.enum(['dev', 'prod'] as const),
LOG_LEVEL: e.enum(['debug', 'info'] as const),
};
const serverSchema = {
PORT: e.number().port(),
HOST: e.string().default('localhost'),
};
const fullSchema = mergeSchemas(baseSchema, serverSchema);
// Extend a base schema
const extended = extendSchema(baseSchema, {
API_KEY: e.string().secret(),
});
// Pick specific fields
const dbOnly = pickSchema(fullSchema, ['DATABASE_URL', 'REDIS_URL']);
// Omit specific fields
const withoutPort = omitSchema(fullSchema, ['PORT']);
// Add prefix to all keys
const prefixed = prefixSchema(serverSchema, 'APP_');
// Result: { APP_PORT: e.number().port(), APP_HOST: e.string()... }Error Output
When validation fails, EnvProof provides clear, actionable error messages:
╭─────────────────────────────────────────────────────────────────╮
│ ❌ Environment Validation Failed │
│ 3 errors found │
╰─────────────────────────────────────────────────────────────────╯
┌─ MISSING VARIABLES ─────────────────────────────────────────────┐
│ │
│ DATABASE_URL │
│ ├─ Status: Missing (required) │
│ ├─ Expected: URL (PostgreSQL connection string) │
│ └─ Example: postgresql://user:pass@localhost:5432/db │
│ │
│ API_KEY │
│ ├─ Status: Missing (required) │
│ ├─ Expected: string (secret) │
│ └─ Example: sk_*** │
│ │
└──────────────────────────────────────────────────────────────────┘
┌─ INVALID VALUES ────────────────────────────────────────────────┐
│ │
│ PORT │
│ ├─ Status: Invalid type │
│ ├─ Expected: number (integer, 1-65535) │
│ ├─ Received: "not-a-number" │
│ └─ Example: 3000 │
│ │
└──────────────────────────────────────────────────────────────────┘
💡 Tip: Run `npx envproof generate` to create a .env.example fileJSON Reporter (for CI)
const env = createEnv(schema, { reporter: "json" });{
"success": false,
"errorCount": 3,
"errors": [
{
"variable": "DATABASE_URL",
"reason": "missing",
"message": "Required variable is not set",
"expected": "URL",
"isSecret": false
}
]
}Minimal Reporter (for logs)
const env = createEnv(schema, { reporter: "minimal" });❌ Environment validation failed (3 errors): DATABASE_URL, API_KEY, PORT.env.example Generation
Programmatic
import { generateExample } from "envproof";
const content = generateExample(schema);
console.log(content);CLI
npx envproof generate
npx envproof generate --output .env.template
npx envproof generate --force # Overwrite existingExample Output
# ============================================================
# Environment Configuration
# Generated by envproof
# ============================================================
# PostgreSQL connection string
# Required: yes
DATABASE_URL=postgresql://user:pass@localhost:5432/mydb
# Server port
# Required: no (default: 3000)
# PORT=3000
# Application environment
# Required: yes
# Options: development | staging | production
NODE_ENV=development
# Enable debug logging
# Required: no
# DEBUG=true
# External API key
# Required: yes
# ⚠️ This is a secret - do not commit real values
API_KEY=your_secret_hereCLI Commands
# Validate environment against schema
npx envproof check
npx envproof check --schema ./config/env.ts
npx envproof check --reporter json
npx envproof check --strict
# Generate .env.example
npx envproof generate
npx envproof generate --output .env.example
npx envproof generate --force
# Scaffold starter files
npx envproof init
npx envproof init --schema ./config/env.ts --output .env.example --forceBest Practices
1. Single Schema File
Create a dedicated file for your env schema:
// src/env.ts
import { createEnv, e } from "envproof";
export const env = createEnv({
// ... your schema
});Import it everywhere:
import { env } from "./env";
console.log(env.DATABASE_URL);2. Never Use process.env Directly
After setting up EnvProof, use only the typed env object:
// ❌ Bad
const port = process.env.PORT;
// ✅ Good
const port = env.PORT;3. Mark Sensitive Variables
Always mark secrets to prevent accidental logging:
API_KEY: e.string().secret(),
DATABASE_URL: e.url().secret(),4. Document Everything
Add descriptions for .env.example generation:
PORT: e.number()
.port()
.default(3000)
.description('HTTP server port')
.example('8080'),Framework Examples
Express
// src/env.ts
import { createEnv, e } from "envproof";
export const env = createEnv({
PORT: e.number().port().default(3000),
NODE_ENV: e.enum(["development", "production"] as const),
});
// src/index.ts
import express from "express";
import { env } from "./env";
const app = express();
app.listen(env.PORT, () => {
console.log(`Server running on port ${env.PORT}`);
});Next.js
// env.config.ts
import { createEnv, e } from "envproof";
export const env = createEnv({
DATABASE_URL: e.url(),
NEXTAUTH_SECRET: e.string().secret(),
NEXTAUTH_URL: e.url().optional(),
});Serverless (AWS Lambda)
import { createEnv, e } from "envproof";
const env = createEnv({
TABLE_NAME: e.string(),
AWS_REGION: e.string().default("us-east-1"),
});
export const handler = async (event) => {
// env is validated before handler runs
const tableName = env.TABLE_NAME;
};TypeScript
EnvProof provides full type inference:
const env = createEnv({
PORT: e.number().default(3000),
DEBUG: e.boolean().optional(),
NODE_ENV: e.enum(["dev", "prod"] as const),
});
// Types are inferred:
// env.PORT -> number
// env.DEBUG -> boolean | undefined
// env.NODE_ENV -> 'dev' | 'prod'Type Extraction
import type { InferEnv } from "envproof";
const schema = {
PORT: e.number(),
};
type Env = InferEnv<typeof schema>;
// { readonly PORT: number }Dotenv Utilities
EnvProof exports standalone dotenv utilities for advanced use cases:
import {
loadDotenv,
loadDotenvFiles,
expandDotenvVars,
parseDotenv,
} from "envproof";
// Load .env file (similar to dotenv)
loadDotenv(); // Loads .env by default
loadDotenv(".env.local"); // Custom path
// Load multiple .env files with priority
loadDotenvFiles(".env", ".env.local"); // .env.local takes precedence
// Expand variable references
expandDotenvVars({
HOST: "localhost",
API_URL: "https://${HOST}/api",
});
// Parse .env file content
const envContent = `
DATABASE_URL=postgresql://localhost:5432/db
PORT=3000
`;
const parsed = parseDotenv(envContent);
console.log(parsed); // { DATABASE_URL: '...', PORT: '3000' }These utilities can be used independently of createEnv() if you need custom dotenv loading logic.
API Reference
createEnv(schema, options?)
Main function to validate and create typed env object.
- schema: Record of variable names to schema definitions
- options: Configuration options (optional)
- returns: Frozen env object by default, or
ValidationResultwithonError: "return" - throws:
EnvValidationErrorif validation fails (unlessonError: "exit"or"return")
validateEnv(schema, options?)
Validate without throwing - returns result object.
const result = validateEnv(schema);
if (result.success) {
console.log(result.data);
} else {
console.error(result.errors);
}generateExample(schema, options?)
Generate .env.example content as string.
writeExampleFile(schema, options?)
Write .env.example file to disk.
e.* Schema Builders
e.string()- String valuese.number()- Numeric valuese.boolean()- Boolean valuese.enum([...])- Enumerated valuese.url()- URL valuese.json<T>()- JSON valuese.array(itemSchema)- Array values (comma-separated)e.duration()- Duration values (e.g., "1h", "30m")e.path()- File system paths
Dotenv Utilities
loadDotenv(path?)- Parse one .env file and return key-value pairsloadDotenvFiles(...paths)- Parse and merge multiple .env filesexpandDotenvVars(vars, context?)- Expand${VAR}references in dotenv valuesparseDotenv(content)- Parse .env file content to object
Schema Composition Utilities
mergeSchemas(...schemas)- Merge multiple schemas into oneextendSchema(base, extension)- Extend a base schema with additional fieldspickSchema(schema, keys)- Create a schema with only specified keysomitSchema(schema, keys)- Create a schema without specified keysprefixSchema(schema, prefix)- Add a prefix to all schema keys
License
MIT © EnvProof Contributors
