The all-in-one JavaScript toolkit that augments Node.js instead of trying to replace it
A TypeScript-first toolchain for Node.js. Run TypeScript files, package.json scripts, and local CLIs on the node and package manager you already have. No new runtime, no lock-in.
$ nub index.ts # run a TypeScript file$ nub run dev # run a package.json script$ nub watch src/server.ts # restart on changes$ nubx prisma generate # run a local CLI, fast$ nub node install 26 # manage Node.js versions
The toolchain
An all-in-one toolkit for Node.js
One Rust binary that runs your files, scripts, and local CLIs — and manages Node itself.
A TypeScript-first Node.js
Run .ts, .tsx, and .jsx on stock Node with full tsconfig.json support, .env loading, and unflagged support for modern syntax and APIs.
# run a TypeScript file$ nub index.ts# restart on changes$ nub watch src/server.ts
A TypeScript-first Node.js
Nub adds support for TypeScript, JSX, decorators, .env files, YAML/TOML imports, and modern APIs syntax on top of stock Node. Flag-for-flag compatible with node. Powered by Rust and oxc.
Architecture
Transpiles in Rust, runs on real Node
Nub transpiles your code in memory with oxc (compiled into a native Node addon) and runs the output on the stock node binary. There’s no Nub runtime, just real Node. Your code is run by the version of Node your project expects. If unavailable, it’s installed on the fly. Runs on Node.js 18 LTS and newer.
$ nub app.ts# oxc transpiles in memory, then stock node runs itrunning on node v26.2.0
TypeScript-first
Full TypeScript support, not just type stripping
Recent versions of Node support type stripping, which erases annotations but rejects non-erasable syntax. Nub’s load hook transpiles each file through its native addon instead, so enums, parameter properties, and extensionless imports that Node doesn’t allow all just work.
import { Model } from "./base" // extensionless → ./base.ts
enum Status { Draft, Sent, Paid }
class Invoice extends Model {
constructor(public status = Status.Draft) {} // parameter property
}tsconfig
Respects your tsconfig.json
Nub resolves your tsconfig.json (including "extends") and feeds its paths into Node’s own resolver through a module.registerHooks() resolve hook. No more tsconfig-paths or disagreement between Node.js and your editor.
// tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@db": ["src/db/index.ts"]
}
}
}Environment
Loads .env files automatically
Nub reads .env, .env.local, and .env.[NODE_ENV] and injects them before Node starts. No dotenv required. Automatic var expansion via ${VAR} just like Vite and Next.js.
# .env
APP=acme
DATABASE_URL=postgres://localhost/${APP}_dev
# No dotenv. No cross-env. No import "dotenv/config".
$ nub server.tsModern syntax
Decorators, JSX, and using
Nub supports decorators and JSX, transpiling it according to your tsconfig.json settings. Full support for emitDecoratorMetadata and explicit resource management, no build step required.
await using db = await connect() // disposed at scope end
@sealed // legacy decorator
class User {}
const view = <Hello name="world" /> // JSX in .tsxLoaders
Import JSON, YAML, and TOML
Import .yml, .yaml, .toml, .json5, and .jsonc files directly. A module.registerHooks() load hook routes them through fast Rust parsers in Nub’s native addon, resolving each import to a plain JavaScript object. (Oh, .txt works too)
import config from "./config.yaml" // parsed object
import flags from "./feature.jsonc" // comments stripped
import pkg from "./Cargo.toml" // parsed object
import prompt from "./prompt.txt" // string
import { host, port } from "./config.yaml" // named exportsAuto-restart
A dependency-aware watch mode
Powered by node --watch, Nub’s watch command watches for changes to your entrypoint or any file transitively imported. It also adds TypeScript/JSX sourcemap support and watches your package.json, tsconfigs, and .env files.
$ nub watch src/server.tsListening on http://localhost:3000↺ src/db.ts changed — restartingListening on http://localhost:3000
Compatibility
100% runtime compatibility with Node
Nub passes Node’s test suite because it is Node. Your code is transpiled and executed with the stock node binary. It’s not a reimplementation; other Node alternatives continue to play catch-up.
Deno’s Node-compat suite, node-relative. The gap is 57 documented deltas — module-hook and native-addon visibility, plus three intended divergences from default-on Web Storage and compile caching. View benchmark repo
Drop-in
Flag-for-flag compatible with node
Nub is a true drop-in replacement for node. Same flags, same argv, same runtime behavior.
$ nub \ --max-old-space-size=4096 \ --inspect \ --import ./instrument.js \ app.ts --port 3000
No Nub-specific APIs
Zero lock-in
Nub is not a runtime. Your code runs on the real node binary: no Nub engine, no reimplementation, no proprietary API surface. Everything Nub ships is a web standard, a TC39 proposal, an unflagged Node feature, or a pragmatic TypeScript affordance. Remove Nub tomorrow and your code keeps working, unchanged.
- No Nub global
- No nub:* module namespace
- No @nub/* npm scope
- No NUB_* environment variables
- No "nub" field in package.json
Forward compatibility
Modern APIs and syntax, fully supported
Nub polyfills APIs like Temporal and Worker, adds support for new ECMAScript syntax like using, and unflags all experimental Node.js features.
An 18× faster pnpm run
A drop-in for npm run and pnpm run with lifecycle hooks, npm_* env vars, and arg forwarding all working, minus the per-call Node bootstrap.
Performance
Run package.json scripts at the speed of Rust
Scripts run with npm run or pnpm run feel perceptibly laggy due to the 100+ms of overhead introduced by these tools. They’re written in Node.js themselves, so they pay the Node.js bootstrap tax.
echo-hi script · hyperfine, 20 runs
Workspaces
Monorepo-friendly
Nub implements pnpm’s --filter grammar and -r, reading workspaces from package.json#workspaces or pnpm-workspace.yaml. Packages run in dependency order, without the per-package Node bootstrap.
$ nub -r run build # every package, topo-ordered$ nub --filter @org/api dev # one package$ nub --filter ...@org/web build # + its deps$ nub --filter "[main]" test # changed since main
A 20× faster npx
nubx resolves node_modules/.bin in Rust and execs the binary directly; no Node process in the wrapper. A drop-in for npx and pnpm exec.
Performance
Makes commands feel instantaneous
When invoking native CLIs like esbuild, npx itself (written in JS) adds a noticeable 200ms of cold-start latency, even when running a CLI command that’s instantaneous. Nub walks node_modules/.bin and execs the binary directly.
esbuild --version · hyperfine, 20 runs
Resolution
Works with any package manager
Nub resolves the CLI the way pnpm, yarn, and npm do, so it runs the exact binary your install put there, even in a monorepo. Add --node to run one under plain Node.
$ nubx eslint . # member's .bin first$ nubx prisma generate # then workspace root$ nubx tsc --noEmit # then ancestors$ nubx --node some-cli # run under plain Node
A built-in Node version manager
Nub reads your .node-version or .nvmrc and, if that Node isn’t installed, downloads it from nodejs.org, verifies the checksum, and installs it. Replaces nvm and fnm.
Per-project
Resolves your project's Node version
Nub automatically resolves the right Node for each project from .node-version, .nvmrc, or package.json#engines before your code runs.
$ nub node which~/.cache/nub/node/26.3.0/bin/node» resolved from package.json#engines.node (>=26)
On demand
Auto-installs Node versions
If the resolved version isn’t on your machine, Nub downloads it from nodejs.org (checksum-verified), then runs your code on it. No nvm use, no prompt, no second step.
$ echo 26 > .node-version$ nub hello.tsUsing Node.js 26.3.0 (resolved from .node-version)Installed in 9.8sHello world!
Direct control
Or manage versions by hand
Install, list, pin, and remove Node versions directly with nub node. No shell hooks, no PATH munging.
$ nub node install 26 # install a version$ nub node ls # what's installed$ nub node pin 26 # write .node-version$ nub node uninstall 22 # remove a version
A built-in package manager
Nub ships a pnpm-compatible package manager — powered by the embedded aube engine, built in partnership with jdx. It reads the lockfile your project already has and writes the same format back.
Your lockfile
Keeps the lockfile you already have
nub install detects your existing lockfile and writes the same format back — pnpm-lock.yaml, package-lock.json, and bun.lock are read and written in place, never replaced with a foreign file. Fresh projects get a standard pnpm-lock.yaml. yarn.lock is honored read-only for now: an install that would rewrite it is refused, with the exact yarn command to run instead.
$ nub install # pnpm-lock.yaml → read, written back$ nub install # package-lock.json → read, written back$ nub install # bun.lock → read, written back$ nub install # yarn.lock → honored read-only
Layout
Isolated installs, hoisted where you expect them
pnpm projects and fresh ones get strict, symlinked, pnpm-style installs, with the virtual store tucked under node_modules/.nub. Projects with an npm, yarn, or bun lockfile default to the flat hoisted layout those tools produce — so nothing about your tree surprises the code that walks it. One .npmrc line (node-linker) overrides either default.
$ nub installnode_modules/express → .nub/express@5.1.0/…$ nub install --node-linker=hoisted # or one .npmrc line
Config
Reads .npmrc, honors your workspaces
Configuration comes from the files you already maintain: .npmrc (registry, auth, flags), pnpm-workspace.yaml, and package.json#workspaces. Nub’s own defaults rank below every user source — a CLI flag, env var, .npmrc entry, or workspace yaml always wins. No new config file, no "nub" field in package.json.
$ cat .npmrcregistry=https://npm.example.comnode-linker=hoisted$ nub install # your config wins, always
Keep your tools
Your package manager still works
Because Nub writes the same lockfile your package manager does, pnpm, npm, and bun keep working side by side — run either tool, commit the same file, switch back any time. Registry, scoped, peer-heavy, and platform-specific dependency trees all round-trip through the real tools today — workspace: links and git dependencies included. And when you want the original tool itself, nub pm use declares and provisions the exact version for the whole team — Corepack’s job, without the PATH shims.
$ nub install # or pnpm install — same lockfile$ nub pm use pnpm@^9using pnpm@9.15.9package.json: packageManager = pnpm@9.15.9 (+sha512)pnpm-lock.yaml: kept (already pnpm's format)