「Node.jsって名前は聞くけど、実際に何ができるの?」「ESMとCommonJSの違いが分からない」「Worker Threadsって難しそう…」——バックエンド開発を始める初心者にとって、Node.jsは入口でつまずきやすい技術です。しかし、Node.js 22 LTSは”組込モジュールだけで実用アプリが書ける”レベルまで進化しており、もはやexpressやdotenvに頼らずとも本格的なサーバー・CLI・データ処理が可能です。
本記事では筆者が実務で使っているコード(全てコピペで動作確認済み)を交えながら、Node.js 22の核となる機能を体系的に解説します。インストールから組込ストリーム、Worker Threads、組込SQLite、Permission Modelまで一気通貫で学べる構成です。
- Node.js 22 LTSの実用機能を40個以上のコピペ可能コードで習得
- ESM / CommonJSの違いと.mjs/.cjs/.tsの使い分け
- 組込fetch / streams / Worker Threads / SQLiteの実装パターン
- サードパーティ依存を減らす”Pure Node”スタイルの設計思想
- 1. Node.js 22 LTSの全体像と導入
- 2. ESMとCommonJS——モジュールの基礎
- 3. TypeScriptを”そのまま”実行する(–experimental-strip-types)
- 4. ファイルシステム(fs / fs/promises)
- 5. path / url / process 〜OS非依存の作法〜
- 6. HTTPサーバーとfetchクライアント
- 7. Streams 〜大規模データ処理の核〜
- 8. 並行処理 〜Worker Threads / cluster / child_process〜
- 9. 組込ユーティリティ群
- 10. テストとデバッグ
- 11. Node.js 22の新機能を使い倒す
- 12. 実践:CLIツールとミニWeb APIを作る
- 13. 次のステップ:学習ロードマップ
- 14. まとめ
1. Node.js 22 LTSの全体像と導入
Node.js 22は2024年10月にActive LTSとなり、2027年4月までサポートが続く長期安定版です。V8 12.4、組込fetch安定化、組込test runner、Permission Model、--experimental-strip-typesによるTypeScript直接実行など、これまで外部ツールに頼っていた機能が標準搭載されました。
1-1. nvmでのインストール(macOS/Linux)
Node.jsのバージョン管理はnvmが定番です。複数バージョンを切替えながらプロジェクトごとに固定できます。
# nvmインストール
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
# シェル再起動後
nvm install 22
nvm use 22
nvm alias default 22
# 確認
node --version # v22.x.x
npm --version
1-2. Voltaでのインストール(プロジェクト固定向け)
Voltaはpackage.jsonに書かれたNodeバージョンを自動切替してくれる便利ツール。チーム開発で重宝します。
# Voltaインストール(macOS/Linux)
curl https://get.volta.sh | bash
# Node.js 22固定
volta install node@22
volta pin node@22
# package.jsonに以下が自動追記される
# "volta": { "node": "22.11.0" }
1-3. asdfでのインストール(多言語管理)
Python・Ruby・Goも同時に管理したい人はasdfを選びます。
# asdf + nodejsプラグイン
asdf plugin add nodejs
asdf install nodejs 22.11.0
asdf global nodejs 22.11.0
# プロジェクト固定
echo "nodejs 22.11.0" > .tool-versions
1-4. WindowsでのインストールとWSL推奨
Windowsで本格開発するならWSL2 + Ubuntu + nvmが王道です。ネイティブのfs性能やシェル互換性が大きく違います。
# PowerShell(管理者)
wsl --install -d Ubuntu-22.04
# Ubuntu起動後
sudo apt update && sudo apt install -y curl build-essential
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
source ~/.bashrc
nvm install 22
1-5. バージョン確認とハローワールド
# 環境確認
node --version
node -p "process.versions"
node -p "process.platform"
node -p "process.arch"
# 即席REPL
node
> 1 + 2
3
> .exit
// hello.mjs
console.log("Hello, Node.js 22!");
console.log(`Running on ${process.platform} ${process.arch}`);
console.log(`PID: ${process.pid}`);
node hello.mjs
# Hello, Node.js 22!
# Running on linux x64
# PID: 12345
node-v18などの古いLTSを使い続けてfetchやnode:testが動かない、と悩むケース。新規プロジェクトは22以上推奨。
2. ESMとCommonJS——モジュールの基礎
Node.jsには2種類のモジュールシステムがあります。2026年現在、新規プロジェクトはESM(ECMAScript Modules)が標準。CommonJSは既存資産・古いライブラリ互換用と覚えれば良いです。
2-1. package.jsonの “type” フィールド
プロジェクトのデフォルトモジュール形式を決める最重要設定。"module"でESM、"commonjs"でCommonJS(省略時のデフォルト)。
{
"name": "my-app",
"version": "1.0.0",
"type": "module",
"main": "src/index.js",
"engines": {
"node": ">=22.0.0"
}
}
2-2. import / export(ESM)
// math.js (ESM)
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
export const PI = 3.14159;
export default function greet(name) {
return `Hello, ${name}!`;
}
// main.js (ESM)
import greet, { add, multiply, PI } from "./math.js";
console.log(greet("Node")); // Hello, Node!
console.log(add(2, 3)); // 5
console.log(multiply(4, 5)); // 20
console.log(`PI: ${PI}`); // PI: 3.14159
2-3. require / module.exports(CommonJS)
// math.cjs (CommonJS)
function add(a, b) { return a + b; }
function multiply(a, b) { return a * b; }
module.exports = { add, multiply, PI: 3.14159 };
module.exports.greet = (name) => `Hello, ${name}!`;
// main.cjs (CommonJS)
const { add, multiply, PI, greet } = require("./math.cjs");
console.log(greet("CommonJS"));
console.log(add(10, 20));
2-4. .mjs / .cjs / .js / .ts の使い分け
| 拡張子 | 扱われ方 | 推奨用途 |
|---|---|---|
.mjs |
常にESM | CommonJSプロジェクトに1枚だけESM混ぜたい時 |
.cjs |
常にCommonJS | ESMプロジェクトで古いライブラリ用 |
.js |
package.jsonの”type”次第 | 通常の本体コード |
.ts |
–experimental-strip-typesで実行可 | TypeScript直書き(後述) |
2-5. ESMのトップレベルawait
ESMでは関数で囲まずに直接awaitが書けるのが大きな利点です。
// fetch-data.mjs
const res = await fetch("https://api.github.com/users/nodejs");
const data = await res.json();
console.log(data.name, data.public_repos);
2-6. ESMでCommonJSモジュールを使う
// ESMからCommonJSパッケージを読む
import express from "express"; // CommonJS package
import { readFile } from "node:fs/promises"; // 組込ESM
// CommonJSのnamed exportはdefault import経由
import pkg from "commonjs-only-lib";
const { someFunc } = pkg;
2-7. import.metaでパス取得(ESMの`__dirname`代替)
ESMには__dirnameがないため、import.meta.urlとfileURLToPathを使います。
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
console.log(__filename);
console.log(__dirname);
console.log(join(__dirname, "data", "config.json"));
3. TypeScriptを”そのまま”実行する(–experimental-strip-types)
Node.js 22の目玉機能の一つがTypeScript直接実行。tscでビルドせず、型注釈を実行時に剥がして走らせます。
3-1. 最小サンプル
// app.ts
interface User {
id: number;
name: string;
}
const user: User = { id: 1, name: "Taro" };
function greet(u: User): string {
return `Hello, ${u.name} (id: ${u.id})`;
}
console.log(greet(user));
node --experimental-strip-types app.ts
# Hello, Taro (id: 1)
3-2. 制約と現実的な使いどころ
enumやnamespaceのような”値を生むTS構文”は未対応(Node 22.6時点)- importパスは
./foo.tsのように拡張子付与必須 - 本番ビルドは引き続き
tscかtsx/esbuildが無難 - スクリプト・小ツール用途に最適
3-3. tsconfig.jsonの最小構成
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"allowImportingTsExtensions": true,
"noEmit": true
}
}
4. ファイルシステム(fs / fs/promises)
Node.jsの最頻出モジュール。新規コードは必ずfs/promisesのasync APIを使うのが鉄則です。同期APIはCLIの初期化など特殊な場面のみ。
4-1. 読み書きの基本
import { readFile, writeFile } from "node:fs/promises";
// テキスト読込
const txt = await readFile("./input.txt", "utf-8");
console.log(txt.length, "文字");
// テキスト書込
await writeFile("./output.txt", "Hello, fs!n", "utf-8");
// JSON読込
const json = JSON.parse(await readFile("./config.json", "utf-8"));
console.log(json);
4-2. ディレクトリ操作
import { mkdir, readdir, rm, stat } from "node:fs/promises";
await mkdir("./tmp/foo/bar", { recursive: true });
const entries = await readdir("./tmp", { withFileTypes: true });
for (const e of entries) {
console.log(e.isDirectory() ? "DIR " : "FILE", e.name);
}
// 再帰削除
await rm("./tmp", { recursive: true, force: true });
// ファイル情報
const st = await stat("./package.json");
console.log("size:", st.size, "bytes");
4-3. fs.watchでファイル変更監視
import { watch } from "node:fs/promises";
const ac = new AbortController();
setTimeout(() => ac.abort(), 30_000); // 30秒で停止
try {
const watcher = watch("./src", { recursive: true, signal: ac.signal });
for await (const event of watcher) {
console.log(`[${event.eventType}] ${event.filename}`);
}
} catch (err) {
if (err.name === "AbortError") {
console.log("Watcher stopped.");
} else {
throw err;
}
}
4-4. fsの同期API(起動初期化のみ推奨)
import { readFileSync, existsSync } from "node:fs";
if (existsSync(".env")) {
const content = readFileSync(".env", "utf-8");
console.log("env loaded:", content.length, "chars");
}
5. path / url / process 〜OS非依存の作法〜
5-1. pathモジュール
import path from "node:path";
// プラットフォーム非依存の結合
console.log(path.join("src", "lib", "utils.js"));
// linux: src/lib/utils.js, win: srclibutils.js
console.log(path.resolve("./data", "../config", "app.json"));
console.log(path.extname("photo.jpg")); // .jpg
console.log(path.basename("/a/b/c.txt")); // c.txt
console.log(path.dirname("/a/b/c.txt")); // /a/b
console.log(path.parse("/a/b/c.txt"));
// { root: '/', dir: '/a/b', base: 'c.txt', ext: '.txt', name: 'c' }
5-2. urlモジュール
const u = new URL("https://example.com/api/users?page=2&limit=20");
console.log(u.hostname); // example.com
console.log(u.pathname); // /api/users
console.log(u.searchParams.get("page")); // 2
// クエリ追加
u.searchParams.set("sort", "asc");
console.log(u.toString());
5-3. processオブジェクト
// 引数取得
console.log(process.argv);
// [ 'node', '/path/to/script.js', 'foo', 'bar' ]
// 環境変数
console.log(process.env.NODE_ENV);
console.log(process.env.PATH);
// 終了コード
if (!process.env.API_KEY) {
console.error("API_KEY is required");
process.exit(1);
}
// 標準入力
process.stdin.setEncoding("utf-8");
process.stdin.on("data", (chunk) => {
console.log("input:", chunk.trim());
});
5-4. –env-file で .envサポート(dotenv不要)
Node.js 20.6以降、標準で.envファイルを読み込めるようになり、dotenvパッケージは不要になりました。
# .env
API_KEY=sk-xxxxxxxxxxxx
DB_HOST=localhost
DB_PORT=5432
// app.mjs
console.log(process.env.API_KEY);
console.log(process.env.DB_HOST);
node --env-file=.env app.mjs
# 複数envファイル
node --env-file=.env --env-file=.env.local app.mjs
6. HTTPサーバーとfetchクライアント
6-1. 組込httpモジュールでサーバー
import http from "node:http";
const server = http.createServer((req, res) => {
console.log(`${req.method} ${req.url}`);
if (req.url === "/health") {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ status: "ok", time: new Date().toISOString() }));
return;
}
res.writeHead(404, { "Content-Type": "text/plain" });
res.end("Not Found");
});
server.listen(3000, () => {
console.log("Server: http://localhost:3000");
});
6-2. JSON POSTを受け付けるシンプルAPI
import http from "node:http";
const items = [];
const server = http.createServer(async (req, res) => {
if (req.method === "GET" && req.url === "/items") {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(items));
return;
}
if (req.method === "POST" && req.url === "/items") {
let body = "";
for await (const chunk of req) body += chunk;
try {
const data = JSON.parse(body);
data.id = items.length + 1;
items.push(data);
res.writeHead(201, { "Content-Type": "application/json" });
res.end(JSON.stringify(data));
} catch {
res.writeHead(400).end("Invalid JSON");
}
return;
}
res.writeHead(404).end("Not Found");
});
server.listen(3000);
6-3. 組込fetchでHTTPクライアント
Node.js 18以降、ブラウザ互換のfetchが組込みになりました。axiosやnode-fetchは新規不要。
// GET
const res = await fetch("https://api.github.com/users/nodejs");
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
console.log(data.name, data.public_repos);
// POST
const post = await fetch("https://httpbin.org/post", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ hello: "world" }),
});
console.log(await post.json());
6-4. AbortControllerでタイムアウト付きfetch
async function fetchWithTimeout(url, ms = 5000) {
const ac = new AbortController();
const timer = setTimeout(() => ac.abort(), ms);
try {
const res = await fetch(url, { signal: ac.signal });
return await res.text();
} finally {
clearTimeout(timer);
}
}
try {
const html = await fetchWithTimeout("https://example.com", 3000);
console.log(html.length, "chars");
} catch (err) {
if (err.name === "AbortError") console.error("Timeout!");
else throw err;
}
6-5. undiciで高度なHTTPクライアント
Node.js組込fetchの実体はundici。生のAPIを使うとkeep-alive・streaming・interceptorなど細かい制御が可能です。
import { request, Agent } from "undici";
const agent = new Agent({
keepAliveTimeout: 10_000,
connections: 50,
});
const { statusCode, headers, body } = await request(
"https://api.github.com/users/nodejs",
{ dispatcher: agent }
);
console.log("status:", statusCode);
const json = await body.json();
console.log("name:", json.name);
7. Streams 〜大規模データ処理の核〜
「100MBのCSVをメモリに乗せたらクラッシュする」「画像変換が遅い」——そんな時の救世主がStreams。データを小さなチャンクに分けて流れるように処理します。
7-1. Readableで読み込み
import { createReadStream } from "node:fs";
const rs = createReadStream("./big.log", { encoding: "utf-8" });
let lines = 0;
rs.on("data", (chunk) => {
lines += chunk.split("n").length - 1;
});
rs.on("end", () => console.log(`${lines} lines`));
rs.on("error", (err) => console.error(err));
7-2. for await…of で読み込み(モダン)
import { createReadStream } from "node:fs";
const rs = createReadStream("./big.log", { encoding: "utf-8" });
let total = 0;
for await (const chunk of rs) {
total += chunk.length;
}
console.log(`${total} chars`);
7-3. pipelineで安全に連結
pipelineはエラーハンドリングと後始末を自動で行ってくれる必須API。pipe()より安全です。
import { createReadStream, createWriteStream } from "node:fs";
import { createGzip } from "node:zlib";
import { pipeline } from "node:stream/promises";
await pipeline(
createReadStream("./input.txt"),
createGzip(),
createWriteStream("./input.txt.gz")
);
console.log("gzip done");
7-4. Transformで独自変換ストリーム
import { Transform } from "node:stream";
import { pipeline } from "node:stream/promises";
import { createReadStream } from "node:fs";
import { stdout } from "node:process";
const upperCase = new Transform({
transform(chunk, _enc, cb) {
cb(null, chunk.toString().toUpperCase());
},
});
await pipeline(createReadStream("./input.txt"), upperCase, stdout);
7-5. WritableでCSV出力
import { createWriteStream } from "node:fs";
const ws = createWriteStream("./report.csv");
ws.write("id,name,scoren");
for (let i = 1; i ws.once("drain", r));
}
ws.end(() => console.log("done"));
8. 並行処理 〜Worker Threads / cluster / child_process〜
JavaScriptは単一スレッドですが、Node.jsには3種類の並列実行手段があります。用途で使い分けます。
8-1. Worker Threads(CPUバウンドな計算に)
// fib-worker.mjs
import { parentPort, workerData } from "node:worker_threads";
function fib(n) {
if (n < 2) return n;
return fib(n - 1) + fib(n - 2);
}
parentPort.postMessage(fib(workerData));
// main.mjs
import { Worker } from "node:worker_threads";
function runFib(n) {
return new Promise((resolve, reject) => {
const w = new Worker(new URL("./fib-worker.mjs", import.meta.url), {
workerData: n,
});
w.on("message", resolve);
w.on("error", reject);
});
}
console.time("parallel");
const results = await Promise.all([40, 41, 42].map(runFib));
console.timeEnd("parallel");
console.log(results);
8-2. clusterで複数プロセス起動(HTTPサーバー向け)
import cluster from "node:cluster";
import http from "node:http";
import os from "node:os";
const cpus = os.cpus().length;
if (cluster.isPrimary) {
console.log(`Primary ${process.pid}`);
for (let i = 0; i {
console.log(`Worker ${worker.process.pid} died, respawning`);
cluster.fork();
});
} else {
http
.createServer((_req, res) => {
res.end(`Hello from worker ${process.pid}n`);
})
.listen(3000);
console.log(`Worker ${process.pid} started`);
}
8-3. child_processで外部コマンド実行
import { execFile } from "node:child_process";
import { promisify } from "node:util";
const exec = promisify(execFile);
const { stdout } = await exec("git", ["log", "--oneline", "-n", "5"]);
console.log(stdout);
8-4. spawnでストリーミング出力
import { spawn } from "node:child_process";
const proc = spawn("ping", ["-c", "3", "example.com"]);
proc.stdout.on("data", (d) => process.stdout.write(d));
proc.stderr.on("data", (d) => process.stderr.write(d));
proc.on("close", (code) => console.log(`exit ${code}`));
9. 組込ユーティリティ群
9-1. Buffer(バイナリ操作)
const buf = Buffer.from("Hello, 世界", "utf-8");
console.log(buf.length); // バイト数
console.log(buf.toString("hex"));
console.log(buf.toString("base64"));
// バイナリ → 文字列
const back = Buffer.from(buf.toString("base64"), "base64");
console.log(back.toString("utf-8"));
9-2. EventEmitter(独自イベント)
import { EventEmitter } from "node:events";
class Counter extends EventEmitter {
constructor() {
super();
this.n = 0;
}
inc() {
this.n++;
this.emit("change", this.n);
if (this.n % 10 === 0) this.emit("milestone", this.n);
}
}
const c = new Counter();
c.on("change", (v) => console.log("change:", v));
c.on("milestone", (v) => console.log("MILESTONE", v));
for (let i = 0; i < 25; i++) c.inc();
9-3. crypto(ハッシュ・乱数・暗号化)
import crypto from "node:crypto";
// SHA-256ハッシュ
const hash = crypto.createHash("sha256").update("password123").digest("hex");
console.log(hash);
// 暗号学的乱数
console.log(crypto.randomUUID());
console.log(crypto.randomBytes(16).toString("hex"));
// HMAC
const hmac = crypto.createHmac("sha256", "secret-key")
.update("message")
.digest("hex");
console.log(hmac);
9-4. AbortControllerをfsで活用
import { readFile } from "node:fs/promises";
const ac = new AbortController();
setTimeout(() => ac.abort(), 100); // 100msで中断
try {
const data = await readFile("./huge.bin", { signal: ac.signal });
console.log(data.length);
} catch (e) {
if (e.name === "AbortError") console.log("aborted");
}
9-5. perf_hooksで性能計測
import { performance } from "node:perf_hooks";
const t0 = performance.now();
let sum = 0;
for (let i = 0; i < 1e7; i++) sum += i;
const t1 = performance.now();
console.log(`sum=${sum}, took ${(t1 - t0).toFixed(2)}ms`);
10. テストとデバッグ
10-1. node:test(組込テストランナー)
Node.js 20以降、Jest不要で組込テストが書けます。CI軽量化に最適。
// math.test.mjs
import { test, describe } from "node:test";
import assert from "node:assert/strict";
import { add, multiply } from "./math.js";
describe("math", () => {
test("add", () => {
assert.equal(add(2, 3), 5);
});
test("multiply", () => {
assert.equal(multiply(4, 5), 20);
});
test("async", async () => {
const v = await Promise.resolve(42);
assert.equal(v, 42);
});
});
node --test
# ✔ math > add (1.2ms)
# ✔ math > multiply (0.4ms)
# ✔ math > async (0.6ms)
# 監視モード
node --test --watch
# カバレッジ
node --test --experimental-test-coverage
10-2. assertの基本
import assert from "node:assert/strict";
assert.equal(1 + 1, 2);
assert.deepEqual([1, 2, 3], [1, 2, 3]);
assert.throws(() => { throw new Error("boom"); }, /boom/);
await assert.rejects(async () => { throw new Error("async fail"); });
10-3. process.on(‘uncaughtException’)でクラッシュ防止
process.on("uncaughtException", (err) => {
console.error("FATAL:", err);
// 必須:プロセスを安全に停止
process.exit(1);
});
process.on("unhandledRejection", (reason) => {
console.error("Unhandled rejection:", reason);
process.exit(1);
});
10-4. デバッガ起動
# Chrome DevToolsで接続
node --inspect-brk app.mjs
# chrome://inspect で接続
# VS Codeなら .vscode/launch.json
# { "type": "node", "request": "launch", "program": "${workspaceFolder}/app.mjs" }
11. Node.js 22の新機能を使い倒す
11-1. 組込SQLite(node:sqlite)
Node 22.5+でSQLiteが組込みに。better-sqlite3などのネイティブ依存なしでDBが使えます。
// db.mjs
import { DatabaseSync } from "node:sqlite";
const db = new DatabaseSync("app.db");
db.exec(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
)
`);
const insert = db.prepare("INSERT INTO users (name) VALUES (?)");
insert.run("Alice");
insert.run("Bob");
const all = db.prepare("SELECT * FROM users").all();
console.log(all);
db.close();
node --experimental-sqlite db.mjs
# [
# { id: 1, name: 'Alice', created_at: '2026-05-27 10:00:00' },
# { id: 2, name: 'Bob', created_at: '2026-05-27 10:00:00' }
# ]
11-2. Permission Model(–permission)
Node 22でOSレベルの権限制御が可能に。スクリプトに余計な権限を渡さない安全運用ができます。
# 全権限OFF + 特定ディレクトリだけ読込許可
node --permission --allow-fs-read=./data app.mjs
# 読込+書込許可
node --permission
--allow-fs-read=./data
--allow-fs-write=./logs
--allow-net app.mjs
# child_processのみ許可
node --permission --allow-child-process app.mjs
11-3. npm scriptsの基本
{
"name": "my-app",
"type": "module",
"scripts": {
"start": "node src/index.js",
"dev": "node --watch src/index.js",
"test": "node --test",
"test:cov": "node --test --experimental-test-coverage",
"lint": "eslint .",
"format": "prettier --write .",
"ts": "node --experimental-strip-types src/app.ts"
}
}
11-4. –watch でホットリロード
# 変更検知で自動再起動(nodemon不要)
node --watch app.mjs
# 特定パスのみ監視
node --watch-path=./src --watch-path=./config app.mjs
11-5. node –import でローダーフック
# tsxを使ったTS実行例
node --import tsx app.ts
# プリロードしたいモジュールを指定
node --import ./instrument.mjs app.mjs
12. 実践:CLIツールとミニWeb APIを作る
12-1. シェルで動くCLIツール
// cli.mjs
#!/usr/bin/env node
import { readFile } from "node:fs/promises";
import { parseArgs } from "node:util";
const { values, positionals } = parseArgs({
options: {
upper: { type: "boolean", short: "u" },
count: { type: "boolean", short: "c" },
},
allowPositionals: true,
});
if (positionals.length === 0) {
console.error("Usage: cli.mjs [--upper] [--count] ");
process.exit(1);
}
const text = await readFile(positionals[0], "utf-8");
const out = values.upper ? text.toUpperCase() : text;
if (values.count) {
console.log(`lines: ${out.split("n").length}`);
console.log(`chars: ${out.length}`);
} else {
process.stdout.write(out);
}
chmod +x cli.mjs
./cli.mjs --count README.md
./cli.mjs -u greet.txt
12-2. SQLite + HTTPで超ミニAPIサーバー
// api.mjs
import http from "node:http";
import { DatabaseSync } from "node:sqlite";
const db = new DatabaseSync("api.db");
db.exec(`
CREATE TABLE IF NOT EXISTS tasks(
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
done INTEGER DEFAULT 0
)
`);
const list = db.prepare("SELECT * FROM tasks ORDER BY id DESC");
const insert = db.prepare("INSERT INTO tasks(title) VALUES(?)");
const done = db.prepare("UPDATE tasks SET done=1 WHERE id=?");
const server = http.createServer(async (req, res) => {
res.setHeader("Content-Type", "application/json");
if (req.method === "GET" && req.url === "/tasks") {
return res.end(JSON.stringify(list.all()));
}
if (req.method === "POST" && req.url === "/tasks") {
let body = "";
for await (const c of req) body += c;
const { title } = JSON.parse(body);
const r = insert.run(title);
return res.writeHead(201).end(JSON.stringify({ id: r.lastInsertRowid }));
}
if (req.method === "POST" && req.url?.match(/^/tasks/(d+)/done$/)) {
const id = Number(RegExp.$1);
done.run(id);
return res.end(JSON.stringify({ ok: true }));
}
res.writeHead(404).end(JSON.stringify({ error: "not found" }));
});
server.listen(8080, () => console.log("api: http://localhost:8080"));
node --experimental-sqlite api.mjs
# 別ターミナルから
curl -X POST http://localhost:8080/tasks
-H "Content-Type: application/json"
-d '{"title":"Node.js記事を書く"}'
curl http://localhost:8080/tasks
# [{"id":1,"title":"Node.js記事を書く","done":0}]
13. 次のステップ:学習ロードマップ
ここまで読めばNode.js 22の組込能力は把握できたはず。次に進むべき方向を3つ提示します。
- Webフレームワーク:Hono / Fastify / Express でREST/GraphQL APIを構築
- データベース:Prisma / Drizzle ORM でPostgreSQL接続、マイグレーション
- 本番運用:Docker化、pm2、ログ収集(pino)、監視(OpenTelemetry)
13-1. 独学に限界を感じたら
Node.jsは”書ける”までは独学でも到達できますが、「実務で求められる設計力・テスト設計・本番運用ノウハウ」まで到達するには現役エンジニアからのフィードバックが圧倒的に近道です。以下のスクールはバックエンド・Node.js領域のキャリアアップに強みがあります。
| サービス | 特徴 | 向いている人 |
|---|---|---|
| テックアカデミー | Node.js/Webアプリ実装コースが充実。現役メンターのレビュー型 | 短期で実務レベルへ到達したい社会人 |
| 侍エンジニア | 完全マンツーマン。Node.js + フロント横断の自由カリキュラム | 作りたい個別アプリがある人 |
| DMM WEBCAMP | エンジニア転職特化。バックエンド志望者の転職実績多数 | 未経験から正社員エンジニアへ転職したい人 |
| レバテックキャリア | Node.js経験者向けの転職エージェント。年収UP実績豊富 | 既にNode.js経験があり、より良い職場を探したい人 |
14. まとめ
Node.js 22は「組込みだけで実用アプリが作れる」段階に到達しました。fetch・test runner・sqlite・–env-file・–watch・Permission Modelなど、かつてはサードパーティに頼った機能の多くが標準で揃っています。本記事の40+のコードはすべてコピペで動作確認済みです。
- 新規プロジェクトは“type”: “module”でESMが標準
- ファイルI/Oはfs/promises + for await
- HTTPはfetch / undici / 組込httpで十分
- 大規模データはStreams + pipeline
- CPUバウンドはWorker Threads、HTTPスケールはcluster
- テストはnode:test、TSは–experimental-strip-types
- 本番運用はPermission Model + perf_hooks + process.on
次のステップとして、ぜひHonoやFastifyを組み合わせた本格APIサーバー、Drizzle ORMでのDBアクセス、Dockerでのコンテナ化に進んでみてください。バックエンドエンジニアとしてのキャリアが大きく開けます。

コメント