VOOZH about

URL: https://dev.to/_06a3df6b50aec966668fb/dotenv-loads-your-env-it-doesnt-check-it-so-i-built-a-typed-validator-42p5

⇱ dotenv loads your .env — it doesn't check it. So I built a typed validator. - DEV Community


A PORT=8O80 typo (that's a letter O), a DATABASE_URL you forgot to set, a NODE_ENV=prodd — none of these fail when your app starts. They fail later: a cryptic stack trace three layers into startup, a service that boots but talks to the wrong database, a feature flag that's silently off in production. The error is never "your env is wrong"; it's whatever broke downstream.

dotenv loads your .env. It doesn't check it. So I built envward: validate the whole environment against a small typed schema up front, and fail loudly — with the actual problem — before a single line of app code runs. Zero dependencies, no network.

$ envward

.env — checked against env.schema.json

 ✗ API_KEY missing — required string
 ✗ NODE_ENV "prodd" is not one of: development, production, test
 ✗ PORT 70000 is above max 65535

3 problem(s), 3 key(s) valid

It exits non-zero on any problem, so it drops straight into a prestart hook or a CI step.

Get a schema in one command

No hand-writing JSON from scratch:

envward --init > env.schema.json

--init reads your existing .env and guesses a type for each key (8080int, https://…url, truebool, …), marking them required. Then you tighten it:

{"PORT":{"type":"int","required":true,"min":1,"max":65535},"DATABASE_URL":{"type":"url","required":true},"NODE_ENV":{"type":"enum","values":["development","production","test"]},"API_KEY":{"type":"string","required":true,"minLength":16}}

Types: string (with minLength/maxLength/pattern), int / number (with min/max), bool, url, email, enum. An empty value (KEY=) counts as missing.

How it's different from a drift checker

A .env drift tool tells you which keys are missing versus .env.example. envward validates the values: is PORT actually an integer in range, is DATABASE_URL actually a URL, is NODE_ENV one of the allowed set. Different failure mode, caught at a different time.

Install

npx envward # Node
pip install envward # Python — same behavior

Two builds (Node + Python) that validate identically, so it fits whatever your stack already runs.

Use it as a gate

# package.json: "prestart": "envward" — refuse to boot with a broken .env
# CI: envward --env .env.ci --strict

--strict also flags keys present in .env but missing from the schema.

A couple of honest notes

  • Zero dependencies, both builds — stdlib only.
  • A malformed schema is an error, not a guess. A non-numeric min, a bad regex pattern, an unknown type — envward exits 2 with a clear message in both builds, rather than crashing or silently passing. (Getting Node and Python to agree on every edge here took a real adversarial pass.)
  • pattern is matched in ASCII mode and as an unanchored search — wrap it in ^…$; keep to a portable regex subset for identical behavior across both builds.

Links


How do you guard environment config today — a hand-rolled startup check, a framework feature, or just hope? And would you gate CI on it?