Q25 of 38 · TypeScript

How do you type environment variables safely in a TypeScript test project?

TypeScriptMidtypescriptenvironment-variablesconfigurationdotenvnode

Short answer

Short answer: Access `process.env.VAR_NAME` and validate at startup — never trust individual accesses to return a defined string. Create a typed config module that reads env vars, throws on missing required values, and exports a typed `config` object. This centralizes validation and gives the rest of the project typed access.

Detail

process.env is typed as Record<string, string | undefined> in Node.js TypeScript — every access can be undefined. This is correct, but it means you must handle undefined on every access unless you centralize the concern.

Pattern: typed config module:

  1. Read all env vars in one file at startup.
  2. Validate required vars are defined; throw if not.
  3. Export a typed config object — the rest of the codebase uses this, not process.env directly.

Non-null assertion on env vars: process.env.BASE_URL! suppresses the undefined error but crashes at runtime if the var is actually missing. Worse than no protection. Always validate first.

dotenv: The dotenv package loads .env files into process.env. For tests, use dotenv.config() in your global setup. Never commit .env files — use .env.example.

Type augmentation for custom env: If you want TypeScript to know about your specific env var names, augment the ProcessEnv interface:

declare global { namespace NodeJS { interface ProcessEnv { BASE_URL: string; API_KEY: string; } } }

Note: this gives all vars the type string (not string | undefined) — combine with a validation function for safety.

// EXAMPLE

// config.ts — centralized env validation
function requireEnv(name: string): string {
  const value = process.env[name];
  if (!value) throw new Error(`Missing required env var: ${name}`);
  return value;
}

export const config = {
  baseUrl:    requireEnv("BASE_URL"),
  apiKey:     requireEnv("API_KEY"),
  environment: process.env.TEST_ENV ?? "staging", // optional with default
  headless:   process.env.HEADLESS !== "false",   // boolean from string
} as const;

// rest of tests use config.baseUrl — never process.env directly
import { config } from "./config";
await page.goto(config.baseUrl);

// Optional: augment ProcessEnv for IDE autocomplete
declare global {
  namespace NodeJS {
    interface ProcessEnv {
      BASE_URL: string;
      API_KEY: string;
      TEST_ENV?: string;
      HEADLESS?: string;
    }
  }
}

// WHAT INTERVIEWERS LOOK FOR

The centralized config module pattern with validation at startup. Avoiding `!` assertions on env vars. The `dotenv` setup. The `ProcessEnv` augmentation for autocomplete. Teams with many flaky CI failures often trace them to missing env vars that silently produce `undefined`.

// COMMON PITFALL

Reading `process.env.BASE_URL` directly in test files — if the var is missing, every test using it fails with a cryptic TypeError, not a clear 'missing config' message. Centralized validation produces actionable error messages.