Bun完全ガイド2026: JavaScriptランタイム・パッケージマネージャー・ビルドツールの全て


BunはJavaScript/TypeScriptランタイム、パッケージマネージャー、ビルドツール、テストランナーを統合した次世代のJavaScriptツールキットです。本記事では、Bunの全機能を実践的なコード例とともに徹底解説します。

Bunとは

概要

Bunは以下の機能を1つのツールで提供します。

  • JavaScriptランタイム: Node.js互換の高速実行環境
  • パッケージマネージャー: npm、yarn、pnpmより高速
  • ビルドツール: webpack、esbuild、Rollupの代替
  • テストランナー: Jest互換のテストフレームワーク
  • バンドラー: 内蔵のJavaScript/TypeScriptバンドラー

Node.jsとの比較

// パフォーマンス比較(ベンチマーク結果)
/**
 * npm install: 25秒
 * yarn: 18秒
 * pnpm: 12秒
 * bun install: 0.5秒(50倍高速)
 *
 * Node.js起動時間: 150ms
 * Bun起動時間: 3ms(50倍高速)
 *
 * TypeScript実行(ts-node): 800ms
 * TypeScript実行(Bun): 20ms(40倍高速)
 */

内部技術

// Bunの技術スタック
/**
 * ランタイムエンジン: JavaScriptCore(Safari)
 * 言語: Zig(低レベル最適化)
 * パッケージマネージャー: カスタム実装(並列インストール)
 * バンドラー: 内蔵(Zig製)
 * トランスパイラー: 内蔵(TypeScript/JSX対応)
 */

インストールと初期設定

インストール

# macOS / Linux
curl -fsSL https://bun.sh/install | bash

# Windows(WSL推奨)
powershell -c "irm bun.sh/install.ps1|iex"

# Homebrewから
brew tap oven-sh/bun
brew install bun

# npmから
npm install -g bun

# バージョン確認
bun --version
# => 1.1.38

# アップグレード
bun upgrade

プロジェクト初期化

# 新規プロジェクト作成
bun init

# 対話形式で設定
# → package.json、tsconfig.json、.gitignore を自動生成

# テンプレートから作成
bun create next-app my-app    # Next.js
bun create react-app my-app   # React
bun create vite my-app        # Vite
bun create hono my-api        # Hono(軽量フレームワーク)

# 既存のNode.jsプロジェクトをBunに移行
cd existing-project
bun install  # package-lock.json から bun.lockb を生成

パッケージ管理

基本コマンド

# パッケージインストール
bun install                    # すべての依存関係
bun install express           # 個別パッケージ
bun add express               # installのエイリアス

# 開発依存関係
bun add -d typescript @types/node
bun add --dev vitest

# グローバルインストール
bun add -g typescript

# パッケージ削除
bun remove express

# パッケージアップデート
bun update                     # すべて更新
bun update express            # 個別更新

# パッケージ情報
bun pm ls                     # インストール済み一覧
bun pm ls --all              # すべての依存関係(ツリー表示)
bun pm cache                  # キャッシュ情報
bun pm cache rm               # キャッシュクリア

package.jsonスクリプト

{
  "name": "my-app",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "bun run --hot src/index.ts",
    "build": "bun build src/index.ts --outdir ./dist --target node",
    "start": "bun dist/index.js",
    "test": "bun test",
    "lint": "bun eslint src/"
  },
  "dependencies": {
    "express": "^4.18.2",
    "hono": "^3.11.7"
  },
  "devDependencies": {
    "@types/express": "^4.17.21",
    "typescript": "^5.3.3"
  }
}
# スクリプト実行
bun run dev        # package.jsonのdevスクリプト
bun dev            # "run"は省略可能
bun run build
bun test

ワークスペース(モノレポ)

// package.json(ルート)
{
  "name": "my-monorepo",
  "workspaces": [
    "packages/*",
    "apps/*"
  ]
}
# ワークスペース構造
my-monorepo/
├── package.json
├── bun.lockb
├── packages/
   ├── shared/
   └── package.json
   └── ui/
       └── package.json
