Back

Bun 1.2徹底解説: SQLite・S3内蔵で本当にNode.js置き換えられる?

Bun 1.2徹底解説: SQLite・S3内蔵で本当にNode.js置き換えられる?

Bunといえば何を思い浮かべる?高速インストール、高速実行、TypeScript標準サポート。でも大半の人にとっては「サイドプロジェクトならまだしも、本番はちょっと...」って感じだったよね。

Bun 1.2でその認識が変わる。

2025年1月リリースのBun 1.2はただのマイナーアップデートじゃない。SQLite内蔵、S3クライアント標準、Postgres対応、そしてNode.js互換性が96%に到達。npmパッケージなしでimportしてすぐ使える。

この記事で、Bun 1.2が何を提供するか、実際のベンチマーク結果、本番投入していいのかを掘り下げていく。

Bun 1.2で何が変わった?

誇張なしで実際に新しいものを見ていこう。

1. SQLite内蔵

SQLiteがBunでファーストクラスサポート。インストール不要:

import { Database } from "bun:sqlite"; const db = new Database("myapp.db"); // テーブル作成 db.run(` CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT UNIQUE NOT NULL, name TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) `); // データ挿入 const insert = db.prepare("INSERT INTO users (email, name) VALUES (?, ?)"); insert.run("[email protected]", "John Doe"); // 型安全にクエリ interface User { id: number; email: string; name: string; created_at: string; } const users = db.prepare("SELECT * FROM users").all() as User[]; console.log(users);

better-sqlite3のラッパーじゃない。ネイティブ実装だから断然速い:

操作better-sqlite3 (Node.js)Bun SQLite
100万行INSERT4.2秒1.8秒2.3倍速
10万行SELECT320ms140ms2.3倍速
トランザクションコミット12ms5ms2.4倍速

JavaScriptCoreと直接統合されていて、Node.jsのN-APIオーバーヘッドがないからこの性能が出る。

2. S3クライアント内蔵

クラウドストレージも依存なし。AWS S3、R2、MinIO全部いける:

import { S3Client } from "bun"; const s3 = new S3Client({ endpoint: "https://s3.amazonaws.com", region: "us-east-1", accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, }); // ファイルアップロード const file = Bun.file("./large-video.mp4"); await s3.write("my-bucket/videos/intro.mp4", file); // ファイルダウンロード const downloaded = await s3.file("my-bucket/videos/intro.mp4"); await Bun.write("./downloaded.mp4", downloaded); // 大きいファイルのストリーミング const stream = s3.file("my-bucket/data/huge.csv").stream(); for await (const chunk of stream) { // メモリに全部載せずにチャンク単位で処理 }

大容量ファイルはマルチパートアップロードを自動でやってくれるし、presigned URLも標準対応。AWS SDKと比較すると:

// AWS SDK v3 - 従来の方法 import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3"; import fs from "fs"; const client = new S3Client({ region: "us-east-1" }); const fileStream = fs.createReadStream("./large-video.mp4"); await client.send(new PutObjectCommand({ Bucket: "my-bucket", Key: "videos/intro.mp4", Body: fileStream, })); // Bun - 新しい方法 const s3 = new S3Client({ /* config */ }); await s3.write("my-bucket/videos/intro.mp4", Bun.file("./large-video.mp4"));

APIがめちゃシンプルになった。

3. Postgres内蔵

SQLiteと一緒にPostgresも内蔵:

import { sql } from "bun"; // 環境変数から接続情報読み込み (DATABASE_URL) const users = await sql`SELECT * FROM users WHERE active = ${true}`; // 明示的な接続も可能 import { SQL } from "bun"; const db = new SQL({ hostname: "localhost", port: 5432, database: "myapp", username: "postgres", password: "secret", }); // パラメータ化クエリは自動 const email = "[email protected]"; const user = await db`SELECT * FROM users WHERE email = ${email}`; // トランザクション await db.begin(async (tx) => { await tx`UPDATE accounts SET balance = balance - 100 WHERE id = 1`; await tx`UPDATE accounts SET balance = balance + 100 WHERE id = 2`; });

テンプレートリテラル方式だからSQLインジェクションが設計上防げるし、DXもいい。

4. Node.js互換性: 96%

Bun導入の最大の障壁だった互換性。1.2では:

  • 96% Node.jsテスト通過
  • 100% node:fsテスト通過
  • 100% node:pathテスト通過
  • 99% node:cryptoテスト通過
  • 98% node:httpテスト通過

ほとんどのnpmパッケージがそのまま動くってこと。人気パッケージのテスト結果:

パッケージ状態備考
Express✅ 動作完全互換
Fastify✅ 動作完全互換
Prisma✅ 動作Bun 1.1から
Next.js⚠️ 部分的ほぼOK、一部エッジケース
NestJS✅ 動作完全互換
Socket.io✅ 動作完全互換

5. Windowsネイティブ対応

やっとWSLなしでWindowsでネイティブ動作:

powershell -c "irm bun.sh/install.ps1 | iex"

Windows性能もLinux/macOSと同等になった。

実践ベンチマーク: APIサーバー作ってみる

同じAPIをNode.jsとBunで作って比較した。

テスト: SQLiteベースのUser CRUD API

Bun実装:

// server.ts (Bun) import { Database } from "bun:sqlite"; const db = new Database(":memory:"); db.run(` CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT UNIQUE, name TEXT ) `); // シードデータ const insert = db.prepare("INSERT INTO users (email, name) VALUES (?, ?)"); for (let i = 0; i < 1000; i++) { insert.run(`user${i}@test.com`, `User ${i}`); } const server = Bun.serve({ port: 3000, async fetch(req) { const url = new URL(req.url); if (url.pathname === "/users" && req.method === "GET") { const users = db.prepare("SELECT * FROM users LIMIT 100").all(); return Response.json(users); } if (url.pathname === "/users" && req.method === "POST") { const body = await req.json(); const result = db.prepare( "INSERT INTO users (email, name) VALUES (?, ?) RETURNING *" ).get(body.email, body.name); return Response.json(result, { status: 201 }); } return new Response("Not Found", { status: 404 }); }, }); console.log(`Server running at http://localhost:${server.port}`);

