# nubjs/nub: a Rust CLI that wraps stock Node.js with TypeScript, watch mode and a fast script runner

> Nub is an all-in-one Node.js toolkit written in Rust that runs TypeScript files, dispatches package scripts, installs dependencies and manages Node versions. It replaces several tools without introducing a new runtime, and it is still on 0.8.0 canary releases.

**nubjs/nub** — The fast all-in-one Node.js toolkit. Script runner, nub run A drop-in for npm run and pnpm run.

- Repository: https://github.com/nubjs/nub
- Website: https://nubjs.com
- Stars: 4,361 · Forks: 64
- Language: Rust
- License: MIT
- Published: 2026-08-04 · Updated: 2026-08-18 · Language: en
- Canonical page: https://hysenlabs.com/projects/nubjs-nub

## The pile of tools Nub is trying to collapse

A typical Node project in 2026 runs node or tsx to execute files, dotenv-cli to load environment variables, npm run or pnpm run for scripts, npx for one-off binaries, nodemon or tsx watch for restarts, nvm or fnm for Node versions, and corepack for package manager shims. Nub's README presents a single Rust binary in place of all of them, with a table mapping each command: nub <file> for node, tsx, ts-node and dotenv-cli; nub run for npm run and pnpm run; nubx for npx and pnpm dlx; nub install for npm and pnpm; nub watch for nodemon and tsx watch; nub node for nvm, fnm, n and volta; nub pm for corepack. The audience is Node developers who like Bun's developer experience but do not want to move off the Node runtime. The README is explicit that there is no new runtime and no vendor-specific API surface, so the bet is on tooling around Node rather than a replacement for it.

## How Nub augments Node instead of replacing it

The README explains the mechanism in a short note: Nub uses Node extension surfaces that mostly did not exist when Deno and Bun were built. Those are --import and --require preloads, module.registerHooks() for transpilation and resolution, and N-API native addons, with oxc embedded for pre-transpilation. So the runtime executing your code is still Node; Nub hooks module resolution and transform before Node loads the file. The repository's package.json comment adds detail: the transformer and parser moved in-process into a native addon (crates/nub-native), so oxc-transform and oxc-parser are no longer dependencies, and only the helper runtime package @oxc-project/runtime remains, imported by transpiled user code. That package is pinned to an exact version that must match the oxc version compiled into the addon, and NUB_VERSION is described as the sole transpile-cache-busting key. This is a tight coupling: upgrading the addon without bumping the helper runtime in lockstep would break the emit that depends on it. A second structural detail from the root Cargo.toml: crates/nub-native is its own Cargo workspace because Cargo's panic setting is profile-global, and the cdylib loaded into the user's Node process must stay panic = "unwind" so a panic surfaces as a JS error instead of killing the host process, while the CLI binary uses panic = "abort". That is a real design constraint, not decoration.

## Installing Nub and running a first TypeScript file

The README lists several install paths. On macOS and Linux the shell installer is the shortest, and Homebrew, Nix, mise and npm are also given.

```bash
curl -fsSL https://nubjs.com/install.sh | bash
```

On Windows the README gives a PowerShell one-liner, and there is an npm package if you prefer a global install through an existing package manager.

```bash
npm install -g @nubjs/nub
```

Once installed, the file runner is the first thing to try. The README shows running a TypeScript entry point directly, with no build step, and a watch variant on the same path.

```bash
nub index.ts
nub --watch app.ts
```

The README states the runner supports .js, .ts, .mjs, .cjs, .mts, .cts, .jsx and .tsx, and that it is flag-for-flag and var-for-var drop-in compatible with node, mostly via passthrough. It also documents automatic .env loading with Next.js and Vite parity, and built-in loaders for .yaml, .toml, .jsonc, .json5 and .txt. For scripts, the README shows the drop-in form and a filtered recursive form.

```bash
nub run build
nub run -r --filter "@org/*" test
```

If you run a file in a directory that pins a Node version, the README says Nub infers the expected version and installs it, printing the resolved version and install time before your program's output.

## Node version resolution and what it overrides

Nub's version inference has a documented precedence order: NODE_EXECUTABLE as an override, then package.json#devEngines, then .node-version, then .nvmrc, then package.json#engines. The README's example writes 26 into .node-version and shows Nub reporting the resolved version and installing it before printing the program output. Two things follow. First, NODE_EXECUTABLE sits above everything, so a machine-level environment variable silently wins over what the repository pins; if a build behaves differently on one developer's laptop than in CI, that variable is the first place to look. Second, adding Nub to a project that already uses .nvmrc means two tools now read the same file, and they may resolve differently if the file uses an alias rather than a full version. The README does not document how aliases such as lts/* are handled, so that is worth checking before you rely on it. The nub node subcommand is listed as the version manager, with nub node install 26 as the example.