└── apps/
    ├── web/
   └── package.json
    └── api/
        └── package.json

# ワークスペースでインストール
bun install  # すべてのワークスペースをインストール

# 特定のワークスペースで実行
bun --filter web dev
bun --filter api test

# すべてのワークスペースで実行
bun --filter '*' test

JavaScriptランタイム

ファイル実行

# JavaScriptファイル実行
bun index.js

# TypeScriptファイル実行(トランスパイル不要)
bun index.ts

# ホットリロード(開発モード)
bun --hot index.ts

# ウォッチモード
bun --watch index.ts

# 環境変数指定
NODE_ENV=production bun index.ts

TypeScript完全サポート

// src/index.ts - トランスパイル不要で直接実行可能

import type { ServerWebSocket } from 'bun'

// TypeScript 5.3の最新機能を全てサポート
type User = {
  id: number
  name: string
  email: string
}

const users: User[] = []

// 装飾的な型注釈
function addUser(user: User): void {
  users.push(user)
}

// Generics
function findById<T extends { id: number }>(items: T[], id: number): T | undefined {
  return items.find(item => item.id === id)
}

// tsconfig.jsonの設定が自動で適用される
console.log(findById(users, 1))
# TypeScriptファイルを直接実行
bun src/index.ts
# => トランスパイル待ち時間なし(3ms程度で起動)

組み込みAPIとNode.js互換性

// Bun独自のAPI + Node.js互換API

// ファイル読み込み(Bun高速API)
const file = Bun.file('data.json')
const content = await file.text()
const json = await file.json()
const buffer = await file.arrayBuffer()

// Node.js互換API
import fs from 'node:fs/promises'
const data = await fs.readFile('data.json', 'utf-8')

// HTTPサーバー(Bun独自の高速サーバー)
const server = Bun.serve({
  port: 3000,
  fetch(req) {
    return new Response('Hello Bun!')
  },
})

console.log(`Server running at http://localhost:${server.port}`)

// Node.js http互換
import { createServer } from 'node:http'

const nodeServer = createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' })
  res.end('Hello Node.js API')
})

nodeServer.listen(3001)

Web標準API

// Bunは最新のWeb標準APIをサポート

// Fetch API
const response = await fetch('https://api.example.com/users')
const users = await response.json()

// WebSocket
const ws = new WebSocket('wss://example.com/socket')

ws.onmessage = (event) => {
  console.log('Received:', event.data)
}

ws.send('Hello WebSocket')

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

// URL / URLSearchParams
const url = new URL('https://example.com/search?q=bun')
console.log(url.searchParams.get('q')) // => "bun"

// TextEncoder / TextDecoder
const encoder = new TextEncoder()
const uint8 = encoder.encode('Hello')

const decoder = new TextDecoder()
const str = decoder.decode(uint8)

// crypto(Web Crypto API)
const hash = await crypto.subtle.digest(
  'SHA-256',
  encoder.encode('password')
)

// FormData
const formData = new FormData()
formData.append('name', 'John')
formData.append('file', new Blob(['content']))

// Headers
const headers = new Headers({
  'Content-Type': 'application/json',
  'Authorization': 'Bearer token',
})

HTTPサーバー

基本的なサーバー

// server.ts - 高速HTTPサーバー

const server = Bun.serve({
  port: 3000,

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

    if (url.pathname === '/') {
      return new Response('Hello Bun!', {
        headers: { 'Content-Type': 'text/plain' },
      })
    }

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

    if (url.pathname === '/users') {
      const users = [
        { id: 1, name: 'Alice' },
        { id: 2, name: 'Bob' },
      ]
      return Response.json(users)
    }

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

  // エラーハンドリング
  error(error) {
    return new Response(`Error: ${error.message}`, { status: 500 })
  },
})

console.log(`Server running at http://localhost:${server.port}`)

RESTful API

// api-server.ts

type User = {
  id: number
  name: string
  email: string
}

