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

@mongez/dotenv

v1.2.4

Published

A small .env loader for Node.js with type coercion (number / boolean / null), `${VAR}` interpolation, environment-specific files via NODE_ENV, and a shared-defaults layer.

Readme

@mongez/dotenv

A small .env loader for Node.js with type coercion, ${VAR} interpolation, NODE_ENV-aware file selection, and a shared-defaults layer.

npm license bundle size downloads


Why @mongez/dotenv?

dotenv hands you string-only values and one file. dotenv-flow adds file layering but still leaves you parsing "3000" into 3000 by hand. @mongez/dotenv is the smallest layer that does both: it picks the right file based on NODE_ENV, layers a .env.shared of defaults underneath, parses ${VAR} references between keys, and coerces values to real JavaScript primitives (number, boolean, null) so env("APP_PORT") returns 3000, not "3000". One source file, zero runtime dependencies.

import { loadEnv, env } from "@mongez/dotenv";

loadEnv();

const port: number = env("APP_PORT", 3000);
const debug: boolean = env("DEBUG", false);
const dbUrl: string = env("DB_URL");

Features

| Feature | Description | |---|---| | Typed coercion | "3000" becomes 3000, "true" becomes true, "null" becomes null — coercion is intentionally narrow and case-sensitive. | | NODE_ENV file resolution | Auto-picks .env.${NODE_ENV} when present, falls back to .env. | | Shared defaults | .env.shared loads first, environment-specific files override it. | | ${VAR} interpolation | Reference earlier keys inside later values for composed URLs and paths. | | Quoted values | ", ', and ` all work; quotes opt out of coercion and allow # inside the value. | | Typed reader | env(key, default) returns the parsed JS type, not the stringified process.env form. | | Read-only mode | Opt out of writing to process.env with override: false. | | Full reset | resetEnv() clears loaded values and removes process.env keys the loader wrote since import. | | Zero dependencies | No runtime or peer deps. One file, Node-only. |


Installation

npm install @mongez/dotenv
yarn add @mongez/dotenv
pnpm add @mongez/dotenv

Quick start

Create a .env file at your project root:

APP_NAME="My App"
APP_HOST=localhost
APP_PORT=3000
APP_URL=http://${APP_HOST}:${APP_PORT}
DEBUG=true
DB_PASS="P@ss#word"

Boot it at process start and read typed values:

import { loadEnv, env } from "@mongez/dotenv";

loadEnv();

env("APP_NAME");           // "My App"                (string)
env("APP_PORT");           // 3000                    (number)
env("DEBUG");              // true                    (boolean)
env("APP_URL");            // "http://localhost:3000" (interpolated)
env("DB_PASS");            // "P@ss#word"             (# preserved inside quotes)
env("MISSING", "default"); // "default"
env.all();                 // { APP_NAME: "My App", APP_PORT: 3000, ... }

That's the entire happy path. Everything below is depth on the same eight exports.


Loading .env files

loadEnv(envPath?, options?) is the entry point you call once at startup. With no arguments it resolves a file automatically:

  1. If loadSharedEnv is true (default) and ${dir}/.env.shared exists, load it first.
  2. Try ${dir}/.env.${process.env.NODE_ENV} (e.g. .env.production).
  3. If that file does not exist, fall back to ${dir}/.env.
import { loadEnv } from "@mongez/dotenv";

loadEnv();                                  // auto-resolve from cwd()
loadEnv("/etc/myapp/secrets.env");          // explicit path
loadEnv(undefined, { dir: __dirname });     // override the search root
loadEnv(undefined, { override: false });    // populate the store but skip process.env
loadEnv(undefined, { loadSharedEnv: false });

EnvLoaderOptions

| Option | Default | Effect | |---|---|---| | override | true | Mirror parsed values into process.env. Set to false to keep process.env untouched. | | dir | process.cwd() | Directory the resolver searches for .env.shared, .env.${NODE_ENV}, and .env. | | loadSharedEnv | true | Whether to load .env.shared before the environment-specific file. |

loadEnvFile(envPath, override) — load one explicit file

Use this when you need to load a file outside the standard resolution chain — multiple env files at different paths, host-specific overrides on disk, or deferred loading. Throws Error: .env file not found at <path> when the file is missing.

import { loadEnvFile } from "@mongez/dotenv";

loadEnvFile("/etc/myapp/base.env",  true);   // load + write to process.env
loadEnvFile("/etc/myapp/local.env", true);   // overrides keys from base

resetEnv() — true revert to import time

Clears the internal store, deletes every process.env key that loadEnv / loadEnvFile wrote since module load, and restores the import-time process.env snapshot. Keys you set directly on process.env outside this loader are not tracked and survive reset.

import { resetEnv } from "@mongez/dotenv";

// Useful in test setup:
afterEach(() => {
  resetEnv();
});

Parsing values

parseValue is intentionally narrow about which strings it converts. Coercion is case-sensitive — "True" stays a string.

| Input | Output | Note | |---|---|---| | "3000" | 3000 | Anything where !isNaN(x) is true becomes a number. | | "3.14" | 3.14 | Decimals supported. | | "-7" | -7 | Negatives supported. | | "true" | true | Lowercase only — "True" stays a string. | | "false" | false | Lowercase only. | | "null" | null | Lowercase only. | | "My App" | "My App" | Plain text passes through. | | '"3000"' | "3000" | Wrapping quotes opt OUT of coercion. | | '"a \\"b\\" c"' | 'a "b" c' | \" inside quotes is unescaped. | | "" | "" | Empty value passes through. |

