Usamos cookies para mejorar tu experiencia en el sitio
CodeWorlds
Volver a colecciones
Guide29 min read

Bun

Bun is an ultrafast JavaScript runtime, bundler, and package manager in one. Alternative to Node.js, npm, and webpack.

Bun - Ultraszybki JavaScript All-in-One Toolkit

Czym jest Bun?

Bun to all-in-one toolkit dla JavaScript i TypeScript. Łączy w sobie runtime (jak Node.js), bundler (jak webpack/esbuild), package manager (jak npm/yarn/pnpm) i test runner - wszystko w jednym narzędziu. Napisany w Zig i oparty na JavaScriptCore (silnik Safari), Bun jest znacząco szybszy od tradycyjnych narzędzi.

Dlaczego Bun?

Problemy z tradycyjnym stackiem

  • Node.js: Wolny cold start, brak natywnego TypeScript
  • npm/yarn/pnpm: Wolna instalacja zależności
  • webpack/vite: Dodatkowa konfiguracja, osobne narzędzia
  • Jest/Vitest: Kolejne zależności do zarządzania

Zalety Bun

  1. Ekstremalnie szybki - 4x szybszy start niż Node.js
  2. All-in-one - Runtime + bundler + package manager + test runner
  3. Natywny TypeScript - Bez transpilacji
  4. Node.js compatible - Większość pakietów npm działa
  5. Web APIs - Fetch, WebSocket, FormData out of the box
  6. SQLite wbudowany - Natywna obsługa bazy danych

Instalacja

macOS / Linux

Code
Bash
curl -fsSL https://bun.sh/install | bash

Windows (WSL)

Code
Bash
# W WSL
curl -fsSL https://bun.sh/install | bash

Homebrew (macOS)

Code
Bash
brew install oven-sh/bun/bun

Weryfikacja instalacji

Code
Bash
bun --version
# 1.x.x

Package Manager

Bun jako package manager jest 10-30x szybszy niż npm.

Podstawowe komendy

Code
Bash
# Instalacja zależności (jak npm install)
bun install

# Dodawanie pakietów
bun add react react-dom
bun add -d typescript @types/react  # dev dependency
bun add -g vercel                   # global

# Usuwanie pakietów
bun remove lodash

# Aktualizacja pakietów
bun update
bun update react

# Lista zainstalowanych
bun pm ls

# Cache
bun pm cache    # Pokaż cache
bun pm cache rm # Wyczyść cache

Lockfile

Bun używa własnego formatu lockfile: bun.lockb (binarny, szybszy do parsowania).

Code
Bash
# Konwersja do yarn.lock (dla kompatybilności)
bun install --yarn

Workspaces (Monorepo)

package.json
JSON
// package.json
{
  "name": "my-monorepo",
  "workspaces": [
    "packages/*",
    "apps/*"
  ]
}
Code
Bash
# Instalacja w monorepo
bun install

# Dodawanie do konkretnego workspace
bun add lodash --filter my-package

# Uruchamianie skryptu w workspace
bun run --filter my-package build

Runtime

Uruchamianie skryptów

Code
Bash
# Uruchom plik TypeScript (bez transpilacji!)
bun run index.ts

# Uruchom skrypt z package.json
bun run dev
bun dev          # Skrót

# Hot reload (watch mode)
bun --watch run index.ts

# REPL
bun repl

TypeScript bez konfiguracji

TSindex.ts
TypeScript
// index.ts - działa natychmiast!
interface User {
  id: number
  name: string
  email: string
}

const users: User[] = [
  { id: 1, name: 'John', email: 'john@example.com' },
]

console.log(users)
Code
Bash
bun run index.ts
# [{ id: 1, name: 'John', email: 'john@example.com' }]

JSX/TSX bez konfiguracji

TSapp.tsx
TypeScript
// app.tsx
const App = () => (
  <div>
    <h1>Hello Bun!</h1>
    <p>JSX works out of the box</p>
  </div>
)

// Renderowanie do stringa
import { renderToString } from 'react-dom/server'
console.log(renderToString(<App />))

HTTP Server

Bun ma wbudowany, ultraszybki HTTP server:

TSserver.ts
TypeScript
// server.ts
Bun.serve({
  port: 3000,

  fetch(request) {
    const url = new URL(request.url)

    // Routing
    if (url.pathname === '/') {
      return new Response('Hello Bun!')
    }

    if (url.pathname === '/json') {
      return Response.json({
        message: 'Hello JSON',
        timestamp: Date.now(),
      })
    }

    if (url.pathname === '/html') {
      return new Response('<h1>Hello HTML</h1>', {
        headers: { 'Content-Type': 'text/html' },
      })
    }

    // 404
    return new Response('Not Found', { status: 404 })
  },

  // Opcjonalne: obsługa błędów
  error(error) {
    return new Response(`Error: ${error.message}`, {
      status: 500,
    })
  },
})

console.log('Server running at http://localhost:3000')

Zaawansowany server

Code
TypeScript
Bun.serve({
  port: 3000,
  hostname: '0.0.0.0', // Dostępny z zewnątrz

  async fetch(request) {
    const url = new URL(request.url)
    const method = request.method

    // POST z body
    if (method === 'POST' && url.pathname === '/api/users') {
      const body = await request.json()
      return Response.json({ created: true, data: body }, { status: 201 })
    }

    // Query parameters
    if (url.pathname === '/search') {
      const query = url.searchParams.get('q')
      return Response.json({ query, results: [] })
    }

    // Headers
    if (url.pathname === '/auth') {
      const authHeader = request.headers.get('Authorization')
      if (!authHeader) {
        return new Response('Unauthorized', { status: 401 })
      }
      return Response.json({ authenticated: true })
    }

    // Static files
    if (url.pathname.startsWith('/static/')) {
      const filePath = `.${url.pathname}`
      const file = Bun.file(filePath)

      if (await file.exists()) {
        return new Response(file)
      }
      return new Response('File not found', { status: 404 })
    }

    // Streaming response
    if (url.pathname === '/stream') {
      const stream = new ReadableStream({
        async start(controller) {
          for (let i = 0; i < 5; i++) {
            controller.enqueue(`data: ${i}\n\n`)
            await Bun.sleep(1000)
          }
          controller.close()
        },
      })

      return new Response(stream, {
        headers: {
          'Content-Type': 'text/event-stream',
          'Cache-Control': 'no-cache',
        },
      })
    }

    return new Response('Not Found', { status: 404 })
  },
})

