Node.jsは、サーバーサイドでJavaScriptを実行するための強力なランタイムとして、多くの開発現場で利用されています。

アプリケーション開発において、設定ファイルの読み込みやログの解析、データのインポートなど、ファイル操作が必要になる場面は非常に多く存在します。

2026年現在のNode.js開発においては、効率的で保守性の高いコードを書くために、標準モジュールであるfs(File System)の正しい理解が不可欠です。

本記事では、Node.jsでファイルを読み込むための基本的な手法から、モダンな開発で主流となっているPromise APIを用いた実践的な実装方法まで詳しく解説します。

Node.jsにおけるファイル読み込みの基本構造

Node.jsでファイルシステムを操作するには、標準で提供されているfsモジュールを使用します。

fsモジュールは、OSの標準的なファイル入出力機能をラップしており、非常に高いパフォーマンスを発揮します。

Node.jsの初期から存在する「コールバック形式」、プログラムの実行を一時停止する「同期形式」、そして現在の主流である「Promise(async/await)形式」の3つのスタイルが用意されています。

現代のプロジェクトでは、可読性とエラーハンドリングの容易さから、Promise APIの使用が推奨されています。

また、Node.js 18以降で標準化されたES Modules(ESM)環境では、import構文を用いてモジュールを読み込むのが一般的です。

モジュールを呼び出す際は、node:プリフィックスを付けて、node:fsnode:fs/promisesのように記述することが現在のベストプラクティスとされています。

Promise APIを使用した非同期読み込み

現在のNode.js開発において、最も推奨されるのがPromise API(async/await)を利用したファイル読み込みです。

非同期処理を同期処理に近い見た目で記述できるため、コードのネストが深くならず、保守性が飛躍的に向上します。

readFileによる一括読み込みの実装

ファイル全体を一度にメモリへ読み込む場合は、readFileメソッドを使用します。

この方法は、設定ファイルや小規模なテキストファイルなど、データ量がそれほど大きくない場合に適しています。

JavaScript
import { readFile } from 'node:fs/promises';

async function loadConfiguration() {
    try {
        // ファイルパスとエンコーディングを指定して読み込む
        const data = await readFile('./config.json', 'utf-8');
        
        // 文字列として読み込まれたデータをJSONとしてパースする
        const config = JSON.parse(data);
        console.log('設定の読み込みに成功しました:', config);
    } catch (error) {
        // ファイルが存在しない場合や権限がない場合のエラーハンドリング
        console.error('ファイルの読み込み中にエラーが発生しました:', error.message);
    }
}

loadConfiguration();
実行結果
設定の読み込みに成功しました: { "api_version": "2026.1", "debug": true }

上記の例では、utf-8を第2引数に指定することで、バイナリデータ(Buffer)ではなく文字列としてデータを取得しています。

エンコーディングを指定しない場合、戻り値はBufferオブジェクトとなり、そのままでは人間が読める文字列にはなりません。

エラーハンドリングの重要性

ファイル操作は、ファイルが存在しない、権限が足りない、ディスクフルであるといった外部要因によるエラーが発生しやすい処理です。

そのため、必ずtry...catch構文を使用して、例外が発生した際の処理を記述しておく必要があります。

エラーを適切にキャッチしないと、アプリケーション全体がクラッシュする原因となります。

同期的なファイル読み込みとその使い所

Node.jsには、処理が完了するまでプログラムの実行をブロックするreadFileSyncというメソッドも存在します。

通常、高頻度なリクエストを捌くサーバーサイドのロジックでこれを使用することは推奨されません。

readFileSyncの使用例

同期処理は、プログラムの起動時に一度だけ読み込む必要がある設定ファイルの取得などに向いています。

JavaScript
import { readFileSync } from 'node:fs';

try {
    // 同期的にファイルを読み込む
    const content = readFileSync('./init.log', 'utf-8');
    console.log('同期読み込み結果:', content);
} catch (err) {
    console.error('同期読み込み中にエラーが発生しました:', err);
}
実行結果
同期読み込み結果: System initialized at 2026-05-10

同期メソッドは、コードの実行順序が保証されるため直感的ですが、実行中は他の処理がすべて停止するというデメリットを理解しておく必要があります。

CLI(コマンドラインインターフェース)ツールの作成など、シングルユーザー向けのアプリケーションであれば問題になりにくい手法です。

巨大なファイルを扱うためのストリーム処理

数GBを超えるような巨大なファイルを読み込む場合、readFileではメモリ不足(Out of Memory)を引き起こす可能性があります。

