npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

express-env-validator

v0.1.0

Published

Type-safe environment variable validation for Express apps with Zod

Readme

express-env-validator

npm version License: MIT

Type-safe, fail-fast environment configuration validation for Express apps with first-class secret handling.

Features

Type-Safe - Full TypeScript support with automatic type inference from Zod schemas ✅ Fail-Fast - Validate environment at startup, not at runtime ✅ Secret Masking - Automatic masking of sensitive values in errors and logs ✅ Async Validation - Test database connections and external services during startup ✅ Express Integration - Built-in middleware with AsyncLocalStorage context ✅ Zero Dependencies - Only peer dependency: Zod ✅ Tiny Bundle - ~6KB minified, tree-shakeable ESM and CJS builds

Installation

npm install express-env-validator zod

Quick Start

import express from 'express';
import { z } from 'zod';
import { validateEnvOrThrow, envMiddleware, getEnv, secret } from 'express-env-validator';

// 1. Define your schema
const envSchema = z.object({
  PORT: z.string().transform(Number),
  NODE_ENV: z.enum(['development', 'production', 'test']),
  DATABASE_URL: secret(z.string().url()),
  API_KEY: secret(z.string().min(32)),
});

// 2. Validate at startup (fail-fast if invalid)
const config = validateEnvOrThrow(envSchema);

// 3. Create Express app
const app = express();

// 4. Inject config into request context
app.use(envMiddleware(config));

// 5. Access config anywhere in your handlers
app.get('/api/info', (req, res) => {
  const env = getEnv<typeof config>();
  res.json({
    environment: env.NODE_ENV,
    port: env.PORT,
  });
});

app.listen(config.PORT);

Core Concepts

1. Fail-Fast Validation

Validate your environment before starting your application. If validation fails, your app won't start:

// Throws with clear error messages if validation fails
const config = validateEnvOrThrow(envSchema);

// Or handle errors manually
const result = validateEnv(envSchema);
if (!result.success) {
  console.error('Environment validation failed:');
  result.errors.forEach(err => {
    console.error(`  - ${err.key}: ${err.message}`);
  });
  process.exit(1);
}

2. Secret Marking & Masking

Mark sensitive values with secret() to automatically mask them in error messages:

const envSchema = z.object({
  API_KEY: secret(z.string()),
  DB_PASSWORD: secret(z.string()),
  PUBLIC_URL: z.string(), // Not a secret
});

// If validation fails, secrets are automatically masked
// Error: "API_KEY must be at least 32 characters"
// Value: "ab***yz" (instead of showing the full key)

3. Async Validation

Test connections to databases, Redis, APIs during startup:

const config = await validateEnvAsyncOrThrow(envSchema, {
  validators: {
    DATABASE_URL: async (url) => {
      const client = new pg.Client({ connectionString: url });
      await client.connect();
      await client.query('SELECT 1');
      await client.end();
    },
    REDIS_URL: async (url) => {
      const client = redis.createClient({ url });
      await client.connect();
      await client.ping();
      await client.quit();
    },
  },
});

// Server only starts if all connections succeed!

4. AsyncLocalStorage Context

Access your config anywhere without prop drilling:

app.use(envMiddleware(config));

// In any route handler
app.get('/route', (req, res) => {
  const env = getEnv();
  // Full type safety!
});

// In middleware
app.use((req, res, next) => {
  const env = getEnv();
  req.context = { nodeEnv: env.NODE_ENV };
  next();
});

// In utility functions
function logDatabaseQuery(query: string) {
  const env = getEnv();
  if (env.LOG_QUERIES) {
    console.log(query);
  }
}

API Reference

See API.md for complete API documentation.

Core Functions

  • validateEnv(schema, options?) - Validate environment variables
  • validateEnvOrThrow(schema, options?) - Validate and throw on error
  • validateEnvAsync(schema, options?) - Async validation with connection checks
  • validateEnvAsyncOrThrow(schema, options?) - Async validate and throw on error