HTTPS Server

Code
TypeScript
Bun.serve({
  port: 443,

  tls: {
    cert: Bun.file('./cert.pem'),
    key: Bun.file('./key.pem'),
  },

  fetch(request) {
    return new Response('Secure!')
  },
})

File I/O

Bun oferuje szybkie API do operacji na plikach:

Bun.file()

Code
TypeScript
// Czytanie pliku
const file = Bun.file('./data.json')

// Metadane
console.log(file.name)        // 'data.json'
console.log(file.size)        // rozmiar w bajtach
console.log(file.type)        // 'application/json'

// Różne formaty odczytu
const text = await file.text()       // string
const json = await file.json()       // parsed JSON
const buffer = await file.arrayBuffer() // ArrayBuffer
const stream = file.stream()         // ReadableStream

// Sprawdzenie istnienia
if (await file.exists()) {
  console.log('File exists')
}

Bun.write()

Code
TypeScript
// Zapisywanie tekstu
await Bun.write('./output.txt', 'Hello World')

// Zapisywanie JSON
await Bun.write('./data.json', JSON.stringify({ name: 'John' }))

// Zapisywanie z Bun.file
const source = Bun.file('./source.txt')
await Bun.write('./copy.txt', source)

// Zapisywanie Response
const response = await fetch('https://example.com/image.png')
await Bun.write('./image.png', response)

// Zapisywanie Blob
const blob = new Blob(['Hello'], { type: 'text/plain' })
await Bun.write('./blob.txt', blob)

Operacje na katalogach

Code
TypeScript
import { readdir, mkdir, rm } from 'fs/promises'

// Tworzenie katalogu
await mkdir('./new-folder', { recursive: true })

// Listowanie plików
const files = await readdir('./src')
console.log(files)

// Usuwanie
await rm('./temp', { recursive: true, force: true })

Glob

Code
TypeScript
// Wbudowany glob
const glob = new Bun.Glob('**/*.ts')

// Synchronicznie
for (const file of glob.scanSync('./src')) {
  console.log(file)
}

// Asynchronicznie
for await (const file of glob.scan('./src')) {
  console.log(file)
}

// Match
const pattern = new Bun.Glob('*.{ts,tsx}')
pattern.match('app.ts')    // true
pattern.match('app.js')    // false

SQLite

Bun ma wbudowaną, natywną obsługę SQLite:

Code
TypeScript
import { Database } from 'bun:sqlite'

// Tworzenie/otwieranie bazy
const db = new Database('myapp.db')
// lub w pamięci:
// const db = new Database(':memory:')

// Tworzenie tabeli
db.run(`
  CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    email TEXT UNIQUE NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
  )
`)

// Insert
const insert = db.prepare(`
  INSERT INTO users (name, email) VALUES ($name, $email)
`)

insert.run({ $name: 'John', $email: 'john@example.com' })
insert.run({ $name: 'Jane', $email: 'jane@example.com' })

// Select wszystko
const selectAll = db.prepare('SELECT * FROM users')
const users = selectAll.all()
console.log(users)
// [{ id: 1, name: 'John', ... }, { id: 2, name: 'Jane', ... }]

// Select jeden
const selectOne = db.prepare('SELECT * FROM users WHERE id = $id')
const user = selectOne.get({ $id: 1 })
console.log(user)
// { id: 1, name: 'John', email: 'john@example.com', ... }

// Update
const update = db.prepare('UPDATE users SET name = $name WHERE id = $id')
update.run({ $id: 1, $name: 'John Doe' })

// Delete
const deleteUser = db.prepare('DELETE FROM users WHERE id = $id')
deleteUser.run({ $id: 2 })

// Transakcje
const insertMany = db.transaction((users) => {
  for (const user of users) {
    insert.run(user)
  }
})

insertMany([
  { $name: 'Alice', $email: 'alice@example.com' },
  { $name: 'Bob', $email: 'bob@example.com' },
])

// Zamknięcie
db.close()

Query builder pattern

Code
TypeScript
import { Database } from 'bun:sqlite'

class UserRepository {
  private db: Database

  constructor(dbPath: string) {
    this.db = new Database(dbPath)
    this.init()
  }

  private init() {
    this.db.run(`
      CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        email TEXT UNIQUE NOT NULL
      )
    `)
  }

  create(name: string, email: string) {
    const stmt = this.db.prepare(
      'INSERT INTO users (name, email) VALUES (?, ?) RETURNING *'
    )
    return stmt.get(name, email)
  }

  findById(id: number) {
    const stmt = this.db.prepare('SELECT * FROM users WHERE id = ?')
    return stmt.get(id)
  }

  findByEmail(email: string) {
    const stmt = this.db.prepare('SELECT * FROM users WHERE email = ?')
    return stmt.get(email)
  }

  findAll(limit = 100, offset = 0) {
    const stmt = this.db.prepare('SELECT * FROM users LIMIT ? OFFSET ?')
    return stmt.all(limit, offset)
  }

  update(id: number, data: { name?: string; email?: string }) {
    const fields = []
    const values: any[] = []

    if (data.name) {
      fields.push('name = ?')
      values.push(data.name)
    }
    if (data.email) {
      fields.push('email = ?')
      values.push(data.email)
    }

    values.push(id)

    const stmt = this.db.prepare(
      `UPDATE users SET ${fields.join(', ')} WHERE id = ? RETURNING *`
    )
    return stmt.get(...values)
  }

  delete(id: number) {
    const stmt = this.db.prepare('DELETE FROM users WHERE id = ?')
    return stmt.run(id)
  }
}

const users = new UserRepository('app.db')
const newUser = users.create('John', 'john@example.com')
console.log(newUser)

