Node.jsでファイルを読み書きするには、標準のfs(File System)モジュールを使います。設定ファイルの読み込み、ログの書き出し、CSVの保存など、サーバーやツールのほとんどで登場します。
fsには3つのAPIがあります。処理を待つ同期、コールバック、そしてasync/awaitで書けるPromise版です。さらに、初心者が必ずつまずく罠として、読み込み時にencodingを指定しないと、文字列ではなくBuffer(バイト列)が返るという挙動があります。この記事では、実機のNode.jsで確認しながら、ファイル操作の基本を整理します。
- 読み込みは
fs.readFileSync(path, "utf8")が最も簡単です。書き込みはfs.writeFileSync(path, data)です。 - encodingに
"utf8"を指定しないと、文字列ではなくBufferが返ります。テキストなら必ず指定します。 - APIは3種類:同期(
...Sync)、コールバック、Promise(fs/promises)です。 - サーバーでは同期版が処理を止めるため、
fs/promisesのawaitを使います。スクリプトなら同期版が手軽です。 - 上書きは
writeFile、末尾に足すのはappendFileです。 - 存在確認は
existsSync、フォルダ作成はmkdirSync(path, { recursive: true })です。
requireとimportのどちらでfsを読み込むかはrequireとimportの違い、巨大なファイルを扱うならストリームの使い方、JSONの基本はJSONファイルの読み込み方もあわせて参考になります。
fsモジュールの3つのAPI
fsの読み書きには、書き方の異なる3つのAPIがあります。まず全体像をつかみます。
| API | 書き方 | 特徴 |
|---|---|---|
| 同期 | fs.readFileSync() |
結果が戻り値。処理を待つ(ブロックする) |
| コールバック | fs.readFile(path, cb) |
従来の非同期。エラーは第1引数 |
| Promise | fs/promises の await |
現在の主流。async/awaitで書ける |
ファイルを読み込む(同期・コールバック・Promise)
同じ「読み込み」を3つのAPIで書くと、次のようになります。fsモジュールはCommonJSならrequire("fs")、ESモジュールならimportで読み込みます。
const fs = require("fs");
// 1. 同期:結果が戻り値で返る(処理を待つ)
const data1 = fs.readFileSync("data.txt", "utf8");
console.log(data1);
// 2. コールバック:第1引数が err、第2引数がデータ
fs.readFile("data.txt", "utf8", (err, data2) => {
if (err) throw err;
console.log(data2);
});
// 3. Promise:async 関数の中で await する
const fsp = require("fs/promises");
async function main() {
const data3 = await fsp.readFile("data.txt", "utf8");
console.log(data3);
}
main();
実機でも、3つとも同じ内容を読み込めました。コールバック版は第1引数が必ずエラー(無ければnull)という決まりがあり、これを確認してからデータを使います。Promise版はfs/promises(またはfs.promises)を使い、awaitで結果を受け取れるため、いちばん読みやすく書けます。
【最重要】encodingを指定しないとBufferが返る
もっともつまずきやすいのがこれです。readFileSyncの第2引数に"utf8"を指定しないと、戻り値は文字列ではなくBuffer(バイト列)になります。
const fs = require("fs");
// NG: encoding を指定しないと Buffer が返る
const buf = fs.readFileSync("data.txt");
console.log(buf); // <Buffer e3 81 93 ...>(バイト列)
console.log(buf.constructor.name); // Buffer
// OK: "utf8" を指定すると文字列が返る
const text = fs.readFileSync("data.txt", "utf8");
console.log(text); // こんにちは(文字列)
console.log(typeof text); // string
// Buffer を後から文字列にするなら toString
console.log(buf.toString("utf8")); // こんにちは
実機で確認したところ、encodingを指定しないreadFileSync("data.txt")はBufferを返し、先頭バイトは227(UTF-8の1バイト目)でした。"utf8"を指定すると、ちゃんと文字列"こんにちは"が返ります。「文字列として処理したいのに、なぜか<Buffer ...>と表示される」「.split()が思った通りに動かない」といったときは、encodingの指定漏れを疑ってください。すでにBufferを受け取ってしまった場合は、buf.toString("utf8")で文字列に変換できます。
ファイルに書き込む・追記する
書き込みはwriteFile系、末尾に足すのはappendFile系です。writeFileは既存の内容を上書きするので注意してください。
const fs = require("fs");
// 上書きで書き込む(ファイルが無ければ作成)
fs.writeFileSync("log.txt", "1行目\n");
// 末尾に追記する(既存の内容は消えない)
fs.appendFileSync("log.txt", "2行目\n");
fs.appendFileSync("log.txt", "3行目\n");
console.log(fs.readFileSync("log.txt", "utf8"));
// 1行目
// 2行目
// 3行目
// Promise 版も同じ
const fsp = require("fs/promises");
async function save() {
await fsp.writeFile("out.txt", "保存する内容");
}
save();
実機でも、writeFileSyncで作ったファイルにappendFileSyncを2回行うと3行になりました。ログのように追記したいのにwriteFileを使うと、毎回上書きされて前の内容が消えてしまいます。「追記したいのか、上書きしたいのか」で使い分けてください。
どのAPIを使うべきか(同期 vs 非同期)
3つのAPIは、使う場面で選びます。判断の軸は「処理を止めても良いか」です。
- 同期(
...Sync):書きやすく分かりやすい。処理が終わるまで他が止まるため、起動時の設定読み込みや、小さなスクリプトに向きます。 - Promise(
fs/promises):awaitで書け、処理を止めません。Webサーバーなど、同時に多くの処理をこなす場面では必ずこちらを使います。 - コールバック:従来の方式。今から書くならPromise版のほうが読みやすくおすすめです。
サーバーの中でreadFileSyncを使うと、その読み込みが終わるまで他のリクエストもすべて待たされます。サーバーではfs/promisesのawaitを基本にしてください。同期版は、起動時に一度だけ読む設定ファイルや、使い捨てのスクリプトに限定するのが安全です。
存在確認・フォルダ作成・一覧
ファイル操作では、読み書き以外もよく使います。存在確認・フォルダ作成・一覧取得の基本です。
const fs = require("fs");
// 存在確認
if (fs.existsSync("data.txt")) {
console.log("ファイルがあります");
}
// フォルダ作成(recursive で親フォルダごと作る)
fs.mkdirSync("logs/2026/03", { recursive: true });
// フォルダ内の一覧
const names = fs.readdirSync(".");
console.log(names); // ["data.txt", "logs", ...]
// ファイルの削除
// fs.unlinkSync("old.txt");
実機でも、mkdirSync("a/b/c", { recursive: true })で親フォルダごと作成でき、readdirSyncで中身の一覧が取れました。recursive: trueを付けないと、途中のフォルダが無いときにエラーになります。すでにフォルダがある場合もrecursive: trueならエラーになりません。
JSONを読み書きする
設定ファイルなどでJSONを扱うときは、fsで文字列として読み書きし、JSON.parse/JSON.stringifyで変換します。
const fs = require("fs");
// 読み込み:文字列で読んで JSON.parse でオブジェクトにする
const text = fs.readFileSync("config.json", "utf8");
const config = JSON.parse(text);
console.log(config.name);
// 書き込み:JSON.stringify で文字列にして書く
const data = { name: "テスト", count: 3 };
fs.writeFileSync(
"out.json",
JSON.stringify(data, null, 2) // null, 2 で見やすく整形
);
実機でも、JSON.parse(fs.readFileSync(..., "utf8"))でJSONをオブジェクトとして読み込めました。JSON.stringify(data, null, 2)の2は、インデント幅の指定で、人が読みやすい整形済みJSONになります。CommonJSならrequire("./config.json")でも読めますが、requireは内容をキャッシュするため、実行中に書き換わるファイルにはfsを使います。
エラー処理
ファイルが無い、権限が無いといった失敗に備えます。エラーの受け取り方はAPIごとに違います。
const fs = require("fs");
// 同期:try-catch で受ける
try {
const data = fs.readFileSync("nothere.txt", "utf8");
} catch (err) {
console.error("読み込み失敗:", err.code); // ENOENT など
}
// Promise:try-catch(async 関数内)
const fsp = require("fs/promises");
async function main() {
try {
await fsp.readFile("nothere.txt", "utf8");
} catch (err) {
console.error("読み込み失敗:", err.code);
}
}
// コールバック:第1引数 err を確認する
fs.readFile("nothere.txt", "utf8", (err, data) => {
if (err) {
console.error("読み込み失敗:", err.code);
return;
}
console.log(data);
});
実機でも、存在しないファイルを読むと、同期版もPromise版もerr.codeがENOENT(ファイルが見つからない)になりました。よくあるエラーコードは、ファイルが無いENOENT、権限が無いEACCESです。err.codeで分岐すると、原因に応じた処理を書けます。
よくある失敗
encodingを指定せずBufferが返って混乱する
テキストとして読むならreadFileSync(path, "utf8")のように必ずencodingを指定します。指定しないとBufferになります。
追記したいのにwriteFileで上書きする
writeFileは既存の内容を上書きします。末尾に足したいならappendFileを使います。
サーバーで同期版(readFileSync)を使う
同期版は処理が終わるまで他をすべて止めます。Webサーバーではfs/promisesのawaitを使ってください。
mkdirで親フォルダが無くてエラーになる
階層ごと作るならmkdirSync(path, { recursive: true })を使います。すでにある場合もエラーになりません。
エラー処理を書かずに落ちる
ファイルが無い・権限が無いは普通に起きます。同期はtry-catch、コールバックは第1引数のerrを必ず確認します。
よくある質問
fs.readFileSync(path, "utf8")のように"utf8"を指定すると文字列が返ります。すでにBufferを受け取った場合はbuf.toString("utf8")で文字列に変換できます。fs/promisesのawaitを使います。起動時の設定読み込みや使い捨てのスクリプトなら、書きやすい同期版(readFileSync)で十分です。fs.appendFileSync(path, data)を使います。writeFileは既存の内容を上書きしてしまうため、ログのように足していきたいときはappendFileを使ってください。fs.mkdirSync("a/b/c", { recursive: true })のようにrecursive: trueを指定します。途中のフォルダが無くても親ごと作成し、すでにある場合もエラーになりません。JSON.parse(fs.readFileSync("config.json", "utf8"))で読み込めます。書き込みはfs.writeFileSync(path, JSON.stringify(data, null, 2))です。実行中に変わるファイルは、キャッシュされるrequireではなくfsで読みます。まとめ
- 読み込みは
readFileSync(path, "utf8")、書き込みはwriteFileSync(path, data)が基本です。 - encodingに
"utf8"を指定しないとBufferが返ります。テキストでは必ず指定します。 - APIは同期・コールバック・Promiseの3種類。今から書くなら
fs/promisesのawaitがおすすめです。 - サーバーでは同期版を避け、
fs/promisesを使います。 - 上書きは
writeFile、追記はappendFile。フォルダ作成はmkdirにrecursive: trueです。 - エラーは同期なら
try-catch、コールバックなら第1引数のerrで受けます。
Node.jsのファイル操作は、3つのAPIの使い分けと、encodingの指定さえ押さえれば難しくありません。普段はfs/promisesのawaitを基本に、起動時のちょっとした読み込みは同期版で、と使い分けると快適に書けます。