Node.js実装:

// server.mjs (Node.js + better-sqlite3) import Database from "better-sqlite3"; import { createServer } from "http"; const db = new Database(":memory:"); db.exec(` CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT UNIQUE, name TEXT ) `); const insert = db.prepare("INSERT INTO users (email, name) VALUES (?, ?)"); for (let i = 0; i < 1000; i++) { insert.run(`user${i}@test.com`, `User ${i}`); } const server = createServer(async (req, res) => { const url = new URL(req.url, `http://${req.headers.host}`); if (url.pathname === "/users" && req.method === "GET") { const users = db.prepare("SELECT * FROM users LIMIT 100").all(); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(users)); return; } if (url.pathname === "/users" && req.method === "POST") { let body = ""; for await (const chunk of req) body += chunk; const data = JSON.parse(body); const result = db.prepare( "INSERT INTO users (email, name) VALUES (?, ?) RETURNING *" ).get(data.email, data.name); res.writeHead(201, { "Content-Type": "application/json" }); res.end(JSON.stringify(result)); return; } res.writeHead(404); res.end("Not Found"); }); server.listen(3000, () => console.log("Server running at http://localhost:3000"));

ベンチマーク結果 (M2 MacBook Pro, 10,000リクエスト)

GET /users (100行読み込み):

ランタイムリクエスト/秒平均レイテンシP99レイテンシ
Node.js 2212,4507.8ms15ms
Bun 1.228,9003.2ms8ms
2.3倍速2.4倍速1.9倍速

POST /users (挿入 + 返却):

ランタイムリクエスト/秒平均レイテンシP99レイテンシ
Node.js 228,20011.5ms22ms
Bun 1.219,4004.8ms12ms
2.4倍速2.4倍速1.8倍速

一貫してBunが2-2.5倍速い。

コールドスタート比較

サーバーレスではコールドスタート重要:

ランタイム起動時間起動時メモリ
Node.js 2245ms52MB
Bun 1.28ms28MB
5.6倍速46%少ない

サーバーレス関数だとこの差はでかい。

いつBunを本番で使うべき?

テスト結果に基づく現実的な評価:

✅ Bunおすすめのケース

  1. 依存がシンプルな新規プロジェクト

    • SQLite/Postgres使うAPI
    • バックグラウンドワーカー
    • CLIツール
    • マイクロサービス
  2. 性能重視のアプリ

    • 高スループットAPI
    • リアルタイムアプリ
    • エッジ関数
  3. サーバーレス

    • コールドスタート重要な時
    • メモリコスト気にする時

⚠️ 慎重に進める

  1. 大きいNext.jsプロジェクト

    • ほぼ動くけどエッジケースあり
    • デプロイ前に十分テスト
  2. ネイティブアドオン使うアプリ

    • 一部動かないかも
    • 互換性先に確認
  3. Node.jsに深く依存するレガシー

    • 移行コスト対効果を検討

❌ まだ非推奨

  1. Electronアプリ

    • 未対応
  2. node:vmが必要なアプリ

    • VM対応が限定的
  3. ミッションクリティカルな金融システム

    • もう少し検証待ち

マイグレーションガイド

Bunに移行するなら:

ステップ1: Bunインストール

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

ステップ2: 互換性チェック

# 既存テストをBunで回す bun test # 問題確認 bun run your-script.ts

ステップ3: package.jsonスクリプト更新

{ "scripts": { "dev": "bun run --watch src/index.ts", "start": "bun run src/index.ts", "test": "bun test" } }

ステップ4: 不要な依存を削除

Bunビルトインで代替可能:

  • better-sqlite3bun:sqlite
  • @aws-sdk/client-s3Bun.S3Client
  • pgbun SQL
  • dotenv → Bunが.env自動読み込み
  • ts-node / tsx → 不要

ステップ5: Dockerfile更新

FROM oven/bun:1.2 WORKDIR /app COPY package.json bun.lockb ./ RUN bun install --frozen-lockfile COPY . . EXPOSE 3000 CMD ["bun", "run", "src/index.ts"]

信用していい?

BunはスタートアップOvenが作ってる。会社潰れたらどうするって心配は当然。

信用できる理由:

  • オープンソース(MITライセンス)
  • コミュニティでかい(GitHubスター7万+)
  • 元WebKit/Safariチームが参加
  • 企業導入増加中

注意点:

  • Node.jsは15年以上の実績あり
  • キーパーソン依存度高い(Jarred Sumner抜けたら影響大)
  • エッジケースまだある

おすすめ:まず重要度低い新規プロジェクトで試す。 問題なければ徐々に拡大。

まとめ

Bun 1.2は意味のあるマイルストーン:

  1. 性能: 2-3倍速いのは本当
  2. DX: 依存減ってAPIシンプルに
  3. 互換性: Node.jsテスト96%通過
  4. 機能: npmパッケージ5個以上必要だったものが内蔵

Node.js置き換えられる?新規プロジェクト: 徐々にYes。 既存本番: 慎重に、段階的に。

Bunを無視するのはもうオプションじゃない。次のプロジェクトで試してみて。思ったよりいいかも。

# Bunインストールして最初のプロジェクト開始 curl -fsSL https://bun.sh/install | bash bun init bun run index.ts
bunnodejsjavascriptruntimesqlitedatabasebackendperformance