const users: User[] = [
  { id: 1, name: 'Alice', email: 'alice@example.com' },
  { id: 2, name: 'Bob', email: 'bob@example.com' },
]

const server = Bun.serve({
  port: 3000,

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

    // CORS設定
    const headers = {
      'Access-Control-Allow-Origin': '*',
      'Content-Type': 'application/json',
    }

    // GET /api/users - 全ユーザー取得
    if (method === 'GET' && url.pathname === '/api/users') {
      return Response.json(users, { headers })
    }

    // GET /api/users/:id - 個別ユーザー取得
    const userMatch = url.pathname.match(/^\/api\/users\/(\d+)$/)
    if (method === 'GET' && userMatch) {
      const id = parseInt(userMatch[1])
      const user = users.find(u => u.id === id)

      if (!user) {
        return Response.json({ error: 'User not found' }, {
          status: 404,
          headers
        })
      }

      return Response.json(user, { headers })
    }

    // POST /api/users - ユーザー作成
    if (method === 'POST' && url.pathname === '/api/users') {
      const body = await req.json() as Omit<User, 'id'>
      const newUser: User = {
        id: Math.max(...users.map(u => u.id)) + 1,
        ...body,
      }
      users.push(newUser)

      return Response.json(newUser, { status: 201, headers })
    }

    // PUT /api/users/:id - ユーザー更新
    if (method === 'PUT' && userMatch) {
      const id = parseInt(userMatch[1])
      const index = users.findIndex(u => u.id === id)

      if (index === -1) {
        return Response.json({ error: 'User not found' }, {
          status: 404,
          headers
        })
      }

      const body = await req.json() as Partial<User>
      users[index] = { ...users[index], ...body }

      return Response.json(users[index], { headers })
    }

    // DELETE /api/users/:id - ユーザー削除
    if (method === 'DELETE' && userMatch) {
      const id = parseInt(userMatch[1])
      const index = users.findIndex(u => u.id === id)

      if (index === -1) {
        return Response.json({ error: 'User not found' }, {
          status: 404,
          headers
        })
      }

      users.splice(index, 1)

      return new Response(null, { status: 204, headers })
    }

    return Response.json({ error: 'Not found' }, { status: 404, headers })
  },
})

console.log(`API server running at http://localhost:${server.port}`)

WebSocketサーバー

// websocket-server.ts

type WebSocketData = {
  username: string
  createdAt: number
}

const server = Bun.serve<WebSocketData>({
  port: 3000,

  fetch(req, server) {
    const url = new URL(req.url)

    // WebSocketアップグレード
    if (url.pathname === '/ws') {
      const username = url.searchParams.get('username') || 'Anonymous'

      const success = server.upgrade(req, {
        data: {
          username,
          createdAt: Date.now(),
        },
      })

      if (success) {
        return undefined // WebSocket接続成功
      }

      return new Response('WebSocket upgrade failed', { status: 500 })
    }

    return new Response('Hello Bun WebSocket Server')
  },

  websocket: {
    // 接続時
    open(ws) {
      console.log(`${ws.data.username} connected`)
      ws.send(`Welcome, ${ws.data.username}!`)

      // 全クライアントに通知
      ws.publish('chat', `${ws.data.username} joined the chat`)
      ws.subscribe('chat')
    },

    // メッセージ受信時
    message(ws, message) {
      console.log(`Received from ${ws.data.username}:`, message)

      // 全クライアントにブロードキャスト
      ws.publish('chat', `${ws.data.username}: ${message}`)
    },

    // 切断時
    close(ws) {
      console.log(`${ws.data.username} disconnected`)
      ws.publish('chat', `${ws.data.username} left the chat`)
    },

    // エラー時
    error(ws, error) {
      console.error('WebSocket error:', error)
    },
  },
})

console.log(`WebSocket server running at ws://localhost:${server.port}/ws`)

ファイル配信

// static-server.ts

