Validation for your environment variables
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Extensible middleware system for transforming and enhancing validated environment objects with custom functionality. Middleware allows customization of the final environment object with additional properties and behaviors.
Creates a strict proxy that prevents access to unvalidated variables and protects against mutations.
/**
* Creates strict proxy that prevents access to unvalidated variables
* Throws errors for undefined variables or mutation attempts
* @param envObj - Validated environment object
* @param rawEnv - Original raw environment object
* @param options - Configuration for proxy behavior
* @returns Proxy object with strict access controls
*/
function strictProxyMiddleware<T extends object>(
envObj: T,
rawEnv: unknown,
options?: StrictProxyMiddlewareOptions
): Proxy<T>;
interface StrictProxyMiddlewareOptions {
/** Additional properties that can be inspected/enumerated */
extraInspectables?: string[];
}Usage Examples:
import { customCleanEnv, str, strictProxyMiddleware } from "envalid";
// Using strict proxy middleware directly
const env = customCleanEnv(
process.env,
{
DATABASE_URL: str(),
API_KEY: str(),
},
(cleanedEnv, rawEnv) => {
return strictProxyMiddleware(cleanedEnv, rawEnv, {
extraInspectables: ["customProperty"]
});
}
);
// Attempting to access undefined variables throws an error
try {
console.log(env.UNDEFINED_VAR); // Throws error
} catch (error) {
console.error("Cannot access unvalidated variable");
}
// Attempting to modify throws an error
try {
env.DATABASE_URL = "new-url"; // Throws error
} catch (error) {
console.error("Environment is immutable");
}Adds convenient NODE_ENV-based boolean properties for environment detection.
/**
* Adds NODE_ENV convenience properties (isDev, isProd, isTest, etc.)
* Computes NODE_ENV from validated or raw environment
* @param envObj - Validated environment object
* @param rawEnv - Original raw environment object
* @returns Environment object with accessor properties
*/
function accessorMiddleware<T>(
envObj: T,
rawEnv: unknown
): T & CleanedEnvAccessors;
interface CleanedEnvAccessors {
/** True if NODE_ENV === 'development' */
isDevelopment: boolean;
/** Alias for isDevelopment */
isDev: boolean;
/** True if NODE_ENV === 'test' */
isTest: boolean;
/** True if NODE_ENV === 'production' */
isProduction: boolean;
/** Alias for isProduction */
isProd: boolean;
}Usage Examples:
import { customCleanEnv, str, accessorMiddleware } from "envalid";
// Using accessor middleware
const env = customCleanEnv(
process.env,
{
DATABASE_URL: str(),
API_KEY: str(),
},
(cleanedEnv, rawEnv) => {
return accessorMiddleware(cleanedEnv, rawEnv);
}
);
// Use convenience properties
if (env.isDevelopment) {
console.log("Running in development mode");
}
if (env.isProd) {
console.log("Production configuration active");
}
// Environment detection without NODE_ENV validation
const config = {
enableDebugLogs: env.isDev,
enableAnalytics: env.isProd,
skipAuth: env.isTest,
};Applies both accessor and strict proxy middleware in the standard order used by cleanEnv.
/**
* Applies both accessor and strict proxy middleware
* Default middleware chain used by cleanEnv
* @param cleanedEnv - Validated environment object
* @param rawEnv - Original raw environment object
* @returns Environment with accessors and strict proxy protection
*/
function applyDefaultMiddleware<T>(
cleanedEnv: T,
rawEnv: unknown
): StrictProxy<T & CleanedEnvAccessors>;
type StrictProxy<T> = Proxy<T>;Usage Examples:
import { customCleanEnv, str, applyDefaultMiddleware } from "envalid";
// Manually applying default middleware (equivalent to cleanEnv)
const env = customCleanEnv(
process.env,
{
DATABASE_URL: str(),
PORT: num({ default: 3000 }),
},
applyDefaultMiddleware
);
// Same as using cleanEnv directly
// const env = cleanEnv(process.env, { DATABASE_URL: str(), PORT: num({ default: 3000 }) });import { customCleanEnv, str, accessorMiddleware } from "envalid";
const loggingMiddleware = <T>(cleanedEnv: T, rawEnv: unknown) => {
const withAccessors = accessorMiddleware(cleanedEnv, rawEnv);
return {
...withAccessors,
log: (level: string, message: string) => {
const timestamp = new Date().toISOString();
const env = withAccessors.isDev ? "DEV" : "PROD";
console.log(`[${timestamp}] [${env}] [${level}] ${message}`);
}
};
};
const env = customCleanEnv(
process.env,
{ DATABASE_URL: str() },
loggingMiddleware
);
env.log("INFO", "Application starting");import { customCleanEnv, str, num, bool, accessorMiddleware } from "envalid";
const configMiddleware = <T>(cleanedEnv: T, rawEnv: unknown) => {
const withAccessors = accessorMiddleware(cleanedEnv, rawEnv);
return {
...withAccessors,
getConfig: () => ({
database: {
url: cleanedEnv.DATABASE_URL,
pool: withAccessors.isProd ? 20 : 5,
},
server: {
port: cleanedEnv.PORT,
cors: withAccessors.isDev,
},
features: {
analytics: withAccessors.isProd,
debugLogs: withAccessors.isDev,
}
})
};
};
const env = customCleanEnv(
process.env,
{
DATABASE_URL: str(),
PORT: num({ default: 3000 }),
},
configMiddleware
);
const appConfig = env.getConfig();import { customCleanEnv, str, strictProxyMiddleware } from "envalid";
const validationMiddleware = <T>(cleanedEnv: T, rawEnv: unknown) => {
// Additional validation after parsing
if (cleanedEnv.DATABASE_URL && !cleanedEnv.DATABASE_URL.startsWith("postgresql://")) {
throw new Error("DATABASE_URL must be a PostgreSQL connection string");
}
const strictEnv = strictProxyMiddleware(cleanedEnv, rawEnv);
return {
...strictEnv,
validate: () => {
console.log("All environment variables validated successfully");
return true;
}
};
};
const env = customCleanEnv(
process.env,
{ DATABASE_URL: str() },
validationMiddleware
);import { customCleanEnv, str, accessorMiddleware } from "envalid";
const cachingMiddleware = <T>(cleanedEnv: T, rawEnv: unknown) => {
const withAccessors = accessorMiddleware(cleanedEnv, rawEnv);
const cache = new Map<string, any>();
return {
...withAccessors,
getCached: <V>(key: string, factory: () => V): V => {
if (!cache.has(key)) {
cache.set(key, factory());
}
return cache.get(key);
},
clearCache: () => cache.clear(),
};
};
const env = customCleanEnv(
process.env,
{ DATABASE_URL: str() },
cachingMiddleware
);
// Cache expensive computations based on environment
const dbPool = env.getCached("dbPool", () => {
return createDatabasePool(env.DATABASE_URL);
});import { customCleanEnv, str, accessorMiddleware, strictProxyMiddleware } from "envalid";
// Compose multiple middleware functions
const composedMiddleware = <T>(cleanedEnv: T, rawEnv: unknown) => {
// Apply middleware in order
let env = accessorMiddleware(cleanedEnv, rawEnv);
// Add custom properties
env = {
...env,
startTime: Date.now(),
version: "1.0.0",
};
// Apply strict proxy last to protect everything
return strictProxyMiddleware(env, rawEnv);
};
const env = customCleanEnv(
process.env,
{ API_KEY: str() },
composedMiddleware
);type StrictProxy<T> = Proxy<T>;
interface StrictProxyMiddlewareOptions {
extraInspectables?: string[];
}
interface CleanedEnvAccessors {
isDevelopment: boolean;
isDev: boolean;
isTest: boolean;
isProduction: boolean;
isProd: boolean;
}Install with Tessl CLI
npx tessl i tessl/npm-envalid