Bundler

Bun ma wbudowany bundler oparty na esbuild:

TSbuild.ts
TypeScript
// build.ts
await Bun.build({
  entrypoints: ['./src/index.ts'],
  outdir: './dist',
  minify: true,
  sourcemap: 'external',
  target: 'browser', // lub 'bun', 'node'

  // Splitting (code splitting)
  splitting: true,

  // External packages (nie bundluj)
  external: ['react', 'react-dom'],

  // Define (zamiana stałych)
  define: {
    'process.env.NODE_ENV': '"production"',
  },

  // Naming
  naming: {
    entry: '[dir]/[name].[hash].[ext]',
    chunk: '[name]-[hash].[ext]',
    asset: '[name]-[hash].[ext]',
  },
})

CLI bundling

Code
Bash
# Bundluj do pojedynczego pliku
bun build ./src/index.ts --outdir ./dist

# Z minifikacją
bun build ./src/index.ts --outdir ./dist --minify

# Watch mode
bun build ./src/index.ts --outdir ./dist --watch

# Compile to executable
bun build ./src/cli.ts --compile --outfile mycli

Executable (kompilacja do binary)

Code
Bash
# Skompiluj do pojedynczego pliku wykonywalnego
bun build --compile ./src/cli.ts --outfile ./dist/mycli

# Dla różnych platform
bun build --compile --target=bun-linux-x64 ./src/cli.ts
bun build --compile --target=bun-darwin-arm64 ./src/cli.ts
bun build --compile --target=bun-windows-x64 ./src/cli.ts

Test Runner

Bun ma wbudowany test runner kompatybilny z Jest:

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

export function multiply(a: number, b: number): number {
  return a * b
}

export async function fetchData(url: string) {
  const response = await fetch(url)
  return response.json()
}
TSmath.test.ts
TypeScript
// math.test.ts
import { describe, it, expect, beforeAll, afterAll, mock } from 'bun:test'
import { add, multiply, fetchData } from './math'

describe('Math functions', () => {
  it('adds two numbers', () => {
    expect(add(2, 3)).toBe(5)
    expect(add(-1, 1)).toBe(0)
  })

  it('multiplies two numbers', () => {
    expect(multiply(2, 3)).toBe(6)
    expect(multiply(0, 100)).toBe(0)
  })
})

describe('Matchers', () => {
  it('demonstrates various matchers', () => {
    // Equality
    expect(1 + 1).toBe(2)
    expect({ a: 1 }).toEqual({ a: 1 })

    // Truthiness
    expect(true).toBeTruthy()
    expect(false).toBeFalsy()
    expect(null).toBeNull()
    expect(undefined).toBeUndefined()
    expect('hello').toBeDefined()

    // Numbers
    expect(10).toBeGreaterThan(5)
    expect(5).toBeLessThanOrEqual(5)
    expect(0.1 + 0.2).toBeCloseTo(0.3)

    // Strings
    expect('hello world').toContain('world')
    expect('hello').toMatch(/ell/)

    // Arrays
    expect([1, 2, 3]).toContain(2)
    expect([1, 2, 3]).toHaveLength(3)

    // Errors
    expect(() => { throw new Error('oops') }).toThrow('oops')
  })
})

describe('Async tests', () => {
  it('handles async/await', async () => {
    const result = await Promise.resolve(42)
    expect(result).toBe(42)
  })

  it('handles promises', () => {
    return Promise.resolve(42).then(result => {
      expect(result).toBe(42)
    })
  })
})

describe('Mocking', () => {
  it('mocks functions', () => {
    const mockFn = mock(() => 42)

    expect(mockFn()).toBe(42)
    expect(mockFn).toHaveBeenCalled()
    expect(mockFn).toHaveBeenCalledTimes(1)
  })

  it('mocks fetch', async () => {
    const originalFetch = globalThis.fetch

    globalThis.fetch = mock(() =>
      Promise.resolve(new Response(JSON.stringify({ data: 'mocked' })))
    )

    const result = await fetchData('https://api.example.com')
    expect(result).toEqual({ data: 'mocked' })

    globalThis.fetch = originalFetch
  })
})

describe('Lifecycle hooks', () => {
  let db: any

  beforeAll(() => {
    db = { connected: true }
  })

  afterAll(() => {
    db = null
  })

  it('uses setup from beforeAll', () => {
    expect(db.connected).toBe(true)
  })
})

Uruchamianie testów

Code
Bash
# Uruchom wszystkie testy
bun test

# Konkretny plik
bun test math.test.ts

# Pattern matching
bun test --filter "Math"

# Watch mode
bun test --watch

# Coverage
bun test --coverage

# Timeout
bun test --timeout 5000

Environment Variables

Code
TypeScript
// .env
DATABASE_URL=postgres://localhost/mydb
API_KEY=secret123
DEBUG=true

// Użycie - automatyczne ładowanie
console.log(Bun.env.DATABASE_URL)  // 'postgres://localhost/mydb'
console.log(Bun.env.API_KEY)       // 'secret123'
console.log(process.env.DEBUG)     // 'true' (kompatybilność z Node.js)

// Sprawdzanie
if (Bun.env.NODE_ENV === 'production') {
  console.log('Production mode')
}

// Różne pliki .env
// .env.local, .env.development, .env.production
// są automatycznie ładowane

WebSocket

Code
TypeScript
// WebSocket server
Bun.serve({
  port: 3000,

  fetch(request, server) {
    // Upgrade do WebSocket
    if (server.upgrade(request)) {
      return // Upgrade succeeded
    }

    return new Response('Not a WebSocket request', { status: 400 })
  },

  websocket: {
    open(ws) {
      console.log('Client connected')
      ws.send('Welcome!')
    },

    message(ws, message) {
      console.log('Received:', message)
      // Echo back
      ws.send(`You said: ${message}`)

      // Broadcast to all
      ws.publish('chat', message)
    },

    close(ws) {
      console.log('Client disconnected')
    },
  },
})

WebSocket z rooms

