Skip to main content
Version: 4.0.0-preview

Env validator

Heads up: the legacy Env provider with checkFile() / checkAll() from earlier docs has been replaced in v4 by the unified Configuration system. This page exists to point you to the new pattern; it no longer documents the old provider.

The v4 way to validate environment variables is the combination of:

  1. loadEnvSync() in src/main.ts to load .env files into process.env.
  2. defineConfig + Env.* builders in src/config/app.config.ts to declare every variable, its type, default, and validation rules.

If anything is missing or misshapen, configuration resolution reports a structured error block at startup. Validation runs on first access to the config (for example appConfig.values or appConfig.isValid()); in production (throwOnError defaults to true when NODE_ENV=production) it also throws and aborts the boot.

Quick example

src/config/app.config.ts
import { defineConfig, Env } from "@expressots/core";

export const appConfig = defineConfig({
app: {
name: Env.string("APP_NAME", { default: "expressots-app" }),
port: Env.port("PORT", { default: 3000 }),
env: Env.enum(
"NODE_ENV",
["development", "production", "test"] as const,
{ default: "development" },
),
},

database: {
url: Env.url("DATABASE_URL", { required: true }),
poolSize: Env.number("DB_POOL_SIZE", { default: 10, min: 1, max: 100 }),
},

auth: {
jwtSecret: Env.secret("JWT_SECRET", { required: true, minLength: 32 }),
},

features: {
flags: Env.array("FEATURE_FLAGS", { default: [], delimiter: "," }),
externalApi: Env.url("EXTERNAL_API_URL", { required: false }),
},
});

export type AppConfig = typeof appConfig;
src/main.ts
import { bootstrap, loadEnvSync } from "@expressots/core";
import { App } from "./app";
import { appConfig } from "./config/app.config";

loadEnvSync(); // Load .env, .env.local, .env.${NODE_ENV} (last wins)

if (!appConfig.isValid()) {
process.exit(1); // Errors were already printed as a structured block
}

void bootstrap(App);

If JWT_SECRET is shorter than 32 chars, or DATABASE_URL is unset, or PORT is non-numeric, the boot fails with a single error block listing every issue at once.

Mapping from the legacy provider

Legacy v2/v3v4 equivalent
this.provider.register(Env)(not needed; configuration is always available)
Env.checkFile()loadEnvSync() in main.ts
Env.checkAll()Automatic during defineConfig resolution
Env.get("FOO")appConfig.values.foo (typed)
Manual fallbacksEnv.string("FOO", { default: "bar" })
Manual existence checksEnv.string("FOO", { required: true })
Custom regexEnv.string("FOO", { pattern: /^[a-z]+$/ })
Secret redaction in logsEnv.secret("FOO") (auto-redacts in logs and toObject())

See also