or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

custom-validators.mderror-handling.mdindex.mdmiddleware.mdvalidation.mdvalidators.md
tile.json

middleware.mddocs/

Middleware System

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.

Capabilities

Strict Proxy Middleware

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");
}

Accessor Middleware

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,
};

Default Middleware Chain

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 }) });

Custom Middleware Patterns

Logging Middleware

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");

Configuration Middleware

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();

Validation Middleware

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
);

Caching Middleware

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);
});

Middleware Composition

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
);

Types

type StrictProxy<T> = Proxy<T>;

interface StrictProxyMiddlewareOptions {
  extraInspectables?: string[];
}

interface CleanedEnvAccessors {
  isDevelopment: boolean;
  isDev: boolean;
  isTest: boolean;
  isProduction: boolean;
  isProd: boolean;
}