nub.jsonc — The config file for Node.js you always wanted — Nub
← BlogTable of Contents
The node CLI doesn't try too hard to be ergonomic. It's designed like a util, with lots of explicit flags and environment variables. Even as new features like "node:test" and watch mode have landed, Node has studiously avoided adding any subcommands (e.g. node test or node watch), favoring --test and --watch instead. It's not uncommon to see large node commands like this in package.jsons or other scripts.
start.sh
node --import ./instrumentation.mjs \<br>--env-file=.env --env-file=.env.local \<br>--enable-source-maps \<br>--conditions=production \<br>--unhandled-rejections=strict \<br>--trace-warnings \<br>--max-old-space-size=8192 \<br>--stack-trace-limit=50 \<br>src/server.ts
Over the years many have asked for Node.js to add support for a project-level config file that could alleviate some of the flag madness.
nodejs/node#53787
nodejs/node#43895
nodejs/loaders#98
As a matter of fact, Node quietly shipped a config file in Node v22.16 last year (node.config.json). But it's disabled by default and (rather antithetically) gated behind a scary-looking flag.
node --experimental-default-config-file index.ts
This is the kind of ergonomic issue in Node.js that Nub is perfectly equipped to solve.
Nub is a Rust toolkit for Node.js. The nub command is flag-for-flag compatible with node, while adding full support for TypeScript, JSX, tsconfig.json, .env loading, and modern Web and ECMAScript APIs. It also includes a fast script runner (nub run), package runner (nubx), Node version manager (nub node), and pnpm-compatible package manager.
nub index.ts # supports TypeScript, JSX, Worker, latest ECMAScript syntax<br>nub run dev # run package.json scripts<br>nub install # install using your existing lockfile<br>nubx prisma generate # run package CLIs<br>nub node install 26 # pin and provision Node
Introducing nub.jsonc
In v0.7, Nub introduced its config file nub.jsonc. It's the config file for Node.js you've always wanted, with the current runtime fields in one place:
"$schema": "https://nubjs.com/schema/latest.json",
// Nub configs<br>"envFile": [".env", ".env.local"], // disable with false<br>"loader": { ".graphql": "text" }, // text|jsonc|json5|toml|yaml|ts|tsx|jsx<br>"conditions": ["development"], // additional export conditions to respect<br>"tsconfig": "./tsconfig.runtime.json", // JSX, decorators, paths/baseURL<br>"verifyDeps": "error", // check node_modules freshness before runs<br>"preload": [ // for telemetry, hardening, etc.<br>"./instrumentation.ts",<br>"dd-trace/initialize.mjs"<br>],<br>"nodeCompat": false, // disables Nub augmentations
// Node.js/v8 configs<br>"v8Flags": [<br>"--stack-size=2000",<br>"--prof",<br>"--allow-natives-syntax",<br>"--trace-deopt"<br>], // not supported in node.config.json<br>"nodeOptions": [ // passed as NODE_OPTIONS<br>"--stack-trace-limit=50",<br>"--max-old-space-size=8192",<br>"--frozen-intrinsics"
Custom environment loading
By default Nub loads these files, from lowest to highest precedence:
.env
.env.${APP_ENV}
.env.local
.env.${APP_ENV}.local
Override discovery with one file:
nub.jsonc
"envFile": ".env.local"
Load several files in order:
nub.jsonc
"envFile": [".env", ".env.production", ".env.local"]
Paths support environment-variable expansion:
nub.jsonc
"envFile": ".env.${NODE_ENV}"
NODE_ENV=production nub index.ts # reads .env.production
Disable environment loading entirely:
nub.jsonc
"envFile": false
Varlock<br>As of Nub v0.7, Nub has first-party Varlock support. If a project has a .env.schema and Varlock is installed, Nub hands environment loading to Varlock automatically.
Register custom loaders
Map an extension onto one of Nub's built-in loaders and that file type becomes directly importable:
LoaderTreats the file astextUTF-8 textjsoncJSON with comments and trailing commasjson5JSON5tomlTOMLyamlYAMLtsTypeScripttsxTypeScript with JSXjsxJavaScript with JSX
nub.jsonc
"loader": {<br>".graphql": "text",<br>".rules": "yaml"
schema.ts
import schema from "./schema.graphql"; // string<br>import rules from "./access.rules"; // parsed YAML
Increase memory allotment
Quite possibly the single most useful feature of nub.jsonc.
nub.jsonc
"nodeOptions": ["--max-old-space-size=8192"]
Configure telemetry
Telemetry has to initialize before application dependencies, so put it in a project preload instead of every entry point:
nub.jsonc
"preload": [<br>"./instrument.ts",<br>"@opentelemetry/auto-instrumentations-node/register",<br>"dd-trace/initialize.mjs",<br>"@sentry/node/preload"
Harden your environment
Node can freeze selected built-ins and reject access to the legacy Object.prototype.__proto__ accessor before application code runs:
nub.jsonc
"nodeOptions": [<br>"--frozen-intrinsics",<br>"--disable-proto=throw"
Clean up stack traces
Preloads can also establish process-wide behavior that application modules should not have to initialize themselves. This formatter removes dependency frames from V8 stack traces before the entry point runs, including in scripts, tests, and...