import { z } from "zod";
const booleanFromString = z.preprocess((val) => {
if (typeof val !== "string") return val;
return ["true", "1", "yes", "on"].includes(val.toLowerCase());
}, z.boolean());
const envSchema = z
.object({
NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
PORT: z.coerce.number().int().positive().default(3000),
LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"),
DATABASE_URL: z.string().url(),
DATABASE_SSL: booleanFromString.default(false),
DATABASE_POOL_SIZE: z.coerce.number().int().min(1).max(50).default(10),
JWT_SECRET: z.string().min(32, "JWT_SECRET must be at least 32 chars"),
})
.refine((cfg) => cfg.NODE_ENV !== "production" || cfg.DATABASE_SSL, {
message: "DATABASE_SSL must be enabled in production",
path: ["DATABASE_SSL"],
});
export type Env = z.infer<typeof envSchema>;
function parseEnv(): Env {
const result = envSchema.safeParse(process.env);
if (!result.success) {
const details = result.error.issues
.map((i) => ` - ${i.path.join(".") || "(root)"}: ${i.message}`)
.join("\n");
console.error(`\u274c Invalid environment configuration:\n${details}`);
process.exit(1);
}
return result.data;
}
export const env: Env = Object.freeze(parseEnv());
import { env } from "./env";
export const config = {
isProd: env.NODE_ENV === "production",
server: {
port: env.PORT,
logLevel: env.LOG_LEVEL,
},
db: {
url: env.DATABASE_URL,
ssl: env.DATABASE_SSL,
poolSize: env.DATABASE_POOL_SIZE,
},
auth: {
jwtSecret: env.JWT_SECRET,
tokenTtlSeconds: 60 * 60,
},
} as const;
export type AppConfig = typeof config;
import http from "node:http";
import { config } from "./config";
const server = http.createServer((_req, res) => {
res.writeHead(200, { "content-type": "application/json" });
res.end(JSON.stringify({ status: "ok", env: config.isProd ? "prod" : "dev" }));
});
server.listen(config.server.port, () => {
console.log(
`listening on :${config.server.port} (log level: ${config.server.logLevel})`
);
});
This snippet shows a common production pattern: validating and parsing environment variables once, at process startup, into a strongly-typed frozen config object. Reading process.env directly throughout an app is fragile because every value is string | undefined, coercion is ad hoc, and a missing variable typically fails deep inside runtime code far from its cause. Centralizing parsing with a schema turns those runtime surprises into a single fail-fast check with a readable error report.
In env.ts, the schema is declared with z.object. Because process.env values are always strings, numeric and boolean fields use z.coerce.number() and a custom booleanFromString helper rather than z.number() directly — coercion is the key detail people miss when first wiring Zod to env vars. Defaults such as PORT and LOG_LEVEL are expressed with .default(...), and z.enum constrains NODE_ENV to a known set. A .refine at the end enforces a cross-field rule: DATABASE_SSL must be enabled in production. The parsed result is exposed through parseEnv, which on failure formats error.issues into a multi-line message and calls process.exit(1) so misconfiguration never boots a half-working server.
The booleanFromString helper in the same file uses z.preprocess to accept the loose set of truthy strings ("true", "1", "yes") that real deployment tooling emits, keeping the boolean logic in one place.
Because env is derived via z.infer<typeof envSchema>, the exported type stays in perfect sync with the schema — adding a field updates the type automatically with no duplicate interface to maintain. Object.freeze prevents accidental mutation later.
In config.ts, the validated env is consumed to build higher-level config objects. Nothing downstream ever touches process.env again; instead config.db and config.server carry already-typed, already-validated values, so editors autocomplete fields and TypeScript rejects typos.
Finally, server.ts imports config and starts listening. Importing env.ts at the top of the module graph means validation runs before any request handling. The trade-off is a slightly stricter startup contract: every required variable must be present or the process refuses to boot — which is usually exactly the behavior wanted for infrastructure config, and far safer than discovering a missing secret under production load.
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
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
createdAt: String!
GraphQL API with Spring Boot
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
Share this code
Here's the card — post it anywhere.