M Logo
๐Ÿ“ TypeScript from Zero ยท Part 35 of 50

TypeScript: Typed Config from process.env with Zod (Fail Fast, Defaults, --env-file)

November 6, 2026

Welcome to episode 35 of TypeScript from Zero, a series for complete beginners (no JavaScript needed). Welcome back to TypeScript from zero! Programs need settings: a port number, a secret key, a level of logging. They come from environment variables, and every environment variable is just a text, or missing. Today we turn them into a typed config object. A new file called config.ts. Every code block below is the exact code and output from the video, ready to copy.

Setup

We start in the terminal and open the project in VS Code:

bash
cd tasks
touch src/config.ts
code .

1. Schema

Process.env is the object with all variables. We describe the ones that we need with Zod. The port arrives as text, so z dot coerce dot number turns it into a number, and then we check that it is a whole number between one and sixty five thousand. If it is missing, the default is three thousand. The log level must be one of three words, with info as the default. The API key has no default: it is required, and must not be empty.

2. Fail fast

Then we parse process.env. If something is wrong, we print a clear message and stop the program at once with exit code one. That is called failing fast: better to stop at the start than to crash in the middle of the night. If all is well, the config is exported, and its type comes from the schema.

3. Run

A second file prints the config. Without the key, the program stops, and tells us exactly what is missing. With the key in front of the command, it works, and look: the port is a number, three thousand, and the log level is the default.

src/config.ts:

ts
import { z } from "zod";

const ConfigSchema = z.object({
  PORT: z.coerce.number().int().min(1).max(65535).default(3000),
  LOG_LEVEL: z.enum(["debug", "info", "error"]).default("info"),
  API_KEY: z.string().min(1),
});

const result = ConfigSchema.safeParse(process.env);
if (!result.success) {
  console.error("Bad configuration:");
  console.error(z.prettifyError(result.error));
  process.exit(1);
}
export const config = result.data;

src/show-config.ts:

ts
import { config } from "./config.ts";
console.log(config);
text
$ node src/show-config.ts
Bad configuration:
โœ– Invalid input: expected string, received undefined
  โ†’ at API_KEY
text
$ API_KEY=secret node src/show-config.ts
{ PORT: 3000, LOG_LEVEL: 'info', API_KEY: 'secret' }

4. Bad value

A bad port, the word abc, is stopped as well, with the reason: expected number, received NaN.

text
$ PORT=abc API_KEY=x node src/show-config.ts
Bad configuration:
โœ– Invalid input: expected number, received NaN
  โ†’ at PORT

5. Error

And the types are real. The port is a number, so asking for to upper case is an error. tsc says: property to upper case does not exist on type number. TS2339.

src/show-config.ts:

ts
import { config } from "./config.ts";
console.log(config);
console.log(config.PORT.toUpperCase());
text
$ npx tsc --noEmit
src/show-config.ts:3:25 - error TS2339: Property 'toUpperCase' does not exist on type 'number'.

3 console.log(config.PORT.toUpperCase());
                          ~~~~~~~~~~~

Found 1 error in src/show-config.ts:3

6. Fix

We delete the line, and tsc is quiet again.

src/show-config.ts:

ts
import { config } from "./config.ts";
console.log(config);
bash
$ npx tsc --noEmit

7. env file

Typing variables before every command gets old. Node can read a file of variables with the flag -- env dash file. We write the key into a file called.env, and run again.

bash
$ echo "API_KEY=secret" > .env
text
$ node --env-file=.env src/show-config.ts
{ PORT: 3000, LOG_LEVEL: 'info', API_KEY: 'secret' }

Recap

Today: environment variables as text, a Zod schema with coerce, defaults and enum, failing fast, a typed config object, and the env file flag. Next episode: an HTTP server.

ยฉ 2026 All Rights Reserved By Mahmudul Alam