web-dev6 min read

Bun Tutorial: Learn JavaScript Runtime from Scratch (2026)

Bun Tutorial: Learn JavaScript Runtime from Scratch (2026)

Published:  |  Category: Web Dev  |  Reading time: ~15 min
Bun Tutorial: Learn JavaScript Runtime from Scratch (2026)

Bun is a fast all-in-one JavaScript runtime, bundler, test runner, and package manager created by Jarred Sumner. Released in 2022, Bun aims to replace Node.js with a single binary that runs JavaScript and TypeScript natively, using the JavaScriptCore engine instead of V8. Bun is designed from the ground up for speed — starting up 4x faster than Node.js and running scripts significantly faster.

Beyond being a runtime, Bun includes a built-in bundler inspired by esbuild, a Jest-compatible test runner, a package manager that is 10-30x faster than npm, and native TypeScript support without additional configuration.

Runtime and JavaScriptCore

Bun uses WebKit’s JavaScriptCore engine rather than V8. JavaScriptCore’s JIT compilation provides fast startup and competitive runtime performance. Bun implements Node.js APIs as native code in Zig, reducing overhead compared to the JavaScript-based implementations in Node.js.

The runtime supports TypeScript and JSX out of the box — no tsconfig or Babel setup needed. Environment variables in .env files are loaded automatically. Bun’s SQLite3 driver is built-in.

// Run TypeScript directly
// bun run server.ts — no compile step needed

import { Database } from 'bun:sqlite';
import { readFileSync, writeFileSync } from 'fs';

const db = new Database('data.sqlite');
db.run('CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)');
db.run('INSERT INTO users (name) VALUES ($name)', { $name: 'Alice' });

const users = db.query('SELECT * FROM users').all();
console.log(users);

// Built-in fetch API (Web API)
const response = await fetch('https://api.example.com/data');
const data = await response.json();

// Bun APIs
console.log(Bun.version);
console.log(Bun.env.NODE_ENV);

const file = Bun.file('data.json');
const json = await file.json();

// No need for dotenv — Bun auto-loads .env
// No need for ts-node, tsx — works natively

Bun’s Package Manager

bun install replaces npm, yarn, and pnpm with dramatically faster speeds. It uses a global module cache and binary-level link resolution. bun install is typically 10-30x faster than npm install for cold caches and nearly instant for hot caches.

Commands: bun add, bun remove, bun update, bun add -d, bun add --global. Bun respects existing package.json and node_modules structures. Compatibility with npm workspaces is excellent. bunx executes packages without installing them.

# Basic commands
bun init                  # Create new package.json
bun install               # Install all dependencies
bun add express           # Install express
bun add -d typescript     # Install dev dependency
bun add react@18          # Specific version
bun remove lodash         # Remove dependency
bun update                # Update all packages

# Speed comparison
time npm install   # ~15-30 seconds typical
time bun install   # ~0.5-2 seconds typical

# Global packages
bun add -g eslint   # Install globally

# Run without install (like npx)
bunx create-react-app my-app
bunx cowsay 'Hello from Bun'

# Workspaces support (package.json)
{
  "workspaces": ["packages/*"],
  "trustedDependencies": ["bcrypt"]
}

Built-in Test Runner

Bun’s test runner mirrors Jest’s API with describe, it, expect, and matchers, but runs tests faster by using the JavaScriptCore engine and parallel execution. Tests are run in isolated worker threads. The watch mode re-runs tests on file changes.

Mocking: bun supports jest.fn(), jest.mock(), and jest.spy() equivalents natively. Snapshot testing works via toMatchSnapshot. Code coverage is available with --coverage.

// math.ts
export function add(a: number, b: number) {
  return a + b;
}

export async function fetchUser(id: number) {
  const res = await fetch(`/api/users/${id}`);
  return res.json();
}

// math.test.ts
import { expect, describe, it, mock, beforeAll, afterAll } from 'bun:test';
import { add, fetchUser } from './math';

describe('add function', () => {
  it('adds positive numbers', () => {
    expect(add(2, 3)).toBe(5);
  });

  it('adds negative numbers', () => {
    expect(add(-1, -2)).toBe(-3);
  });

  it('handles zero', () => {
    expect(add(0, 5)).toBe(5);
  });
});

describe('fetchUser', () => {
  const fetchMock = mock(() =>
    Promise.resolve(new Response(JSON.stringify({ id: 1, name: 'Alice' })))
  );
  global.fetch = fetchMock;

  it('returns user data', async () => {
    const user = await fetchUser(1);
    expect(user.name).toBe('Alice');
    expect(fetchMock).toHaveBeenCalledWith('/api/users/1');
  });
});

// Run: bun test
// Run with coverage: bun test --coverage
// Run in watch mode: bun test --watch

Built-in APIs: Bun.serve, SQLite, and File I/O

