Node.jsを使用してアプリケーションを開発する際、サーバー上の不要なファイルを削除する処理は避けて通れません。
プログラム内で生成された一時ファイルやログファイルを適切に整理することは、ストレージ容量の最適化だけでなくシステムの健全性を維持するためにも非常に重要です。
Node.jsには標準でfs(File System)モジュールが備わっており、これを利用することで簡単にファイル操作を実現できます。
本記事では、Node.jsでファイルを削除するための標準的な手法であるfs.unlinkおよびfs.promisesの正しい使い方について詳しく解説します。
2026年現在のモダンな開発環境において、どの手法を選択すべきか、それぞれのメリットとデメリットを比較しながら見ていきましょう。
Node.jsにおけるファイル削除の基本
Node.jsでファイルを削除する場合、基本的には標準ライブラリであるfsモジュールを使用します。
このモジュールには、「非同期(コールバック形式)」「非同期(Promise形式)」「同期」の3種類のメソッドが用意されています。
現代のJavaScript開発では、可読性と保守性の観点からPromise形式(async/await)を利用することが推奨されています。 しかし、古いプロジェクトのメンテナンスや特定の条件下では、コールバック形式や同期処理が必要になる場面もあります。
それぞれのメソッドがどのように動作し、どのような違いがあるのかを正確に理解することが、バグの少ないコードを書くための第一歩です。
まずは、最も基本的なfs.unlinkの役割について学んでいきましょう。
fs.unlinkを使用した非同期(コールバック)削除
fs.unlinkは、Node.jsの初期から存在する非同期のファイル削除メソッドです。
このメソッドは、削除処理の完了を待たずに次の処理へと進むため、サーバーのパフォーマンスを損なうことなく操作を実行できます。
処理が完了したタイミングで実行されるコールバック関数を引数に渡す仕組みとなっています。
以下に、fs.unlinkを使用した基本的なコード例を示します。
const fs = require('fs');
// 削除するファイルのパスを指定します
const filePath = './sample-callback.txt';
// fs.unlinkを使用して非同期で削除を実行します
fs.unlink(filePath, (err) => {
if (err) {
// エラーが発生した場合の処理(ファイルが存在しない場合など)
console.error('ファイルの削除中にエラーが発生しました:', err);
return;
}
console.log('ファイルを正常に削除しました');
});
ファイルを正常に削除しました
このコードでは、第一引数に削除したいファイルのパスを指定し、第二引数にエラーハンドリング用の関数を記述しています。
Node.jsの伝統的な「エラーファースト・コールバック」形式に従っており、最初の引数でエラーの有無を確認します。
シンプルですが、複数の非同期処理を連続して行う場合に「コールバック地獄」と呼ばれる複雑な構造になりやすい欠点があります。
そのため、新規の開発においては次に紹介するfs.promisesの利用が一般的です。
モダンな開発に最適なfs.promisesとasync/await
現在のNode.js開発において、最も推奨されるファイル削除の方法はfs.promises.unlinkを使用する手法です。
このメソッドはPromiseを返すため、async/await構文と組み合わせて同期処理のような見た目で非同期処理を記述できます。
コードの構造が直線的になり、エラーハンドリングもtry-catchブロックで一括して行えるため、可読性が劇的に向上します。
最新のNode.js環境を想定した、fs.promisesによる実装例を確認しましょう。
const fs = require('fs').promises;
async function deleteFile(path) {
try {
// awaitを使用して削除完了を待ちます
await fs.unlink(path);
console.log(`${path} を正常に削除しました`);
} catch (error) {
// エラーが発生した際の処理をここに記述します
console.error('削除失敗:', error.message);
}
}
// 関数の実行
deleteFile('./sample-promise.txt');
./sample-promise.txt を正常に削除しました
このように、try-catchを利用することでエラー処理が明確になります。
また、複数のファイルを順番に削除したり、削除後に別の処理を行ったりする場合でも、コードが複雑化しません。
大規模なアプリケーションやAPI開発においては、このPromiseベースの記述が標準となっています。
モジュールシステムとしてES Modules(ESM)を使用している場合は、import fs from 'fs/promises';と記述することでさらに簡潔に利用可能です。
同期処理fs.unlinkSyncの使い所とリスク
Node.jsには、削除処理が完了するまでメインスレッドの実行を停止させるfs.unlinkSyncというメソッドも存在します。
このメソッドを使用すると、非同期処理のようなコールバックやawaitの記述が不要になります。
しかし、Webサーバーなどのマルチユーザー環境で同期メソッドを使用することは避けるべきです。 なぜなら、ファイルの削除が完了するまでサーバー全体の処理が止まってしまい、他のユーザーのリクエストに応答できなくなるからです。
同期メソッドが許容されるのは、主に以下のようなケースに限られます。
- CLIツールの初期化処理など、パフォーマンスが最優先されない場合
- スクリプトの実行開始時に、設定ファイルを必ず削除する必要がある場合
- 他の処理がファイル削除の結果に完全に依存しており、順序が絶対である場合
以下にfs.unlinkSyncのコード例を示します。
const fs = require('fs');
const filePath = './sample-sync.txt';
try {
// 同期的にファイルを削除します
fs.unlinkSync(filePath);
console.log('同期処理でファイルを削除しました');
} catch (err) {
console.error('削除中にエラーが発生しました:', err);
}
同期処理でファイルを削除しました
同期処理は一見便利に感じられますが、基本的には非同期処理を選択する習慣を身につけておくことが大切です。
ファイルが存在しない場合のエラーハンドリング
ファイル削除を実行する際、最も頻繁に遭遇するエラーは「指定したパスにファイルが存在しない」というものです。
Node.jsでは、存在しないファイルを削除しようとするとENOENTというエラーコードが返されます。
これを適切に処理しないと、アプリケーションが予期せず終了してしまう恐れがあります。
事前にファイルの存在確認を行うか、エラーをキャッチして無視するなどの工夫が必要です。
以下に、ファイルの存在を確認してから削除する、より安全な実装パターンを紹介します。
const fs = require('fs').promises;
async function safeDelete(path) {
try {
// 最初にファイルへのアクセス権限を確認します(存在チェック)
await fs.access(path);
// 存在が確認できたら削除を実行します
await fs.unlink(path);
console.log('安全に削除が完了しました');
} catch (error) {
if (error.code === 'ENOENT') {
console.warn('ファイルが見つかりませんでしたが、処理を続行します');
} else {
console.error('予期しないエラーが発生しました:', error);
}
}
}
safeDelete('./maybe-missing.txt');
ファイルが見つかりませんでしたが、処理を続行します
fs.accessを使用することで、削除前に状態を確認できます。
ただし、ファイルシステムの状態は刻一刻と変化するため、チェックと削除の間にファイルが消える可能性(レースコンディション)も考慮しなければなりません。
実務上は、直接削除を試みてENOENTエラーをキャッチする手法が最も効率的で確実な場合も多いです。
ディレクトリの削除(fs.rmとfs.rmdir)
ここまでは「ファイル」の削除について解説してきましたが、空ではない「ディレクトリ」を削除したい場合には別のメソッドが必要です。
かつてはfs.rmdirが使われていましたが、現在はより汎用的なfs.rmメソッドの使用が推奨されています。
fs.rmを使用すると、ディレクトリの中にファイルが含まれていても、再帰的にすべて削除することが可能です。
const fs = require('fs').promises;
async function deleteDirectory(dirPath) {
try {
// recursive: true で中身ごと削除、force: true で存在しなくてもエラーを出さない
await fs.rm(dirPath, { recursive: true, force: true });
console.log('ディレクトリを再帰的に削除しました');
} catch (error) {
console.error('ディレクトリ削除失敗:', error);
}
}
deleteDirectory('./temp-folder');
ディレクトリを再帰的に削除しました
recursive: trueオプションを指定することで、中身が空でないディレクトリも強制的に削除できます。
これは一時的な作業ディレクトリを丸ごとクリーンアップする際に非常に便利です。
ただし、間違ったパスを指定すると重要なデータまで消去してしまうため、パスの指定には細心の注意を払ってください。
ファイル削除メソッドの比較まとめ
各メソッドの特徴を理解し、プロジェクトの状況に合わせて適切なものを選択しましょう。
以下の表に、今回紹介した主なメソッドの比較をまとめました。
| メソッド名 | 処理形式 | 推奨される用途 | 主なメリット |
|---|---|---|---|
| fs.unlink | 非同期(コールバック) | 既存の古いコードの維持 | 追加の依存関係なく動作 |
| fs.promises.unlink | 非同期(Promise) | モダンなアプリケーション開発 | async/awaitによる高い可読性 |
| fs.unlinkSync | 同期 | 初期化スクリプト・CLIツール | フロー制御が容易 |
| fs.rm | 非同期(Promise/等) | ディレクトリや中身の全削除 | 再帰的削除(Recursive)に対応 |
基本的には、まず「fs.promises.unlink」を検討し、ディレクトリごと消したい場合に「fs.rm」を使うのが現代のベストプラクティスです。
まとめ
Node.jsでファイルを削除する方法は複数ありますが、それぞれの特徴を正しく理解することが重要です。
非同期処理を基本とし、特にPromise形式(fs.promises)を活用することで、エラーに強くメンテナンス性の高いコードを実現できます。
また、ファイルが存在しない場合のエラーハンドリングや、ディレクトリを削除する際のオプション指定など、実務で役立つテクニックを組み合わせて使用しましょう。
セキュリティの観点からは、ユーザーから入力されたパスをそのまま削除処理に渡さないよう、パスの正規化やバリデーションを行うことも忘れないでください。
今回紹介した手法を適切に使い分け、効率的なファイル管理機能を実装していきましょう。
