People ask us whether Bun is ready to replace Node yet. The honest answer splits down the middle. Some of what Bun does, it does well enough to trust with real traffic. Other parts still deserve a second look before you bet a client’s uptime on them.
We’re not going to tell you to migrate based on a blog post, ours or anyone else’s. Everything below traces back to Bun’s own documentation so you can check it yourself, and none of it is a substitute for testing your own dependency tree against it before a real deploy.
What Bun replaces well today
The runtime runs TypeScript and JSX files directly, with no separate compile step, and starts faster than a Node process cold-booting the same script. Bun’s docs describe the goal as complete Node.js API compatibility, and the modules most projects actually touch are marked fully implemented: path and events each pass 100 percent of Node’s own test suite for that module, fs passes 92 percent, and stream and buffer are listed as complete. Web-standard globals like fetch, ReadableStream, TextEncoder, and URL behave the same way they do in a browser, useful if your code already runs on both sides.
Package installs are where the difference shows up fastest. bun install reads an existing package.json and works against the standard npm registry, so switching doesn’t mean rewriting a dependency graph. Bun’s own benchmarks put the installer at up to 25 times faster than npm install on a cold cache, using platform-specific tricks like clonefile on macOS and hardlinks on Linux instead of copying files, with a fallback to plain copying when those aren’t available. That’s Bun’s own number, measured on their hardware, so treat it as a ceiling rather than a promise. In a monorepo with a slow install step in CI, even a fraction of that adds up over a week of builds.
The bundler ships built in and handles JavaScript, TypeScript, JSX, CSS, and HTML, with code splitting, tree-shaking, and minification included. It outputs ESM by default, with CommonJS and IIFE available as experimental targets. One thing straight from the docs: it isn’t meant to replace tsc for type checking, so a separate type-check step still belongs in your pipeline if you want type errors caught before they ship.
The test runner gives you a Jest-like API through bun:test: describe, it, expect, mocks, snapshots, test.each for parametrized cases, and modifiers like .skip and .only. Bun’s docs state the long-term goal is full Jest compatibility, and most of the common matchers are already there. A suite that doesn’t lean on obscure Jest plugins usually just runs, and it starts fast enough that a full pass feels instant on a smaller codebase.
Where Node still wins
Bun implements Node-API from scratch, and most existing native addons work without modification. Most isn’t all. If a dependency tree includes a native module built against a specific Node ABI, or one nobody has tested against Bun yet, that needs verifying before it ships.
node:cluster can’t pass handles or file descriptors between workers, so load-balancing HTTP requests across processes only works on Linux, through SO_REUSEPORT. node:worker_threads is missing stdin, stdout, stderr, trackedUnmanagedFds, and resourceLimits on the Worker constructor, along with markAsUntransferable and moveMessagePortToContext. An architecture that leans on either module in detail is worth reading the compatibility notes for before assuming parity.
Bun’s own node:test support, added in version 1.3, runs Node’s test module on the same engine as bun:test, but the overlap isn’t total. Snapshot testing, mock.module(), code coverage, and --test-only aren’t implemented, test.only() is accepted but doesn’t actually filter which tests run, and subtests execute serially even when concurrency is requested. Bun’s own bun:test doesn’t have these gaps. A project standardized on node:test specifically, rather than bun:test, isn’t a drop-in candidate yet.
Bun’s 1.3 release notes report the runtime now passes over 98 percent of Node’s N-API test suite and 98.4 percent of the timers suite, and the number keeps rising with each release. That same release added node:vm support, including vm.SourceTextModule and vm.compileFunction, which was on the gap list a version earlier, and says something about the pace of the work. A high pass rate is still a good sign rather than a guarantee. A production app touches dozens of dependencies, and it only takes one landing on an unsupported corner to turn a quick migration into a slow debugging session.
{
"scripts": {
"dev": "bun run --hot src/index.ts",
"test": "bun test",
"build": "bun build src/index.ts --outdir dist --target bun"
}
}
How we’d actually decide
We don’t treat this as an ideological choice. Bun and Node solve overlapping problems, and which one fits depends on what’s already sitting in your node_modules folder. A new service with a small, modern dependency set, a REST API, a CLI tool, an internal script, is a reasonable place to try Bun and measure the difference in install time, cold start, and test speed. An app with years of accumulated native dependencies isn’t the place to find out what’s still unsupported. That’s a migration you plan and benchmark against your own dependency tree first. Run the actual test suite on Bun before deciding anything, since a passing suite tells you more about your specific dependencies than any compatibility percentage in a changelog.
If you’re weighing this for a real project, tell us what’s in your dependency tree and we’ll tell you honestly which side of the line it falls on, the same way we laid out the Tauri or Electron call. Get in touch before you touch a production runtime, not after something breaks in it.