Careeroza
Validating Environment Variables
Fail Fast on Bad Config
A missing JWT_SECRET should stop the app at startup, not crash the first login in production. ConfigModule.forRoot accepts a validate function. A common approach uses class-validator:
src/config/env.validation.ts
import { plainToInstance } from 'class-transformer';
import { IsEnum, IsInt, IsString, Max, Min, MinLength, validateSync } from 'class-validator';
enum Environment { Development = 'development', Production = 'production', Test = 'test' }
class EnvironmentVariables {
@IsEnum(Environment) NODE_ENV: Environment;
@IsInt() @Min(1) @Max(65535) PORT: number;
@IsString() DATABASE_URL: string;
@IsString() @MinLength(32) JWT_SECRET: string;
}
export function validate(config: Record<string, unknown>) {
const env = plainToInstance(EnvironmentVariables, config, { enableImplicitConversion: true });
const errors = validateSync(env, { skipMissingProperties: false });
if (errors.length > 0) throw new Error(errors.toString());
return env;
}
// app.module.ts: ConfigModule.forRoot({ isGlobal: true, validate })Example
npm run start:devExample output
Error: An instance of EnvironmentVariables has failed the validation: - property JWT_SECRET has failed the following constraints: minLength
The Same Idea, Runnable
Example
A small typed env loader: convert strings, apply defaults and collect every problem before failing.
type EnvSpec = {
[key: string]: { type: 'string' | 'number' | 'enum'; values?: string[]; min?: number; default?: string };
};
function loadEnv(raw: Record<string, string | undefined>, spec: EnvSpec) {
const errors: string[] = [];
const out: Record<string, string | number> = {};
for (const [key, rule] of Object.entries(spec)) {
const value = raw[key] ?? rule.default;
if (value === undefined) { errors.push(key + ' is required'); continue; }
if (rule.type === 'number') {
const n = Number(value);
if (Number.isNaN(n)) errors.push(key + ' must be a number');
else out[key] = n;
} else if (rule.type === 'enum' && !rule.values!.includes(value)) {
errors.push(key + ' must be one of ' + rule.values!.join('|'));
} else if (rule.min && value.length < rule.min) {
errors.push(key + ' must be at least ' + rule.min + ' characters');
} else out[key] = value;
}
if (errors.length) throw new Error('Invalid environment: ' + errors.join('; '));
return out;
}
const spec: EnvSpec = {
NODE_ENV: { type: 'enum', values: ['development', 'production', 'test'], default: 'development' },
PORT: { type: 'number', default: '3000' },
JWT_SECRET: { type: 'string', min: 16 },
};
console.log(loadEnv({ JWT_SECRET: 'a-very-long-secret-value' }, spec));
try {
loadEnv({ NODE_ENV: 'prod', PORT: 'abc', JWT_SECRET: 'short' }, spec);
} catch (e) {
console.log((e as Error).message);
}Output
{
NODE_ENV: 'development',
PORT: 3000,
JWT_SECRET: 'a-very-long-secret-value'
}
Invalid environment: NODE_ENV must be one of development|production|test; PORT must be a number; JWT_SECRET must be at least 16 characters