We use cookies to enhance your experience on the site
CodeWorlds
Back to collections
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.