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 🙏

© 2026 – Pkg Stats / Ryan Hefner

molex-env

v0.3.3

Published

Native .menv loader with profiles, typing, and origin tracking.

Readme

npm version npm downloads GitHub License: MIT Node.js Dependencies

Native .menv environment loader with profile support, typed parsing, origin tracking, and live reload. Zero dependencies.

Features

  • Zero dependencies - Pure Node.js implementation
  • Profile support - Environment-specific configs (dev, prod, staging)
  • Type-safe parsing - Automatic conversion of booleans, numbers, JSON, and dates
  • Strict validation - Schema enforcement with required fields and type checking
  • Origin tracking - Know exactly which file and line each value came from
  • Debug mode - See which files override values during cascading
  • Immutable config - Deep-freeze protection prevents accidental modifications
  • Live reload - Watch mode automatically reloads on file changes
  • Deterministic merging - Predictable cascading from base to profile files

Installation

npm install molex-env

Quick Start

const { load } = require('molex-env');

// Simplest usage - loads .menv files and attaches to process.menv
require('molex-env').load();
console.log(process.menv.PORT);  // Access typed values

// With profile and schema validation
const result = load({
  profile: 'prod',
  strict: true,
  schema: {
    PORT: 'number',
    DEBUG: 'boolean',
    SERVICE_URL: { type: 'string', required: true },
    METADATA: 'json'
  }
});

console.log(result.parsed.PORT);              // 3000 (number)
console.log(result.parsed.DEBUG);             // false (boolean)
console.log(result.origins.SERVICE_URL);      // { file: '.menv', line: 3, raw: 'https://api.example.com' }
console.log(process.menv.METADATA.region);    // 'us-east-1' (parsed JSON)

Setup

1. Create .menv files in your project root:

# .menv (base configuration)
PORT=3000
DEBUG=false
SERVICE_URL=https://api.example.com
DATABASE_URL=postgres://localhost:5432/myapp

2. Add profile-specific overrides (optional):

# .menv.prod (production overrides)
DEBUG=false
SERVICE_URL=https://api.production.com
DATABASE_URL=postgres://prod-server:5432/myapp
# .menv.local (local machine overrides - add to .gitignore)
DEBUG=true
DATABASE_URL=postgres://localhost:5432/myapp_dev

3. Load during application startup:

// Load with production profile
require('molex-env').load({ profile: 'prod' });

// Now use your typed config
const app = express();
app.listen(process.menv.PORT);

File Format

molex-env supports simple key=value syntax with automatic type detection:

# Comments start with #
# Strings (quotes are optional)
SERVICE_URL=https://api.example.com
API_KEY="secret-key-123"

# Numbers (integers and floats)
PORT=3000
TIMEOUT=30.5

# Booleans (case-insensitive)
DEBUG=true
ENABLE_CACHE=FALSE

# JSON objects and arrays
METADATA={"region":"us-east-1","tier":"premium"}
ALLOWED_IPS=["192.168.1.1","10.0.0.1"]

# Dates (ISO 8601 format)
START_DATE=2026-02-02
EXPIRES_AT=2026-12-31T23:59:59Z

# Empty values
OPTIONAL_KEY=

File Precedence

Files are loaded and merged in this order (later files override earlier ones):

  1. .menv - Base configuration
  2. .menv.local - Local overrides
  3. .menv.{profile} - Profile-specific config (e.g., .menv.prod)
  4. .menv.{profile}.local - Profile + local overrides (e.g., .menv.prod.local)

Example with profile: 'prod':

.menv            →  PORT=3000, DEBUG=true
.menv.local      →  (overrides) DEBUG=false
.menv.prod       →  (overrides) PORT=8080
.menv.prod.local →  (overrides) PORT=9000
Final result: PORT=9000, DEBUG=false

Debug mode - Use debug: true to see which files override values:

load({ profile: 'prod', debug: true });
// Console output:
// [molex-env] Override: DEBUG
//   Previous: .menv:2 = true
//   New:      .menv.local:1 = false
// [molex-env] Override: PORT
//   Previous: .menv.local:3 = 3000
//   New:      .menv.prod:1 = 8080

API Reference

load(options)Object

Load, merge, parse, and validate .menv files. This is the primary method you'll use.

Options:

