foundation
Configuration
Load configuration deterministically, validate it at startup, separate config from secrets, and expose safe defaults.
Configuration is data that changes between environments but not between deploy artifacts — load it once at startup, validate shape, and inject into modules instead of reading env vars everywhere.
import { z } from 'zod';
const ConfigSchema = z.object({
port: z.coerce.number().default(3000),
databaseUrl: z.string().url(),
logLevel: z.enum(['debug', 'info', 'warn']).default('info'),
});
export const config = ConfigSchema.parse({
port: process.env.PORT,
databaseUrl: process.env.DATABASE_URL,
logLevel: process.env.LOG_LEVEL,
});
Fail fast: invalid config should prevent listening on a port. Separate secrets from non-secret config — secrets rotate on different cadence and need stricter access.
12-factor style: store config in environment, but centralize parsing in one module. Avoid `process.env.FEATURE_X` scattered in business logic.
On interviews: startup validation; feature flags vs config; how to test with alternate config without mutating global env.
Common pitfalls: silent defaults hiding misconfiguration; different config paths in dev vs prod; baking secrets into config files in the image.
The trade-off is balancing simplicity, performance, safety, and operability — name which axis you optimized and what cost you accepted.
Checklist:
- Single config module exported as readonly object.
- Schema validation at boot.
- Document required variables.
- Immutable after initialization.