Environment files
Automatic environment-file loading — the file set, precedence, variable expansion, the skip under the test environment, and how an explicit env-file flag disables auto-discovery.
Nub reads your .env* files and injects them into the environment before Node starts — no dotenv import, no --env-file flag. Loading happens from the nearest directory with a package.json (walking up from your cwd), matching Vite's single-directory model. Works on every Node version Nub supports (18.19+).
nub server.ts # .env* in the project root are loaded automaticallyFile precedence
Four filenames are loaded, highest priority first. The shell environment always wins over all of them — a value already set in the process environment is never overridden.
.env.[NODE_ENV].local.env.local.env.[NODE_ENV].env
The [NODE_ENV] slots only exist when NODE_ENV is set, so with NODE_ENV=production the full set is .env.production.local, .env.local, .env.production, .env. Among the .env* files, the first one to define a key wins (first-writer-wins); the shell env sits above all of them.
NODE_ENV=production nub server.ts # reads .env.production.local, .env.local, .env.production, .envTest environment
Under NODE_ENV=test, the .env.local slot is skipped — only .env.test.local, .env.test, and .env load. This keeps developer-machine secrets in .env.local out of the test environment.
Variable expansion
Values support ${VAR} and $VAR references. References resolve against the other loaded values first, then the shell environment; an undefined reference resolves to the empty string. Expansion is multi-pass, so a value can reference another value that itself references a third.
# .env
HOST=localhost
PORT=5432
DATABASE_URL=postgres://${HOST}:${PORT}/app # both forms work; $HOST is equivalent to ${HOST}Escape a literal dollar sign with \$. Watch the classic footgun: a value like PASSWORD=foo$bar truncates to foo when bar is unset, since $bar expands to the empty string — quote and escape it as PASSWORD="foo\$bar".
Explicit files
Passing --env-file=<path> disables the automatic .env* discovery entirely — only the named file loads. Nub reads it through the same parser and the same ${VAR} expansion as the automatic files, and the shell environment still wins over it. This matches Bun: ask for a file by name and Nub stops guessing which files you meant.
nub --env-file=.env.ci server.ts # only .env.ci loads; auto .env* discovery is skipped; shell env still winsModule resolution
The TypeScript-aware resolution Nub layers on top of Node — tsconfig path aliases and base URL, extends chains, extensionless imports, and the JavaScript-to-TypeScript extension swap.
Loaders
Import JSON, JSONC, JSON5, TOML, YAML, and plain-text files directly as default exports, via an extension-keyed load hook.