Node.jsはbrowser外でJavaScriptを実行するruntimeです。productionではNode.js公式release表を確認し、support中のLTSを使います。
最頻出10項目
| 操作 | 例 |
|---|---|
| file実行 | node app.js |
| script実行 | npm run dev |
| version確認 | node --version |
| syntax確認 | node --check app.js |
| watch実行 | node --watch app.js |
| 環境変数file | node --env-file=.env app.js |
| ESM import | import fs from "node:fs" |
| file読込 | readFile(path, "utf8") |
| HTTP server | createServer(handler) |
| test | node --test |
CLI
node app.js
node --watch app.js
node --inspect app.js
node --check app.js
node --test
node --env-file=.env app.js
--inspectはdebug portを開きます。共有networkへ無条件に公開しないでください。.envにはsecretが含まれるためGitへcommitせず、productionではplatformのsecret管理を使います。
command line argumentはprocess.argvから読みます。
const [, , name = "Ada"] = process.argv;
console.log(`Hello, ${name}`);
複雑なCLIではnode:utilのparseArgsを使えます。
ES Modules
新規projectではESMを基準にできます。
{
"type": "module",
"scripts": {
"dev": "node --watch src/index.js",
"start": "node src/index.js",
"test": "node --test"
}
}
// math.js
export function add(left, right) {
return left + right;
}
import { add } from "./math.js";
console.log(add(2, 3));
relative importではfile extensionを書きます。組み込みmoduleはnode:接頭辞を付けると明確です。
import path from "node:path";
import { fileURLToPath } from "node:url";
const filename = fileURLToPath(import.meta.url);
const dirname = path.dirname(filename);
CommonJS
const fs = require("node:fs");
module.exports = { fs };
.cjsまたは"type": "commonjs"で扱います。ESMとCommonJSを混在させる場合はpackageのexportsと対象runtimeをtestします。
fs/promises
import {
mkdir,
readFile,
writeFile,
} from "node:fs/promises";
await mkdir("./data", { recursive: true });
const config = JSON.parse(
await readFile("./config.json", "utf8"),
);
await writeFile(
"./data/output.json",
JSON.stringify(config, null, 2),
"utf8",
);
writeFileは既存fileを上書きします。重要dataでは一時fileへ書いてrenameする方法、backup、同時writeの制御を検討します。pathをuser inputから受け取る場合は、許可directory外へ出ないよう検証します。
よく使うAPI
| API | 用途 |
|---|---|
readFile | file全体を読む |
writeFile | file全体を書く |
appendFile | 末尾へ追記 |
mkdir | directory作成 |
readdir | directory一覧 |
stat | file情報 |
rename | 名前変更・移動 |
rm | file・directory削除 |
rm({ recursive: true })は破壊的です。空文字、workspace root、home directoryを対象にしないよう、削除前に解決済みpathを表示・検証してください。
path
import path from "node:path";
const file = path.join("data", "users.json");
const absolute = path.resolve(file);
const extension = path.extname(file);
const base = path.basename(file);
path文字列を"/"で連結せずpath.joinを使うとOS差を扱いやすくなります。URL pathの操作にはURLを使い、filesystem pathと混ぜません。
processと環境変数
const port = Number(process.env.PORT ?? "3000");
if (!Number.isInteger(port)) {
throw new Error("PORT must be an integer");
}
console.log({
pid: process.pid,
platform: process.platform,
cwd: process.cwd(),
});
環境変数はすべてstringまたはundefinedです。number・booleanへ変換し、起動時に必須値を検証します。secretをlogへ出しません。
終了codeは成功が0、失敗は通常0以外です。
try {
await main();
} catch (error) {
console.error(error);
process.exitCode = 1;
}
cleanupを待たず即終了するprocess.exit()より、exitCodeを設定する方法を基本にします。
HTTP server
import { createServer } from "node:http";
const server = createServer((request, response) => {
if (request.method === "GET" && request.url === "/health") {
response.writeHead(200, {
"content-type": "application/json; charset=utf-8",
});
response.end(JSON.stringify({ status: "ok" }));
return;
}
response.writeHead(404, {
"content-type": "application/json; charset=utf-8",
});
response.end(JSON.stringify({ error: "not_found" }));
});
server.listen(3000, "127.0.0.1");
request bodyにはsize上限を設け、timeout、method、content-type、validationを確認します。Internet公開にはTLS、reverse proxy、logging、graceful shutdownなども必要です。
fetch
const response = await fetch("https://api.example.com/users", {
signal: AbortSignal.timeout(5000),
headers: {
accept: "application/json",
},
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const users = await response.json();
fetchは404や500だけではrejectしないためresponse.okを確認します。tokenをsource codeやerror messageへ入れません。
JSON
import { readFile } from "node:fs/promises";
async function readJson(file) {
const text = await readFile(file, "utf8");
return JSON.parse(text);
}
JSON.parseはsyntax errorをthrowします。外部dataはparse後にschema validationします。
EventとStream
大きなfileを全体読込せずstreamで処理できます。
import { createReadStream } from "node:fs";
import { createGzip } from "node:zlib";
import { pipeline } from "node:stream/promises";
await pipeline(
createReadStream("input.log"),
createGzip(),
process.stdout,
);
pipelineはerror伝播とstream終了をまとめます。binary dataを文字列として扱わないようencodingを確認します。
Test
import assert from "node:assert/strict";
import test from "node:test";
test("add", () => {
assert.equal(2 + 3, 5);
});
node --test
node --test --test-name-pattern="add"
利用可能optionは使用中のNode.js versionのCLI referenceを確認してください。
npm scripts
{
"scripts": {
"dev": "node --watch src/index.js",
"start": "node src/index.js",
"test": "node --test",
"check": "node --check src/index.js"
}
}
dependency installではlockfileをcommitし、CIではnpm ciを使います。install scriptを含むdependencyは、実行内容と供給chain riskを確認します。
Abortと並列処理
独立した非同期処理はPromise.allでまとめられます。1つがrejectすると全体もrejectします。
const [user, settings] = await Promise.all([
fetchUser(),
fetchSettings(),
]);
大量のfileやURLへ無制限に同時requestを送らず、同時実行数を制限します。timeoutやcancelに対応するAPIではAbortSignalを渡します。
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 5000);
try {
await fetch("https://example.com/", {
signal: controller.signal,
});
} finally {
clearTimeout(timer);
}
AbortSignal.timeout()を利用できるNode.js versionでは短く書けます。対象versionのAPI referenceを確認してください。
Graceful shutdown
function shutdown(signal) {
console.log(`received ${signal}`);
server.close((error) => {
if (error) {
console.error(error);
process.exitCode = 1;
}
});
}
process.once("SIGTERM", shutdown);
process.once("SIGINT", shutdown);
新規requestの受付を止め、処理中request、DB connection、queueを終了してからprocessを閉じます。強制終了までのtimeoutは実行platformの猶予より短く設定します。
トラブル確認
node --version
npm --version
node -p "process.execPath"
node -p "process.cwd()"
npm ls
ERR_MODULE_NOT_FOUND: path、extension、package exportsを確認require is not defined: ESMでCommonJS構文を使っていないか確認EADDRINUSE: 同じportを別processが使用EACCES: file・port権限を確認- unhandled rejection:
awaitとerror handlingを確認
高度なWorker、child process、stream変換、暗号、production server設計は必要なmoduleの公式documentへ進んでください。