| Option | Type | Default | Description | |--------|------|---------|-------------| | cwd | string | process.cwd() | Base directory to resolve files from | | profile | string | undefined | Profile name for .menv.{profile} files | | files | Array<string> | Auto-detected | Custom file list (absolute or relative to cwd) | | schema | Object | {} | Schema definition for validation and typing | | strict | boolean | false | Reject unknown keys, within-file duplicates, and invalid lines | | cast | boolean\|Object | true | Enable/disable type casting (see Type Casting) | | exportEnv | boolean | false | Write parsed values to process.env | | override | boolean | false | Override existing process.env values | | attach | boolean | true | Attach parsed values to process.menv | | freeze | boolean | true | Deep-freeze the parsed config object | | debug | boolean | false | Log file precedence overrides to console | | onWarning | Function | undefined | Callback for non-strict warnings |

Returns:

{
  parsed: Object,   // Typed configuration values
  raw: Object,      // Raw string values before type casting
  origins: Object,  // Source tracking: { KEY: { file, line, raw } }
  files: Array      // List of resolved file paths that were read
}

Examples:

// Basic usage
const result = load();
console.log(result.parsed);

// With profile
const result = load({ profile: 'production' });

// Custom directory
const result = load({ cwd: '/app/config' });

// Export to process.env
load({ exportEnv: true });
console.log(process.env.PORT);  // Now available in process.env

// Custom files
load({
  files: ['config/.menv', 'config/.menv.custom'],
  schema: { PORT: 'number', HOST: 'string' }
});

// Override existing environment variables
load({ 
  exportEnv: true, 
  override: true  // Will replace existing process.env values
});

parse(text, options)Object

Parse a string of .menv content without loading files. Useful for testing or processing environment strings from other sources.

Options:

| Option | Type | Default | Description | |--------|------|---------|-------------| | schema | Object | {} | Schema definition for validation | | strict | boolean | false | Enable strict validation (rejects unknown keys, within-file duplicates, invalid lines) | | cast | boolean\|Object | true | Enable/disable type casting | | freeze | boolean | true | Deep-freeze the result | | filePath | string | '<inline>' | Virtual file path used in origin tracking and error messages | | onWarning | Function | undefined | Callback for non-strict warnings (e.g., within-string duplicates) |

Note: The parse() function processes a single string, so the debug option for file precedence and cross-file features don't apply here.

Returns:

{
  parsed: Object,   // Typed values
  raw: Object,      // Raw string values before type casting
  origins: Object,  // Source tracking: { KEY: { file, line, raw } }
  files: Array      // Contains [filePath] if filePath option was set, otherwise []
}

Example:

const { parse } = require('molex-env');

const envContent = `
PORT=3000
DEBUG=true
METADATA={"env":"production"}
`;

const result = parse(envContent, {
  schema: {
    PORT: 'number',
    DEBUG: 'boolean',
    METADATA: 'json'
  },
  strict: true
});

console.log(result.parsed.PORT);       // 3000 (number)
console.log(result.parsed.DEBUG);      // true (boolean)
console.log(result.parsed.METADATA);   // { env: 'production' } (object)
console.log(result.origins.PORT);      // { file: '<inline>', line: 2, raw: '3000' }

watch(options, onChange)

Watch .menv files and reload automatically when they change. Perfect for development environments.

Arguments:

  • options - Same options as load(), including debug for automatic change logging
  • onChange(error, result) - Callback fired on file changes

Automatic Change Detection:

When debug: true is enabled, watch mode automatically logs what values changed on each reload:

const { watch } = require('molex-env');

watch({ 
  profile: 'dev',
  debug: true,  // Automatically logs changes
  schema: {
    PORT: 'number',
    DEBUG: 'boolean',
    SERVICE_URL: 'string'
  }
}, (err, result) => {
  if (err) {
    console.error('Config reload failed:', err.message);
    return;
  }
  console.log('✅ Config successfully reloaded');
});

// When you edit .menv files, automatic output:
// [molex-env] Config reloaded - changes detected:
//   PORT: 3000 → 8080
//   SERVICE_URL: https://api.example.com → https://api.production.com
// ✅ Config successfully reloaded

Manual Change Detection:

Without debug: true, you can manually detect changes in your callback:

let currentConfig;