Code
TypeScript
Bun.serve({
  port: 3000,

  fetch(request, server) {
    const url = new URL(request.url)
    const room = url.searchParams.get('room') || 'default'

    if (server.upgrade(request, { data: { room } })) {
      return
    }

    return new Response('Upgrade failed', { status: 400 })
  },

  websocket: {
    open(ws) {
      const room = ws.data.room
      ws.subscribe(room)
      ws.send(`Joined room: ${room}`)
    },

    message(ws, message) {
      const room = ws.data.room
      // Broadcast to room
      ws.publish(room, `[${room}] ${message}`)
    },

    close(ws) {
      const room = ws.data.room
      ws.unsubscribe(room)
    },
  },
})

Bun APIs

Bun.sleep()

Code
TypeScript
// Async sleep
await Bun.sleep(1000) // 1 sekunda
await Bun.sleep(500)  // 500ms

Bun.hash()

Code
TypeScript
// Szybkie hashowanie
const hash = Bun.hash('hello world')
console.log(hash) // number

// Różne algorytmy
Bun.hash.wyhash('data')
Bun.hash.adler32('data')
Bun.hash.crc32('data')
Bun.hash.cityHash32('data')
Bun.hash.cityHash64('data')
Bun.hash.murmur32v3('data')
Bun.hash.murmur64v2('data')

Bun.password

Code
TypeScript
// Hashowanie haseł (bcrypt-like)
const hash = await Bun.password.hash('mypassword')
console.log(hash) // $argon2id$...

// Weryfikacja
const isValid = await Bun.password.verify('mypassword', hash)
console.log(isValid) // true

// Z opcjami
const customHash = await Bun.password.hash('mypassword', {
  algorithm: 'argon2id', // lub 'argon2d', 'argon2i', 'bcrypt'
  memoryCost: 65536,
  timeCost: 3,
})

Bun.spawn()

Code
TypeScript
// Uruchamianie procesów
const proc = Bun.spawn(['ls', '-la'])

// Czekanie na zakończenie
const output = await new Response(proc.stdout).text()
console.log(output)

// Z stdin
const proc2 = Bun.spawn(['cat'], {
  stdin: 'pipe',
})
proc2.stdin.write('Hello from Bun!')
proc2.stdin.end()

// Synchronicznie
const result = Bun.spawnSync(['echo', 'Hello'])
console.log(result.stdout.toString())

Bun.Transpiler

Code
TypeScript
const transpiler = new Bun.Transpiler({
  loader: 'tsx',
})

const code = `
  const App: React.FC = () => <div>Hello</div>
  export default App
`

const result = transpiler.transformSync(code)
console.log(result)
// Transpilowany JavaScript

Node.js Compatibility

Bun jest w dużej mierze kompatybilny z Node.js:

Code
TypeScript
// Większość Node.js APIs działa
import fs from 'fs'
import path from 'path'
import crypto from 'crypto'
import { EventEmitter } from 'events'

// fs
const content = fs.readFileSync('./file.txt', 'utf-8')
fs.writeFileSync('./output.txt', 'Hello')

// path
const fullPath = path.join(__dirname, 'file.txt')
const ext = path.extname('file.txt') // '.txt'

// crypto
const hash = crypto.createHash('sha256').update('data').digest('hex')

// events
const emitter = new EventEmitter()
emitter.on('event', (data) => console.log(data))
emitter.emit('event', 'Hello!')

// process
console.log(process.cwd())
console.log(process.env.PATH)

Express compatibility

Code
TypeScript
// Express działa z Bun
import express from 'express'

const app = express()

app.use(express.json())

app.get('/', (req, res) => {
  res.json({ message: 'Hello from Express on Bun!' })
})

app.listen(3000, () => {
  console.log('Server running on port 3000')
})

Integracja z frameworkami

Next.js

JSNext.js
JavaScript
# Next.js z Bun
bunx create-next-app my-app
cd my-app
bun dev

Hono

Code
TypeScript
import { Hono } from 'hono'
import { serveStatic } from 'hono/bun'

const app = new Hono()

app.use('/static/*', serveStatic({ root: './public' }))
app.get('/', (c) => c.text('Hello Hono with Bun!'))

export default {
  port: 3000,
  fetch: app.fetch,
}

Elysia (native Bun framework)

Code
TypeScript
import { Elysia } from 'elysia'

const app = new Elysia()
  .get('/', () => 'Hello Elysia!')
  .get('/user/:id', ({ params: { id } }) => ({ userId: id }))
  .post('/users', ({ body }) => ({ created: body }))
  .listen(3000)

console.log(`Server running at ${app.server?.hostname}:${app.server?.port}`)

Scripts i aliases

package.json
JSON
// package.json
{
  "scripts": {
    "dev": "bun --watch run src/index.ts",
    "build": "bun build src/index.ts --outdir dist --minify",
    "test": "bun test",
    "start": "bun run dist/index.js"
  }
}
Code
Bash
# Uruchamianie
bun run dev    # lub po prostu: bun dev
bun run build
bun run test
bun run start

Porównanie wydajności

OperacjaBunNode.jsRóżnica
Startup time~25ms~100ms4x szybciej
npm install~5s~50s10x szybciej
TypeScriptNativets-node3x szybciej
HTTP server~100k req/s~30k req/s3x szybciej
SQLiteNativebetter-sqlite32x szybciej
File I/OOptimizedStandard2-3x szybciej

Best Practices

1. Używaj natywnych APIs Bun

Code
TypeScript
// Zamiast
import fs from 'fs'
const content = fs.readFileSync('file.txt', 'utf-8')

// Preferuj
const file = Bun.file('file.txt')
const content = await file.text()

2. Wykorzystaj wbudowane SQLite

Code
TypeScript
// Zamiast zewnętrznych pakietów
import { Database } from 'bun:sqlite'

const db = new Database('app.db')

3. Typuj środowisko

TStypes/bun.d.ts
TypeScript
// types/bun.d.ts
declare module 'bun' {
  interface Env {
    DATABASE_URL: string
    API_KEY: string
    NODE_ENV: 'development' | 'production'
  }
}

4. Używaj bunx zamiast npx

