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 (8080 → int, https://… → url, true → bool, …), 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 regexpattern, an unknown type — envward exits2with 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.) -
patternis 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
- npm: https://www.npmjs.com/package/envward
- PyPI: https://pypi.org/project/envward/
- Source: https://github.com/jjdoor/envward
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?
For further actions, you may consider blocking this person and/or reporting abuse
