Bun: The Fast JavaScript Runtime
JavaScript has always had a speed problem — not in the browser, but in the tooling. Installing packages, running tests, bundling assets, and spinning up a dev server have traditionally meant juggling multiple tools each with their own startup overhead. Bun is a radical answer to that problem: a single, blazing-fast runtime that replaces Node.js, npm, Webpack, and Jest in one install. Built from scratch in Zig and powered by Apple's JavaScriptCore engine, Bun can install packages up to 30× faster than npm and execute scripts significantly faster than Node.js. In this guide you'll learn exactly what Bun is, how to set it up, and when it's the right choice for your next project.
Bun uses Apple's JavaScriptCore engine (the same engine that powers Safari) instead of V8. JavaScriptCore has faster cold-start times. Bun is also written in Zig, a systems language that compiles to highly optimized native code with minimal overhead — no GC pauses, no abstractions layered on abstractions.
What Is Bun?
Bun is not just another JavaScript runtime. It is an all-in-one toolkit that ships four capabilities in a single binary:
- Runtime: Execute JavaScript and TypeScript directly — no transpilation step needed
- Package manager:
bun installreadspackage.jsonand installs faster than npm, yarn, or pnpm - Bundler: Bundle JavaScript/TypeScript for production, with ESM and CJS support
- Test runner: A Jest-compatible test runner built into the runtime, no extra dependencies
Created by Jarred Sumner and released as stable (v1.0) in September 2023, Bun is fully open-source under the MIT license and has quickly become one of the most-starred JavaScript tools on GitHub.
Bun bundles runtime, package manager, bundler, and test runner into a single binary
Installation and Setup
Installing Bun is a single command on macOS, Linux, or WSL. There is no installer wizard, no PATH fiddling — the install script puts a single binary in ~/.bun/bin/bun and adds it to your shell profile.
Install Bun
Run the official install script. It works on macOS, Linux, and Windows via WSL.
# macOS / Linux / WSL (recommended)
curl -fsSL https://bun.sh/install | bash
# macOS with Homebrew
brew install oven-sh/bun/bun
# npm (if you already have Node.js)
npm install -g bun
# Verify installation
bun --version
# e.g. 1.1.34
Create a new project
Use bun init to scaffold a new project interactively, or bun create to use a template.
# Interactive project init
bun init
# Create from a template
bun create react my-app # React + Vite
bun create next my-app # Next.js
bun create hono my-api # Hono web framework
bun create elysia my-api # Elysia (Bun-first framework)
# Change directory and run
cd my-app
bun run dev
Run any JavaScript or TypeScript file
Bun runs .js, .ts, .jsx, and .tsx files natively — no tsc, no Babel, no ts-node needed.
# Run a TypeScript file directly
bun run index.ts
# Watch mode (re-run on file changes)
bun --watch run index.ts
# Execute a one-liner
bun -e "console.log(Bun.version)"
Native Windows support (without WSL) became stable in Bun v1.1. You can install it with powershell -c "irm bun.sh/install.ps1 | iex" on Windows 10/11.
Bun as a Package Manager
One of Bun's most immediate wins over Node.js is its package manager. bun install reads your existing package.json and downloads packages from the npm registry — but it caches every package globally in a binary format that makes reinstalls nearly instantaneous.
Key Package Manager Commands
# Install all dependencies from package.json
bun install
# Install without devDependencies (production)
bun install --production
# Install frozen (fails if bun.lockb would change)
bun install --frozen-lockfile
# Force re-download (ignore cache)
bun install --force
# Add a dependency
bun add express
bun add zod
# Add a dev dependency
bun add -d typescript @types/express
# Add a peer dependency
bun add --peer react
# Remove a package
bun remove lodash
# Upgrade all packages
bun update
# Run a script from package.json
bun run build
bun run test
bun run dev
# Shorthand — if no ambiguity with built-in commands
bun dev
bun build
# Run a local binary from node_modules
bun x tsc --version # like npx but faster
bunx eslint src/ # alias for bun x
# package.json (root of a monorepo)
{
"name": "my-monorepo",
"workspaces": ["packages/*", "apps/*"]
}
# Install all workspace dependencies at once
bun install
# Run a script in a specific workspace
bun run --filter="@myapp/ui" build
# Run a script in all workspaces
bun run --filter="*" test
Lockfile: bun.lockb
Bun uses a binary lockfile (bun.lockb) instead of the text-based package-lock.json. It is faster to parse and smaller on disk. Commit it to your repository just like any other lockfile. To inspect it as text, run:
# Print lockfile as human-readable YAML
bun bun.lockb
Building HTTP Servers with Bun
Bun ships a high-performance HTTP server via Bun.serve(). It is up to 4× faster than Node.js's http module in benchmarks and supports WebSockets natively. Here is a minimal REST API:
const PORT = process.env.PORT ? Number(process.env.PORT) : 3000;
const server = Bun.serve({
port: PORT,
async fetch(req) {
const url = new URL(req.url);
// GET /
if (url.pathname === "/" && req.method === "GET") {
return Response.json({ message: "Hello from Bun!", version: Bun.version });
}
// GET /users
if (url.pathname === "/users" && req.method === "GET") {
const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
];
return Response.json(users);
}
// POST /users
if (url.pathname === "/users" && req.method === "POST") {
const body = await req.json();
// ... save to database
return Response.json({ created: true, user: body }, { status: 201 });
}
return new Response("Not Found", { status: 404 });
},
});
console.log(`Listening on http://localhost:${server.port}`);
bun run server.ts
# Listening on http://localhost:3000
WebSocket Support
Bun's server also supports WebSockets with zero extra dependencies. Add a websocket handler alongside fetch:
Bun.serve({
port: 3000,
fetch(req, server) {
// Upgrade HTTP request to WebSocket
if (server.upgrade(req)) return; // upgrade succeeded
return new Response("Use WebSocket", { status: 426 });
},
websocket: {
open(ws) {
console.log("Client connected");
ws.send("Welcome!");
},
message(ws, data) {
console.log("Received:", data);
ws.send(`Echo: ${data}`);
},
close(ws) {
console.log("Client disconnected");
},
},
});
Bun's Built-in Test Runner
Bun ships a Jest-compatible test runner. You can migrate most Jest suites to Bun with zero or minimal changes — the API (describe, it, expect, beforeEach, afterAll, mocks) is intentionally identical.
import { describe, it, expect, beforeEach, mock } from "bun:test";
// Simple utility being tested
function add(a: number, b: number) {
return a + b;
}
describe("add()", () => {
it("adds two positive numbers", () => {
expect(add(2, 3)).toBe(5);
});
it("handles negative numbers", () => {
expect(add(-1, 1)).toBe(0);
});
it("returns the correct type", () => {
expect(typeof add(1, 2)).toBe("number");
});
});
// Mocking
const fetchData = mock(async (id: number) => {
return { id, name: "Mocked User" };
});
describe("fetchData()", () => {
it("calls the mock once", async () => {
const result = await fetchData(1);
expect(fetchData).toHaveBeenCalledTimes(1);
expect(result.name).toBe("Mocked User");
});
});
# Run all test files
bun test
# Watch mode
bun test --watch
# Run a specific file
bun test math.test.ts
# Run tests matching a pattern
bun test --test-name-pattern "adds two"
# Code coverage
bun test --coverage
Change import { describe, it, expect } from "@jest/globals" or jest to "bun:test" and remove the jest and ts-jest dependencies. Most suites work without any other changes.
Bun's Bundler
The bun build command bundles JavaScript and TypeScript for browsers or other targets. It handles ESM, CommonJS, CSS, and static assets. While it is not as feature-rich as Webpack or Rollup for complex plugin ecosystems, its speed makes it ideal for fast build pipelines and server-side bundling.
# Bundle for browser (default target)
bun build ./src/index.ts --outdir ./dist
# Minified production build
bun build ./src/index.ts --outdir ./dist --minify
# Bundle as CommonJS module
bun build ./src/lib.ts --outdir ./dist --format cjs
# Bundle with sourcemaps
bun build ./src/index.ts --outdir ./dist --sourcemap external
# Bundle everything into a single file
bun build ./src/index.ts --outfile ./dist/bundle.js
# Target specific environments
bun build ./src/index.ts --target browser # Browser (default)
bun build ./src/index.ts --target bun # Bun runtime
bun build ./src/index.ts --target node # Node.js
Using the Bundler API
You can also drive the bundler programmatically from a build script:
const result = await Bun.build({
entrypoints: ["./src/index.ts"],
outdir: "./dist",
minify: true,
sourcemap: "external",
target: "browser",
define: {
"process.env.NODE_ENV": '"production"',
},
plugins: [
// Bun has a plugin API compatible with esbuild plugins
],
});
if (!result.success) {
console.error("Build failed:", result.logs);
process.exit(1);
}
console.log(`Built ${result.outputs.length} files`);
TypeScript and JSX — Zero Config
Bun treats TypeScript as a first-class citizen. There is no need to install typescript, configure tsconfig.json for execution, or run tsc first. Bun strips types at runtime using its own transpiler.
Bun executes TypeScript by stripping types — it does not type-check your code. For type checking, still run bun x tsc --noEmit in your CI pipeline. Think of Bun as a replacement for ts-node, not for the TypeScript compiler.
// Bun reads jsxImportSource from tsconfig.json automatically
// No Babel, no extra config needed
import { renderToString } from "react-dom/server";
function App() {
return (
<html>
<head><title>My App</title></head>
<body>
<h1>Hello from Bun + React SSR!</h1>
</body>
</html>
);
}
const html = renderToString(<App />);
Bun.serve({
port: 3000,
fetch() {
return new Response(html, {
headers: { "Content-Type": "text/html" },
});
},
});
Bun vs Node.js: Performance Comparison
Numbers vary by workload, but these benchmarks represent typical results from the official Bun documentation and community testing:
| Benchmark | Node.js | Bun | Speedup |
|---|---|---|---|
bun install vs npm install (cold) |
~8s | ~0.7s | ~11× |
bun install vs npm install (cached) |
~2s | ~0.1s | ~20× |
Script startup time (hello.ts) |
~120ms | ~10ms | ~12× |
| HTTP requests/sec (hello world) | ~80k req/s | ~250k req/s | ~3× |
| Running a test suite (100 tests) | ~3.2s (Jest) | ~0.4s (bun test) | ~8× |
| Bundling a medium SPA | ~4s (Webpack) | ~0.3s (bun build) | ~13× |
Cold install of a typical project with ~200 packages
Node.js Compatibility
Bun aims for full Node.js API compatibility. The vast majority of npm packages and Node.js code works as-is. Bun implements:
- Node.js built-in modules:
fs,path,http,crypto,stream,os,events, and more - CommonJS and ESM: Both module systems work, and you can mix them
- npm packages: Works with Express, Fastify, Prisma, Drizzle, Mongoose, and most popular packages
- Environment variables:
process.env,.envfile loading built-in (no dotenv needed) - Web APIs:
fetch,Request,Response,WebSocket,URL,crypto(Web Crypto)
Bun's Built-in Globals (No Imports Needed)
| Global | Description |
|---|---|
Bun.file(path) |
Open a file as a lazy blob — very fast I/O |
Bun.write(path, data) |
Write a string, Buffer, or blob to disk |
Bun.serve(options) |
Start an HTTP/WebSocket server |
Bun.build(options) |
Bundle files programmatically |
Bun.hash(data) |
Super-fast non-cryptographic hash (Wyhash) |
Bun.password.hash(pw) |
Hash a password with Argon2 or bcrypt |
Bun.sql |
Built-in PostgreSQL client (Bun v1.1+) |
Bun.sqlite |
Built-in SQLite driver — zero dependencies |
Bun.spawn() |
Run child processes with streaming I/O |
Bun.env |
Alias for process.env with .env auto-loaded |
Built-in SQLite — Zero Dependencies
One of Bun's most useful built-in features for backend developers is bun:sqlite — a fast, synchronous SQLite client with no npm install required. It uses the same API shape as better-sqlite3:
import { Database } from "bun:sqlite";
// Open or create a database file
const db = new Database("myapp.db");
// Create a table
db.run(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`);
// Prepared statement (reusable, prevents SQL injection)
const insertUser = db.prepare(
"INSERT INTO users (name, email) VALUES ($name, $email)"
);
// Insert a user
insertUser.run({ $name: "Alice", $email: "alice@example.com" });
insertUser.run({ $name: "Bob", $email: "bob@example.com" });
// Query all users
const allUsers = db.query("SELECT * FROM users").all();
console.log(allUsers);
// [{ id: 1, name: 'Alice', email: 'alice@example.com', ... }, ...]
// Query a single user
const getById = db.prepare("SELECT * FROM users WHERE id = $id");
const user = getById.get({ $id: 1 });
console.log(user?.name); // "Alice"
db.close();
When to Use Bun vs Node.js
Bun is not a drop-in replacement for every Node.js use case today — certain native addons (.node files), some Webpack plugins, and a small number of npm packages that use Node.js-specific internals may not work. Here is a practical guide:
| Scenario | Recommended | Reason |
|---|---|---|
| New greenfield project | Bun | Faster DX from day one, native TS, built-in test runner |
| CLI tools and scripts | Bun | Instant startup (~10ms vs ~120ms), no install step needed |
| High-throughput API server | Bun | Bun.serve() handles more req/s with less memory |
| Monorepo with npm workspaces | Bun | Dramatically faster installs in CI |
| Project with native addons (.node) | Node.js | Bun does not yet support native Node addons (napi is partial) |
| Legacy Express + complex middleware stack | Node.js | Some edge cases in middleware that rely on Node internals |
| Next.js / Nuxt (full-stack framework) | Both | Use Bun as package manager; runtime support improves each release |
Conclusion and Key Takeaways
Bun represents the most significant shift in the JavaScript tooling landscape since npm was created. By collapsing four separate tools into a single, hand-optimized binary, it eliminates a huge source of incidental complexity for JavaScript developers. Whether you adopt it for package management alone or commit to it as your full runtime, the productivity gains are real and measurable.
Key Takeaways
- All-in-one: Runtime, package manager, bundler, and test runner in one binary
- Speed: 10–30× faster installs; 3–12× faster scripts and tests than Node.js equivalents
- TypeScript first-class: Run
.tsfiles directly, no build step for development - Node.js compatible: Most npm packages and Node.js APIs work without changes
- Built-in batteries: SQLite, password hashing, HTTP server, WebSockets — no extra installs
- Excellent for scripts and CLIs: Near-instant cold start makes Bun ideal for dev tooling
- Not yet universal: Native addons and some complex Webpack ecosystems still require Node.js
"The goal of Bun is to run most of the world's JavaScript outside of browsers, faster."
— Jarred Sumner, Creator of Bun
The JavaScript ecosystem is rarely still, and Bun is one of the most exciting things to happen to it in years. Try swapping npm install for bun install in your next project — you'll feel the difference immediately, and you may never look back.