envwise
v0.1.0
Published
Tiny, zero-dependency, type-safe environment variable validation for the native .env era. Declare a schema, get a typed config, and fail fast with a clear message listing every bad or missing var.
Maintainers
Readme
envwise
Type-safe environment variables in ~2KB. Zero dependencies. Fails fast with a message that actually tells you what's wrong.
Node now loads .env for you (node --env-file, process.loadEnvFile()) — but it still hands you untyped strings. PORT is "3000", DEBUG="false" is truthy, and a missing DATABASE_URL blows up three modules deep in production. envwise closes that gap: declare a schema, get a fully-typed config, and crash at startup with every problem listed at once.
import { env, str, num, port, bool, url, oneOf } from "envwise";
export const config = env({
NODE_ENV: oneOf(["development", "production", "test"], { default: "development" }),
PORT: port({ default: 3000 }),
DATABASE_URL: url(),
DEBUG: bool({ default: false }),
API_KEY: str({ optional: true }),
});
config.PORT; // number (not "3000")
config.DEBUG; // boolean (not "false")
config.NODE_ENV; // "development" | "production" | "test"
config.API_KEY; // string | undefined
config.DATABASE_URL; // string, guaranteed a valid URLIf something's wrong you get one error with all the problems:
EnvError: Invalid environment variables (2):
• PORT: must be a port 1–65535 (got "nope")
• DATABASE_URL: missing required value (expected url)Why envwise
- Zero dependencies. No zod, no runtime bloat. ~2KB.
- Built for the native
.envera. Assumes your.envis already loaded; it just validates + coerces + types. - Fail fast, fail clearly. Every invalid/missing var reported together, with the value it saw — not one-at-a-time crashes in prod.
- Fully typed. The returned object's types are inferred from your schema.
optionalwidens to| undefined;oneOfnarrows to a literal union. - Tiny API, no magic. Validators are just functions.
Install
npm install envwiseLoad your .env however you like, then validate:
node --env-file=.env dist/server.js # native, no dotenv needed// or explicitly
import { loadEnvFile } from "node:process";
loadEnvFile(); // Node 20.6+
import { config } from "./config.js";Validators
| Validator | Coerces to | Notes |
|-----------|-----------|-------|
| str() | string | |
| num() | number | any finite number |
| int() | number | integer only |
| port() | number | integer 1–65535 |
| bool() | boolean | true/1/yes/y/on vs false/0/no/n/off |
| url() | string | must parse as a URL |
| email() | string | basic email shape |
| json<T>() | T | JSON.parse'd |
| oneOf([...] as const) | literal union | membership-checked |
| list(item?) | T[] | comma-separated, each item validated |
Every validator takes { default?, optional? }:
default— used when the var is unset or empty.optional: true— unset yieldsundefined(and widens the type).- neither — the var is required; missing → error.
API
env(schema, options?) → typed config
Validates options.source (default process.env) and returns a typed object, or throws EnvError.
safeEnv(schema, options?) → { success, data | error }
Non-throwing variant for when you want to handle failures yourself.
const result = safeEnv(schema);
if (!result.success) {
console.error(result.error.message);
process.exit(1);
}
result.data.PORT; // typedoptions.source
Point envwise at any Record<string, string | undefined> — handy for tests:
env(schema, { source: { PORT: "8080" } });Errors
EnvError— thrown byenv();.issuesis aFieldError[].FieldError— one field;.keyand.detail.
Recipe: one typed config module
// config.ts
import { env, oneOf, port, url, bool, str } from "envwise";
export const config = env({
NODE_ENV: oneOf(["development", "production", "test"], { default: "development" }),
PORT: port({ default: 3000 }),
DATABASE_URL: url(),
REDIS_URL: url({ optional: true }),
LOG_LEVEL: oneOf(["debug", "info", "warn", "error"], { default: "info" }),
ENABLE_SIGNUPS: bool({ default: true }),
SESSION_SECRET: str(),
});Import config anywhere; if the environment is bad, the process never starts.
Develop
npm install
npm run build
npm testLicense
MIT © Anicodeth
