Dotenv
A small, zero-dependency .env loader for Node.js. Reads KEY=VALUE lines, coerces values to typed primitives when they look like one (number, boolean, null), supports ${VAR} interpolation between keys, picks the right file based on NODE_ENV, and layers a .env.shared file of defaults underneath.
Highlighted features
Typed primitives, not strings
env(“APP_PORT”, 3000) returns the number 3000 — not the string “3000”. Same for booleans and null.
${VAR} interpolation
DB_URL=postgres://${DB_USER}:${DB_PASS}@${DB_HOST}/db — substitution happens at parse time, references resolve from already-loaded keys.
NODE_ENV file picker
Reads .env.<NODE_ENV> automatically and layers .env.shared underneath for cross-environment defaults.
Zero deps, one file
One source file (src/index.ts), no runtime or peer dependencies. Node-only — uses fs and process.
Install
npm install @mongez/dotenvyarn add @mongez/dotenvpnpm add @mongez/dotenvZero runtime / peer dependencies. Node-only.
Quick peek
import { loadEnv, env } from "@mongez/dotenv";
loadEnv();
const port: number = env("APP_PORT", 3000); // 3000, not "3000"const debug: boolean = env("DEBUG", false); // true / false, not "true"const dbUrl: string = env("DB_URL");Boot once at process start, then read typed values from anywhere. env() returns real primitives, not the stringified process.env form.
Mental model
| Concept | Where it lives | What it is |
|---|---|---|
| Internal store | Module-level envData object | The typed view of every loaded key. Read via env() / env.all(). |
process.env mirror | Real Node process.env | Optional write-through, controlled by override. Always stores strings. |
| Initial snapshot | Module-level initialProcessEnvData | Captured at first import. resetEnv restores keys from this snapshot. |
The loader is stateful and module-scoped. One store per Node process. Calling loadEnv twice merges the second file’s keys into the first store rather than starting over.
Scope boundaries
| Concern | Lives in | Why |
|---|---|---|
| Validation / schema | zod, valibot, your code | Doesn’t type-check loaded values |
| Higher-level config (groups, defaults, dot-notation) | @mongez/config | This is one slice — the file-loading slice |
| Browser/cookie/localStorage | Other packages | This is a Node filesystem reader |
Quirks worth knowing
process.envalways stringifies. Even though the package writesprocess.env.PORT = 3000, Node’sprocess.envsetter coerces to"3000". Useenv()for the typed value.${VAR}is parse-time only. Substitutions happen the moment the value is parsed. Later updates to the referenced key do not re-trigger substitution in earlier lines.