Code
Bash
# npx equivalent
bunx create-next-app
bunx prisma migrate dev
bunx eslint .

Podsumowanie

Bun to rewolucyjne narzędzie, które łączy wszystkie elementy JavaScript toolchaina w jedno ultraszybkie rozwiązanie. Główne zalety:

  • Wydajność - 4-30x szybszy od tradycyjnych narzędzi
  • All-in-one - Runtime + bundler + package manager + test runner
  • TypeScript native - Bez konfiguracji
  • Node.js compatible - Większość pakietów npm działa
  • Wbudowane APIs - SQLite, WebSocket, password hashing
  • DX - Prostota użycia, mniej konfiguracji

Jeśli szukasz szybszej alternatywy dla Node.js lub chcesz uprościć swój toolchain, Bun jest doskonałym wyborem.


Bun - Ultrafast JavaScript All-in-One Toolkit

What is Bun?

Bun is an all-in-one toolkit for JavaScript and TypeScript. It combines a runtime (like Node.js), a bundler (like webpack/esbuild), a package manager (like npm/yarn/pnpm), and a test runner - all in a single tool. Written in Zig and powered by JavaScriptCore (the Safari engine), Bun is significantly faster than traditional tools.

Why Bun?

Problems with the traditional stack

  • Node.js: Slow cold start, no native TypeScript
  • npm/yarn/pnpm: Slow dependency installation
  • webpack/vite: Additional configuration, separate tools
  • Jest/Vitest: More dependencies to manage

Bun's advantages

  1. Extremely fast - 4x faster startup than Node.js
  2. All-in-one - Runtime + bundler + package manager + test runner
  3. Native TypeScript - No transpilation needed
  4. Node.js compatible - Most npm packages work
  5. Web APIs - Fetch, WebSocket, FormData out of the box
  6. Built-in SQLite - Native database support

Installation

macOS / Linux

Code
Bash
curl -fsSL https://bun.sh/install | bash

Windows (WSL)

Code
Bash
# In WSL
curl -fsSL https://bun.sh/install | bash

Homebrew (macOS)

Code
Bash
brew install oven-sh/bun/bun

Verifying installation

Code
Bash
bun --version
# 1.x.x

Package Manager

Bun as a package manager is 10-30x faster than npm.

Basic commands

Code
Bash
# Install dependencies (like npm install)
bun install

# Adding packages
bun add react react-dom
bun add -d typescript @types/react  # dev dependency
bun add -g vercel                   # global

# Removing packages
bun remove lodash

# Updating packages
bun update
bun update react

# List installed packages
bun pm ls

# Cache
bun pm cache    # Show cache
bun pm cache rm # Clear cache

Lockfile

Bun uses its own lockfile format: bun.lockb (binary, faster to parse).

Code
Bash
# Convert to yarn.lock (for compatibility)
bun install --yarn

Workspaces (Monorepo)

package.json
JSON
// package.json
{
  "name": "my-monorepo",
  "workspaces": [
    "packages/*",
    "apps/*"
  ]
}
Code
Bash
# Install in monorepo
bun install

# Add to a specific workspace
bun add lodash --filter my-package

# Run a script in a workspace
bun run --filter my-package build

Runtime

Running scripts

Code
Bash
# Run a TypeScript file (no transpilation needed!)
bun run index.ts

# Run a script from package.json
bun run dev
bun dev          # Shorthand

# Hot reload (watch mode)
bun --watch run index.ts

# REPL
bun repl

TypeScript without configuration

TSindex.ts
TypeScript
// index.ts - works immediately!
interface User {
  id: number
  name: string
  email: string
}

const users: User[] = [
  { id: 1, name: 'John', email: 'john@example.com' },
]

console.log(users)
Code
Bash
bun run index.ts
# [{ id: 1, name: 'John', email: 'john@example.com' }]

JSX/TSX without configuration

TSapp.tsx
TypeScript
// app.tsx
const App = () => (
  <div>
    <h1>Hello Bun!</h1>
    <p>JSX works out of the box</p>
  </div>
)

// Rendering to string
import { renderToString } from 'react-dom/server'
console.log(renderToString(<App />))

HTTP Server

Bun has a built-in, ultrafast HTTP server:

TSserver.ts
TypeScript
// server.ts
Bun.serve({
  port: 3000,

  fetch(request) {
    const url = new URL(request.url)

    // Routing
    if (url.pathname === '/') {
      return new Response('Hello Bun!')
    }

    if (url.pathname === '/json') {
      return Response.json({
        message: 'Hello JSON',
        timestamp: Date.now(),
      })
    }

    if (url.pathname === '/html') {
      return new Response('<h1>Hello HTML</h1>', {
        headers: { 'Content-Type': 'text/html' },
      })
    }

    // 404
    return new Response('Not Found', { status: 404 })
  },

  // Optional: error handling
  error(error) {
    return new Response(`Error: ${error.message}`, {
      status: 500,
    })
  },
})

console.log('Server running at http://localhost:3000')

Advanced server

Code
TypeScript
Bun.serve({
  port: 3000,
  hostname: '0.0.0.0', // Accessible from outside

  async fetch(request) {
    const url = new URL(request.url)
    const method = request.method

    // POST with body
    if (method === 'POST' && url.pathname === '/api/users') {
      const body = await request.json()
      return Response.json({ created: true, data: body }, { status: 201 })
    }

    // Query parameters
    if (url.pathname === '/search') {
      const query = url.searchParams.get('q')
      return Response.json({ query, results: [] })
    }

    // Headers
    if (url.pathname === '/auth') {
      const authHeader = request.headers.get('Authorization')
      if (!authHeader) {
        return new Response('Unauthorized', { status: 401 })
      }
      return Response.json({ authenticated: true })
    }

    // Static files
    if (url.pathname.startsWith('/static/')) {
      const filePath = `.${url.pathname}`
      const file = Bun.file(filePath)

      if (await file.exists()) {
        return new Response(file)
      }
      return new Response('File not found', { status: 404 })
    }

    // Streaming response
    if (url.pathname === '/stream') {
      const stream = new ReadableStream({
        async start(controller) {
          for (let i = 0; i < 5; i++) {
            controller.enqueue(`data: ${i}\n\n`)
            await Bun.sleep(1000)
          }
          controller.close()
        },
      })

      return new Response(stream, {
        headers: {
          'Content-Type': 'text/event-stream',
          'Cache-Control': 'no-cache',
        },
      })
    }

    return new Response('Not Found', { status: 404 })
  },
})

