What is Bun? The JavaScript Runtime That Made npm Install Fast
Bun is a JavaScript runtime, package manager, bundler, and test runner in a single binary written in Zig. It's also the fastest thing to happen to JavaScript tooling in years — and it runs your existing Node.js code.
The JavaScript toolchain had a performance problem. npm install on a fresh clone of a medium-sized project took 45 seconds. Running a TypeScript file required a compiler, a module bundler, and three config files. Starting a development server involved waiting. The toolchain felt like it was fighting you.
Jarred Sumner started Bun to fix this. Not incrementally. He rewrote the runtime, the package manager, the bundler, and the test runner in Zig, used JavaScriptCore instead of V8, and optimized every slow path he could find.
bun install on that same medium-sized project takes two seconds. Sometimes less.
What Bun Is
Bun is a JavaScript and TypeScript runtime with four major components:
Runtime: Executes JavaScript and TypeScript. Runs .js, .ts, .jsx, and .tsx files natively without a separate compile step.
Package Manager: An npm-compatible package manager. Reads package.json, writes bun.lockb (binary lockfile), installs to node_modules. Commands mirror npm's interface.
Bundler: Bundles JavaScript and TypeScript for deployment. Replaces Webpack, esbuild, or Rollup.
Test Runner: A Jest-compatible test runner built in. bun test reads test files matching *.test.ts patterns and runs them.
All four are part of one binary. No configuration to install. No plugins to choose.
The Tech Stack
Bun uses JavaScriptCore, the engine from Safari and WebKit. Node.js and Deno use V8, Chrome's engine. This is a real architectural difference — not just a branding choice.
JavaScriptCore has different performance characteristics. Startup time is faster than V8, which helps for scripts and CLI tools that run briefly. For long-running servers, the difference narrows.
Bun itself is written in Zig, a systems programming language that competes with C and C++ in the low-level space. Zig gives Bun control over memory allocation and the ability to write very tight I/O code. The package installation speed comes directly from this: Bun parallelizes downloads, writes files concurrently, and uses a hardlink-based cache to skip re-downloading packages you've already installed.
Getting Started
# Install Bun
curl -fsSL https://bun.sh/install | bash
# Verify
bun --version
# Run a file
bun run app.ts
# Or just:
bun app.ts
The Package Manager
bun init # Initialize a new project
bun install # Install all dependencies
bun add express # Add a dependency
bun add -d typescript # Add a dev dependency
bun remove express # Remove a dependency
bun update # Update all packages
The bun.lockb lockfile is binary. You commit it to version control. bun install with an existing lockfile is reproducible and fast.
Writing a Bun Application
HTTP Server with Bun.serve
Bun has a built-in HTTP server. Bun.serve is part of the Bun API — no imports needed.
const server = Bun.serve({
port: 3000,
fetch(req) {
const url = new URL(req.url);
if (url.pathname === "/health") {
return Response.json({ status: "ok", runtime: "bun" });
}
return new Response("Not Found", { status: 404 });
},
});
console.log(`Server running at ${server.url}`);
Bun.serve uses the same Request/Response web standard APIs that browsers and Deno use. Code written against this API is portable.
Routing with a Framework
For real applications, you'd use a framework on top of Bun.serve. Elysia is the most Bun-native option. Hono works well across Bun, Deno, and Node.
import { Elysia, t } from "elysia";
const app = new Elysia()
.get("/", () => "Hello from Bun + Elysia")
.post("/users", ({ body }) => {
return { id: crypto.randomUUID(), ...body };
}, {
body: t.Object({
name: t.String(),
email: t.String({ format: "email" }),
})
})
.listen(3000);
console.log(`Running at ${app.server?.url}`);
Elysia includes schema validation (the t.Object block), TypeScript inference for request/response types, and performance-focused routing.
File I/O
Bun has its own file API that's faster than Node's fs module.
// Read a file
const file = Bun.file("./config.json");
const config = await file.json();
// Write a file
await Bun.write("./output.txt", "Hello, Bun");
// Write a Response body to a file
const res = await fetch("https://example.com/data.json");
await Bun.write("./data.json", res);
Bun.file() returns a BunFile object that's lazy — it doesn't read the file until you call a method on it. Bun.write() handles strings, typed arrays, Response bodies, and other BunFile objects.
SQLite
Bun includes a built-in SQLite driver. No dependencies. No native module compilation issues.
import { Database } from "bun:sqlite";
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 INTEGER NOT NULL
)
`);
// Prepared statements
const insert = db.prepare("INSERT INTO users (name, email, created_at) VALUES (?, ?, ?)");
const getUser = db.prepare("SELECT * FROM users WHERE email = ?");
// Insert
insert.run("Traven", "[email protected]", Date.now());
// Query
const user = getUser.get("[email protected]");
console.log(user);
// Batch operations
const insertMany = db.transaction((users) => {
for (const user of users) {
insert.run(user.name, user.email, Date.now());
}
});
insertMany([
{ name: "Alice", email: "[email protected]" },
{ name: "Bob", email: "[email protected]" },
]);
The transaction wrapper is important for batch inserts. SQLite is fast when writes are batched inside a transaction. Without it, each insert.run() opens and commits its own transaction, which is much slower.
For applications that need a serious relational database, SQLite isn't the answer. PostgreSQL handles concurrent writes, large datasets, and complex queries at a level SQLite can't match. But for local tooling, prototypes, and lightweight production applications with low write concurrency, bun:sqlite is genuinely convenient.
The Test Runner
// math.test.ts
import { expect, test, describe } from "bun:test";
import { add, subtract } from "./math";
describe("math operations", () => {
test("add returns correct sum", () => {
expect(add(2, 3)).toBe(5);
expect(add(-1, 1)).toBe(0);
expect(add(0, 0)).toBe(0);
});
test("subtract returns correct difference", () => {
expect(subtract(5, 3)).toBe(2);
});
});
Run with:
bun test # Run all tests
bun test --watch # Re-run on file changes
bun test --coverage # Generate coverage report
The API is Jest-compatible. If your team already knows Jest, switching to bun test requires no learning curve.
The Bundler
Bun's bundler is fast and supports TypeScript, JSX, and CSS modules out of the box.
await Bun.build({
entrypoints: ["./src/index.ts"],
outdir: "./dist",
target: "bun", // or "browser" or "node"
minify: true,
sourcemap: "external",
});
For browser builds, Bun produces optimized JavaScript bundles. For server deployments, it can bundle your application into a single file.
Node.js Compatibility
Bun implements the Node.js API surface. Most Node.js code runs on Bun without changes. require(), Buffer, process, path, fs, http — these work. npm packages that use Node internals generally work.
There are gaps. Some packages that rely on deep Node.js internals, native addons (.node files compiled with node-gyp), or specific libuv behavior may not work. Bun's compatibility page documents known issues.
For new projects, Bun is a drop-in replacement for most use cases. For existing projects, test your dependency tree before committing.
What Bun Doesn't Have (Yet)
The permission system. Deno requires explicit permission flags for file system and network access. Bun does not. A Bun script has full access by default, like Node.
Deno's KV store and Deploy platform have no Bun equivalent yet. Bun is focused on the runtime and toolchain layer; the deployment story is standard (Docker, fly.io, Railway, VPS).
The community tooling for Bun is smaller than Node's. Elysia and Hono are excellent, but the breadth of options at every layer is narrower. This is changing fast — Bun's growth trajectory has been steep.
The Performance Story
The benchmark numbers are real. Bun starts faster than Node and handles more requests per second in HTTP serving benchmarks. Package installation is dramatically faster.
For CPU-bound work, the performance picture is more nuanced. V8's JIT compiler and optimization pipeline are extremely mature, built from Google's investment over many years. JavaScriptCore is also heavily optimized, but the relative performance varies by workload. Don't pick Bun for the benchmarks. Pick it because the development experience is faster and the toolchain is simpler.
For the direct comparison with Node.js and Deno across different dimensions, see Node.js vs Deno vs Bun.
The Zig Factor
Zig is worth knowing about separately from Bun. It's a systems programming language that competes with C and Rust in different ways. No garbage collector. Manual memory management.Comptime for compile-time code execution. The fact that Bun is built in Zig and achieves the performance it does is an advertisement for the language.
If systems programming interests you, the bytecode VM article covers building a runtime in C — similar concepts, closer to what Bun itself does at the implementation layer.
When to Use Bun
Bun makes the most sense when you want:
- Fast local development without toolchain friction
- TypeScript execution without a compile step
- Built-in SQLite for lightweight data persistence
- Faster CI pipelines (package installation alone can save significant time)
- A simpler dependency on tooling (one binary, not npm + prettier + jest + esbuild)
For teams already running Node in production, Bun as a development-time tool (for bun install and bun test) with Node in production is a low-risk way to get the speed wins without a full migration.