Type-Safe Environment Config and Runtime Validation
Validate environment variables and external input at runtime with schema libraries inferred into static types.
Why Validate process.env?
In Node.js, every value on process.env is a string or undefined — the runtime gives you no guarantees.
process.env.PORTmight be"3000","", or missing entirely.- A typo like
DATABSE_URLsilently yieldsundefined. - TypeScript types
process.envasRecord<string, string | undefined>, so it can't catch missing keys.
Reading config ad-hoc throughout your app means crashes surface deep inside request handlers — long after startup. The fix: validate once at boot and fail fast with a clear message.
// Untyped, unsafe access scattered everywhere
const port = process.env.PORT; // string | undefined
const dbUrl = process.env.DATABASE_URL; // string | undefined
console.log(typeof port); // "string" or "undefined"
console.log(Number(process.env.MISSING)); // NaN — silent failureSchema Libraries and Type Inference
A schema library lets you describe the shape and constraints of data once, then validate at runtime AND infer a static TypeScript type from the same definition.
- Popular choices: Zod, Valibot, ArkType, TypeBox.
- One source of truth — the schema — produces both the runtime check and the compile-time type.
- No duplicated
interfacethat can drift out of sync.
We'll use Zod, the most common in the Node.js ecosystem. z.infer<typeof schema> extracts the type the schema validates.
import { z } from "zod";
const UserSchema = z.object({
id: z.number().int(),
email: z.string().email(),
});
// Static type inferred from the runtime schema
type User = z.infer<typeof UserSchema>;
// type User = { id: number; email: string }All lessons in this course
- Migrating from CommonJS to Native ES Modules
- Configuring tsconfig for Node Backend Projects
- Type-Safe Environment Config and Runtime Validation
- Fast Iteration with tsx, Hot Reload, and Source Maps