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万行INSERT | 4.2秒 | 1.8秒 | 2.3倍速 |
| 10万行SELECT | 320ms | 140ms | 2.3倍速 |
| トランザクションコミット | 12ms | 5ms | 2.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 22 | 12,450 | 7.8ms | 15ms |
| Bun 1.2 | 28,900 | 3.2ms | 8ms |
| 差 | 2.3倍速 | 2.4倍速 | 1.9倍速 |
POST /users (挿入 + 返却):
| ランタイム | リクエスト/秒 | 平均レイテンシ | P99レイテンシ |
|---|---|---|---|
| Node.js 22 | 8,200 | 11.5ms | 22ms |
| Bun 1.2 | 19,400 | 4.8ms | 12ms |
| 差 | 2.4倍速 | 2.4倍速 | 1.8倍速 |
一貫してBunが2-2.5倍速い。
コールドスタート比較
サーバーレスではコールドスタート重要:
| ランタイム | 起動時間 | 起動時メモリ |
|---|---|---|
| Node.js 22 | 45ms | 52MB |
| Bun 1.2 | 8ms | 28MB |
| 差 | 5.6倍速 | 46%少ない |
サーバーレス関数だとこの差はでかい。
いつBunを本番で使うべき?
テスト結果に基づく現実的な評価:
✅ Bunおすすめのケース
-
依存がシンプルな新規プロジェクト
- SQLite/Postgres使うAPI
- バックグラウンドワーカー
- CLIツール
- マイクロサービス
-
性能重視のアプリ
- 高スループットAPI
- リアルタイムアプリ
- エッジ関数
-
サーバーレス
- コールドスタート重要な時
- メモリコスト気にする時
⚠️ 慎重に進める
-
大きいNext.jsプロジェクト
- ほぼ動くけどエッジケースあり
- デプロイ前に十分テスト
-
ネイティブアドオン使うアプリ
- 一部動かないかも
- 互換性先に確認
-
Node.jsに深く依存するレガシー
- 移行コスト対効果を検討
❌ まだ非推奨
-
Electronアプリ
- 未対応
-
node:vmが必要なアプリ- VM対応が限定的
-
ミッションクリティカルな金融システム
- もう少し検証待ち
マイグレーションガイド
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-sqlite3→bun:sqlite@aws-sdk/client-s3→Bun.S3Clientpg→bunSQLdotenv→ 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は意味のあるマイルストーン:
- 性能: 2-3倍速いのは本当
- DX: 依存減ってAPIシンプルに
- 互換性: Node.jsテスト96%通過
- 機能: npmパッケージ5個以上必要だったものが内蔵
Node.js置き換えられる?新規プロジェクト: 徐々にYes。 既存本番: 慎重に、段階的に。
Bunを無視するのはもうオプションじゃない。次のプロジェクトで試してみて。思ったよりいいかも。
# Bunインストールして最初のプロジェクト開始 curl -fsSL https://bun.sh/install | bash bun init bun run index.ts