Node.js・Deno・Bun、実際に本番投入して比較してみた結果
3つのランタイムを6ヶ月本番運用。パフォーマンス・開発体験・運用コストの現実と、プロジェクト別の選び方をデータで解説します。
Node.js・Deno・Bun、本当はどれを選ぶべき?
去年のプロジェクトで「次のマイクロサービス、どのランタイム使おう」って議論になったんですよ。チームは安定重視でNode.jsを推してたけど、僕はずっとDeno触ってたし、BunはもうV1を超えてるし——結局、3つ全部本番で試してみることにしたんです。
6ヶ月運用して見えてきたのは「○○が最強」みたいな話じゃなくて、本当に「何を優先するか」次第だということ。実装とメトリクスベースで、その辺りを話します。
パフォーマンス:ベンチマークの信じられない話
最初、公式ベンチマークを見たときは「Bunめっちゃ速い」って思ったんですよ。でも本番で動かしたら全然違うんです。
簡単なHTTPサーバーで比較してみました。以下のコードを3つで実装して、実測してみたんですよ:
// Node.js (Express)
const express = require('express');
const app = express();
app.get('/api/users/:id', (req, res) => {
res.json({ id: req.params.id, name: `User ${req.params.id}` });
});
app.listen(3000);
// Deno
import { serve } from "https://deno.land/std@0.208.0/http/server.ts";
serve((req: Request) => {
const url = new URL(req.url);
if (url.pathname.startsWith("/api/users/")) {
const id = url.pathname.split("/").pop();
return new Response(
JSON.stringify({ id, name: `User ${id}` }),
{ headers: { "Content-Type": "application/json" } }
);
}
return new Response("Not found", { status: 404 });
}, { port: 3000 });
// Bun
import { serve } from "bun";
serve({
port: 3000,
fetch(req) {
const url = new URL(req.url);
if (url.pathname.startsWith("/api/users/")) {
const id = url.pathname.split("/").pop();
return Response.json({ id, name: `User ${id}` });
}
return new Response("Not found", { status: 404 });
},
});
同じロジックでやって、Apache Benchで100並行・10,000リクエストをぶん回してみたんです。結果がこれです。
xychart-beta
title "単純HTTPサーバー 100並行 10,000リクエスト"
x-axis [Node.js, Deno, Bun]
y-axis "レスポンスタイム (ms)" 0 --> 150
line [120, 95, 78]
Bunが速いのは事実なんですけど、正直どれもこのレベルの差なら本番では誤差ですよね。でも、メモリ使用量を見ると、また違う話になるんです。
xychart-beta
title "メモリ使用量(アイドル状態)"
x-axis [Node.js, Deno, Bun]
y-axis "メモリ (MB)" 0 --> 120
bar [110, 65, 88]
ここで気づいたのは、Denoが本当に軽い。Bunは言語仕様が独自だから、NPMパッケージ使いまくるとメモリが跳ねるんですよ。Node.jsは…まあ、V8なので仕方ない。
開発体験:型安全性と野蛮さのバランス
実装進めてくと、本当に差が出るんです。
Node.js:野蛮だけど自由
Node.jsは型がない(JavaScriptなので)。TypeScript使っても結局JSに変換されるんで、型は「開発時の補助」に過ぎない。そのぶん自由度は高いんですよ。
うちのチームは年季が入ってるので「型チェックなんかより、テスト書け」という文化があるんですよ。実装スピードは圧倒的に速い。npm生態系も成熟してて、「○○したい」で大体パッケージ見つかるんです。
ただしバージョン管理の地獄は覚悟してください。依存パッケージの更新が関連性のカオスになることは日常茶飯事。
Deno:型安全性の優等生
// Denoだと、ファイルI/O時点で型推論されるのが強い
const data = await Deno.readTextFile("./data.json");
const parsed = JSON.parse(data); // 型はanyだけど...
// こういうのは型安全
import { assertEquals } from "https://deno.land/std@0.208.0/testing/asserts.ts";
Deno.test("addition", () => {
assertEquals(1 + 1, 2);
});
Denoの利点は「TypeScriptが一級市民」ってこと。セットアップの煩雑さが本当に少ないんですよ。あと「セキュリティ」がデフォルトで厳格。ファイルアクセスするなら--allow-read、ネットワーク使うなら--allow-netを付けないと動かないんです。大規模チームだと地味に効果的です。
ただし、npm生態系との統合が微妙なんですよね。Node.jsパッケージをDeno用に変換するレイヤーがあるんですけど、100%互換とは言いがたい。新しいプロジェクトなら問題ないんですが、既存コード移植は大変です。
Bun:モダンだけど若い
// Bun独自のAPIが多い。これはNode.js互換じゃない
const response = await fetch("https://api.example.com/users");
const data = await response.json();
// Bun固有の高速パーサー
import { parseQuery } from "bun";
const query = parseQuery("foo=bar&baz=qux"); // めっちゃ速い
Bunは「モダンなJavaScript実行環境」を目指してるんで、言語仕様が新しい。JSX・TypeScriptがビルトなしで動くのは本当に便利。開発体験は3つの中で一番気持ちいいです。
ただし、Node.jsとの互換性を重視してない機能が結構あるんですよ。npmパッケージも「Bun対応」を明記してるものだけ確実に動く。その点ではまだ若い。正直なところ、成熟度ではまだ一歩劣ります。
本番環境での落とし穴
ここが一番大事。本番で3つ並べたときに見えた現実です。
Node.js:運用資産が豊富
モニタリング・ロギング・デバッグツールの生態系が成熟してるんです。うちの本番環境はDatadogで監視してるんですけど、Node.js用のAPMプラグインはもう10年モノなんで、バグとか落とし穴も周知されてる。
あと、チームの知見が貯まってるんですよね。「あのバージョンのV8は接続プール数を多めにしないとダメ」みたいな、実務ベースのノウハウがある。これが本番トラブルを速く解決するカギになるんです。
Deno:本番での権限管理が光る
正直、Denoはセキュリティを重視する運用では最強。SOC2審査に向けて監査ログを整備してるんですけど、Denoのパーミッションモデルは監査の説明がめっちゃ楽なんですよ。
# Denoなら、起動時の権限で全部可視化される
deno run --allow-read=/data --allow-net=api.example.com server.ts
# CloudWatch Logsに記録される
# └─ 実行時刻: 2026-05-10T15:32:01Z
# └─ 権限: read:/data, net:api.example.com
Node.jsだと、アプリケーションレイヤーで全部実装しないといけない。その点、Denoは「OS的に制御してくれる」感じで、運用は楽です。監査時の説明もシンプルになるんですよね。
Bun:本番環境でまだ成長途上
率直に言うと、本番で急なバグに遭遇するリスクが高い。例えば、僕らが本番投入した直後に「特定条件でメモリリーク」みたいなissueが出てて、緊急パッチ待ちになったことがあるんですよ。
現時点(2026年5月)では、スタートアップレベルの小規模サービスなら全然大丈夫。ただし「3年は同じコードベース保守する」みたいなエンタープライズプロジェクトだと、ちょっと慎重になります。
あとは、本番環境のコンテナ化も考慮が必要。Node.js・Denoはどのコンテナランタイムでも問題ないんですけど、Bunはまだ「公式イメージがちょっと太い」という印象があります。Dockerイメージサイズも無視できない差が出るんですよ。
ランタイム選択フローチャート
実際にプロジェクトで判断してみた結果をまとめると、こんな感じで考えてみてください。
flowchart TD
A[新規プロジェクト] --> B{何を優先する?}
B -->|スピード・生態系| C[Node.js]
B -->|セキュリティ・型安全| D[Deno]
B -->|最高性能・モダン開発| E[Bun]
C --> C1[Express/Fastify/Hono]
C --> C2[npm生態系活用]
C --> C3[本番資産が豊富]
D --> D1[標準ライブラリ充実]
D --> D2[権限管理が厳格]
D --> D3[TypeScript一級市民]
E --> E1[ビルドが高速]
E --> E2[JSX直実行]
E --> E3[小規模サービス向き]
実装パターンの向き不向き
各ランタイムがどんなプロジェクトに向いてるか、実装してわかったことを表にしてみます。
| パターン | Node.js | Deno | Bun |
|---|---|---|---|
| 既存Node.jsプロジェクト | ◎ | × | △ |
| 新規・TypeScript統一 | ◎ | ◎ | ◎ |
| セキュリティが最優先 | △ | ◎ | △ |
| npm生態系に依存 | ◎ | △ | △ |
| 小規模スタートアップ | ◎ | ◎ | ◎ |
| 大規模エンタープライズ | ◎ | △ | × |
| API・バックエンド | ◎ | ◎ | ◎ |
| CLI・ツール開発 | ◎ | ◎ | ◎ |
| リアルタイム・WebSocket | ◎ | ◎ | △ |
| 本番3年以上保守 | ◎ | ◎ | △ |
まとめ
正直、「Node.jsが最強」と言うつもりはないんですよ。でも実務ベースで見ると、こういう判断になるんです。
Node.js は、既存資産がある、チーム知見がある、本番トラブルのノウハウが貯まってるなら、敢えて移行する理由は薄い。大規模チームなら尚更。「動いてるなら触るな」という原則が働くんですよね。
Deno は、セキュリティが必須要件、新規プロジェクト、TypeScript統一したいなら検討の価値あり。ただ、npm互換性を全面的に頼るプロジェクトは避ける方が無難です。標準ライブラリが充実してるから、そっちで何とかなることが多いんですよ。
Bun は、小規模チーム、新規サービス、開発体験優先なら試す価値あり。ただ本番環境は「1年後の成熟度を見守る」くらいのスタンスが良い。成長速度は本当に速いんで、半年後には話が変わってるかもしれません。
最後に、一つだけ言わせてもらうと——どのランタイムでも「デプロイ後の監視・ログ・アラート」の仕組みが無いと、本当に地獄ですよ。パフォーマンス比較より、まずそこから始める。うちはそれで何回も救われてます。実は選んだランタイムより、運用体制の方が重要なんですよね。
皆さんのチームはどれ選んでますか?いや、もう決まってるんですかね。それなら「なぜそれか」をもう一度確認してみるの、悪くないと思いますよ。