Bun: The Fast JavaScript Runtime That's Replacing Node.js
Node.js has dominated JavaScript server-side development for 15 years, but it wasn't designed for today's toolchain. The runtime predates TypeScript, ES modules, and modern build requirements — so running JavaScript on the server today often means running Node plus a bundler plus a transpiler plus a separate package manager.
Bun takes a different approach: one binary that handles execution, package management, bundling, and testing, with TypeScript support built in. It runs most Node.js code without modification.
What Bun Actually Is
Bun is a JavaScript runtime built on WebKit's JavaScriptCore engine (the same engine that powers Safari), written in Zig. It ships as a single binary and includes:
- Runtime: Execute JavaScript and TypeScript files directly
- Package manager:
bun install— often 10-30x faster than npm - Bundler:
bun build— bundle for browsers or other environments - Test runner:
bun test— Jest-compatible API - Script runner: Replaces npm scripts
The emphasis on speed is real. Bun starts faster than Node, installs packages faster than npm, and runs many workloads 2-4x faster. The gap narrows for I/O-bound work and widens for CPU-bound tasks.
Installation
# macOS and Linux
curl -fsSL https://bun.sh/install | bash
# macOS with Homebrew
brew install oven-sh/bun/bun
# Windows (via npm)
npm install -g bun
Verify:
bun --version
Running JavaScript and TypeScript
Bun runs .js, .ts, .jsx, and .tsx files directly with no configuration:
bun run index.ts
bun run server.js
TypeScript files are transpiled on the fly without generating output files. You don't need ts-node, tsx, or any separate TypeScript toolchain for execution.
For scripts listed in package.json, bun run works the same as npm run:
bun run dev
bun run build
bun run test
Package Management
Bun installs packages compatible with the npm registry. The lock file is bun.lockb (binary format, much smaller than package-lock.json).
Common commands:
bun install # install all dependencies
bun add express # add a package
bun add -d typescript # add a dev dependency
bun remove express # remove a package
bun update # update all packages
The speed difference is most noticeable on cold installs and CI. A project with 500 dependencies that takes 30 seconds with npm often installs in under 3 seconds with Bun, because Bun uses a global cache and hard links instead of copying files.
Node.js Compatibility
Bun implements the Node.js API. Most code that runs on Node runs on Bun without changes:
fs,path,os,crypto,http,https,net,events— all implemented- CommonJS (
require) and ES Modules (import) — both work node_modulesresolution — works the same- Popular frameworks — Express, Fastify, Hono, Next.js, Vite, Nuxt (with caveats)
- Environment variables via
process.env
Known gaps: some native modules (.node files compiled with node-gyp) may not work if they use Node.js internal ABI. Bun provides its own native module format. Most popular packages that require native code have pre-compiled builds that work fine.
Check compatibility before migrating production workloads. For new projects, start with Bun.
Built-in TypeScript Support
Bun reads tsconfig.json and applies path aliases, strict mode settings, and other TypeScript configuration without additional tooling:
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"paths": {
"@lib/*": ["./src/lib/*"]
}
}
}
Imports using @lib/something resolve correctly at runtime with no bundler configuration.
Test Runner
bun test is a Jest-compatible test runner. Existing Jest test suites often run with bun test unchanged:
import { describe, test, expect, beforeAll } from "bun:test";
describe("user", () => {
let db: Database;
beforeAll(() => {
db = new Database(":memory:");
});
test("creates user", () => {
const user = db.createUser({ name: "Alice" });
expect(user.id).toBeGreaterThan(0);
expect(user.name).toBe("Alice");
});
});
Run tests:
bun test # run all tests
bun test --watch # re-run on file change
bun test src/user.test.ts # run specific file
bun test --coverage # generate coverage report
The test runner is faster than Jest because it runs in the same process as Bun rather than spinning up a separate worker for each file. On large test suites this is a meaningful difference.
Bun APIs
Bun exposes its own high-performance APIs under the Bun global namespace:
File I/O:
// Read a file
const text = await Bun.file("./config.json").text();
const json = await Bun.file("./config.json").json();
// Write a file
await Bun.write("./output.txt", "hello world");
HTTP server (faster than Node's http module):
Bun.serve({
port: 3000,
fetch(request) {
const url = new URL(request.url);
if (url.pathname === "/") {
return new Response("Hello, Bun!");
}
return new Response("Not Found", { status: 404 });
},
});
Subprocess:
const proc = Bun.spawn(["ls", "-la"]);
const output = await new Response(proc.stdout).text();
SQLite (built-in, no dependencies):
import { Database } from "bun:sqlite";
const db = new Database("mydb.sqlite");
const query = db.query("SELECT * FROM users WHERE id = $id");
const user = query.get({ $id: 1 });
The built-in SQLite driver uses libsqlite3 directly without the overhead of a JavaScript wrapper package. For applications that use SQLite, this eliminates a dependency entirely.
When to Use Bun Over Node
Good fit for Bun:
- New TypeScript projects where you want minimal tooling
- Scripts and automation (startup time matters)
- Projects with heavy test suites (test runner speed)
- Monorepos with large
node_modules(package install time) - SQLite-backed applications (built-in driver)
- HTTP servers with throughput requirements
Stick with Node when:
- Existing project with native modules that haven't been tested with Bun
- You need features only available in newer Node versions that Bun hasn't implemented
- Your deployment environment doesn't support Bun (Lambda, some containers)
- Your team isn't ready to change toolchain
Migrating an Existing Node.js Project
For most projects, migration is straightforward:
- Install Bun
- Replace
npm installwithbun install— this generatesbun.lockb - Replace
node src/index.jswithbun run src/index.js - Replace npm scripts with
bun run <script> - Optionally replace
jestwithbun testif the test suite is compatible
Test thoroughly. Most projects work immediately; a few need minor adjustments for native modules or Node-specific APIs Bun hasn't implemented yet.
Production Deployment
Bun can be used in production. Bun's blog documents production deployments at scale. For Docker, the official image is oven/bun:
FROM oven/bun:1 AS base
WORKDIR /usr/src/app
FROM base AS install
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile --production
FROM base AS release
COPY --from=install /usr/src/app/node_modules ./node_modules
COPY . .
USER bun
EXPOSE 3000/tcp
ENTRYPOINT ["bun", "run", "src/index.ts"]
Bun's startup time (under 5ms typically) makes it practical for serverless and short-lived containers in ways that Node is too slow for.
Ecosystem Status
Bun reached 1.0 in September 2023. It's actively developed by Oven (Bun's commercial entity) and has substantial community adoption. The GitHub repository has 70,000+ stars.
The runtime is stable for production use, though some edge cases in Node.js compatibility surface in complex applications. The recommended approach for existing projects: test in staging, measure your actual use case, then decide.
For new projects where you control the full stack, Bun is worth defaulting to — the developer experience is genuinely better than Node plus the usual toolchain accumulation.