watch({ profile: 'dev' }, (err, result) => {
  if (err) {
    console.error('Config reload failed:', err.message);
    return;
  }
  
  if (!currentConfig) {
    console.log('Initial config loaded');
  } else {
    // Manually check what changed
    if (currentConfig.PORT !== result.parsed.PORT) {
      console.log(`PORT changed: ${currentConfig.PORT} → ${result.parsed.PORT}`);
    }
  }
  
  currentConfig = result.parsed;
  
  // Restart your server or update app state here
  if (global.server) {
    global.server.close();
    global.server = startServer(result.parsed);
  }
});

console.log('Watching for .menv file changes...');

Example with Express hot reload:

const express = require('express');
const { watch } = require('molex-env');

let server;

function startServer(config) {
  const app = express();
  app.get('/', (req, res) => res.json({ port: config.PORT }));
  return app.listen(config.PORT, () => {
    console.log(`Server running on port ${config.PORT}`);
  });
}

// Start with initial config
const initial = require('molex-env').load({ profile: 'dev' });
server = startServer(initial.parsed);

// Watch for changes with automatic change logging
watch({ 
  profile: 'dev',
  debug: true,
  schema: {
    PORT: 'number',
    DEBUG: 'boolean'
  }
}, (err, result) => {
  if (!err && result.parsed.PORT !== initial.parsed.PORT) {
    console.log('Port changed, restarting server...');
    server.close(() => {
      server = startServer(result.parsed);
    });
  }
});

Schema Definition

Schemas provide type validation, required field enforcement, and default values.

Schema Formats

Simple string format:

const schema = {
  PORT: 'number',
  DEBUG: 'boolean',
  SERVICE_URL: 'string',
  METADATA: 'json',
  START_DATE: 'date'
};

Object format with options:

const schema = {
  PORT: { 
    type: 'number', 
    default: 3000 
  },
  DEBUG: { 
    type: 'boolean', 
    default: false 
  },
  SERVICE_URL: { 
    type: 'string', 
    required: true  // Will throw error if missing
  },
  METADATA: { 
    type: 'json',
    default: { region: 'us-east-1' }
  },
  START_DATE: { 
    type: 'date' 
  }
};

Schema Options

| Option | Type | Description | |--------|------|-------------| | type | string | Value type: 'string', 'boolean', 'number', 'json', or 'date' | | default | any | Default value if key is missing (must match type) | | required | boolean | If true, throws error when key is missing |

Type Parsing Rules

| Type | Description | Examples | |------|-------------|----------| | string | Plain text (default) | "hello", hello, "123" | | boolean | Case-insensitive true/false | true, TRUE, false, False | | number | Integer or float | 3000, 3.14, -42, 1e6 | | json | Valid JSON string | {"key":"value"}, [1,2,3], null | | date | ISO 8601 date string | 2026-02-02, 2026-02-02T10:30:00Z |

Example with all types:

load({
  schema: {
    // String (explicit)
    API_KEY: { type: 'string', required: true },
    
    // Boolean
    DEBUG: { type: 'boolean', default: false },
    ENABLE_LOGGING: 'boolean',
    
    // Number
    PORT: { type: 'number', default: 3000 },
    TIMEOUT: 'number',
    RETRY_COUNT: { type: 'number', default: 3 },
    
    // JSON (objects and arrays)
    METADATA: { type: 'json', default: {} },
    ALLOWED_HOSTS: 'json',  // Can be array or object
    
    // Date
    START_DATE: 'date',
    EXPIRES_AT: { type: 'date', required: true }
  },
  strict: true
});

Type Casting

Control how values are automatically converted from strings.

Enable/Disable All Casting

// Default: all types are cast
load({ cast: true });

// Disable all casting (everything stays as strings)
load({ cast: false });
console.log(typeof process.menv.PORT);  // 'string' (was '3000')

Selective Casting

// Only cast specific types
load({
  cast: {
    boolean: true,   // Cast booleans
    number: true,    // Cast numbers
    json: false,     // Keep JSON as strings
    date: false      // Keep dates as strings
  }
});

Strict Mode

Strict mode provides rigorous validation to catch configuration errors early.

What Strict Mode Enforces

When strict: true:

  • Unknown keys - Keys not in schema are rejected
  • Duplicate keys - Same key appearing twice in the same file throws error
    • Note: File precedence still works - different files can define the same key
  • Invalid lines - Malformed lines throw errors
  • Type validation - When schema is present, type mismatches throw errors (enabled by default with schema)