const server = Bun.serve({
  port: 3000,

  async fetch(req) {
    const url = new URL(req.url)
    let filePath = url.pathname

    // ルートパスは index.html を返す
    if (filePath === '/') {
      filePath = '/index.html'
    }

    // publicディレクトリから配信
    const file = Bun.file(`./public${filePath}`)

    // ファイルが存在するか確認
    const exists = await file.exists()

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

    // ファイルを返す(Content-Typeは自動設定)
    return new Response(file)
  },
})

console.log(`Static server running at http://localhost:${server.port}`)

ビルドとバンドル

基本的なビルド

# 単一ファイルビルド
bun build src/index.ts --outdir ./dist

# 出力ファイル名指定
bun build src/index.ts --outfile dist/bundle.js

# 複数エントリーポイント
bun build src/index.ts src/worker.ts --outdir ./dist

# minify(圧縮)
bun build src/index.ts --outdir ./dist --minify

# ソースマップ生成
bun build src/index.ts --outdir ./dist --sourcemap

# ターゲット指定
bun build src/index.ts --outdir ./dist --target browser  # ブラウザ用
bun build src/index.ts --outdir ./dist --target node     # Node.js用
bun build src/index.ts --outdir ./dist --target bun      # Bun用

プログラマティックビルド

// build.ts

const result = await Bun.build({
  entrypoints: ['./src/index.ts'],
  outdir: './dist',
  target: 'browser',
  minify: true,
  sourcemap: 'external',
  splitting: true, // コード分割

  // 外部パッケージ(バンドルしない)
  external: ['react', 'react-dom'],

  // プラグイン(カスタム処理)
  plugins: [],
})

if (result.success) {
  console.log('Build successful!')
  console.log('Outputs:', result.outputs)
} else {
  console.error('Build failed')
  for (const message of result.logs) {
    console.error(message)
  }
}

React/Vue/Svelteのビルド

// React プロジェクトのビルド
const result = await Bun.build({
  entrypoints: ['./src/index.tsx'],
  outdir: './dist',
  target: 'browser',
  minify: true,
  splitting: true,

  // JSXファイルを自動処理
  loader: {
    '.tsx': 'tsx',
    '.jsx': 'jsx',
  },
})

// CSS/画像も含めてバンドル
const fullBuild = await Bun.build({
  entrypoints: ['./src/index.tsx'],
  outdir: './dist',

  loader: {
    '.png': 'file',
    '.jpg': 'file',
    '.css': 'css',
    '.svg': 'dataurl',
  },
})

環境変数の埋め込み

// build-with-env.ts

const result = await Bun.build({
  entrypoints: ['./src/index.ts'],
  outdir: './dist',

  define: {
    'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production'),
    'process.env.API_URL': JSON.stringify('https://api.example.com'),
    '__VERSION__': JSON.stringify('1.0.0'),
  },
})
// src/config.ts - ビルド時に置換される

export const config = {
  env: process.env.NODE_ENV,        // => "production"
  apiUrl: process.env.API_URL,      // => "https://api.example.com"
  version: __VERSION__,             // => "1.0.0"
}

テストランナー

基本的なテスト

// math.test.ts

import { expect, test, describe } from 'bun:test'

function add(a: number, b: number): number {
  return a + b
}

test('add function', () => {
  expect(add(1, 2)).toBe(3)
  expect(add(-1, 1)).toBe(0)
})

describe('Math operations', () => {
  test('addition', () => {
    expect(1 + 1).toBe(2)
  })

  test('subtraction', () => {
    expect(5 - 3).toBe(2)
  })

  test('multiplication', () => {
    expect(2 * 3).toBe(6)
  })
})
# テスト実行
bun test

# 特定のファイルのみ
bun test math.test.ts

# ウォッチモード
bun test --watch

# カバレッジ
bun test --coverage

非同期テスト

// async.test.ts

import { expect, test } from 'bun:test'

async function fetchUser(id: number) {
  const response = await fetch(`https://api.example.com/users/${id}`)
  return response.json()
}

test('fetch user', async () => {
  const user = await fetchUser(1)
  expect(user).toHaveProperty('id')
  expect(user).toHaveProperty('name')
})

