Introduction
The JavaScript runtime landscape was disrupted when Jarred Sumner released Bun 1.0 in September 2023, presenting a credible alternative to Node.js that promised dramatically faster performance across every dimension: startup time, HTTP throughput, package installation, bundling, and test execution. Built on Apple's JavaScriptCore engine (the same engine powering Safari) instead of V8, and written in Zig instead of C++, Bun was designed from the ground up for performance without the historical baggage that Node.js carries from its 14-year evolution.
Bun's value proposition is ambitious: replace Node.js, npm/yarn/pnpm, webpack/Vite/esbuild, and Jest/Vitest — all with a single tool. Instead of a fragmented toolchain where each tool has its own configuration format, dependency resolution algorithm, and plugin system, Bun offers a unified, batteries-included experience. You install Bun, and you have a runtime, package manager, bundler, and test runner out of the box with zero configuration.
This comprehensive guide covers everything you need to know about Bun 1.0: installation, runtime features, the built-in package manager, bundler, test runner, Node.js compatibility, and production readiness. We will benchmark Bun against Node.js and Deno, explore real-world migration paths, and identify the current limitations that might delay your adoption.
Installation and Setup
Installing Bun
Bun installs in seconds with a single command:
# macOS / Linux / WSL
curl -fsSL https://bun.sh/install | bash
# Windows (Bun 1.0+)
powershell -c "irm bun.sh/install.ps1 | iex"
# npm (if you have Node.js)
npm install -g bun
# Verify installation
bun --versionCreating a New Project
# Initialize a new Bun project
bun init
# This creates:
# - package.json (with bun as the package manager)
# - index.ts (entry point with Bun.serve() example)
# - tsconfig.json (TypeScript configuration)
# - bunfig.toml (Bun configuration)Project Structure
A typical Bun project has a minimal structure:
my-app/
├── src/
│ ├── index.ts # Application entry point
│ ├── routes/
│ │ └── api.ts # API route handlers
│ └── lib/
│ └── database.ts # Database utilities
├── tests/
│ └── api.test.ts # Test files
├── package.json
├── tsconfig.json
└── bunfig.toml # Bun-specific configuration
The Bun Runtime
HTTP Server Performance
Bun's HTTP server is built on a custom HTTP parser and uses io_uring on Linux for async I/O, achieving significantly higher throughput than Node.js:
// Bun native server
const server = Bun.serve({
port: 3000,
fetch(req) {
const url = new URL(req.url);
if (url.pathname === '/api/hello') {
return Response.json({ message: 'Hello from Bun!' });
}
if (url.pathname === '/api/users') {
return Response.json(getUsers());
}
return new Response('Not Found', { status: 404 });
},
error(error) {
return new Response(`Error: ${error.message}`, { status: 500 });
},
});
console.log(`Server running on http://localhost:${server.port}`);File I/O
Bun provides optimized file I/O APIs that are significantly faster than Node.js fs:
// Reading files — 3x faster than Node.js fs.readFile
const text = await Bun.file('data.txt').text();
const json = await Bun.file('config.json').json();
const buffer = await Bun.file('image.png').arrayBuffer();
// Writing files
await Bun.write('output.txt', 'Hello, Bun!');
await Bun.write('output.json', JSON.stringify({ key: 'value' }, null, 2));
// Streaming large files
const file = Bun.file('large-video.mp4');
const stream = file.stream();
const reader = stream.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
// Process chunk
}
// File metadata
const file = Bun.file('document.pdf');
console.log(file.size); // Size in bytes
console.log(file.type); // MIME type
console.log(file.lastModified); // Last modified timestampSQLite (Built-in)
Bun includes a built-in SQLite driver that is faster than better-sqlite3:
import { Database } from 'bun:sqlite';
const db = new Database('app.db');
// Create 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 TEXT DEFAULT CURRENT_TIMESTAMP
)
`);
// Prepared statements (faster than raw queries)
const insertUser = db.prepare(
'INSERT INTO users (name, email) VALUES (?, ?)'
);
const findUser = db.prepare(
'SELECT * FROM users WHERE email = ?'
);
// Transaction
const insertMany = db.transaction((users: Array<{name: string, email: string}>) => {
for (const user of users) {
insertUser.run(user.name, user.email);
}
});
insertMany([
{ name: 'Alice', email: 'alice@example.com' },
{ name: 'Bob', email: 'bob@example.com' },
]);
const user = findUser.get('alice@example.com');
console.log(user);Bun as a Package Manager
Speed Advantage
Bun's package manager is its most immediately impressive feature. It installs dependencies 10-100x faster than npm by using a global cache, parallel downloads, and optimized extraction:
# Install all dependencies
bun install # ~1-2 seconds for typical project
npm install # ~15-30 seconds for same project
# Add a dependency
bun add express # ~0.5 seconds
npm install express # ~5-10 seconds
# Add a dev dependency
bun add -d typescript
# Remove a dependency
bun remove express
# Install exact versions (frozen lockfile)
bun install --frozen-lockfile # For CI/CDLockfile and Cache
Bun uses bun.lockb (a binary lockfile) instead of package-lock.json or yarn.lock. The binary format is faster to parse and write:
# The global cache stores packages by content hash
# Multiple projects share the same cached packages
~/.bun/install/cache/
# bun.lockb is created automatically
# It's a binary format — you can inspect it with:
bun lockb --print > bun-lock.jsonWorkspace Support
Bun supports monorepo workspaces natively:
// package.json (root)
{
"name": "monorepo",
"workspaces": ["packages/*", "apps/*"]
}# Install all workspace dependencies
bun install
# Run scripts in a specific workspace
bun run --cwd packages/core build
bun run --cwd apps/web dev
# Add dependency to a specific workspace
bun add lodash --cwd packages/utilsBun as a Bundler
Basic Bundling
Bun includes a built-in bundler that supports JavaScript, TypeScript, JSX, and CSS:
// build.ts
await Bun.build({
entrypoints: ['./src/index.ts'],
outdir: './dist',
target: 'browser', // or 'node', 'bun'
format: 'esm',
splitting: true, // Code splitting
sourcemap: 'external',
minify: true,
define: {
'process.env.NODE_ENV': '"production"',
},
});
console.log('Build complete!');# Run the build script
bun run build.tsCSS Bundling
Bun can bundle CSS with support for @import resolution:
await Bun.build({
entrypoints: ['./src/styles/main.css'],
outdir: './dist',
naming: '[name].[hash].[ext]',
});Asset Handling
Bun can bundle and optimize images and other assets:
await Bun.build({
entrypoints: ['./src/app.tsx'],
outdir: './dist',
loader: {
'.png': 'file',
'.jpg': 'file',
'.svg': 'dataurl',
'.woff2': 'file',
},
});Bun as a Test Runner
Writing Tests
Bun's test runner is built-in and requires zero configuration:
import { describe, it, expect, beforeAll, afterAll, mock } from 'bun:test';
describe('UserService', () => {
let service: UserService;
beforeAll(() => {
service = new UserService(new TestDatabase());
});
afterAll(async () => {
await service.cleanup();
});
it('should create a user', async () => {
const user = await service.create({
name: 'Alice',
email: 'alice@example.com',
});
expect(user.id).toBeDefined();
expect(user.name).toBe('Alice');
expect(user.email).toBe('alice@example.com');
expect(user.createdAt).toBeInstanceOf(Date);
});
it('should not allow duplicate emails', async () => {
await service.create({ name: 'Bob', email: 'bob@example.com' });
expect(
service.create({ name: 'Bob2', email: 'bob@example.com' })
).rejects.toThrow('Email already exists');
});
it('should mock external API calls', async () => {
const fetchMock = mock(() =>
Promise.resolve(new Response(JSON.stringify({ id: 1 })))
);
globalThis.fetch = fetchMock;
const result = await service.fetchExternalData(1);
expect(fetchMock).toHaveBeenCalledWith(
expect.stringContaining('/api/data/1')
);
expect(result.id).toBe(1);
});
});Running Tests
# Run all tests
bun test
# Run tests matching a pattern
bun test --grep "UserService"
# Run tests in a specific file
bun test src/services/user.test.ts
# Watch mode
bun test --watch
# Coverage
bun test --coverageSnapshot Testing
Bun supports snapshot testing out of the box:
import { expect, it } from 'bun:test';
it('should match the API response snapshot', async () => {
const response = await fetch('/api/users');
const data = await response.json();
expect(data).toMatchSnapshot();
});Real-World Use Cases
REST API with Bun.serve()
interface User {
id: number;
name: string;
email: string;
}
const users: User[] = [];
let nextId = 1;
const server = Bun.serve({
port: 3000,
async fetch(req) {
const url = new URL(req.url);
const method = req.method;
// GET /api/users
if (method === 'GET' && url.pathname === '/api/users') {
return Response.json(users);
}
// POST /api/users
if (method === 'POST' && url.pathname === '/api/users') {
const body = await req.json();
const user: User = { id: nextId++, ...body };
users.push(user);
return Response.json(user, { status: 201 });
}
// GET /api/users/:id
const match = url.pathname.match(/^\/api\/users\/(\d+)$/);
if (method === 'GET' && match) {
const user = users.find(u => u.id === parseInt(match[1]));
if (!user) {
return Response.json({ error: 'Not found' }, { status: 404 });
}
return Response.json(user);
}
return Response.json({ error: 'Not found' }, { status: 404 });
},
});WebSocket Server
const server = Bun.serve({
port: 3000,
fetch(req, server) {
if (new URL(req.url).pathname === '/ws') {
const upgraded = server.upgrade(req);
if (!upgraded) {
return new Response('Upgrade failed', { status: 400 });
}
return;
}
return new Response('Hello World');
},
websocket: {
open(ws) {
ws.subscribe('chat');
ws.publish('chat', 'A user joined');
},
message(ws, message) {
ws.publish('chat', message);
ws.send(`You said: ${message}`);
},
close(ws) {
ws.unsubscribe('chat');
},
},
});CLI Tool with Bun
// cli.ts
const args = process.argv.slice(2);
const command = args[0];
switch (command) {
case 'build':
await Bun.build({
entrypoints: ['./src/index.ts'],
outdir: './dist',
minify: true,
});
console.log('Build complete!');
break;
case 'serve':
await import('./server.ts');
break;
case 'migrate':
const db = new Database('app.db');
await import('./migrations.ts');
break;
default:
console.log('Usage: cli <build|serve|migrate>');
}// package.json
{
"name": "my-cli",
"bin": "./cli.ts"
}Node.js Compatibility
Compatibility Status
Bun aims for high Node.js compatibility, but there are gaps:
| Category | Compatibility | Notes |
|---|---|---|
| Core modules (fs, path, http, etc.) | ~95% | Most APIs implemented; some edge cases missing |
| npm packages | ~90% | Popular packages work; niche packages may break |
| CommonJS | Yes | Full require() support |
| ESM | Yes | Full import support |
| TypeScript | Yes | Built-in, no build step needed |
| JSX/TSX | Yes | Built-in transpilation |
| Worker Threads | Partial | Basic support; some APIs missing |
| Native addons (N-API) | Partial | Some N-API modules work; others need recompilation |
| Child processes | Yes | spawn, exec, fork all supported |
| Streams | Partial | Most stream APIs work; some edge cases differ |
Migration Guide
Migrating a Node.js project to Bun:
# 1. Install Bun
curl -fsSL https://bun.sh/install | bash
# 2. Replace npm/yarn with Bun
rm -rf node_modules package-lock.json yarn.lock
bun install
# 3. Update scripts in package.json
# Replace "node" with "bun" in scripts
# "start": "bun run src/index.ts"
# "test": "bun test"
# 4. Run and test
bun run start
bun test
# 5. Fix compatibility issues
# Replace Node-specific APIs if needed
# e.g., fs.watch may behave differentlyBenchmarks and Performance
HTTP Server Benchmarks
| Metric | Bun | Node.js | Deno | Improvement |
|---|---|---|---|---|
| Requests/sec (Hello World) | 250,000 | 100,000 | 150,000 | 2.5x over Node |
| Startup time | 5ms | 30ms | 25ms | 6x faster |
| Memory (idle) | 15MB | 35MB | 30MB | 2.3x less |
| Package install (100 deps) | 0.5s | 15s | N/A | 30x faster |
Real-World Application Benchmarks
| Application Type | Bun | Node.js | Notes |
|---|---|---|---|
| JSON API (Express-like) | 2x faster | Baseline | Using Bun.serve() vs Express |
| File processing | 3x faster | Baseline | Bun.file() vs fs.readFile |
| SQLite queries | 2x faster | Baseline | bun:sqlite vs better-sqlite3 |
| TypeScript execution | 10x faster | Baseline | No transpilation step vs ts-node |
| Test execution | 5x faster | Jest | bun test vs jest |
Limitations and Considerations
Current Limitations
-
Windows support: Bun on Windows is newer and less mature than macOS/Linux. Some features may behave differently or be missing.
-
Native addon compatibility: While N-API support exists, some Node.js native addons (especially those using older NAN API) may not work without modification.
-
Ecosystem maturity: The Bun ecosystem is younger. Some npm packages that rely heavily on Node.js internals may not work perfectly.
-
Debugging: Bun's debugging story is improving but is not as mature as Node.js with Chrome DevTools. The
--inspectflag is supported but tooling is less polished. -
Long-running stability: For production servers running 24/7, Node.js has years of battle-tested stability. Bun is improving rapidly but may have edge cases in long-running processes.
When to Use Bun
Great fit:
- New projects where you control the full stack
- TypeScript projects where you want zero-config TS support
- Developer tooling (CLIs, build scripts, dev servers)
- Projects that benefit from fast startup (serverless functions)
- Applications that use SQLite heavily
Consider carefully:
- Enterprise applications with complex native addon dependencies
- Projects with extensive Jest test suites that rely on specific Jest behaviors
- Applications that require maximum production stability guarantees
- Windows-only deployments
Migration from Node.js
Step-by-Step Migration
# 1. Test Bun compatibility first
bun run src/index.ts # Does it start?
bun test # Do tests pass?
# 2. Replace package manager
# Update CI/CD to use bun install
# Update Dockerfile to use bun image
# 3. Update scripts
# "start": "bun run src/index.ts"
# "build": "bun run build.ts"
# "test": "bun test"
# 4. Replace Node-specific APIs if needed
# fs.watch → Bun.file().text() + polling
# child_process → works as-is
# streams → works as-is
# 5. Performance testing
# Benchmark your specific workload
# Compare memory usage under loadDocker Setup
FROM oven/bun:1 AS base
WORKDIR /app
FROM base AS deps
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile --production
FROM base AS build
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN bun run build
FROM base AS runtime
COPY --from=deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY package.json .
USER bun
EXPOSE 3000
CMD ["bun", "run", "dist/index.js"]Comparison with Node.js and Deno
| Feature | Bun | Node.js | Deno |
|---|---|---|---|
| JavaScript engine | JavaScriptCore | V8 | V8 |
| Language | Zig | C++ | Rust |
| Package manager | Built-in | npm | Built-in (URL imports) |
| TypeScript | Built-in | Requires build step | Built-in |
| Test runner | Built-in | Requires Jest/Vitest | Built-in |
| Bundler | Built-in | Requires webpack/Vite/esbuild | Built-in |
| npm compatibility | High (~90%) | Native | Good (via npm: specifier) |
| Node.js API compat | High (~95%) | Native | Moderate (~80%) |
| Startup time | Fastest | Moderate | Fast |
| Ecosystem maturity | Growing | Most mature | Growing |
| Production readiness | Good | Excellent | Good |
Future Outlook
Bun is evolving rapidly with a clear roadmap. Upcoming features include improved Windows support, better native addon compatibility, built-in database ORM, edge runtime support, and more sophisticated bundling features. The project is backed by Oven (the company behind Bun) with significant funding, ensuring continued development.
The JavaScript runtime space is healthy with competition driving innovation across all three major runtimes. Node.js is adopting features from Bun and Deno (built-in test runner, built-in watch mode), Deno is improving npm compatibility, and Bun is maturing its production readiness. Developers benefit from this competition regardless of which runtime they choose.
Conclusion
Bun 1.0 represents a genuine step forward for the JavaScript ecosystem. Its key strengths are:
- Dramatically faster package installation — 10-100x faster than npm, eliminating the "waiting for node_modules" experience
- Built-in TypeScript support — zero-config TypeScript execution without build steps
- Unified toolchain — runtime, package manager, bundler, and test runner in one tool
- Excellent Node.js compatibility — most npm packages work without modification
- Superior performance — faster startup, higher HTTP throughput, more efficient memory usage
The main consideration for adoption is ecosystem maturity. For new projects, Bun is an excellent choice that simplifies the toolchain and improves developer experience. For existing projects with complex dependencies, a gradual migration starting with the package manager (bun install) is recommended before moving runtime and test execution to Bun.