Skip to main content
Technology & EngineeringDeno Bun238 lines

Bun Basics

Bun runtime fundamentals including speed optimizations, built-in APIs, and package management

Quick Summary24 lines
You are an expert in Bun runtime fundamentals for building high-performance JavaScript and TypeScript applications.

## Key Points

- **Use `Bun.file()` over `fs.readFile`** — it is lazy and significantly faster. Access `.text()`, `.json()`, `.arrayBuffer()` only when you need the data.
- **Prefer `bun:sqlite`** for local data persistence — it is embedded, requires no external process, and is extremely fast.
- **Use `Bun.serve`** instead of Express for new projects — it is simpler and handles significantly more requests per second.
- **Run scripts with `bun run`** — it starts faster than `node` and transpiles TypeScript automatically.
- **Use workspace support** in `package.json` for monorepos — Bun respects the same workspace protocol as npm/yarn.
- **Commit `bun.lockb`** to version control for deterministic installs.
- **Native Node addons (`.node` files)** — Bun supports many but not all N-API addons. C++ addons using raw V8 APIs will not work.
- **`bun.lockb` is binary** — you cannot review it in PRs like `package-lock.json`. Use `bun install --yarn` during migration if your team needs a readable lockfile.
- **Global installs behave differently** — `bun install -g` installs to `~/.bun/bin`. Ensure it is on your `PATH`.
- **Hot module replacement** — `bun --hot` re-executes the module but preserves global state, which can cause subtle bugs if your module has side effects.
- **Memory usage patterns differ from Node** — JSC has a different garbage collector. Monitor memory in production and adjust if your workload relies on V8-specific GC behavior.

## Quick Example

```bash
bun run app.ts       # Just works
bun run app.tsx      # JSX works too
```
skilldb get deno-bun-skills/Bun BasicsFull skill: 238 lines
Paste into your CLAUDE.md or agent config

Bun Basics — Modern JS Runtimes

You are an expert in Bun runtime fundamentals for building high-performance JavaScript and TypeScript applications.

Overview

Bun is an all-in-one JavaScript runtime built on the JavaScriptCore engine (from WebKit) rather than V8. It includes a package manager, bundler, test runner, and transpiler in a single binary. Bun is designed as a drop-in replacement for Node.js with dramatically faster startup, install times, and I/O throughput. It runs TypeScript and JSX natively without configuration.

Core Concepts

Runtime Architecture

Bun uses JavaScriptCore (JSC) instead of V8, Zig for its internals instead of C++, and implements hot paths with hand-tuned native code. This yields significant performance gains for startup time, HTTP serving, file I/O, and package installation.

Package Manager

Bun's package manager is compatible with package.json and node_modules but installs packages significantly faster than npm or yarn.

bun init                    # Create a new project
bun install                 # Install all dependencies
bun add express             # Add a dependency
bun add -d typescript       # Add a dev dependency
bun remove lodash           # Remove a dependency
bun update                  # Update all dependencies

The lockfile is bun.lockb (binary format for speed). Use bun install --yarn to generate a yarn.lock if needed for compatibility.

TypeScript and JSX Support

Bun transpiles TypeScript and JSX on the fly. No tsc, no Babel, no configuration:

bun run app.ts       # Just works
bun run app.tsx      # JSX works too

Built-in APIs

Bun extends the Web Standard APIs with high-performance native implementations:

// Bun.serve — HTTP server
// Bun.file — fast file I/O
// Bun.write — fast file writing
// Bun.spawn / Bun.spawnSync — subprocesses
// Bun.password — password hashing (argon2, bcrypt)
// Bun.sql — built-in PostgreSQL client
// Bun.redis — built-in Redis client

Implementation Patterns

HTTP Server

Bun.serve({
  port: 3000,
  fetch(req) {
    const url = new URL(req.url);

    if (url.pathname === "/api/health") {
      return Response.json({ status: "ok" });
    }

    if (url.pathname === "/api/data" && req.method === "POST") {
      const body = await req.json();
      return Response.json({ received: body }, { status: 201 });
    }

    return new Response("Not Found", { status: 404 });
  },
  error(err) {
    return new Response(`Internal Error: ${err.message}`, { status: 500 });
  },
});

console.log("Server running on http://localhost:3000");

File I/O

// Reading files — returns a BunFile (lazy, no immediate read)
const file = Bun.file("./data.json");
const text = await file.text();       // as string
const json = await file.json();       // parsed JSON
const bytes = await file.arrayBuffer(); // as ArrayBuffer
console.log(file.size, file.type);    // metadata without reading

// Writing files
await Bun.write("./output.txt", "Hello, Bun!");
await Bun.write("./copy.json", Bun.file("./source.json")); // copy file
await Bun.write("./data.bin", new Uint8Array([1, 2, 3]));

// Streaming large files
const writer = Bun.file("./large-output.csv").writer();
writer.write("id,name\n");
for (const row of rows) {
  writer.write(`${row.id},${row.name}\n`);
}
writer.flush();

SQLite (Built-in)

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
  )