## Watch mode watches the dependency graph, not a glob list

The README describes watch mode as restart-on-change driven by the resolved dependency graph plus off-graph files that still invalidate a run, so there is no glob list to maintain. It names the off-graph invalidators explicitly: .env files, the tsconfig.json extends chain, and package.json. It also states that watch runs on Node's own --watch engine and preserves output by default. The trade-off is that graph-based watching depends on Nub's resolver agreeing with your project's actual resolution. If your code loads files in a way the resolver cannot see, for example a dynamic import built from a runtime string, the change may not trigger a restart. A glob-based watcher over a directory would catch it, at the cost of restarting on files that do not matter. The README does not describe any option to add extra paths to the watch set, so projects with unusual loading patterns should test this before switching away from nodemon.

## Release cadence, licence and the cost of upgrading

The most recent releases listed are all canary builds: v0.8.0-canary.20260829.400, .399 and .396, all pushed on 2026-08-29, and the last push to the repository was on 2026-08-29. There is no stable 0.8.0 in the release list, so anyone adopting today is running canary builds. The repository is not archived, but the release naming is the fact that matters for planning. Three canary builds within about three hours on the same day suggests a fast-moving main branch, and the README's own installation instructions point at a curl-pipe-to-bash script rather than a versioned artifact, which means the installer fetches whatever is current. For upgrade cost, the repository states that @oxc-project/runtime is pinned to an exact version that must match the oxc version compiled into the native addon, and that NUB_VERSION is the sole transpile-cache-busting key. In practice that means upgrading Nub is not a matter of swapping a binary: the addon and the helper runtime move together, and a mismatch breaks transpiled output such as the using down-leveling that needs the runtime helper. The licence is MIT, which permits commercial use and modification; the repository does not include a separate patent grant or trademark terms, so if your legal team cares about those, the LICENSE file is the thing to read. Nothing here is legal advice.

## Where Nub is the wrong tool, and what to use instead

Nub is the wrong choice when you need a stable release line. If your organisation pins tooling to tagged, non-canary versions, the current release list does not offer one. It is also a poor fit if you cannot accept a tool that changes how dependencies are laid out on disk: the package.json comment states that CI legs install the root with nub install --frozen-lockfile --node-linker hoisted, and explains that hoisted is required because the runtime staging and nub-core's build script copy real directories out of node_modules and reject a symlinked store. A pnpm-style symlinked node_modules is therefore not what Nub's own build expects, and the README does not document how nub install behaves against an existing symlinked store. If that describes your repository, pnpm run plus tsx plus nodemon is the conservative path: pnpm keeps its symlinked store, tsx handles TypeScript execution, and nodemon restarts on a glob you control. The difference in approach is that Nub resolves and transpiles inside the Node process through registerHooks and a native addon, while tsx is a JavaScript loader you install as a dependency and version independently of your runtime. Independent versioning is slower at startup, which is the trade Nub is making, but it also means a tsx upgrade cannot desynchronise a native addon from a helper runtime package.

## Conclusion

Adopt Nub if you want TypeScript execution, script dispatch, dependency install and Node version management behind one binary while keeping stock Node as the runtime, and you accept canary releases. Do not adopt it if you need a stable tagged release, a documented rollback path, or a tool that does not touch your node_modules layout, since the repository states the root install uses --node-linker hoisted because the runtime staging rejects a symlinked store. Verify first that the version resolution order (NODE_EXECUTABLE, package.json#devEngines, .node-version, .nvmrc, package.json#engines) matches how your project pins Node, and check what nub install does to your existing lockfile before running it on a repository you care about.

## FAQ

### Is Node.js better than React?

This question compares a runtime with a UI library, so there is no answer to give here. Nub itself makes no such comparison; the README only positions Nub as tooling that runs on stock Node rather than a replacement runtime.

### What does Node.js actually do?

The Nub README treats Node as the runtime that executes your files, and states that Nub augments it through --import and --require preloads, module.registerHooks() and N-API native addons. Nub does not replace that runtime.

### Why is Node.js on my computer?

The Nub README does not explain general Node installation history. It does describe Nub's own version manager, where nub node install 26 installs a Node version and Nub resolves one from NODE_EXECUTABLE, package.json#devEngines, .node-version, .nvmrc or package.json#engines.

### What does npm actually stand for?

The Nub README does not expand the acronym. It does show npm as one install route for Nub itself, npm install -g @nubjs/nub, and lists nub install as the replacement for npm and pnpm when installing a project's dependencies.

## Sources

- [Official documentation](https://nubjs.com)
- [Official README](https://github.com/nubjs/nub#readme)
- [Project repository](https://github.com/nubjs/nub)
- [Release notes](https://github.com/nubjs/nub/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/nubjs-nub