test('async timeout', async () => {
  await new Promise(resolve => setTimeout(resolve, 100))
  expect(true).toBe(true)
}, { timeout: 1000 }) // 1秒タイムアウト

モック

// mock.test.ts

import { expect, test, mock, spyOn } from 'bun:test'

test('mock function', () => {
  const mockFn = mock((a: number, b: number) => a + b)

  mockFn(1, 2)
  mockFn(3, 4)

  expect(mockFn).toHaveBeenCalledTimes(2)
  expect(mockFn).toHaveBeenCalledWith(1, 2)
  expect(mockFn).toHaveBeenCalledWith(3, 4)
})

test('spy on method', () => {
  const obj = {
    method: (x: number) => x * 2,
  }

  const spy = spyOn(obj, 'method')

  obj.method(5)

  expect(spy).toHaveBeenCalledWith(5)
  expect(spy).toHaveReturnedWith(10)
})

// fetch のモック
test('mock fetch', async () => {
  globalThis.fetch = mock(async (url: string) => {
    return new Response(JSON.stringify({ id: 1, name: 'Test' }), {
      headers: { 'Content-Type': 'application/json' },
    })
  })

  const response = await fetch('https://api.example.com/users/1')
  const data = await response.json()

  expect(data).toEqual({ id: 1, name: 'Test' })
})

スナップショットテスト

// snapshot.test.ts

import { expect, test } from 'bun:test'

test('snapshot', () => {
  const data = {
    id: 1,
    name: 'Alice',
    email: 'alice@example.com',
  }

  expect(data).toMatchSnapshot()
})

パフォーマンス最適化

ファイル I/O

// Bunの高速ファイルAPI

// 方法1: Bun.file(最速)
const file = Bun.file('large-file.json')
const data1 = await file.json()

// 方法2: Node.js fs(互換性のため)
import fs from 'node:fs/promises'
const data2 = JSON.parse(await fs.readFile('large-file.json', 'utf-8'))

// 方法3: Bun.write(ファイル書き込み)
await Bun.write('output.txt', 'Hello Bun!')
await Bun.write('output.json', JSON.stringify({ key: 'value' }))
await Bun.write('output.bin', new Uint8Array([1, 2, 3]))

// ストリーム処理
const response = await fetch('https://example.com/large-file.zip')
await Bun.write('downloaded.zip', response)

パスワードハッシュ

// Bunの高速ハッシュAPI

const password = 'my-password'

// bcrypt風のハッシュ化(Bunの組み込み実装)
const hashed = await Bun.password.hash(password)

// 検証
const isValid = await Bun.password.verify(password, hashed)

console.log(isValid) // => true

// アルゴリズム指定
const argon2Hash = await Bun.password.hash(password, {
  algorithm: 'argon2id',
  memoryCost: 65536,
  timeCost: 3,
})

SQLite(内蔵)

// Bunの組み込みSQLite

import { Database } from 'bun:sqlite'

// データベース作成/接続
const db = new Database('mydb.sqlite')

// テーブル作成
db.run(`
  CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    email TEXT UNIQUE NOT NULL
  )
`)

// データ挿入
const insert = db.prepare('INSERT INTO users (name, email) VALUES (?, ?)')
insert.run('Alice', 'alice@example.com')
insert.run('Bob', 'bob@example.com')

// データ取得
const getUser = db.prepare('SELECT * FROM users WHERE id = ?')
const user = getUser.get(1)
console.log(user) // => { id: 1, name: 'Alice', email: 'alice@example.com' }

// 全件取得
const allUsers = db.prepare('SELECT * FROM users').all()
console.log(allUsers)

// トランザクション
const insertMany = db.transaction((users: Array<{ name: string; email: string }>) => {
  const stmt = db.prepare('INSERT INTO users (name, email) VALUES (?, ?)')
  for (const user of users) {
    stmt.run(user.name, user.email)
  }
})

insertMany([
  { name: 'Charlie', email: 'charlie@example.com' },
  { name: 'David', email: 'david@example.com' },
])

// クローズ
db.close()

フレームワーク統合

Express