HTTPS Server

Code
TypeScript
Bun.serve({
  port: 443,

  tls: {
    cert: Bun.file('./cert.pem'),
    key: Bun.file('./key.pem'),
  },

  fetch(request) {
    return new Response('Secure!')
  },
})

File I/O

Bun offers a fast API for file operations:

Bun.file()

Code
TypeScript
// Reading a file
const file = Bun.file('./data.json')

// Metadata
console.log(file.name)        // 'data.json'
console.log(file.size)        // size in bytes
console.log(file.type)        // 'application/json'

// Different reading formats
const text = await file.text()       // string
const json = await file.json()       // parsed JSON
const buffer = await file.arrayBuffer() // ArrayBuffer
const stream = file.stream()         // ReadableStream

// Checking existence
if (await file.exists()) {
  console.log('File exists')
}

Bun.write()

Code
TypeScript
// Writing text
await Bun.write('./output.txt', 'Hello World')

// Writing JSON
await Bun.write('./data.json', JSON.stringify({ name: 'John' }))

// Writing with Bun.file
const source = Bun.file('./source.txt')
await Bun.write('./copy.txt', source)

// Writing a Response
const response = await fetch('https://example.com/image.png')
await Bun.write('./image.png', response)

// Writing a Blob
const blob = new Blob(['Hello'], { type: 'text/plain' })
await Bun.write('./blob.txt', blob)

Directory operations

Code
TypeScript
import { readdir, mkdir, rm } from 'fs/promises'

// Creating a directory
await mkdir('./new-folder', { recursive: true })

// Listing files
const files = await readdir('./src')
console.log(files)

// Deleting
await rm('./temp', { recursive: true, force: true })

Glob

Code
TypeScript
// Built-in glob
const glob = new Bun.Glob('**/*.ts')

// Synchronously
for (const file of glob.scanSync('./src')) {
  console.log(file)
}

// Asynchronously
for await (const file of glob.scan('./src')) {
  console.log(file)
}

// Match
const pattern = new Bun.Glob('*.{ts,tsx}')
pattern.match('app.ts')    // true
pattern.match('app.js')    // false

SQLite

Bun has built-in, native SQLite support:

Code
TypeScript
import { Database } from 'bun:sqlite'

// Creating/opening a database
const db = new Database('myapp.db')
// or in-memory:
// const db = new Database(':memory:')

// Creating a 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 DATETIME DEFAULT CURRENT_TIMESTAMP
  )
`)

// Insert
const insert = db.prepare(`
  INSERT INTO users (name, email) VALUES ($name, $email)