Secret Handling

  • secret(schema) - Mark a Zod schema as containing secret data
  • isSecret(schema) - Check if a schema is marked as secret
  • maskSecret(value) - Mask a secret value for display

Context Management

  • withEnvContext(env, fn) - Run a function with env in AsyncLocalStorage
  • getEnv<T>() - Get env from context (throws if not found)
  • tryGetEnv<T>() - Get env from context (returns undefined if not found)

Express Integration

  • envMiddleware(env) - Express middleware to inject env into context

Examples

Basic Express App

import { validateEnvOrThrow, envMiddleware, getEnv } from 'express-env-validator';

const config = validateEnvOrThrow(z.object({
  PORT: z.string().transform(Number),
  NODE_ENV: z.enum(['development', 'production']),
}));

app.use(envMiddleware(config));

app.get('/health', (req, res) => {
  const env = getEnv();
  res.json({ status: 'healthy', env: env.NODE_ENV });
});

With Async Validation

const config = await validateEnvAsyncOrThrow(envSchema, {
  validators: {
    DATABASE_URL: async (url) => {
      // Returns string error message on failure
      try {
        await testConnection(url);
      } catch (error) {
        return `Database connection failed: ${error.message}`;
      }
    },
  },
});

Error Handling

const result = validateEnv(envSchema, {
  onError: (errors) => {
    errors.forEach(err => {
      console.error(`❌ ${err.key}: ${err.message}`);
      if (err.isSecret && err.value) {
        console.error(`   Value: ${err.value} (masked)`);
      }
    });
  },
  onSuccess: (config) => {
    console.log('✅ Environment validated successfully');
  },
});

Custom Environment Source

// Useful for testing
const config = validateEnv(schema, {
  env: {
    PORT: '3000',
    NODE_ENV: 'test',
  },
});

Type Safety

Full TypeScript inference from your Zod schema:

const schema = z.object({
  PORT: z.string().transform(Number),
  DEBUG: z.enum(['true', 'false']).transform(v => v === 'true'),
  ITEMS: z.string().transform(s => s.split(',')),
});

const config = validateEnvOrThrow(schema);

config.PORT;   // Type: number
config.DEBUG;  // Type: boolean
config.ITEMS;  // Type: string[]

Error Messages

Clear, actionable error messages:

Environment validation failed:
  - PORT: Required
  - NODE_ENV: Invalid enum value. Expected 'development' | 'production' | 'test', received 'prod'
  - API_KEY: String must contain at least 32 character(s)
  - DATABASE_URL: Invalid url

Secrets are automatically masked:

Environment validation failed:
  - API_KEY: String must contain at least 32 character(s)
    Value: ap***ey (masked)

Best Practices

  1. Validate at Startup: Call validateEnvOrThrow() before creating your Express app
  2. Mark Secrets: Always use secret() for sensitive values (API keys, passwords, tokens)
  3. Use Async Validators: Test external service connections during startup
  4. Centralize Config: Create a single config.ts module that exports validated config
  5. Type Your Schema: Let TypeScript infer types from your Zod schema

Real-World Example

See the examples directory for complete working examples:

  • Basic Express App - Simple setup with sync validation
  • Async Validation - Connection checks for PostgreSQL and Redis
  • Error Handling - Custom error handling and logging patterns

Why express-env-validator?

  • Better than dotenv alone: Dotenv loads .env files but doesn't validate or type them
  • Better than process.env: No runtime surprises, full type safety, fail-fast
  • Better than config libraries: Simpler, smaller, focused on Express with Zod
  • Production-Ready: Built for fintech-grade rigor with secret handling

Requirements

  • Node.js >= 18.0.0
  • Express ^4.18.0 or ^5.0.0
  • Zod ^3.23.0

License

MIT © [Your Name]

Contributing

See CONTRIBUTING.md for contribution guidelines.

Changelog

See CHANGELOG.md for release history.