import { z } from "zod";
const booleanFromString = z
.string()
.transform((v) => v.trim().toLowerCase())
.pipe(z.enum(["true", "false", "1", "0"]))
.transform((v) => v === "true" || v === "1");
export const envSchema = z
.object({
NODE_ENV: z
.enum(["development", "test", "production"])
.default("development"),
PORT: z.coerce.number().int().positive().max(65535).default(3000),
DATABASE_URL: z.string().url(),
DB_POOL_SIZE: z.coerce.number().int().min(1).max(100).default(10),
LOG_LEVEL: z
.enum(["debug", "info", "warn", "error"])
.default("info"),
SESSION_SECRET: z.string().min(1),
METRICS_ENABLED: booleanFromString.default(false),
})
.superRefine((env, ctx) => {
if (env.NODE_ENV === "production" && env.SESSION_SECRET.length < 32) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["SESSION_SECRET"],
message: "must be at least 32 characters in production",
});
}
});
export type ParsedEnv = z.infer<typeof envSchema>;
import { envSchema, type ParsedEnv } from "./env.schema";
export type AppConfig = {
env: ParsedEnv["NODE_ENV"];
server: { port: number; logLevel: ParsedEnv["LOG_LEVEL"] };
db: { url: string; poolSize: number };
auth: { sessionSecret: string };
metricsEnabled: boolean;
};
let cached: AppConfig | null = null;
function toConfig(env: ParsedEnv): AppConfig {
return {
env: env.NODE_ENV,
server: { port: env.PORT, logLevel: env.LOG_LEVEL },
db: { url: env.DATABASE_URL, poolSize: env.DB_POOL_SIZE },
auth: { sessionSecret: env.SESSION_SECRET },
metricsEnabled: env.METRICS_ENABLED,
};
}
export function loadConfig(): AppConfig {
const result = envSchema.safeParse(process.env);
if (!result.success) {
const details = result.error.issues
.map((i) => ` - ${i.path.join(".") || "(root)"}: ${i.message}`)
.join("\n");
throw new Error(`Invalid environment configuration:\n${details}`);
}
return toConfig(result.data);
}
export function getConfig(): AppConfig {
if (cached === null) {
cached = loadConfig();
}
return cached;
}
import http from "node:http";
import { getConfig, type AppConfig } from "./config";
function start(config: AppConfig): void {
const server = http.createServer((_req, res) => {
res.writeHead(200, { "content-type": "application/json" });
res.end(JSON.stringify({ status: "ok", env: config.env }));
});
server.listen(config.server.port, () => {
console.log(
`listening on :${config.server.port} (${config.env}, log=${config.server.logLevel})`
);
});
}
function main(): void {
let config: AppConfig;
try {
config = getConfig();
} catch (err) {
console.error((err as Error).message);
process.exit(1);
return;
}
start(config);
}
main();
This snippet shows how a Node service turns the untyped, all-string process.env into a validated, strongly-typed configuration object that the rest of the application can trust. The core idea is fail-fast configuration: rather than discovering a missing or malformed variable deep inside request handling, the process refuses to boot when the environment is invalid, and it surfaces every problem at once.
In env.schema.ts, a Zod schema describes the shape of the environment. Because environment variables always arrive as strings, the schema leans on coercion and transforms: z.coerce.number() parses PORT, and a small booleanFromString helper maps "true"/"1" to real booleans. NODE_ENV is constrained to an enum with a default, DATABASE_URL must be a valid URL, and LOG_LEVEL falls back to a sane value. A superRefine block encodes a cross-field rule that a single-field validator cannot express: in production, SESSION_SECRET must be long enough, which catches weak secrets before they ship.
In config.ts, loadConfig() runs envSchema.safeParse(process.env) and inspects the result. On failure it walks result.error.issues, formats each path and message into a readable list, and throws a single aggregated error — so an operator sees all misconfigurations in one log line instead of fixing them one boot at a time. On success it maps the flat parsed values into a nested, domain-shaped object (server, db, auth) that reads naturally at call sites. The module memoizes the result in cached so parsing happens once, and it exports an inferred AppConfig type via z.infer so the config and its type never drift apart.
In server.ts, getConfig() is called at startup; if it throws, the process exits with a non-zero code before binding a port. This is the key trade-off: strictness at boot in exchange for confidence at runtime. Downstream code receives config.server.port as a number and config.auth.sessionSecret as a guaranteed non-empty string, with no defensive parsing or optional-chaining scattered around. The pattern suits any twelve-factor service, and the main pitfall to watch is keeping the schema authoritative — new variables belong in the schema first, not read ad hoc from process.env.
Related snips
export interface RetryOptions {
retries: number;
baseMs: number;
maxMs: number;
signal?: AbortSignal;
onRetry?: (attempt: number, delay: number, err: unknown) => void;
Exponential backoff with jitter for retries
class SignupForm
include ActiveModel::Model
include ActiveModel::Attributes
attribute :account_name, :string
attribute :email, :string
Shallow Controller, Deep Params: Form Object Pattern
import axios, { AxiosError } from 'axios'
import { v4 as uuidv4 } from 'uuid'
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000/api/v1',
timeout: 15000,
Axios API client with interceptors
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
interface FilterState {
search: string
category: string | null
Zustand for lightweight state management
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Form Validation Example</title>
<style>
HTML forms with validation and accessibility
import React from "react";
type FallbackProps = {
error: Error;
reset: () => void;
};
React Error Boundary + error reporting hook
Share this code
Here's the card — post it anywhere.