`)

insert.run({ $name: 'John', $email: 'john@example.com' })
insert.run({ $name: 'Jane', $email: 'jane@example.com' })

// Select all
const selectAll = db.prepare('SELECT * FROM users')
const users = selectAll.all()
console.log(users)
// [{ id: 1, name: 'John', ... }, { id: 2, name: 'Jane', ... }]

// Select one
const selectOne = db.prepare('SELECT * FROM users WHERE id = $id')
const user = selectOne.get({ $id: 1 })
console.log(user)
// { id: 1, name: 'John', email: 'john@example.com', ... }

// Update
const update = db.prepare('UPDATE users SET name = $name WHERE id = $id')
update.run({ $id: 1, $name: 'John Doe' })

// Delete
const deleteUser = db.prepare('DELETE FROM users WHERE id = $id')
deleteUser.run({ $id: 2 })

// Transactions
const insertMany = db.transaction((users) => {
  for (const user of users) {
    insert.run(user)
  }
})

insertMany([
  { $name: 'Alice', $email: 'alice@example.com' },
  { $name: 'Bob', $email: 'bob@example.com' },
])

// Closing
db.close()

Query builder pattern

Code
TypeScript
import { Database } from 'bun:sqlite'

class UserRepository {
  private db: Database

  constructor(dbPath: string) {
    this.db = new Database(dbPath)
    this.init()
  }

  private init() {
    this.db.run(`
      CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        email TEXT UNIQUE NOT NULL
      )
    `)
  }

  create(name: string, email: string) {
    const stmt = this.db.prepare(
      'INSERT INTO users (name, email) VALUES (?, ?) RETURNING *'
    )
    return stmt.get(name, email)
  }

  findById(id: number) {
    const stmt = this.db.prepare('SELECT * FROM users WHERE id = ?')
    return stmt.get(id)
  }

  findByEmail(email: string) {
    const stmt = this.db.prepare('SELECT * FROM users WHERE email = ?')
    return stmt.get(email)
  }

  findAll(limit = 100, offset = 0) {
    const stmt = this.db.prepare('SELECT * FROM users LIMIT ? OFFSET ?')
    return stmt.all(limit, offset)
  }

  update(id: number, data: { name?: string; email?: string }) {
    const fields = []
    const values: any[] = []

    if (data.name) {
      fields.push('name = ?')
      values.push(data.name)
    }
    if (data.email) {
      fields.push('email = ?')
      values.push(data.email)
    }

    values.push(id)

    const stmt = this.db.prepare(
      `UPDATE users SET ${fields.join(', ')} WHERE id = ? RETURNING *`
    )
    return stmt.get(...values)
  }

  delete(id: number) {
    const stmt = this.db.prepare('DELETE FROM users WHERE id = ?')
    return stmt.run(id)
  }
}

const users = new UserRepository('app.db')
const newUser = users.create('John', 'john@example.com')
console.log(newUser)

Bundler

Bun has a built-in bundler based on esbuild:

TSbuild.ts
TypeScript
// build.ts
await Bun.build({
  entrypoints: ['./src/index.ts'],
  outdir: './dist',
  minify: true,
  sourcemap: 'external',
  target: 'browser', // or 'bun', 'node'

  // Splitting (code splitting)
  splitting: true,

  // External packages (don't bundle)
  external: ['react', 'react-dom'],

  // Define (constant replacement)
  define: {
    'process.env.NODE_ENV': '"production"',
  },

  // Naming
  naming: {
    entry: '[dir]/[name].[hash].[ext]',
    chunk: '[name]-[hash].[ext]',
    asset: '[name]-[hash].[ext]',
  },
})

CLI bundling

Code
Bash
# Bundle to a single file
bun build ./src/index.ts --outdir ./dist

# With minification
bun build ./src/index.ts --outdir ./dist --minify

# Watch mode
bun build ./src/index.ts --outdir ./dist --watch

# Compile to executable
bun build ./src/cli.ts --compile --outfile mycli

Executable (compiling to binary)

Code
Bash
# Compile to a single executable file
bun build --compile ./src/cli.ts --outfile ./dist/mycli

# For different platforms
bun build --compile --target=bun-linux-x64 ./src/cli.ts
bun build --compile --target=bun-darwin-arm64 ./src/cli.ts
bun build --compile --target=bun-windows-x64 ./src/cli.ts

Test Runner

Bun has a built-in test runner compatible with Jest:

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

export function multiply(a: number, b: number): number {
  return a * b
}

export async function fetchData(url: string) {
  const response = await fetch(url)
  return response.json()
}
TSmath.test.ts
TypeScript
// math.test.ts
import { describe, it, expect, beforeAll, afterAll, mock } from 'bun:test'
import { add, multiply, fetchData } from './math'

describe('Math functions', () => {
  it('adds two numbers', () => {
    expect(add(2, 3)).toBe(5)
    expect(add(-1, 1)).toBe(0)
  })

  it('multiplies two numbers', () => {
    expect(multiply(2, 3)).toBe(6)
    expect(multiply(0, 100)).toBe(0)
  })
})

describe('Matchers', () => {
  it('demonstrates various matchers', () => {
    // Equality
    expect(1 + 1).toBe(2)
    expect({ a: 1 }).toEqual({ a: 1 })

    // Truthiness
    expect(true).toBeTruthy()
    expect(false).toBeFalsy()
    expect(null).toBeNull()
    expect(undefined).toBeUndefined()
    expect('hello').toBeDefined()

    // Numbers
    expect(10).toBeGreaterThan(5)
    expect(5).toBeLessThanOrEqual(5)
    expect(0.1 + 0.2).toBeCloseTo(0.3)

    // Strings
    expect('hello world').toContain('world')
    expect('hello').toMatch(/ell/)

    // Arrays
    expect([1, 2, 3]).toContain(2)
    expect([1, 2, 3]).toHaveLength(3)

    // Errors
    expect(() => { throw new Error('oops') }).toThrow('oops')
  })
})

describe('Async tests', () => {
  it('handles async/await', async () => {
    const result = await Promise.resolve(42)
    expect(result).toBe(42)
  })

  it('handles promises', () => {
    return Promise.resolve(42).then(result => {
      expect(result).toBe(42)
    })
  })
})

describe('Mocking', () => {
  it('mocks functions', () => {
    const mockFn = mock(() => 42)

    expect(mockFn()).toBe(42)
    expect(mockFn).toHaveBeenCalled()
    expect(mockFn).toHaveBeenCalledTimes(1)
  })

  it('mocks fetch', async () => {
    const originalFetch = globalThis.fetch

    globalThis.fetch = mock(() =>
      Promise.resolve(new Response(JSON.stringify({ data: 'mocked' })))
    )

    const result = await fetchData('https://api.example.com')
    expect(result).toEqual({ data: 'mocked' })

    globalThis.fetch = originalFetch
  })
})

describe('Lifecycle hooks', () => {
  let db: any

  beforeAll(() => {
    db = { connected: true }
  })

  afterAll(() => {
    db = null
  })

  it('uses setup from beforeAll', () => {
    expect(db.connected).toBe(true)
  })
})

Running tests

Code
Bash
# Run all tests
bun test

# Specific file
bun test math.test.ts

# Pattern matching
bun test --filter "Math"

# Watch mode
bun test --watch

# Coverage
bun test --coverage

# Timeout
bun test --timeout 5000

Environment Variables

Code
TypeScript
// .env
DATABASE_URL=postgres://localhost/mydb
API_KEY=secret123
DEBUG=true

// Usage - automatic loading
console.log(Bun.env.DATABASE_URL)  // 'postgres://localhost/mydb'
console.log(Bun.env.API_KEY)       // 'secret123'
console.log(process.env.DEBUG)     // 'true' (Node.js compatibility)

// Checking
if (Bun.env.NODE_ENV === 'production') {
  console.log('Production mode')
}

// Different .env files
// .env.local, .env.development, .env.production
// are loaded automatically

WebSocket

Code
TypeScript
// WebSocket server
Bun.serve({
  port: 3000,

  fetch(request, server) {
    // Upgrade to WebSocket
    if (server.upgrade(request)) {
      return // Upgrade succeeded
    }

    return new Response('Not a WebSocket request', { status: 400 })
  },

  websocket: {
    open(ws) {
      console.log('Client connected')
      ws.send('Welcome!')
    },

    message(ws, message) {
      console.log('Received:', message)
      // Echo back
      ws.send(`You said: ${message}`)

      // Broadcast to all
      ws.publish('chat', message)
    },

    close(ws) {
      console.log('Client disconnected')
    },
  },
})

WebSocket with rooms

Code
TypeScript
Bun.serve({
  port: 3000,

  fetch(request, server) {
    const url = new URL(request.url)
    const room = url.searchParams.get('room') || 'default'

    if (server.upgrade(request, { data: { room } })) {
      return
    }

    return new Response('Upgrade failed', { status: 400 })
  },

  websocket: {
    open(ws) {
      const room = ws.data.room
      ws.subscribe(room)
      ws.send(`Joined room: ${room}`)
    },

    message(ws, message) {
      const room = ws.data.room
      // Broadcast to room
      ws.publish(room, `[${room}] ${message}`)
    },

    close(ws) {
      const room = ws.data.room
      ws.unsubscribe(room)
    },
  },
})

Bun APIs

Bun.sleep()

Code
TypeScript
// Async sleep
await Bun.sleep(1000) // 1 second
await Bun.sleep(500)  // 500ms

Bun.hash()

Code
TypeScript
// Fast hashing
const hash = Bun.hash('hello world')
console.log(hash) // number

// Different algorithms
Bun.hash.wyhash('data')
Bun.hash.adler32('data')
Bun.hash.crc32('data')
Bun.hash.cityHash32('data')
Bun.hash.cityHash64('data')
Bun.hash.murmur32v3('data')
Bun.hash.murmur64v2('data')

Bun.password

Code
TypeScript
// Password hashing (bcrypt-like)
const hash = await Bun.password.hash('mypassword')
console.log(hash) // $argon2id$...

// Verification
const isValid = await Bun.password.verify('mypassword', hash)
console.log(isValid) // true

// With options
const customHash = await Bun.password.hash('mypassword', {
  algorithm: 'argon2id', // or 'argon2d', 'argon2i', 'bcrypt'
  memoryCost: 65536,
  timeCost: 3,
})

Bun.spawn()

Code
TypeScript
// Running processes
const proc = Bun.spawn(['ls', '-la'])

// Waiting for completion
const output = await new Response(proc.stdout).text()
console.log(output)

// With stdin
const proc2 = Bun.spawn(['cat'], {
  stdin: 'pipe',
})
proc2.stdin.write('Hello from Bun!')
proc2.stdin.end()

// Synchronously
const result = Bun.spawnSync(['echo', 'Hello'])
console.log(result.stdout.toString())

Bun.Transpiler

Code
TypeScript
const transpiler = new Bun.Transpiler({
  loader: 'tsx',
})

const code = `
  const App: React.FC = () => <div>Hello</div>
  export default App
