← Back to list

I Got Tired of My App Crashing Because of a Missing .env Variable. So I Built a Package.

You’ve been there.

Mohib Habib · 2026-06-07 09:52 · 5 claps · 3.5 min read
#javascript #typescript #env #nodejs #validation
Open on Medium ↗
Wiki topics: 🌐 · Web Development

I Got Tired of My App Crashing Because of a Missing .env Variable. So I Built a Package.

You’ve been there.

You deploy your app. Everything looks fine. Then, five minutes later, you get a message saying something is broken. You check the logs, and buried somewhere in a stack trace is:

TypeError: Cannot read properties of undefined (reading 'split')

You stare at it for a minute. You trace it back. And eventually, you find it — someone forgot to add DATABASE_URL to the production .env file.

Not a code bug. Not a logic error. A missing environment variable.

I’ve hit this more times than I want to admit. And every time, the fix takes thirty seconds, but finding the cause takes twenty minutes. So I decided to actually do something about it.

The Problem With How We Usually Handle This

The most common approach I’ve seen and used myself is something like this at the top of your entry file:

if (!process.env.DATABASE_URL) {
  throw new Error('DATABASE_URL is required');
}

Which works. Until you have fifteen variables. Then it becomes this wall of if-statements that everyone quietly agrees is bad, but nobody fixes.

The next step up is reaching for Zod:

const envSchema = z.object({
  DATABASE_URL: z.string().url(),
  PORT: z.string().transform(Number),
  NODE_ENV: z.enum(['development', 'production', 'test']),
});
const env = envSchema.parse(process.env);

Zod is great. But it adds a dependency, it requires you to know Zod’s API, and the coercion story is awkward; environment variables are always strings, so PORT comes in as "3000" and you have to transform it manually.

I wanted something simpler. Something that just works.

What I Built

typed-env-guard is a zero-dependency environment variable validator for Node.js. You give it a schema, it validates your environment, coerces the types automatically, and throws a clear error if anything is wrong — before your app even starts.

npm install typed-env-guard

Here’s what it looks like in practice:

import { guardEnv } from 'typed-env-guard';
export const env = guardEnv({
  DATABASE_URL: { type: 'url', required: true },
  PORT:         { type: 'number', default: 3000 },
  NODE_ENV:     { type: 'enum', values: ['development', 'production', 'test'] as const },
  API_KEY:      { type: 'string', required: true, minLength: 32 },
  IS_PUBLIC:    { type: 'boolean', default: false },
});

That’s it. You call it once, at startup, and from that point forward env is fully typed and validated. env.PORT is a number. env.NODE_ENV is 'development' | 'production' | 'test'. TypeScript knows. Your editor knows.

The Error Output

This was the part I spent the most time on. When something goes wrong, you shouldn’t have to go digging. The error should tell you exactly what failed and why:

[typed-env-guard] Missing or invalid environment variables:
DATABASE_URL  →  required, got undefined
  API_KEY       →  must be at least 32 characters, got 8
  NODE_ENV      →  must be one of: development | production | test, got 
"staging"

All errors are collected in a single pass. You don’t fix one, restart, find the next one, restart again. You see everything at once.

The Things I Had to Think About

Building a small library forces you to make decisions you normally don’t think about.

Should it throw or call process.exit?

My first instinct was process.exit(1)fail fast, stop the app. But that makes the package impossible to test properly and hostile to any framework that wants to handle errors itself (Next.js, Lambda handlers, test environments). Throwing a named EnvGuardError is the right call. It's catchable, it's testable, and it instanceof EnvGuardError works reliably.

How do you handle empty strings?

process.env.SOME_VAR can be undefined (not set) or "" (set but empty). These are different situations. Some CI systems explicitly set variables to empty strings to clear them. If your app has DATABASE_URL set to "" and the error says got undefined, that's confusing. The package now correctly reports got "" in that case.

CJS vs ESM

This one cost me some time. TypeScript alone doesn’t emit .mjs files, so a dual-format package needs a bundler. I used tsup which handles both CommonJS and ESM output cleanly, and tsc --emitDeclarationOnly separately for the type declarations. The exports field in package.json wires it all together so consumers using import or require both get the right version.

The prototype chain bug nobody talks about

When TypeScript compiles a class that extends a built-in like Error, instanceof can return false in transpiled environments. The fix is one line:

Object.setPrototypeOf(this, new.target.prototype);

I only caught this because I had an AI agent review the code before publishing. Worth knowing if you ever write a custom error class.

What’s Supported

stringValidates a string. Optional minLength, maxLength numberCoerces "3000"3000. Optional min, max booleanCoerces "true", "1", "yes", "on"true urlValidates with the native URL constructor emailRegex validation for email format enumRestricts to a set of string values. Use as const for literal inference

Zero dependencies. Works in Node 18+.

Try It

npm install typed-env-guard

GitHub: mohibatventurenox/typed-env-guard

npm: npmjs.com/package/typed-env-guard

If you’ve ever spent twenty minutes debugging a production incident that turned out to be a missing environment variable, this is for you. It’s a small thing, but small things that consistently cause pain are worth fixing.

Let me know what you think, or open an issue if there’s a type or edge case you wish it handled.


메타데이터
post_id
5eea94d77440
slug
i-got-tired-of-my-app-crashing-because-of-a-missing-env-variable-so-i-built-a-package-5eea94d77440
url
https://medium.com/@mohib.habib42/i-got-tired-of-my-app-crashing-because-of-a-missing-env-variable-so-i-built-a-package-5eea94d77440
canonical_url
https://medium.com/@mohib.habib42/i-got-tired-of-my-app-crashing-because-of-a-missing-env-variable-so-i-built-a-package-5eea94d77440
author_url
https://medium.com/@mohib.habib42
status
ok
fetched_at
2026-06-11 05:11:55