Node.js / Bun コピペで使える実践 Tips 集【中級者向け】
Node.js / Bun コピペで即使えるコードを、現場でよく使うトピック別にまとめました。Node.js と Bun の両対応コードを中心に、Bun 固有の高速 API も併記します。
ファイル操作(fs/promises と Bun.file)
テキストファイルの読み書き
// Node.js
import { readFile, writeFile } from "node:fs/promises";
const content = await readFile("input.txt", "utf-8");
await writeFile("output.txt", content.toUpperCase());
// Bun
const file = Bun.file("input.txt");
const content = await file.text();
await Bun.write("output.txt", content.toUpperCase());
ディレクトリ内のファイル一覧を再帰取得
import { readdir } from "node:fs/promises";
import { join } from "node:path";
async function walk(dir: string): Promise<string[]> {
const entries = await readdir(dir, { withFileTypes: true });
const files = await Promise.all(
entries.map((e) =>
e.isDirectory() ? walk(join(dir, e.name)) : [join(dir, e.name)]
)
);
return files.flat();
}
const allFiles = await walk("./src");
ファイルの存在確認
// Node.js
import { access } from "node:fs/promises";
import { constants } from "node:fs";
async function exists(path: string): Promise<boolean> {
try {
await access(path, constants.F_OK);
return true;
} catch {
return false;
}
}
// Bun
const exists = await Bun.file("config.json").exists();
ストリーム処理(大きなファイルの読み書き)
大容量 CSV をチャンクで読み込む(Node.js)
import { createReadStream } from "node:fs";
import { createInterface } from "node:readline";
async function processLargeCSV(path: string) {
const stream = createReadStream(path, { encoding: "utf-8" });
const rl = createInterface({ input: stream, crlfDelay: Infinity });
let lineCount = 0;
for await (const line of rl) {
lineCount++;
const cols = line.split(",");
// 1行ずつ処理
if (lineCount % 10_000 === 0) {
console.log(`processed ${lineCount} lines`);
}
}
}
await processLargeCSV("data.csv");
ストリームをパイプして変換(Node.js)
import { createReadStream, createWriteStream } from "node:fs";
import { Transform } from "node:stream";
import { pipeline } from "node:stream/promises";
import { createGzip } from "node:zlib";
const upper = new Transform({
transform(chunk, _enc, callback) {
callback(null, chunk.toString().toUpperCase());
},
});
await pipeline(
createReadStream("input.txt"),
upper,
createGzip(),
createWriteStream("output.txt.gz")
);
Bun でストリームを読む
const file = Bun.file("large.json");
const stream = file.stream();
const reader = stream.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
// value は Uint8Array
console.log(value.byteLength);
}
子プロセス実行(execa / Bun.spawn)
execa でコマンド実行(Node.js)
import { execa } from "execa";
// 結果を文字列で受け取る
const { stdout } = await execa("git", ["log", "--oneline", "-5"]);
console.log(stdout);
// 失敗時に例外
try {
await execa("npm", ["test"]);
} catch (err) {
if (err instanceof Error && "exitCode" in err) {
console.error("exit code:", (err as { exitCode: number }).exitCode);
}
}
ストリームで出力を受け取る(execa)
import { execa } from "execa";
const proc = execa("npm", ["run", "build"], { stdout: "pipe" });
proc.stdout?.on("data", (chunk: Buffer) => {
process.stdout.write(chunk);
});
await proc;
Bun.spawn でコマンド実行
const proc = Bun.spawn(["git", "log", "--oneline", "-5"], {
stdout: "pipe",
});
const output = await new Response(proc.stdout).text();
await proc.exited;
console.log(output);
Bun.spawnSync(同期版)
const result = Bun.spawnSync(["git", "rev-parse", "HEAD"]);
const hash = result.stdout.toString().trim();
console.log(hash);
環境変数管理(dotenv / Bun の組み込みサポート)
Node.js + dotenv
// npm i dotenv
import "dotenv/config";
const port = Number(process.env.PORT ?? 3000);
const dbUrl = process.env.DATABASE_URL;
if (!dbUrl) {
throw new Error("DATABASE_URL is required");
}
型安全な環境変数ユーティリティ
function requireEnv(key: string): string {
const value = process.env[key];
if (!value) throw new Error(`Missing env var: ${key}`);
return value;
}
function optionalEnv(key: string, fallback: string): string {
return process.env[key] ?? fallback;
}
const config = {
databaseUrl: requireEnv("DATABASE_URL"),
port: Number(optionalEnv("PORT", "3000")),
nodeEnv: optionalEnv("NODE_ENV", "development"),
} as const;
Bun は .env を自動ロード
// Bun は .env, .env.local, .env.production などを自動で読む
// dotenv パッケージ不要
const port = Number(Bun.env.PORT ?? 3000);
const secret = Bun.env.JWT_SECRET;
if (!secret) {
throw new Error("JWT_SECRET is required");
}
HTTP サーバーの最小構成(Bun.serve)
ルーターなしの基本サーバー
const server = Bun.serve({
port: 3000,
fetch(req) {
const url = new URL(req.url);
if (url.pathname === "/health") {
return new Response("OK", { status: 200 });
}
if (url.pathname === "/api/echo" && req.method === "POST") {
return new Response(req.body, {
headers: { "Content-Type": "application/json" },
});
}
return new Response("Not Found", { status: 404 });
},
});
console.log(`Listening on http://localhost:${server.port}`);
JSON API サーバー(型付き)
type User = { id: number; name: string };
const users: User[] = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
];
function json(data: unknown, status = 200): Response {
return new Response(JSON.stringify(data), {
status,
headers: { "Content-Type": "application/json" },
});
}
Bun.serve({
port: 3000,
async fetch(req) {
const url = new URL(req.url);
if (req.method === "GET" && url.pathname === "/users") {
return json(users);
}
if (req.method === "POST" && url.pathname === "/users") {
const body = (await req.json()) as Partial<User>;
if (!body.name) return json({ error: "name required" }, 400);
const user: User = { id: users.length + 1, name: body.name };
users.push(user);
return json(user, 201);
}
return json({ error: "Not Found" }, 404);
},
});
Node.js の最小 HTTP サーバー(標準ライブラリ)
import { createServer } from "node:http";
const server = createServer(async (req, res) => {
if (req.url === "/health") {
res.writeHead(200).end("OK");
return;
}
res.writeHead(404).end("Not Found");
});
server.listen(3000, () => console.log("http://localhost:3000"));
Worker Threads で並列処理
メインスレッドからワーカーを起動(Node.js)
// worker.ts
import { parentPort, workerData } from "node:worker_threads";
function heavyCalc(n: number): number {
let result = 0;
for (let i = 0; i < n; i++) result += Math.sqrt(i);
return result;
}
parentPort?.postMessage(heavyCalc(workerData as number));
// main.ts
import { Worker } from "node:worker_threads";
import { cpus } from "node:os";
function runWorker(n: number): Promise<number> {
return new Promise((resolve, reject) => {
const w = new Worker(new URL("./worker.ts", import.meta.url), {
workerData: n,
});
w.on("message", resolve);
w.on("error", reject);
});
}
const cores = cpus().length;
const tasks = Array.from({ length: cores }, (_, i) => runWorker(1_000_000 * (i + 1)));
const results = await Promise.all(tasks);
console.log(results);
ワーカープールで再利用
import { Worker } from "node:worker_threads";
class WorkerPool {
private workers: Worker[] = [];
private queue: Array<{ resolve: (v: number) => void; data: number }> = [];
constructor(private size: number, private scriptUrl: URL) {
for (let i = 0; i < size; i++) this.spawn();
}
private spawn() {
const w = new Worker(this.scriptUrl, { workerData: 0 });
w.on("message", (result: number) => {
const next = this.queue.shift();
if (next) {
next.resolve(result);
w.postMessage(next.data);
} else {
this.workers.push(w);
}
});
this.workers.push(w);
}
run(data: number): Promise<number> {
return new Promise((resolve) => {
const w = this.workers.pop();
if (w) {
w.once("message", resolve);
w.postMessage(data);
} else {
this.queue.push({ resolve, data });
}
});
}
}
Bun でワーカー起動
// Bun は Web Worker API を使う
const worker = new Worker(new URL("./worker.ts", import.meta.url));
worker.postMessage({ n: 1_000_000 });
worker.onmessage = (e: MessageEvent<number>) => {
console.log("result:", e.data);
worker.terminate();
};
まとめ
各 Tips をランタイムとカテゴリ別に整理します。
| カテゴリ | Node.js | Bun |
|---|---|---|
| ファイル読み書き | fs/promises の readFile / writeFile | Bun.file().text() / Bun.write() |
| ファイル存在確認 | fs.access + try/catch | Bun.file().exists() |
| ストリーム処理 | readline / stream.pipeline | Web Streams API(file.stream()) |
| 子プロセス | execa(npm パッケージ) | Bun.spawn / Bun.spawnSync |
| 環境変数 | dotenv/config + process.env | 自動ロード、Bun.env |
| HTTP サーバー | node:http の createServer | Bun.serve(fetch ベース) |
| 並列処理 | worker_threads + Worker | Web Worker API |
Bun は Node.js 互換モードを持つため、node:fs や node:http の多くは Bun でもそのまま動きます。新規プロジェクトでは Bun ネイティブ API を使うと起動速度・スループットの恩恵を最大限に受けられます。既存の Node.js コードベースへの段階的な移行にも役立ててください。