`);

// Prepared statements (recommended for performance)
const insert = db.prepare("INSERT INTO users (name, email) VALUES (?, ?)");
insert.run("Alice", "alice@example.com");

// Query
const getUser = db.prepare("SELECT * FROM users WHERE email = ?");
const user = getUser.get("alice@example.com");

// Transactions
const insertMany = db.transaction((users) => {
  for (const u of users) {
    insert.run(u.name, u.email);
  }
});
insertMany([
  { name: "Bob", email: "bob@example.com" },
  { name: "Carol", email: "carol@example.com" },
]);

Password Hashing

const hash = await Bun.password.hash("my-password", {
  algorithm: "argon2id",
  memoryCost: 65536,
  timeCost: 3,
});

const isValid = await Bun.password.verify("my-password", hash);

Subprocesses

// Simple execution
const result = Bun.spawnSync(["ls", "-la"]);
console.log(result.stdout.toString());

// Async with streaming
const proc = Bun.spawn(["ffmpeg", "-i", "input.mp4", "output.webm"], {
  stdout: "pipe",
  stderr: "pipe",
});

const output = await new Response(proc.stdout).text();
await proc.exited; // wait for completion
console.log("Exit code:", proc.exitCode);

WebSocket Server

Bun.serve({
  port: 3000,
  fetch(req, server) {
    if (server.upgrade(req)) return;
    return new Response("Upgrade failed", { status: 500 });
  },
  websocket: {
    open(ws) {
      ws.subscribe("chat");
      ws.publish("chat", JSON.stringify({ type: "join" }));
    },
    message(ws, message) {
      ws.publish("chat", message);
    },
    close(ws) {
      ws.unsubscribe("chat");
    },
  },
});

Core Philosophy

Bun is built on the premise that developer tooling should be fast enough to be invisible. By combining a runtime, package manager, bundler, test runner, and transpiler into a single binary, Bun eliminates the configuration overhead and startup latency that plague traditional Node.js toolchains. You run bun run app.ts and it just works, with TypeScript, JSX, and module resolution handled automatically.

The performance story is not just marketing: Bun uses JavaScriptCore (the WebKit engine) instead of V8, implements hot paths in Zig, and optimizes I/O at the system call level. These choices produce measurably faster HTTP serving, file operations, and package installations. But performance is a means to an end, not the end itself. The real value is that Bun reduces the friction between writing code and seeing results, enabling tighter development loops.

Bun's compatibility strategy is pragmatic rather than purist. It implements Node.js APIs directly so that existing package.json projects, npm packages, and node_modules directories work without modification. At the same time, it provides Bun-native APIs (Bun.serve, Bun.file, Bun.password, bun:sqlite) that are simpler and faster than their Node equivalents. The migration path is: swap the runtime first, adopt Bun-native APIs gradually.

Anti-Patterns

  • Using fs.readFile when Bun.file() is available. Bun.file() is lazy (no read until you call .text() or .json()), significantly faster, and provides metadata without reading the file. Defaulting to the Node fs API when writing new code misses performance gains.

  • Installing Express for new projects instead of using Bun.serve. Express works on Bun, but Bun.serve is simpler (one function, Web Fetch API) and handles significantly more requests per second. Use Express only when you need its middleware ecosystem.

  • Assuming all Node.js APIs are fully implemented. Bun covers the most common Node built-ins but niche APIs may be missing or behave differently. Check the compatibility table for your specific dependency before deploying to production.

  • Using bun --hot without understanding its semantics. Hot reloading re-executes the module but preserves global state, which can cause subtle bugs if your module has initialization side effects. Clear global state explicitly or use --watch (full restart) for modules with side effects.

  • Relying on V8-specific behavior in production. JavaScriptCore has a different garbage collector, different JIT characteristics, and different error formatting than V8. Profile and test on Bun itself rather than assuming Node benchmarks transfer directly.

Best Practices

  • Use Bun.file() over fs.readFile — it is lazy and significantly faster. Access .text(), .json(), .arrayBuffer() only when you need the data.
  • Prefer bun:sqlite for local data persistence — it is embedded, requires no external process, and is extremely fast.
  • Use Bun.serve instead of Express for new projects — it is simpler and handles significantly more requests per second.
  • Run scripts with bun run — it starts faster than node and transpiles TypeScript automatically.
  • Use workspace support in package.json for monorepos — Bun respects the same workspace protocol as npm/yarn.
  • Commit bun.lockb to version control for deterministic installs.

Common Pitfalls

  • Not all Node.js APIs are implemented — Bun covers the most common ones (fs, path, http, crypto, child_process) but niche APIs may be missing or behave slightly differently. Check the compatibility table.
  • Native Node addons (.node files) — Bun supports many but not all N-API addons. C++ addons using raw V8 APIs will not work.
  • bun.lockb is binary — you cannot review it in PRs like package-lock.json. Use bun install --yarn during migration if your team needs a readable lockfile.
  • Global installs behave differentlybun install -g installs to ~/.bun/bin. Ensure it is on your PATH.
  • Hot module replacementbun --hot re-executes the module but preserves global state, which can cause subtle bugs if your module has side effects.
  • Memory usage patterns differ from Node — JSC has a different garbage collector. Monitor memory in production and adjust if your workload relies on V8-specific GC behavior.

Install this skill directly: skilldb add deno-bun-skills

Get CLI access →