MinhVo

Minh Vo

rss feed

Slaying code & making it lit fr fr 🔥 tagline

Hey there 👋 I'm an AI Engineer with 7 years of experience building scalable web and mobile applications. Currently at Neurond AI (May 2025 — present), architecting an Enterprise AI Assistant Platform with multi-tenant RAG on pgvector, multi-provider LLM orchestration, and Azure-native infrastructure. Previously spent 5+ years at SNAPTEC (Sep 2019 — Apr 2025), leading SaaS themes, admin dashboards, and e-commerce platforms — earned the Hero of the Year award in 2021. I specialize in TypeScript, React, Next.js, and AI-Native engineering with Claude Code and Cursor.bio

Back to blogs

Bun 1.0: The All-in-One JavaScript Runtime

Bun 1.0 release: package manager, bundler, test runner, and Node.js compatibility.

BunRuntimeJavaScriptTypeScript

By MinhVo

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.

Bun JavaScript runtime performance benchmark

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 --version

Creating 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 timestamp

SQLite (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/CD

Lockfile 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.json

Workspace 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/utils

Bun 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.ts

CSS 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 --coverage

Snapshot 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"
}

Modern JavaScript development workspace

Node.js Compatibility

Compatibility Status

Bun aims for high Node.js compatibility, but there are gaps:

CategoryCompatibilityNotes
Core modules (fs, path, http, etc.)~95%Most APIs implemented; some edge cases missing
npm packages~90%Popular packages work; niche packages may break
CommonJSYesFull require() support
ESMYesFull import support
TypeScriptYesBuilt-in, no build step needed
JSX/TSXYesBuilt-in transpilation
Worker ThreadsPartialBasic support; some APIs missing
Native addons (N-API)PartialSome N-API modules work; others need recompilation
Child processesYesspawn, exec, fork all supported
StreamsPartialMost 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 differently

Benchmarks and Performance

HTTP Server Benchmarks

MetricBunNode.jsDenoImprovement
Requests/sec (Hello World)250,000100,000150,0002.5x over Node
Startup time5ms30ms25ms6x faster
Memory (idle)15MB35MB30MB2.3x less
Package install (100 deps)0.5s15sN/A30x faster

Real-World Application Benchmarks

Application TypeBunNode.jsNotes
JSON API (Express-like)2x fasterBaselineUsing Bun.serve() vs Express
File processing3x fasterBaselineBun.file() vs fs.readFile
SQLite queries2x fasterBaselinebun:sqlite vs better-sqlite3
TypeScript execution10x fasterBaselineNo transpilation step vs ts-node
Test execution5x fasterJestbun test vs jest

Limitations and Considerations

Current Limitations

  1. Windows support: Bun on Windows is newer and less mature than macOS/Linux. Some features may behave differently or be missing.

  2. Native addon compatibility: While N-API support exists, some Node.js native addons (especially those using older NAN API) may not work without modification.

  3. Ecosystem maturity: The Bun ecosystem is younger. Some npm packages that rely heavily on Node.js internals may not work perfectly.

  4. Debugging: Bun's debugging story is improving but is not as mature as Node.js with Chrome DevTools. The --inspect flag is supported but tooling is less polished.

  5. 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 load

Docker 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

FeatureBunNode.jsDeno
JavaScript engineJavaScriptCoreV8V8
LanguageZigC++Rust
Package managerBuilt-innpmBuilt-in (URL imports)
TypeScriptBuilt-inRequires build stepBuilt-in
Test runnerBuilt-inRequires Jest/VitestBuilt-in
BundlerBuilt-inRequires webpack/Vite/esbuildBuilt-in
npm compatibilityHigh (~90%)NativeGood (via npm: specifier)
Node.js API compatHigh (~95%)NativeModerate (~80%)
Startup timeFastestModerateFast
Ecosystem maturityGrowingMost matureGrowing
Production readinessGoodExcellentGood

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:

  1. Dramatically faster package installation — 10-100x faster than npm, eliminating the "waiting for node_modules" experience
  2. Built-in TypeScript support — zero-config TypeScript execution without build steps
  3. Unified toolchain — runtime, package manager, bundler, and test runner in one tool
  4. Excellent Node.js compatibility — most npm packages work without modification
  5. 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.