Example:

// .menv file
PORT=3000
DEBUG=true
UNKNOWN_KEY=value  // ← Not in schema

load({
  schema: {
    PORT: 'number',
    DEBUG: 'boolean'
  },
  strict: true  // Will throw error about UNKNOWN_KEY
});

Valid with strict mode (different files):

// .menv
PORT=3000

// .menv.prod
PORT=8080  // ✅ OK - overrides from different file

load({ profile: 'prod', strict: true });
// Result: PORT=8080

Invalid with strict mode (same file):

// .menv
PORT=3000
PORT=8080  // ❌ ERROR - duplicate in same file

load({ strict: true });  // Throws error

Non-Strict Mode (Default)

Without strict mode, the file precedence feature works as intended:

  • ✅ Unknown keys are allowed and parsed
  • Duplicate keys override - Later files can override keys from earlier files
  • ✅ Invalid lines are skipped
  • ⚠️ Warnings can be logged via onWarning callback for within-file duplicates

Example with warning handler:

// .menv file with duplicate keys
// PORT=3000
// PORT=8080

load({
  strict: false,
  onWarning: (info) => {
    if (info.type === 'duplicate') {
      console.warn(`Warning: Duplicate key '${info.key}' in ${info.file}:${info.line}`);
    }
  }
});
// Output: Warning: Duplicate key 'PORT' in .menv:2
// Result: PORT=8080 (last value wins)

Tip: Use debug: true to see cross-file overrides (file precedence), or onWarning to catch within-file duplicates.


Origin Tracking

Every configuration value includes its source file and line number, making debugging easy.

Example:

const result = load({ profile: 'prod' });

console.log(result.origins);
// {
//   PORT: { file: '.menv', line: 1, raw: '3000' },
//   DEBUG: { file: '.menv.local', line: 2, raw: 'false' },
//   SERVICE_URL: { file: '.menv.prod', line: 3, raw: 'https://api.production.com' }
// }

// Debug where a value came from
const portOrigin = result.origins.PORT;
console.log(`PORT is defined in ${portOrigin.file} at line ${portOrigin.line}`);

Practical debugging use case:

const { load } = require('molex-env');

const result = load({ profile: 'prod', strict: true });

// Verify configuration sources before deployment
Object.keys(result.parsed).forEach(key => {
  const origin = result.origins[key];
  console.log(`${key}=${result.parsed[key]} (from ${origin.file}:${origin.line})`);
});

// Example output:
// PORT=8080 (from .menv.prod:1)
// DEBUG=false (from .menv.prod:2)
// DATABASE_URL=postgres://prod:5432/db (from .menv.prod.local:3)

Advanced Examples

Complete Production Setup

const { load } = require('molex-env');

const config = load({
  profile: process.env.NODE_ENV || 'development',
  strict: true,
  exportEnv: true,
  schema: {
    // Server config
    NODE_ENV: { type: 'string', required: true },
    PORT: { type: 'number', default: 3000 },
    HOST: { type: 'string', default: '0.0.0.0' },
    
    // Database
    DATABASE_URL: { type: 'string', required: true },
    DB_POOL_SIZE: { type: 'number', default: 10 },
    
    // Redis
    REDIS_URL: { type: 'string', required: true },
    REDIS_TTL: { type: 'number', default: 3600 },
    
    // Feature flags
    ENABLE_CACHE: { type: 'boolean', default: true },
    ENABLE_METRICS: { type: 'boolean', default: false },
    
    // API config
    API_KEYS: { type: 'json', required: true },
    RATE_LIMITS: { type: 'json', default: { default: 100 } },
    
    // Dates
    MAINTENANCE_START: 'date',
    MAINTENANCE_END: 'date'
  }
});

console.log('Configuration loaded successfully');
console.log(`Running in ${config.parsed.NODE_ENV} mode on port ${config.parsed.PORT}`);

module.exports = config.parsed;

Dynamic Profile from Command Line

// Load profile from CLI argument
// Usage: node app.js --env=staging

const args = process.argv.slice(2);
const envArg = args.find(arg => arg.startsWith('--env='));
const profile = envArg ? envArg.split('=')[1] : 'development';

