const { z } = require('zod');
const envSchema = z
.object({
NODE_ENV: z
.enum(['development', 'test', 'production'])
.default('development'),
PORT: z.coerce.number().int().positive().default(3000),
DATABASE_URL: z.string().url(),
REDIS_URL: z.string().url().optional(),
JWT_SECRET: z.string().min(32, 'JWT_SECRET must be at least 32 chars'),
LOG_LEVEL: z
.enum(['debug', 'info', 'warn', 'error'])
.default('info'),
SENTRY_DSN: z.string().url().optional(),
})
.superRefine((env, ctx) => {
if (env.NODE_ENV === 'production' && !env.SENTRY_DSN) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ['SENTRY_DSN'],
message: 'SENTRY_DSN is required in production',
});
}
});
module.exports = { envSchema };
require('dotenv').config();
const { envSchema } = require('./env.schema');
const parsed = envSchema.safeParse(process.env);
if (!parsed.success) {
const errors = parsed.error.flatten().fieldErrors;
console.error('\n\u274c Invalid environment configuration:\n');
for (const [key, messages] of Object.entries(errors)) {
console.error(` ${key}: ${messages.join(', ')}`);
}
console.error('\nFix the variables above and restart.\n');
process.exit(1);
}
const config = Object.freeze({
...parsed.data,
isProduction: parsed.data.NODE_ENV === 'production',
});
module.exports = { config };
const { config } = require('./config');
const express = require('express');
const { createPool } = require('./db');
const app = express();
const pool = createPool(config.DATABASE_URL);
app.get('/healthz', async (_req, res) => {
try {
await pool.query('SELECT 1');
res.json({ status: 'ok', env: config.NODE_ENV });
} catch (err) {
res.status(503).json({ status: 'degraded' });
}
});
app.listen(config.PORT, () => {
console.log(`Listening on :${config.PORT} (${config.NODE_ENV})`);
});
This snippet shows the fail-fast configuration pattern: instead of scattering process.env.SOMETHING reads across a codebase and discovering a missing or malformed secret at 3am in production, the environment is parsed and validated exactly once at boot against a schema. If validation fails, the process refuses to start with a readable error listing every problem. This turns a whole class of runtime surprises into an immediate, loud startup failure.
In env.schema.js the shape of the environment is described with zod. Each variable is coerced and constrained: PORT uses z.coerce.number() because env vars are always strings, NODE_ENV is an enum with a default, DATABASE_URL must be a valid URL, and secrets like JWT_SECRET enforce a minimum length so a weak key can't slip through. The z.coerce helpers matter here — without them a numeric-looking string stays a string and downstream code silently misbehaves. A superRefine block adds a cross-field rule that only production requires SENTRY_DSN, which a flat per-field schema can't express.
In config.js the schema is applied. dotenv loads a .env file first so local development works, then envSchema.safeParse(process.env) runs. safeParse is used rather than parse so the code can format the errors itself: flatten().fieldErrors produces a compact per-field map that is printed before calling process.exit(1). The parsed result is frozen with Object.freeze and exported as config, giving the rest of the app a single typed, immutable source of truth. Because this file runs its validation at module load, simply requiring it triggers the check.
In server.js the very first line imports config, guaranteeing the guard runs before any server or database connection is attempted. The rest of the app reads config.PORT and config.DATABASE_URL instead of process.env, so values are already the right types and are known to exist. The trade-off is a small amount of boot-time ceremony and one dependency, in exchange for eliminating an entire category of latent misconfiguration bugs. This pattern is worth reaching for in any service deployed across multiple environments.
Related snips
class SignupForm
include ActiveModel::Model
include ActiveModel::Attributes
attribute :account_name, :string
attribute :email, :string
Shallow Controller, Deep Params: Form Object Pattern
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Form Validation Example</title>
<style>
HTML forms with validation and accessibility
import type { IncomingMessage, ServerResponse } from "http";
const MIN_BYTES = 1024;
const INCOMPRESSIBLE = /^(image|video|audio)\/|application\/(zip|gzip|x-brotli|pdf|octet-stream)/i;
Response compression (only when it helps)
import pino, { Logger } from 'pino';
import { AsyncLocalStorage } from 'node:async_hooks';
export interface Store {
requestId: string;
logger: Logger;
Request ID + structured logging (Express + pino)
import axios from 'axios';
export type NormalizedErrors = {
fields: Record<string, string>;
formLevel: string | null;
};
Frontend: normalize and display server validation errors
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
Laravel form requests for validation
Share this code
Here's the card — post it anywhere.