この記事の対象読者
- JavaScriptまたはTypeScriptの基本文法を理解している方
- Node.jsやDenoの名前を聞いたことがある方
- 「ランタイムって結局何なの?」とモヤモヤしている方
この記事で得られること
- ランタイムの概念と役割の理解
- JavaScriptエンジンとランタイムの違いの把握
- Node.js、Deno、Bunの内部構造と特徴の比較
- 各ランタイムで実際にコードを動かすスキル
この記事で扱わないこと
- JavaScriptの基礎文法
- 各ランタイムの詳細なAPI解説
- クラウドデプロイの方法
1. ランタイムとの出会い
「このコード、ブラウザでは動くのにサーバーでは動かない...」
JavaScriptを書いていて、そんな経験をしたことはありませんか?
私は何度もあります。
fetchが使えない、windowが存在しない、requireが見つからない。
同じJavaScriptなのに、なぜ動いたり動かなかったりするのか。
この謎を解くカギが「ランタイム」です。
ランタイムとは、あなたが書いたコードを実行するための「環境」のこと。
人間で言えば、コードという「脚本」を演じる「舞台」のようなものです。
舞台が変われば、使える小道具(API)も変わる。
ブラウザという舞台とサーバーという舞台では、用意されている小道具が違うのです。
最近、この「舞台」の選択肢が増えてきました。
老舗のNode.js、セキュリティ重視のDeno、爆速を謳うBun。
それぞれが「うちの舞台が一番だ」と主張しています。
「結局どれを使えばいいの?」
この疑問に答えるには、まず「ランタイムとは何か」を理解する必要があります。
ここまでで、ランタイムがコードを動かす「環境」であることがイメージできたでしょうか。
次は、この記事で使う用語を整理しておきましょう。
2. 前提知識の確認
本題に入る前に、この記事で登場する用語を確認します。
2.1 JavaScriptエンジンとは
JavaScriptのコードを読み取り、実行するプログラムです。
コードを機械語に変換して、CPUが理解できる形にします。
代表的なエンジンは以下の通りです。
| エンジン名 | 開発元 | 採用先 |
|---|---|---|
| V8 | Chrome、Node.js、Deno | |
| JavaScriptCore | Apple | Safari、Bun |
| SpiderMonkey | Mozilla | Firefox |
2.2 非同期I/O(Asynchronous I/O)とは
入出力操作を待たずに次の処理を進める仕組みです。
ファイル読み込みやネットワーク通信は時間がかかります。
非同期I/Oを使うと、待ち時間を無駄にせず他の処理を実行できます。
2.3 イベントループ(Event Loop)とは
非同期処理を管理するための仕組みです。
「この処理が終わったら、次はこれを実行して」という順番を管理します。
JavaScriptがシングルスレッドでも高い並行性を実現できる理由です。
2.4 API(Application Programming Interface)とは
プログラムが他のプログラムと通信するための窓口です。
ランタイムが提供するAPIを通じて、ファイル操作やネットワーク通信が可能になります。
ブラウザではfetchやlocalStorage、サーバーではファイルシステムAPIなどが提供されます。
2.5 ECMAScript(エクマスクリプト)とは
JavaScriptの標準仕様を定めた規格です。
Object、Array、Promiseなどの基本機能はECMAScriptで定義されています。
ただし、setTimeoutやfetchはECMAScriptには含まれません。
これらの用語が押さえられたら、ランタイムの歴史を見ていきましょう。
3. ランタイムが生まれた背景
3.1 ブラウザの外でJavaScriptを動かしたい
1995年、Brendan Eich氏がNetscape Navigator用にJavaScriptを開発しました。
当初はブラウザ内でしか動かない言語でした。
フォームのバリデーションや簡単なアニメーションが主な用途でした。
しかし、開発者たちは考えました。
「フロントエンドもバックエンドも同じ言語で書けたら便利じゃない?」
この夢を実現したのがNode.jsです。
2009年、Ryan Dahl氏がNode.jsを発表しました。
GoogleのV8エンジンを取り出し、サーバーサイドで動くように仕立て上げたのです。
3.2 なぜエンジンだけでは足りないのか
V8エンジンは優秀ですが、それだけではサーバーは作れません。
V8が実装するのはECMAScriptの仕様だけ。
ファイルを読み書きする機能も、ネットワーク通信の機能も含まれていません。
ここで「ランタイム」の出番です。
ランタイムは、エンジンを中核に据えつつ、以下を追加で提供します。
| 追加される機能 | 説明 |
|---|---|
| ファイルシステムAPI | ファイルの読み書き |
| ネットワークAPI | HTTP通信、TCP/UDP |
| イベントループ | 非同期処理の管理 |
| 標準ライブラリ | よく使う機能のセット |
つまり、エンジンは「計算する脳」、ランタイムは「手足を含めた全身」です。
3.3 ランタイムの進化
Node.jsの成功を受けて、新しいランタイムが登場しました。
| ランタイム | 登場年 | エンジン | 実装言語 | 特徴 |
|---|---|---|---|---|
| Node.js | 2009 | V8 | C++ | 巨大なエコシステム |
| Deno | 2018 | V8 | Rust | セキュリティ重視 |
| Bun | 2022 | JavaScriptCore | Zig | 爆速パフォーマンス |
それぞれが異なる思想で設計されています。
Node.jsは互換性とエコシステムを、Denoはセキュリティと開発体験を、Bunは速度を重視しています。
背景がわかったところで、抽象的な概念から順に、具体的な仕組みを見ていきましょう。
4. ランタイムの基本概念
4.1 ランタイムの構成要素
ランタイムは複数のコンポーネントから構成されています。
それぞれの役割を理解しましょう。
┌─────────────────────────────────────────────────────────┐
│ JavaScript Runtime │
│ ┌───────────────────────────────────────────────────┐ │
│ │ JavaScript Engine (V8等) │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌──────────┐ │ │
│ │ │ Parser │ │ Compiler │ │ GC │ │ │
│ │ │(構文解析器) │ │(コンパイラ)│ │(ガベコレ)│ │ │
│ │ └─────────────┘ └─────────────┘ └──────────┘ │ │
│ └───────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ 非同期I/Oライブラリ (libuv/Tokio等) │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌──────────┐ │ │
│ │ │ Event Loop │ │ Thread Pool │ │ I/O操作 │ │ │
│ │ └─────────────┘ └─────────────┘ └──────────┘ │ │
│ └───────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ 標準API・標準ライブラリ │ │
│ │ fs, http, crypto, path, url, stream, etc. │ │
│ └───────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
4.2 イベントループの仕組み
イベントループは、ランタイムの心臓部です。
シングルスレッドのJavaScriptが、多くの処理を並行して行える秘密がここにあります。
レストランで例えてみましょう。
ウェイター(メインスレッド)は1人しかいません。
でも、厨房(スレッドプール)には複数のシェフがいます。
1. お客様から注文を受ける(リクエスト受付)
2. 時間のかかる料理はシェフに依頼(I/O操作をスレッドプールへ)
3. その間、他のお客様の注文を受ける(次の処理を実行)
4. 料理ができたら、お客様に届ける(コールバック実行)
このウェイターの動きが「イベントループ」です。
待ち時間を無駄にせず、効率的に仕事を回しています。
実際のイベントループは、以下のフェーズで構成されています。
| フェーズ | 処理内容 |
|---|---|
| timers |
setTimeout、setIntervalのコールバック |
| pending callbacks | 延期されたI/Oコールバック |
| poll | 新しいI/Oイベントの取得 |
| check |
setImmediateのコールバック |
| close callbacks | closeイベントのコールバック |
4.3 JavaScriptエンジンとランタイムの違い
この違いを理解することが、ランタイムを理解する核心です。
// ECMAScript(エンジンが処理)
const arr = [1, 2, 3];
const doubled = arr.map(x => x * 2);
console.log(doubled); // [2, 4, 6]
// ランタイムが提供するAPI
setTimeout(() => console.log('hello'), 1000); // Node.js/Deno/Bun
await fetch('https://api.example.com'); // Deno/Bun(Node.jsは18以降)
const file = await Deno.readTextFile('a.txt'); // Deno固有
Array.mapはECMAScriptの仕様なので、どのエンジンでも動きます。
一方、setTimeoutやfetchはランタイムが提供するAPIです。
ランタイムごとに使えるAPIが異なるのは、このためです。
4.4 各ランタイムの非同期I/O実装
各ランタイムは、異なるライブラリで非同期I/Oを実現しています。
| ランタイム | 非同期I/Oライブラリ | 特徴 |
|---|---|---|
| Node.js | libuv(C言語) | 歴史が長く安定 |
| Deno | Tokio(Rust) | メモリ安全性が高い |
| Bun | 独自実装(Zig) | 極限まで最適化 |
Node.jsのlibuvは、10年以上の実績があります。
クロスプラットフォーム対応も万全で、Windows/macOS/Linuxで同じように動作します。
Denoが採用するTokioは、Rust製の非同期ランタイムです。
Rustのメモリ安全性を活かし、特定の種類のバグを構造的に防いでいます。
Bunは独自のZig実装で、メモリコピーの最小化などの最適化を施しています。
基本概念が理解できたところで、これらの抽象的な概念を具体的なコードで実装していきましょう。
5. 実際に使ってみよう
5.1 環境構築
3つのランタイムをインストールします。
Node.js:
# macOS (Homebrew)
brew install node
# Windows (公式インストーラー)
# https://nodejs.org/ からダウンロード
# バージョン確認
node --version
Deno:
# macOS / Linux
curl -fsSL https://deno.land/install.sh | sh
# Windows (PowerShell)
irm https://deno.land/install.ps1 | iex
# バージョン確認
deno --version
Bun:
# macOS / Linux
curl -fsSL https://bun.sh/install | bash
# Windows
powershell -c "irm bun.sh/install.ps1 | iex"
# バージョン確認
bun --version
5.2 設定ファイルの準備
以下の4種類の設定ファイルを用意しています。用途に応じて選択してください。
Node.js用(package.json)
{
"name": "runtime-demo",
"version": "1.0.0",
"type": "module",
"scripts": {
"start": "node server.js",
"dev": "node --watch server.js",
"test": "node --test"
},
"engines": {
"node": ">=20.0.0"
}
}
このままコピーしてpackage.jsonとして保存できます。
Deno用(deno.json)
{
"compilerOptions": {
"strict": true,
"lib": ["deno.window"]
},
"imports": {
"@std/": "jsr:@std/"
},
"tasks": {
"start": "deno run --allow-net server.ts",
"dev": "deno run --watch --allow-net server.ts",
"test": "deno test --allow-read"
},
"fmt": {
"useTabs": false,
"lineWidth": 80,
"indentWidth": 2,
"singleQuote": true
}
}
このままコピーしてdeno.jsonとして保存できます。
Bun用(bunfig.toml)
# bunfig.toml - Bun設定ファイル
# このままコピーして使える設定
[run]
# 自動リロードを有効化
watch = true
[install]
# パッケージインストールの設定
production = false
[test]
# テスト実行の設定
coverage = true
coverageDir = "coverage"
このままコピーしてbunfig.tomlとして保存できます。
TypeScript共通設定(tsconfig.json)
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"skipLibCheck": true,
"esModuleInterop": true,
"declaration": true,
"outDir": "./dist"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
Node.jsとBunで共通して使えるTypeScript設定です。
5.3 Hello World比較
同じ「Hello World」を各ランタイムで実装してみましょう。
Node.js版(hello_node.js):
/**
* Node.js版 Hello World
* 実行方法: node hello_node.js
*/
// ランタイム情報を表示
console.log('=== Node.js Runtime ===');
console.log(`Node.js version: ${process.version}`);
console.log(`V8 version: ${process.versions.v8}`);
console.log(`Platform: ${process.platform}`);
console.log(`Architecture: ${process.arch}`);
// 非同期処理のデモ
async function main() {
console.log('\n--- 非同期処理デモ ---');
// Promise.resolve(マイクロタスク)
Promise.resolve().then(() => console.log('1. Promise resolved'));
// setTimeout(マクロタスク)
setTimeout(() => console.log('3. setTimeout callback'), 0);
// setImmediate(Node.js固有)
setImmediate(() => console.log('2. setImmediate callback'));
console.log('0. Synchronous code');
}
main().catch(console.error);
Deno版(hello_deno.ts):
/**
* Deno版 Hello World
* 実行方法: deno run hello_deno.ts
*/
// ランタイム情報を表示
console.log('=== Deno Runtime ===');
console.log(`Deno version: ${Deno.version.deno}`);
console.log(`V8 version: ${Deno.version.v8}`);
console.log(`TypeScript version: ${Deno.version.typescript}`);
// 権限情報を表示(Deno固有の機能)
const permissions = {
read: await Deno.permissions.query({ name: 'read' }),
write: await Deno.permissions.query({ name: 'write' }),
net: await Deno.permissions.query({ name: 'net' }),
};
console.log(`Permissions: ${JSON.stringify(permissions)}`);
// 非同期処理のデモ
async function main(): Promise<void> {
console.log('\n--- 非同期処理デモ ---');
// Promise.resolve(マイクロタスク)
Promise.resolve().then(() => console.log('1. Promise resolved'));
// setTimeout(タイマー)
setTimeout(() => console.log('2. setTimeout callback'), 0);
// queueMicrotask(マイクロタスク)
queueMicrotask(() => console.log('1.5 queueMicrotask'));
console.log('0. Synchronous code');
}
main().catch(console.error);
Bun版(hello_bun.ts):
/**
* Bun版 Hello World
* 実行方法: bun run hello_bun.ts
*/
// ランタイム情報を表示
console.log('=== Bun Runtime ===');
console.log(`Bun version: ${Bun.version}`);
console.log(`Revision: ${Bun.revision}`);
// Bun固有の高速API
const file = Bun.file(import.meta.path);
console.log(`This file size: ${file.size} bytes`);
// 非同期処理のデモ
async function main(): Promise<void> {
console.log('\n--- 非同期処理デモ ---');
// Promise.resolve(マイクロタスク)
Promise.resolve().then(() => console.log('1. Promise resolved'));
// setTimeout(タイマー)
setTimeout(() => console.log('2. setTimeout callback'), 0);
// Bun.sleep(Bun固有の高精度スリープ)
await Bun.sleep(1);
console.log('3. After Bun.sleep(1ms)');
console.log('0. Synchronous code');
}
main().catch(console.error);
5.4 HTTPサーバー比較
各ランタイムでHTTPサーバーを実装します。
Node.js版(server_node.js):
/**
* Node.js版 HTTPサーバー
* 実行方法: node server_node.js
* アクセス: http://localhost:3000
*/
import { createServer } from 'node:http';
const PORT = 3000;
const server = createServer((req, res) => {
const url = new URL(req.url, `http://localhost:${PORT}`);
// ルーティング
if (url.pathname === '/') {
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end('Hello from Node.js!');
return;
}
if (url.pathname === '/api/info') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
runtime: 'Node.js',
version: process.version,
timestamp: new Date().toISOString()
}));
return;
}
// 404
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
});
server.listen(PORT, () => {
console.log(`Node.js server running at http://localhost:${PORT}/`);
});
Deno版(server_deno.ts):
/**
* Deno版 HTTPサーバー
* 実行方法: deno run --allow-net server_deno.ts
* アクセス: http://localhost:3000
*/
const PORT = 3000;
function handler(request: Request): Response {
const url = new URL(request.url);
// ルーティング
if (url.pathname === '/') {
return new Response('Hello from Deno!', {
status: 200,
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
});
}
if (url.pathname === '/api/info') {
return Response.json({
runtime: 'Deno',
version: Deno.version.deno,
typescript: Deno.version.typescript,
timestamp: new Date().toISOString(),
});
}
// 404
return new Response('Not Found', { status: 404 });
}
console.log(`Deno server running at http://localhost:${PORT}/`);
Deno.serve({ port: PORT }, handler);
Bun版(server_bun.ts):
/**
* Bun版 HTTPサーバー
* 実行方法: bun run server_bun.ts
* アクセス: http://localhost:3000
*/
const PORT = 3000;
const server = Bun.serve({
port: PORT,
fetch(request: Request): Response {
const url = new URL(request.url);
// ルーティング
if (url.pathname === '/') {
return new Response('Hello from Bun!', {
status: 200,
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
});
}
if (url.pathname === '/api/info') {
return Response.json({
runtime: 'Bun',
version: Bun.version,
revision: Bun.revision,
timestamp: new Date().toISOString(),
});
}
// 404
return new Response('Not Found', { status: 404 });
},
});
console.log(`Bun server running at http://localhost:${server.port}/`);
5.5 実行結果
各サーバーを起動し、/api/infoにアクセスした結果です。
Node.js:
$ node server_node.js
Node.js server running at http://localhost:3000/
$ curl http://localhost:3000/api/info
{"runtime":"Node.js","version":"v22.0.0","timestamp":"2026-01-11T10:30:00.000Z"}
Deno:
$ deno run --allow-net server_deno.ts
Deno server running at http://localhost:3000/
Listening on http://0.0.0.0:3000/
$ curl http://localhost:3000/api/info
{"runtime":"Deno","version":"2.1.0","typescript":"5.7.3","timestamp":"2026-01-11T10:30:00.000Z"}
Bun:
$ bun run server_bun.ts
Bun server running at http://localhost:3000/
$ curl http://localhost:3000/api/info
{"runtime":"Bun","version":"1.2.0","revision":"abc123","timestamp":"2026-01-11T10:30:00.000Z"}
5.6 よくあるエラーと対処法
| エラー | ランタイム | 原因 | 対処法 |
|---|---|---|---|
Cannot find module |
Node.js | モジュールが見つからない |
npm installを実行 |
require is not defined |
Node.js (ESM) | CommonJS構文を使用 |
importに変更、またはpackage.jsonで"type": "module"を削除 |
PermissionDenied |
Deno | 権限フラグ未指定 |
--allow-net等のフラグを追加 |
Module not found |
Deno | 拡張子の省略 |
.tsや.jsを明記 |
error: Could not resolve |
Bun | パッケージ未インストール |
bun addでパッケージを追加 |
EADDRINUSE |
全て | ポートが使用中 | 別のポート番号を指定、または既存プロセスを終了 |
私が最初にハマったのは、Denoの権限エラーです。
Node.js脳でdeno run server.tsと実行したら、ネットワークアクセスが拒否されました。
「--allow-netを付けるのか...」と気づくまで30分かかりました。
基本的な使い方をマスターしたので、次は応用例を見ていきましょう。
6. ユースケース別ガイド
6.1 ユースケース1: 既存のNode.jsプロジェクトを維持したい
- 想定読者: 既にNode.jsで運用中のプロジェクトを持っている方
- 推奨ランタイム: Node.js
- 理由: 豊富なnpmエコシステム、圧倒的な情報量、安定した運用実績
package.json(本番環境用):
{
"name": "production-app",
"version": "1.0.0",
"type": "module",
"engines": {
"node": ">=20.0.0"
},
"scripts": {
"start": "NODE_ENV=production node dist/server.js",
"build": "tsc",
"dev": "node --watch src/server.ts",
"test": "node --test",
"lint": "eslint src/"
},
"dependencies": {
"express": "^4.18.0"
},
"devDependencies": {
"@types/express": "^4.17.0",
"@types/node": "^20.0.0",
"typescript": "^5.0.0",
"eslint": "^8.0.0"
}
}
サンプルコード:
/**
* Express.jsを使ったAPIサーバー
* Node.jsエコシステムの強みを活かした実装
*/
import express from 'express';
const app = express();
app.use(express.json());
app.get('/api/health', (req, res) => {
res.json({ status: 'ok', runtime: 'Node.js' });
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
6.2 ユースケース2: セキュリティ重視の新規プロジェクト
- 想定読者: セキュリティを最優先に考える方、TypeScriptをネイティブで使いたい方
- 推奨ランタイム: Deno
- 理由: デフォルトで安全、TypeScriptがゼロ設定、Web標準API準拠
deno.json(セキュリティ重視設定):
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"noUnusedLocals": true,
"noUnusedParameters": true
},
"imports": {
"@std/": "jsr:@std/",
"@oak/": "jsr:@oak/"
},
"tasks": {
"start": "deno run --allow-net=:3000 --allow-env=PORT server.ts",
"dev": "deno run --watch --allow-net=:3000 server.ts",
"check": "deno check server.ts",
"lint": "deno lint",
"fmt": "deno fmt"
},
"permissions": {
"net": ["localhost:3000"],
"env": ["PORT"]
}
}
権限を最小限に絞っている点に注目してください。
--allow-net=:3000とすることで、ポート3000以外への接続を拒否しています。
サンプルコード:
/**
* Denoのセキュリティ機能を活かしたAPIサーバー
* 権限を最小限に絞った安全な実装
*/
const PORT = parseInt(Deno.env.get('PORT') || '3000');
async function handler(request: Request): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === '/api/health') {
return Response.json({
status: 'ok',
runtime: 'Deno',
secure: true,
permissions: await getPermissionStatus(),
});
}
return new Response('Not Found', { status: 404 });
}
async function getPermissionStatus() {
return {
net: (await Deno.permissions.query({ name: 'net' })).state,
read: (await Deno.permissions.query({ name: 'read' })).state,
write: (await Deno.permissions.query({ name: 'write' })).state,
};
}
console.log(`Secure Deno server running on port ${PORT}`);
Deno.serve({ port: PORT }, handler);
6.3 ユースケース3: 高速な開発体験とパフォーマンス
- 想定読者: 開発速度を重視する方、起動時間を短縮したい方
- 推奨ランタイム: Bun
- 理由: 圧倒的な速度、Node.js互換性、オールインワンツール
package.json(Bun最適化設定):
{
"name": "bun-fast-app",
"version": "1.0.0",
"type": "module",
"scripts": {
"start": "bun run src/server.ts",
"dev": "bun --watch run src/server.ts",
"test": "bun test",
"build": "bun build src/server.ts --outdir=dist --target=bun"
},
"dependencies": {
"hono": "^4.0.0"
},
"devDependencies": {
"@types/bun": "latest"
}
}
サンプルコード:
/**
* BunとHonoフレームワークを使った高速APIサーバー
* Bunの速度を最大限に活かした実装
*/
import { Hono } from 'hono';
const app = new Hono();
// Bunの高速ファイルAPI
const packageJson = await Bun.file('package.json').json();
app.get('/api/health', (c) => {
return c.json({
status: 'ok',
runtime: 'Bun',
version: Bun.version,
appName: packageJson.name,
});
});
// Bunのネイティブサーバー
export default {
port: 3000,
fetch: app.fetch,
};
console.log('Bun + Hono server running on port 3000');
6.4 ランタイム選択フローチャート
あなたのプロジェクトに最適なランタイムは?
┌─ 既存のNode.jsプロジェクトがある?
│ └─ YES → Node.js(互換性重視)
│
├─ セキュリティが最優先?
│ └─ YES → Deno(権限システム)
│
├─ TypeScriptをゼロ設定で使いたい?
│ └─ YES → Deno or Bun
│
├─ 起動速度・実行速度が最優先?
│ └─ YES → Bun(JavaScriptCore)
│
├─ 情報量・コミュニティの大きさ重視?
│ └─ YES → Node.js(圧倒的エコシステム)
│
└─ 新規プロジェクトで最新技術を試したい?
└─ YES → Deno or Bun(モダンな設計)
ユースケースが把握できたところで、この記事を読んだ後の学習パスを確認しましょう。
7. 学習ロードマップ
この記事を読んだ後、次のステップとして以下をおすすめします。
初級者向け(まずはここから)
-
公式チュートリアルを試す
-
Hello Worldを各ランタイムで実行
- この記事のサンプルコードを動かしてみる
- 同じ処理を3つのランタイムで書き比べる
-
シンプルなAPIサーバーを作る
- TODOアプリのバックエンドを実装
- JSONを返すAPIを作成
中級者向け(実践に進む)
-
フレームワークを使ってみる
- Node.js: Express.js, Fastify
- Deno: Oak, Fresh
- Bun: Hono, Elysia
-
データベースと接続する
- PostgreSQL, MySQL, MongoDB
- ORM(Prisma, Drizzle)の活用
-
パフォーマンス計測を行う
- wrkでベンチマーク
- 各ランタイムの速度を比較
上級者向け(さらに深く)
-
内部実装を読む
-
イベントループを深掘り
-
コントリビュートする
- Issue報告、ドキュメント改善
- 標準ライブラリへの貢献
8. まとめ
この記事では、JavaScriptランタイムについて以下を解説しました。
- ランタイムの定義: コードを実行する「環境」であり、エンジン + API + イベントループで構成される
- エンジンとの違い: エンジンはECMAScriptの実行、ランタイムはI/OやAPIを追加で提供
- イベントループ: シングルスレッドで高い並行性を実現する仕組み
- 3大ランタイム: Node.js(エコシステム)、Deno(セキュリティ)、Bun(速度)
- 選択基準: プロジェクトの要件に応じて最適なランタイムを選ぶ
私の所感
正直に言うと、「どのランタイムが最強か」という問いに正解はありません。
状況によって答えが変わるからです。
既存のNode.jsプロジェクトを無理にDenoやBunに移行する必要はありません。
Node.jsのエコシステムは、10年以上の積み重ねで成り立っています。
その価値は、新しいランタイムの速度向上では簡単に覆りません。
一方で、新規プロジェクトなら選択肢が広がります。
セキュリティが重要ならDeno、速度が命ならBun。
「一度試してみて、肌に合うものを選ぶ」というアプローチが現実的です。
私自身、最近のプロジェクトではDenoを使うことが増えました。
--allow-netと明示的に書くことで、「このコードは何にアクセスするのか」が一目でわかります。
この「意図の明示」が、チーム開発でのコードレビューを楽にしてくれています。
ランタイムの世界は今、群雄割拠の時代です。
競争があるからこそ、それぞれが進化を続けています。
どれか1つに固執せず、選択肢を持っておくことが、エンジニアとしての柔軟性につながると思います。
ぜひ3つのランタイムを試してみてください。
きっと、新しい発見があるはずです。