Bun.serve() creates HTTP servers with the Web Standard Request/Response API, supporting TLS, WebSockets, and streaming. Bun.file() provides a lazy file reader with auto-detected content type. Bun.write() writes buffers or streams efficiently. Bun.spawn() spawns subprocesses.

Bun.sqlite is a high-performance SQLite3 binding. Bun.password provides bcrypt and argon2 for password hashing. Bun.peek() inspects promise state without awaiting.

import { serve, file, write, spawn } from 'bun';

// HTTP server
serve({
  port: 3000,
  async fetch(request) {
    const url = new URL(request.url);

    if (url.pathname === '/api/time') {
      return Response.json({ time: new Date().toISOString() });
    }

    if (url.pathname === '/stream') {
      const stream = new ReadableStream({
        start(controller) {
          controller.enqueue(new TextEncoder().encode('Hello '));
          controller.enqueue(new TextEncoder().encode('World!'));
          controller.close();
        }
      });
      return new Response(stream);
    }

    // Serve static files
    const filePath = url.pathname === '/' ? './public/index.html' : `.${url.pathname}`;
    const staticFile = Bun.file(filePath);
    return new Response(staticFile);
  },
  tls: { key: Bun.file('key.pem'), cert: Bun.file('cert.pem') },
  websocket: {
    open(ws) { ws.send('Connected!'); },
    message(ws, msg) { ws.send(`Echo: ${msg}`); },
  },
});

// File operations
await Bun.write('output.txt', 'Hello from Bun');
const content = await Bun.file('output.txt').text();

// Subprocess
const proc = spawn(['echo', 'Hello from subprocess'], { stdout: 'pipe' });
const output = await new Response(proc.stdout).text();
console.log(output);

Bun as a Bundler

Bun includes a native bundler that can replace esbuild, Webpack, or Rollup for many use cases. The bun build command compiles TypeScript and JavaScript into optimized bundles. It supports code splitting, minification, source maps, external dependencies, and targeting different platforms.

The bundler handles JSX, TypeScript, CSS imports, JSON imports, and asset imports. Output formats include ESM, CommonJS, and IIFE. Plugin API enables custom loaders and transforms.

# Basic bundling
bun build ./src/index.ts --outdir ./dist
bun build ./src/index.ts --outdir ./dist --minify
bun build ./src/index.ts --outdir ./dist --sourcemap=external

# Target environment
bun build ./src/index.ts --target bun          # Node.js/Bun API
bun build ./src/index.ts --target browser      # Browser globals
bun build ./src/index.ts --target node          # Node.js require

# Code splitting
bun build ./src/index.ts --outdir ./dist --splitting

# Format
bun build ./src/index.ts --format esm           # ES modules
bun build ./src/index.ts --format cjs           # CommonJS
bun build ./src/index.ts --format iife          # IIFE (browser)

# package.json build script
{
  "scripts": {
    "build": "bun build ./src/index.ts --outdir ./dist --minify --target browser",
    "dev": "bun --watch run ./src/index.ts"
  }
}

Migrating from Node.js to Bun

Bun aims for Node.js compatibility — most Node.js packages work without changes. Bun implements the Node.js API as native Zig code. Common issues: native modules may need recompilation for Bun. Bun uses package.json scripts and supports lifecycle hooks.

The bunfig.toml file configures Bun-specific behavior. It supports private registries, scoped packages, install overrides, and build configuration. Bun’s process.nextTick, setImmediate, and Buffer are fully compatible.

# package.json — no changes needed usually
{
  "name": "my-app",
  "scripts": {
    "start": "bun run src/index.ts",
    "test": "bun test",
    "build": "bun build ./src/index.ts --outdir ./dist"
  },
  "dependencies": {
    "express": "^4.18.0"
  }
}

# bunfig.toml
[install]
registry = "https://registry.npmjs.org"

[test]
preload = "./test/setup.ts"

# Run existing Node.js scripts
# bun run start
# bun run test

# Potential issues:
# - bcrypt, sharp may need bun-native versions
# - Use `bun add -g bun-bcrypt` for native replacements

Frequently Asked Questions

Is Bun a drop-in replacement for Node.js?

Most Node.js packages work without changes. Bun implements the Node.js API comprehensively. Some native modules and edge cases may differ. Check Bun’s compatibility table for specific APIs.

Can Bun run existing Node.js projects?

Yes. Run bun install to install dependencies, then bun run src/index.js to start your Node.js project. TypeScript projects need no configuration.

Is Bun production-ready?

Bun 1.0 was released in September 2023 and is used in production by many companies. The ecosystem is maturing rapidly. For production use, test your specific dependencies.

How does Bun’s performance compare to Node.js?

Bun starts 4x faster, installs packages 10-30x faster, and runs many workloads faster due to JavaScriptCore and native Zig implementations.

Originally published on Ayodhyyya. Last updated June 1, 2026.