require('molex-env').load({
  profile,
  strict: true,
  schema: {
    PORT: 'number',
    DATABASE_URL: { type: 'string', required: true }
  }
});

console.log(`Started with profile: ${profile}`);
console.log(`PORT: ${process.menv.PORT}`);

Development with Hot Reload

const { watch } = require('molex-env');

watch({
  profile: 'dev',
  debug: true,  // Automatic change detection
  schema: {
    PORT: 'number',
    DEBUG: 'boolean',
    API_URL: 'string'
  }
}, (err, result) => {
  if (err) {
    console.error('Config error:', err.message);
    return;
  }
  
  console.log('Config reloaded and ready to use');
  // result.parsed has the new values
});

console.log('Watching for changes...');
// Output on file change:
// [molex-env] Config reloaded - changes detected:
//   DEBUG: false → true
//   API_URL: https://api.example.com → https://api.dev.local

Validation and Error Handling

const { load } = require('molex-env');

try {
  const config = load({
    profile: 'prod',
    strict: true,
    schema: {
      PORT: { type: 'number', required: true },
      DATABASE_URL: { type: 'string', required: true },
      REDIS_URL: { type: 'string', required: true }
    }
  });
  
  // Validate ranges
  if (config.parsed.PORT < 1024 || config.parsed.PORT > 65535) {
    throw new Error(`Invalid PORT: ${config.parsed.PORT} (must be 1024-65535)`);
  }
  
  // Validate URLs
  if (!config.parsed.DATABASE_URL.startsWith('postgres://')) {
    throw new Error('DATABASE_URL must be a PostgreSQL connection string');
  }
  
  console.log('Configuration validated successfully');
  
} catch (err) {
  console.error('Configuration error:', err.message);
  process.exit(1);
}

Best Practices

Environment Strategy

Development:   .menv + .menv.local
Staging:       .menv + .menv.staging
Production:    .menv + .menv.prod + .menv.prod.local

Security Tips

  • DO use strict: true in production to catch unknown keys and configuration errors
  • DO use debug: true during development to understand file precedence
  • DO validate sensitive values (URLs, ports, etc.) after loading
  • DON'T use exportEnv: true if you need immutable config

Performance

  • Config loading is synchronous and fast (~1-2ms for typical files)
  • Frozen configs (default) prevent accidental mutations
  • Use watch() only in development (slight memory overhead)

Example Project

A complete example application is included in examples/full.

cd examples/full
npm install
npm start

The example demonstrates:

  • Profile switching (dev/prod)
  • Schema validation
  • Type casting
  • Origin tracking
  • Live reload with watch mode

Troubleshooting

"Unknown key" error in strict mode

Problem: Getting errors about unknown keys when loading config.

Cause: You have a key in your .menv file that isn't defined in your schema, and strict: true is enabled.

Solution: Either add the key to your schema or disable strict mode:

// Option 1: Add missing key to schema
load({ 
  strict: true,
  schema: { 
    PORT: 'number',
    YOUR_MISSING_KEY: 'string'  // Add this
  }
});

// Option 2: Disable strict mode to allow unknown keys
load({ strict: false });

Note: This is different from "required" - unknown keys exist in your file but not in schema. Required keys exist in schema but not in your file.

Values are strings instead of typed

Problem: PORT is "3000" (string) instead of 3000 (number).

Solution: Enable casting or add schema:

load({ 
  cast: true,  // Ensure casting is enabled
  schema: { PORT: 'number' }
});

Changes to .menv not reflected

Problem: Modified .menv file but app still uses old values.

Solution:

  • If using attach: true (default), restart the app
  • Or use watch() for automatic reloading in development

Type casting fails

Problem: Getting parse errors for JSON or dates.

Solution: Verify the format in your .menv file:

# Valid JSON (use double quotes)
METADATA={"key":"value"}

# Valid date (ISO 8601)
START_DATE=2026-02-02

Understanding which file sets a value

Problem: Not sure which file is providing a specific config value.

Solution: Use debug: true to see file precedence in action:

load({ profile: 'prod', debug: true });
// Shows console output for each override

Or check the origins object:

const result = load({ profile: 'prod' });
console.log(result.origins.PORT);  // { file: '.menv.prod', line: 1, raw: '8080' }

License

MIT License