そのようなケースでは、ファイルを分割して読み込むストリーム(Stream)を利用するのが最適です。

createReadStreamによる効率的な処理

ストリームを使用すると、ファイルの内容を少しずつ読み込み、データが到着するたびに処理を実行できます。

JavaScript
import { createReadStream } from 'node:fs';

const stream = createReadStream('./large-log-file.txt', { encoding: 'utf-8', highWaterMark: 64 * 1024 });

stream.on('data', (chunk) => {
    // 64KBごとのチャンクとしてデータが届く
    console.log('データを一部受信しました。サイズ:', chunk.length);
});

stream.on('end', () => {
    console.log('全てのデータの読み込みが完了しました。');
});

stream.on('error', (err) => {
    console.error('ストリーム処理中にエラーが発生しました:', err);
});
実行結果
データを一部受信しました。サイズ: 65536
データを一部受信しました。サイズ: 65536
...
全てのデータの読み込みが完了しました。

ストリームを利用することで、メモリ使用量を一定に保ちながら大規模なデータを安全に処理することが可能になります。

highWaterMarkオプションを調整することで、一度に読み込むバッファサイズを制御し、パフォーマンスを最適化できます。

ファイル読み込み手法の比較表

プロジェクトの要件に応じて、適切なメソッドを選択することが重要です。

以下の表は、それぞれの読み込み手法の特徴をまとめたものです。

手法特徴主な用途
fs/promises (readFile)非同期・可読性が高い一般的なWebアプリケーションの設定・データ読み込み
fs (readFileSync)同期・処理をブロックするCLIツール、起動時の設定読み込み
fs (createReadStream)非同期・逐次読み込み大容量ログ、動画、巨大なCSVファイルの処理
fs (callback形式)非同期・旧来の手法レガシーコードのメンテナンス

応用:readlineモジュールによる行単位の読み込み

ログファイルなどを解析する際、1行ずつ読み込んで特定のキーワードを検索したい場合があります。

Node.jsには標準でreadlineモジュールが用意されており、ファイルストリームと組み合わせて効率的に行処理を行えます。

JavaScript
import { createReadStream } from 'node:fs';
import { createInterface } from 'node:readline';

async function processLineByLine() {
    const fileStream = createReadStream('./access.log');

    const rl = createInterface({
        input: fileStream,
        crlfDelay: Infinity
    });

    for await (const line of rl) {
        // 各行に対する処理
        if (line.includes('ERROR')) {
            console.log('エラー行を発見:', line);
        }
    }
}

processLineByLine();

for await...of構文を利用することで、非同期的な行読み込みを非常にクリーンに記述できます。

この手法は、メモリ効率と可読性のバランスが非常に良く、テキストベースのデータ解析において最も推奨される方法の一つです。

現代のNode.jsにおける注意点

2026年の環境では、Node.jsのバージョンアップに伴い、いくつかの古い書き方が推奨されなくなっています。

まず、fs.exists()は非推奨となって久しく、代わりにfs.access()や、読み込み時のエラーハンドリングで対応するのが標準的です。

また、ファイルパスの指定には、相対パスよりも絶対パスを使用する方が実行環境に依存しない堅牢なコードになります。

import.meta.urlを活用して、現在のファイルからの相対的な絶対パスを生成する手法が一般的です。

JavaScript
import { readFile } from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';

// 現在のファイルのディレクトリパスを取得
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

async function safeRead() {
    const targetPath = join(__dirname, 'data', 'sample.txt');
    const content = await readFile(targetPath, 'utf-8');
    return content;
}

このように、pathモジュールと組み合わせてパスを構築することで、WindowsやLinuxといったOS間の差異を吸収できます。

Node.js本体の進化により、より安全で高機能なファイル操作が可能になっているため、常に最新の公式ドキュメントを確認する習慣も大切です。

まとめ

Node.jsでのファイル読み込みは、fsモジュールの進化によって、より簡潔で強力なものとなりました。

基本的には、fs/promisesモジュールのreadFileを使い、async/awaitで非同期処理を記述するのがベストプラクティスです。

ただし、扱うデータのサイズやアプリケーションの種類によっては、同期処理のreadFileSyncや、メモリ効率に優れたcreateReadStreamを適切に使い分ける必要があります。

エラーハンドリングを徹底し、適切なエンコーディングを指定することで、バグの少ない堅牢なプログラムを構築できます。

今回解説した手法をマスターし、効率的なNode.jsアプリケーションの開発に役立ててください。