The All-in-One Promise#
The JavaScript ecosystem has a tooling problem. To run a TypeScript project, you need Node.js for the runtime, npm (or pnpm, or yarn) for packages, webpack or Vite for bundling, ts-node for TypeScript execution, and Jest or Vitest for tests. Each tool has its own config file, its own quirks, and its own version to manage. It works, but it’s a lot of moving parts for what is fundamentally “run my code.”
Bun says: what if one tool did all of that?
What Bun Actually Is#
Bun is a JavaScript/TypeScript runtime written in Zig, powered by JavaScriptCore (Safari’s engine, not V8). But calling it a runtime undersells it. It’s also a package manager, a bundler, and a test runner — all in one binary.
bun install # package manager (replaces npm/pnpm)
bun run server.ts # runtime (replaces node + ts-node)
bun test # test runner (replaces Jest/Vitest)
bun build ./src # bundler (replaces webpack/esbuild)
One install. No tsconfig.json needed for basic TypeScript execution. No ts-node. No build step between writing TypeScript and running it.
Batteries Included (And Why That Matters for Security)#
Here’s something that doesn’t show up in benchmarks but matters for supply chain security : Bun ships with built-in modules for things that traditionally require third-party npm packages.
| Need | npm Ecosystem (third-party) | Bun Built-in (maintained by Oven) |
|---|---|---|
| SQLite | better-sqlite3 | bun:sqlite (3-6x faster) |
| Password hashing | bcrypt (C++ addon) | Bun.password (bcrypt/argon2) |
| Test runner | jest / vitest | bun:test |
| HTTP server | express / fastify | Bun.serve() |
| WebSocket | ws | Built into Bun.serve() |
| S3 client | @aws-sdk/client-s3 | Bun.S3 |
| Database client | pg / mysql2 | Bun.SQL (PostgreSQL, MySQL, SQLite) |
| File I/O | fs-extra | Bun.file() / Bun.write() |
| Crypto | crypto (Node built-in) | Web Crypto API (native) |
Every third-party package is a dependency you’re trusting. Each one can be compromised, abandoned, or typosquatted. When Bun provides these as built-in modules maintained by the core team, that’s fewer dependencies in your node_modules, fewer potential attack vectors, and fewer packages to audit.
This isn’t theoretical — npm supply chain attacks have hit major packages. The fewer third-party dependencies your project needs, the smaller your attack surface. Bun’s batteries-included approach turns a performance feature into a security feature.
The Benchmarks (And Why They Lie)#
Every Bun article leads with benchmarks, so let’s get them out of the way:
| What | Bun | Node.js | Difference |
|---|---|---|---|
| Package install (1,847 deps) | ~5 seconds | ~28 minutes (npm) | 300x+ |
| Cold start | 8-15ms | 60-120ms | 5-8x |
| HTTP server (synthetic) | 52,000 req/s | 13,000 req/s | 4x |
| HTTP server (real app with DB) | ~12,000 req/s | ~12,000 req/s | Same |
That last row is the one that matters. In synthetic benchmarks — “how fast can you respond to an empty HTTP request” — Bun crushes Node.js. In real applications where you’re querying a database, running business logic, and serializing responses, the runtime overhead becomes noise. The bottleneck moves from the JavaScript engine to I/O.
Does this mean the speed doesn’t matter? No. Package installs going from 28 minutes to 5 seconds is genuinely transformative for CI/CD pipelines. Cold starts dropping by 5-8x matters for serverless. But if someone tells you “Bun makes my API 4x faster,” ask them what their database latency is.
The Project: BHVR Stack#
I’ve been building with the BHVR stack — Bun, Hono, Vite, React — in a Turborepo monorepo. Here’s what the project looks like:
my-bhvr/
├── backend/
│ ├── auth/ # Authentication service (Hono + Bun)
│ ├── gateway/ # API gateway (Hono + Zod validation)
│ └── todos/ # Todo service (Hono + Bun)
├── frontend/
│ └── client/ # React + Vite + TanStack
├── packages/
│ ├── types/ # Shared TypeScript types
│ ├── ui/ # Shared UI components
│ └── eslint-config/
├── turbo.json # Turborepo task orchestration
└── bun.lock # Bun lockfile (binary)
Bun is both the runtime and the package manager. Each backend service runs with bun --watch run src/server.ts in development — native TypeScript execution with file watching, no build step. The gateway uses Hono (a lightweight web framework designed for edge runtimes) with Zod for request validation.
Hono: The Express Replacement#
If Bun is the runtime, Hono is the framework. It’s what Express would be if it were designed in 2024 for modern runtimes instead of 2010 for Node.js.
import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator";
import { z } from "zod";
const app = new Hono();
// Looks familiar if you've used Express
app.get("/api/todos", async (c) => {
const todos = await getTodos();
return c.json(todos);
});
// But with built-in Zod validation
app.post(
"/api/todos",
zValidator(
"json",
z.object({
title: z.string().min(1),
completed: z.boolean().default(false),
}),
),
async (c) => {
const data = c.req.valid("json"); // typed and validated
const todo = await createTodo(data);
return c.json(todo, 201);
},
);
Hono is tiny (~14KB), works across runtimes (Bun, Node.js, Deno, Cloudflare Workers), and has first-class TypeScript support. The Zod validator middleware means your request body is validated and typed before your handler runs. No more if (!req.body.title) checks scattered through your code.
What Actually Changed#
The Install Speed Is Real#
bun install on the monorepo takes about 3 seconds. The equivalent npm install would take close to a minute. In development, this means switching branches with different dependencies is instant. In CI, this means faster pipelines.
TypeScript Without a Build Step#
This is the one that changes your workflow the most. In Node.js land, running TypeScript means either:
tscto compile →node dist/index.jsto run (two steps, rebuild on every change)ts-nodeortsxas a wrapper (slower startup, sometimes broken)
With Bun:
bun run src/server.ts # just works, native TS execution
No tsconfig.json required for basic execution (though you’ll still want one for strict type checking). No build step. Save the file, the watcher restarts, you see the result. The feedback loop tightens significantly.
Turborepo Ties It Together#
The monorepo uses Turborepo for task orchestration — turbo dev starts all services in parallel, turbo build builds everything in dependency order, turbo test runs all test suites. Combined with Bun workspaces, shared packages (@repo/types, @repo/ui) are resolved locally without publishing.
{
"workspaces": ["./backend/*", "./frontend/*", "./packages/*"]
}
The gateway imports types from @repo/types and the frontend imports components from @repo/ui — all resolved at the workspace level, all type-checked across boundaries.
The Honest Comparison#
| Aspect | npm + Node.js + Express | Bun + Hono |
|---|---|---|
| Install speed | 30-60s | 3-5s |
| TypeScript execution | Requires ts-node or build step | Native |
| Framework weight | Express: ~200 deps | Hono: ~14KB, minimal deps |
| Type safety | Manual validation or middleware | Zod integration built-in |
| Monorepo support | npm workspaces (works) | Bun workspaces (faster) |
| Ecosystem maturity | 15 years of packages | ~98% npm compatibility |
| Production track record | Battle-tested | Growing, not yet industry standard |
| Debugging tools | Mature (Node inspector, Chrome DevTools) | Good (improving) |
When to Use Bun#
Use Bun if you’re starting a new project, want fast TypeScript execution without build tooling, care about install speed (especially in CI), or are building for edge/serverless where cold start matters.
Stick with Node.js if you’re maintaining a large existing codebase, depend on native C++ addons, need maximum ecosystem stability, or your team doesn’t have bandwidth to learn a new runtime.
The middle ground: use bun install as your package manager with an existing Node.js project. You get the install speed without changing anything else. This is the lowest-risk way to try Bun — swap one command, keep everything else.
The pnpm Question#
I use pnpm at work. It’s fast, disk-efficient, and strict about dependencies (no phantom deps). For existing Node.js projects, pnpm is the right choice — it solves the package management problem without requiring a runtime change.
Bun is faster at installs, but pnpm is “more correct” — its strict dependency resolution catches problems that Bun’s more permissive approach misses. In a large monorepo with dozens of packages, that strictness matters more than a few seconds of install time.
For new projects where I’m choosing the full stack? Bun. For existing Node.js projects at work? pnpm. Different tools for different contexts — a familiar theme .