${VAR} interpolation

A value containing ${VAR} substitutes another key from the internal store at parse time. The referenced key must appear earlier in the same file — or in .env.shared, which loads first.

APP_HOST=localhost
APP_PORT=3000
APP_URL=http://${APP_HOST}:${APP_PORT}
env("APP_URL"); // "http://localhost:3000"

Substitutions read from @mongez/dotenv's internal store, not from process.env. Unresolved references substitute the literal string "undefined".

Standalone parseLine / parseValue

You can call the parser directly without a file:

import { parseLine, parseValue } from "@mongez/dotenv";

parseLine("APP_PORT=3000");        // ["APP_PORT", 3000]
parseLine('APP_NAME="My App"');    // ["APP_NAME", "My App"]
parseLine("# comment");            // []                — non-data
parseLine("NO_EQUALS_HERE");       // []                — non-data
parseLine("KEY=a=b=c");            // ["KEY", "a=b=c"]  — splits on FIRST = only

parseValue("3000");                // 3000
parseValue('"3000"');              // "3000"

Reading typed values

env(key, defaultValue?) is the typed reader. It returns the value as it was parsed — number, boolean, string, or null — not the stringified process.env form.

import { env } from "@mongez/dotenv";

env("APP_PORT");                 // 3000          (number)
env("APP_PORT", 8080);           // 3000          (loaded value wins)
env("MISSING");                  // undefined
env("MISSING", "default");       // "default"
env("MISSING", 0);               // 0
env.all();                       // full record, by reference

Treat env.all() as read-only. It returns the underlying store by reference — mutating it mutates the store.

process.env vs env()

When override: true (the default), the loader writes each parsed value back to process.env[key]. Node's process.env setter coerces every value to a string, so the typed view only survives through env():

process.env.APP_PORT;  // "3000"  — string (Node coerced it)
env("APP_PORT");       // 3000    — number (typed)

Recipes

Boot at process start

Reach for this when you want a single import that guarantees env is loaded before any other module runs.

// src/bootstrap.ts — imported first by your entry point
import { loadEnv } from "@mongez/dotenv";

loadEnv();
// src/index.ts
import "./bootstrap"; // must run before any module that reads env
import express from "express";
import { env } from "@mongez/dotenv";

const app = express();
app.listen(env("APP_PORT", 3000), env("APP_HOST", "localhost"));

Layer a shared base across dev and prod

Reach for this when most env values are the same across environments and only a handful (DB URL, debug flag, log level) need to change.

# config/.env.shared
APP_NAME="My App"
APP_DESCRIPTION="A web app"

# config/.env.development
DB_URL="mongodb://localhost/dev"
DEBUG=true
LOG_LEVEL=debug

# config/.env.production
DB_URL="mongodb+srv://prod-host/app?retryWrites=true&w=majority"
DEBUG=false
LOG_LEVEL=info
import path from "node:path";
import { loadEnv, env } from "@mongez/dotenv";

loadEnv(undefined, {
  dir: path.resolve(__dirname, "../config"),
});

env("APP_NAME"); // "My App"     — shared
env("DEBUG");    // true | false — per-environment
env("DB_URL");   // mongo URL    — per-environment

Build a validated, typed config object

Reach for this when you want one place that names every env value your app needs and crashes loudly on startup if any are missing or wrong-typed. env() returns any, so layer zod on top for runtime validation.

import { z } from "zod";
import { loadEnv, env } from "@mongez/dotenv";

loadEnv();

const schema = z.object({
  APP_PORT: z.number().int().positive(),
  DEBUG: z.boolean(),
  DB_URL: z.string().url(),
});

export const config = schema.parse({
  APP_PORT: env("APP_PORT"),
  DEBUG: env("DEBUG"),
  DB_URL: env("DB_URL"),
});

// config.APP_PORT is `number`, config.DEBUG is `boolean`, etc.

Read-only mode alongside an orchestrator

Reach for this when a parent process (Docker, systemd, CI) already populated process.env and you want the .env file as a fallback view rather than something that overwrites the orchestrator's values.

import { loadEnv, env } from "@mongez/dotenv";

loadEnv(undefined, { override: false });

process.env.APP_PORT; // whatever the orchestrator set (or undefined)
env("APP_PORT");      // typed value from the file

Full reset between tests

Reach for this when test suites need each test to start from the same process.env baseline. resetEnv() deletes every key the loader wrote since import and restores the import-time snapshot.

import { afterEach } from "vitest";
import { loadEnvFile, resetEnv } from "@mongez/dotenv";

afterEach(() => {
  resetEnv();
});

it("reads APP_PORT as 3000", () => {
  loadEnvFile("./fixtures/.env.test", true);
  // ...assertions...
}); // resetEnv() runs after — store cleared, process.env restored

Related packages

| Package | Use when you need | |---|---| | @mongez/config | A higher-level app-config layer with dot-notation lookups, grouped namespaces, and defaults that sits on top of env(). | | @mongez/cache | A pluggable cache layer for memoizing the values you derive from env (DB clients, computed URLs, etc.). |

For the full API reference in a single LLM-friendly file, see llms-full.txt. For release history, see CHANGELOG.md.


License

MIT