// express-app.ts

import express from 'express'

const app = express()
const port = 3000

app.use(express.json())

app.get('/', (req, res) => {
  res.send('Hello Express on Bun!')
})

app.get('/api/users', (req, res) => {
  res.json([
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' },
  ])
})

app.listen(port, () => {
  console.log(`Server running at http://localhost:${port}`)
})
# 依存関係インストール
bun add express
bun add -d @types/express

# 実行
bun express-app.ts

Hono(Bun最適化フレームワーク)

// hono-app.ts

import { Hono } from 'hono'

const app = new Hono()

app.get('/', (c) => {
  return c.text('Hello Hono on Bun!')
})

app.get('/api/users', (c) => {
  return c.json([
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' },
  ])
})

app.post('/api/users', async (c) => {
  const body = await c.req.json()
  return c.json({ success: true, user: body }, 201)
})

export default app
# 依存関係インストール
bun add hono

# 実行
bun --hot hono-app.ts

Elysia(Bun専用フレームワーク)

// elysia-app.ts

import { Elysia } from 'elysia'

const app = new Elysia()
  .get('/', () => 'Hello Elysia!')
  .get('/api/users', () => [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' },
  ])
  .post('/api/users', ({ body }) => ({
    success: true,
    user: body,
  }))
  .listen(3000)

console.log(`Server running at http://localhost:${app.server?.port}`)
# 依存関係インストール
bun add elysia

# 実行
bun --hot elysia-app.ts

Docker化

Dockerfile

# Dockerfile

FROM oven/bun:1 AS base
WORKDIR /app

# 依存関係インストール
FROM base AS install
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile

# ビルド
FROM base AS build
COPY --from=install /app/node_modules ./node_modules
COPY . .
RUN bun run build

# 本番環境
FROM base AS production
COPY --from=install /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY package.json ./

USER bun
EXPOSE 3000
ENTRYPOINT ["bun", "dist/index.js"]
# ビルド
docker build -t my-bun-app .

# 実行
docker run -p 3000:3000 my-bun-app

docker-compose.yml

# docker-compose.yml

version: '3.8'

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://user:pass@db:5432/mydb
    depends_on:
      - db
    restart: unless-stopped

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: mydb
    volumes:
      - postgres_data:/var/lib/postgresql/data
    restart: unless-stopped

volumes:
  postgres_data:

トラブルシューティング

よくある問題

# キャッシュクリア
bun pm cache rm

# 依存関係の再インストール
rm -rf node_modules bun.lockb
bun install

# グローバルパッケージの確認
bun pm ls -g

# Bunのアップグレード
bun upgrade

# デバッグモード
bun --inspect index.ts

# 詳細ログ
bun --verbose install

パフォーマンス計測

// benchmark.ts

import { bench, run } from 'mitata'

// 関数のベンチマーク
bench('Array.push', () => {
  const arr = []
  for (let i = 0; i < 1000; i++) {
    arr.push(i)
  }
})

bench('Array spread', () => {
  let arr = []
  for (let i = 0; i < 1000; i++) {
    arr = [...arr, i]
  }
})

await run()

まとめ

Bunは、JavaScript/TypeScript開発者に統合された高速なツールキットを提供します。

主な利点

  • ランタイム、パッケージマネージャー、ビルダー、テストランナーを統合
  • Node.jsより圧倒的に高速(起動時間50倍、パッケージインストール50倍)
  • TypeScriptをトランスパイル不要で直接実行
  • 組み込みSQLite、WebSocket、HTTPサーバー

適用場面

  • 新規プロジェクト(制約なし)
  • CLIツール・スクリプト開発
  • 高速なAPI開発
  • テスト実行の高速化

注意点

  • エコシステムがNode.jsより小さい
  • 一部のnpmパッケージが未対応
  • プロダクション実績がNode.jsより少ない

2026年現在、Bunは安定版1.1がリリースされ、プロダクション環境での採用事例も増加しています。Node.jsとの高い互換性により、既存プロジェクトからの移行も容易です。

参考リンク