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
- Ekstremalnie szybki - 4x szybszy start niż Node.js
- All-in-one - Runtime + bundler + package manager + test runner
- Natywny TypeScript - Bez transpilacji
- Node.js compatible - Większość pakietów npm działa
- Web APIs - Fetch, WebSocket, FormData out of the box
- SQLite wbudowany - Natywna obsługa bazy danych
Instalacja
macOS / Linux
curl -fsSL https://bun.sh/install | bashWindows (WSL)
# W WSL
curl -fsSL https://bun.sh/install | bashHomebrew (macOS)
brew install oven-sh/bun/bunWeryfikacja instalacji
bun --version
# 1.x.xPackage Manager
Bun jako package manager jest 10-30x szybszy niż npm.
Podstawowe komendy
# 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ść cacheLockfile
Bun używa własnego formatu lockfile: bun.lockb (binarny, szybszy do parsowania).
# Konwersja do yarn.lock (dla kompatybilności)
bun install --yarnWorkspaces (Monorepo)
// package.json
{
"name": "my-monorepo",
"workspaces": [
"packages/*",
"apps/*"
]
}# Instalacja w monorepo
bun install
# Dodawanie do konkretnego workspace
bun add lodash --filter my-package
# Uruchamianie skryptu w workspace
bun run --filter my-package buildRuntime
Uruchamianie skryptów
# 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 replTypeScript bez konfiguracji
// 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)bun run index.ts
# [{ id: 1, name: 'John', email: 'john@example.com' }]JSX/TSX bez konfiguracji
// 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:
// 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
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
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()
// 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()
// 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
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
// 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') // falseSQLite
Bun ma wbudowaną, natywną obsługę SQLite:
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
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:
// 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
# 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 mycliExecutable (kompilacja do binary)
# 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.tsTest Runner
Bun ma wbudowany test runner kompatybilny z Jest:
// 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()
}// 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
# 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 5000Environment Variables
// .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 ładowaneWebSocket
// 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
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()
// Async sleep
await Bun.sleep(1000) // 1 sekunda
await Bun.sleep(500) // 500msBun.hash()
// 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
// 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()
// 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
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 JavaScriptNode.js Compatibility
Bun jest w dużej mierze kompatybilny z Node.js:
// 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
// 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
# Next.js z Bun
bunx create-next-app my-app
cd my-app
bun devHono
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)
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
{
"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"
}
}# Uruchamianie
bun run dev # lub po prostu: bun dev
bun run build
bun run test
bun run startPorównanie wydajności
| Operacja | Bun | Node.js | Różnica |
|---|---|---|---|
| Startup time | ~25ms | ~100ms | 4x szybciej |
| npm install | ~5s | ~50s | 10x szybciej |
| TypeScript | Native | ts-node | 3x szybciej |
| HTTP server | ~100k req/s | ~30k req/s | 3x szybciej |
| SQLite | Native | better-sqlite3 | 2x szybciej |
| File I/O | Optimized | Standard | 2-3x szybciej |
Best Practices
1. Używaj natywnych APIs Bun
// 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
// Zamiast zewnętrznych pakietów
import { Database } from 'bun:sqlite'
const db = new Database('app.db')3. Typuj środowisko
// 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
# 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
- Extremely fast - 4x faster startup than Node.js
- All-in-one - Runtime + bundler + package manager + test runner
- Native TypeScript - No transpilation needed
- Node.js compatible - Most npm packages work
- Web APIs - Fetch, WebSocket, FormData out of the box
- Built-in SQLite - Native database support
Installation
macOS / Linux
curl -fsSL https://bun.sh/install | bashWindows (WSL)
# In WSL
curl -fsSL https://bun.sh/install | bashHomebrew (macOS)
brew install oven-sh/bun/bunVerifying installation
bun --version
# 1.x.xPackage Manager
Bun as a package manager is 10-30x faster than npm.
Basic commands
# 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 cacheLockfile
Bun uses its own lockfile format: bun.lockb (binary, faster to parse).
# Convert to yarn.lock (for compatibility)
bun install --yarnWorkspaces (Monorepo)
// package.json
{
"name": "my-monorepo",
"workspaces": [
"packages/*",
"apps/*"
]
}# 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 buildRuntime
Running scripts
# 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 replTypeScript without configuration
// 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)bun run index.ts
# [{ id: 1, name: 'John', email: 'john@example.com' }]JSX/TSX without configuration
// 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:
// 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
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
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()
// 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()
// 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
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
// 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') // falseSQLite
Bun has built-in, native SQLite support:
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
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:
// 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
# 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 mycliExecutable (compiling to binary)
# 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.tsTest Runner
Bun has a built-in test runner compatible with Jest:
// 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()
}// 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
# 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 5000Environment Variables
// .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 automaticallyWebSocket
// 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
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()
// Async sleep
await Bun.sleep(1000) // 1 second
await Bun.sleep(500) // 500msBun.hash()
// 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
// 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()
// 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
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 JavaScriptNode.js Compatibility
Bun is largely compatible with Node.js:
// 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
// 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
# Next.js with Bun
bunx create-next-app my-app
cd my-app
bun devHono
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)
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
{
"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"
}
}# Running
bun run dev # or simply: bun dev
bun run build
bun run test
bun run startPerformance comparison
| Operation | Bun | Node.js | Difference |
|---|---|---|---|
| Startup time | ~25ms | ~100ms | 4x faster |
| npm install | ~5s | ~50s | 10x faster |
| TypeScript | Native | ts-node | 3x faster |
| HTTP server | ~100k req/s | ~30k req/s | 3x faster |
| SQLite | Native | better-sqlite3 | 2x faster |
| File I/O | Optimized | Standard | 2-3x faster |
Best Practices
1. Use native Bun APIs
// 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
// Instead of external packages
import { Database } from 'bun:sqlite'
const db = new Database('app.db')3. Type your environment
// 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
# 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.