`

const result = transpiler.transformSync(code)
console.log(result)
// Transpiled JavaScript

Node.js Compatibility

Bun is largely compatible with Node.js:

Code
TypeScript
// Most Node.js APIs work
import fs from 'fs'
import path from 'path'
import crypto from 'crypto'
import { EventEmitter } from 'events'

// fs
const content = fs.readFileSync('./file.txt', 'utf-8')
fs.writeFileSync('./output.txt', 'Hello')

// path
const fullPath = path.join(__dirname, 'file.txt')
const ext = path.extname('file.txt') // '.txt'

// crypto
const hash = crypto.createHash('sha256').update('data').digest('hex')

// events
const emitter = new EventEmitter()
emitter.on('event', (data) => console.log(data))
emitter.emit('event', 'Hello!')

// process
console.log(process.cwd())
console.log(process.env.PATH)

Express compatibility

Code
TypeScript
// Express works with Bun
import express from 'express'

const app = express()

app.use(express.json())

app.get('/', (req, res) => {
  res.json({ message: 'Hello from Express on Bun!' })
})

app.listen(3000, () => {
  console.log('Server running on port 3000')
})

Framework integration

Next.js

JSNext.js
JavaScript
# Next.js with Bun
bunx create-next-app my-app
cd my-app
bun dev

Hono

Code
TypeScript
import { Hono } from 'hono'
import { serveStatic } from 'hono/bun'

const app = new Hono()

app.use('/static/*', serveStatic({ root: './public' }))
app.get('/', (c) => c.text('Hello Hono with Bun!'))

export default {
  port: 3000,
  fetch: app.fetch,
}

Elysia (native Bun framework)

Code
TypeScript
import { Elysia } from 'elysia'

const app = new Elysia()
  .get('/', () => 'Hello Elysia!')
  .get('/user/:id', ({ params: { id } }) => ({ userId: id }))
  .post('/users', ({ body }) => ({ created: body }))
  .listen(3000)

console.log(`Server running at ${app.server?.hostname}:${app.server?.port}`)

Scripts and aliases

package.json
JSON
// package.json
{
  "scripts": {
    "dev": "bun --watch run src/index.ts",
    "build": "bun build src/index.ts --outdir dist --minify",
    "test": "bun test",
    "start": "bun run dist/index.js"
  }
}
Code
Bash
# Running
bun run dev    # or simply: bun dev
bun run build
bun run test
bun run start

Performance comparison

OperationBunNode.jsDifference
Startup time~25ms~100ms4x faster
npm install~5s~50s10x faster
TypeScriptNativets-node3x faster
HTTP server~100k req/s~30k req/s3x faster
SQLiteNativebetter-sqlite32x faster
File I/OOptimizedStandard2-3x faster

Best Practices

1. Use native Bun APIs

Code
TypeScript
// Instead of
import fs from 'fs'
const content = fs.readFileSync('file.txt', 'utf-8')

// Prefer
const file = Bun.file('file.txt')
const content = await file.text()

2. Leverage the built-in SQLite

Code
TypeScript
// Instead of external packages
import { Database } from 'bun:sqlite'

const db = new Database('app.db')

3. Type your environment

TStypes/bun.d.ts
TypeScript
// types/bun.d.ts
declare module 'bun' {
  interface Env {
    DATABASE_URL: string
    API_KEY: string
    NODE_ENV: 'development' | 'production'
  }
}

4. Use bunx instead of npx

Code
Bash
# npx equivalent
bunx create-next-app
bunx prisma migrate dev
bunx eslint .

Summary

Bun is a revolutionary tool that combines all elements of the JavaScript toolchain into one ultrafast solution. Key advantages:

  • Performance - 4-30x faster than traditional tools
  • All-in-one - Runtime + bundler + package manager + test runner
  • Native TypeScript - No configuration needed
  • Node.js compatible - Most npm packages work
  • Built-in APIs - SQLite, WebSocket, password hashing
  • DX - Simplicity of use, less configuration

If you are looking for a faster alternative to Node.js or want to simplify your toolchain, Bun is an excellent choice.