@liahus/safe-env
v1.0.3
Published
Type-safe environment variable validation for TypeScript & JavaScript. Framework-agnostic, validates at startup, secrets masked, DoS protected.
Downloads
37
Maintainers
Readme
🔒 safe-env
Type-safe environment variable validation for TypeScript & JavaScript
Framework-agnostic • Validates at startup • Secrets masked • DoS protected
Installation • Quick Start • Documentation • Security • Contributing
🚀 Installation
npm install @liahus/safe-envThat's it! No configuration needed. Works in Node.js, Bun, Deno, Next.js, Express, and more.
⚡ Quick Start
import { env } from "@liahus/safe-env";
const ENV = env({
DATABASE_URL: "string:url",
PORT: "number:default=3000",
NODE_ENV: "enum:development,production,test",
JWT_SECRET: "string:min=32:secret",
});
// ENV is fully typed! 🎉
console.log(ENV.PORT); // 3000 (number, not string!)
console.log(ENV.NODE_ENV); // "development" | "production" | "test"Works in JavaScript too! Type inference is optional.
import { env } from "@liahus/safe-env";
const ENV = env({
PORT: "number:default=3000",
API_URL: "string:url",
});
console.log(ENV.PORT); // 3000 (coerced to number)✨ Features
- ✅ Type-safe - Numbers are numbers, enums are union types
- ✅ Framework-agnostic - Works in Node.js, Bun, Deno, Next.js, Express, Fastify, NestJS, Vite, Webpack
- ✅ Validates at startup - Fail fast with clear errors
- ✅ Secret masking - Secrets never leak in error messages
- ✅ DoS protected - Built-in input validation limits
- ✅ Zero config - No filesystem access, no
.envloading - ✅ Simple DSL -
"string:url","number:default=3000","enum:dev,prod"
📖 DSL Syntax
Types
| Type | Description | Example |
|------|-------------|---------|
| string | String value | "string" |
| number | Number (auto-coerced) | "number" |
| enum | Enum with values | "enum:dev,prod,staging" |
Modifiers
| Modifier | Applies To | Description | Example |
|----------|------------|-------------|---------|
| url | string | Validates URL format | "string:url" |
| min=N | string, number | Minimum length/value | "string:min=10", "number:min=1000" |
| default=value | All | Default if missing | "string:default=info" |
| secret | All | Masks in errors | "string:secret" |
Examples
env({
// String with URL validation
API_URL: "string:url",
// String with minimum length and secret masking
API_KEY: "string:min=32:secret",
// Number with default value
PORT: "number:default=3000",
// Number with minimum value
TIMEOUT: "number:min=1000",
// Enum with specific values
NODE_ENV: "enum:development,production,test",
// String with default
LOG_LEVEL: "string:default=info",
});🛡️ Security
Secret Protection
Secrets are fully masked in error messages - only length is shown:
env({
JWT_SECRET: "string:min=32:secret",
});
// Error: [REDACTED (length: 12)]
// Never exposes partial values or actual contentDoS Protection
Built-in limits prevent resource exhaustion:
- DSL strings: Max 1000 characters
- Environment variables: Max 1000 per schema
- Enum values: Max 100 values, 200 chars each
Best Practices
// ✅ Always mark sensitive values as secret
env({
JWT_SECRET: "string:min=32:secret",
API_KEY: "string:min=20:secret",
DATABASE_URL: "string:url:secret",
});
// ❌ Don't log environment variables
console.log("API Key:", ENV.API_KEY); // Bad!
// ✅ Check existence instead
console.log("API Key configured:", !!ENV.API_KEY);🎨 Examples
Next.js
// app/config/env.ts
import { env } from "@liahus/safe-env";
export const ENV = env({
DATABASE_URL: "string:url",
NEXTAUTH_SECRET: "string:min=32:secret",
NEXTAUTH_URL: "string:url",
});Express / Fastify
// config/env.ts
import { env } from "@liahus/safe-env";
export const ENV = env({
PORT: "number:default=3000",
DATABASE_URL: "string:url",
JWT_SECRET: "string:min=32:secret",
});
// server.ts
import { ENV } from "./config/env";
app.listen(ENV.PORT);Custom Environment Source
import { env } from "@liahus/safe-env";
// Inject custom environment (useful for testing)
const ENV = env(
{
API_KEY: "string:min=10:secret",
TIMEOUT: "number:default=5000",
},
{
env: {
API_KEY: "my-secret-key",
// TIMEOUT will use default: 5000
},
}
);🛡️ Error Handling
Clean, human-readable errors with automatic secret masking:
import { env, EnvValidationError } from "@liahus/safe-env";
try {
const ENV = env({
DATABASE_URL: "string:url",
PORT: "number",
JWT_SECRET: "string:min=32:secret",
});
} catch (error) {
if (error instanceof EnvValidationError) {
console.error(error.message);
// Environment variable validation failed:
// DATABASE_URL: Invalid url (received: undefined)
// PORT: Required (received: undefined)
// JWT_SECRET: String must contain at least 32 character(s) (received: [REDACTED (length: 12)])
// Access individual errors (no secrets exposed)
error.errors.forEach((err) => {
console.log(`${err.key}: ${err.message}`);
});
}
}📚 API Reference
env(schema, options?)
Validates and returns typed environment variables.
Parameters:
schema- Object mapping variable names to DSL stringsoptions.env- Optional custom environment source (defaults toprocess.envif available)
Returns:
- Fully typed object matching your schema
Throws:
EnvValidationError- When validation fails
EnvValidationError
Custom error class for validation failures.
Properties:
message- Human-readable error messageerrors- Array of{ key: string, message: string }objects
🔧 Advanced
With dotenv
import "dotenv/config"; // Load .env file
import { env } from "@liahus/safe-env";
export const ENV = env({
DATABASE_URL: "string:url",
});Type Inference
Full TypeScript type inference from your schema:
const ENV = env({
PORT: "number:default=3000",
NODE_ENV: "enum:development,production",
});
// ENV.PORT is number
// ENV.NODE_ENV is "development" | "production"🏗️ Development
Prerequisites
- Node.js 18+ or Bun
- npm or yarn
Setup
# Clone the repository
git clone https://github.com/suhailopensource/safe-env.git
cd safe-env
# Install dependencies
npm install
# Build
npm run buildProject Structure
safe-env/
├── src/
│ ├── index.ts # Main entry point
│ ├── dsl.ts # DSL parser
│ ├── schema.ts # Zod schema builder
│ └── errors.ts # Error handling
├── dist/ # Compiled output (generated)
├── test.ts # Test suite
├── package.json
└── README.mdRunning Tests
npx tsx test.tsBuilding
npm run buildThis generates:
dist/index.js(ESM)dist/index.cjs(CommonJS)dist/index.d.ts(TypeScript definitions)
🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Development Guidelines
- Follow TypeScript best practices
- Add tests for new features
- Update documentation as needed
- Ensure all tests pass before submitting
📝 License
MIT © SUHAIL
See LICENSE for more information.
🙏 Acknowledgments
- Built with Zod for validation
- Inspired by the need for type-safe environment variable handling
Made with ❤️ for developers who care about type safety
