Node.jsを用いたWebアプリケーション開発において、ファイルのアップロード機能はサービスに不可欠な要素の一つとなっています。

2026年現在、高解像度の画像や大容量の動画、膨大なドキュメントデータを扱う機会が増え、効率的かつ安全なアップロード処理の重要性がかつてないほど高まっています。

Node.jsは非同期I/Oをベースとしたアーキテクチャを持ち、複数のアップロード処理を並行して効率的に捌くことが得意です。

本記事では、Node.jsでファイルアップロードを実装する際のスタンダードな手法から、パフォーマンスを最大化するためのストリーム処理まで、実践的なテクニックを網羅的に解説します。

Node.jsにおけるファイルアップロードの基本原理

Webブラウザからファイルをサーバーへ送信する際、一般的にmultipart/form-dataというエンコーディング形式が使用されます。

これは、一つのHTTPリクエストの中にテキストデータとバイナリデータを混在させて送信するための仕組みです。

Node.jsの標準的なHTTPモジュールだけでは、この複雑なフォーマットを解析(パース)するのに多くのコードを記述しなければなりません。

そのため、現代の開発では信頼性の高いライブラリを利用して実装を簡略化するのが一般的です。

特にExpressなどのWebフレームワークと組み合わせて使用されることが多いのが、ミドルウェアとしての「Multer」です。

まずは、このMulterを使用した基本的なアップロード処理の流れから見ていきましょう。

定番ライブラリ「Multer」を使用した実装

Multerは、multipart/form-dataを処理するためのNode.js用ミドルウェアであり、非常に高い普及率を誇ります。

まずは必要なパッケージをインストールすることから始めましょう。

Shell
npm install express multer

基本のファイルアップロード構成

もっともシンプルな実装では、特定のディレクトリを指定してファイルを保存します。

以下のコードは、サーバー側のuploads/ディレクトリにファイルを保存する基本的な例です。

JavaScript
const express = require('express');
const multer = require('multer');
const app = express();

// 保存先の設定(diskStorageを使用)
const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, 'uploads/'); // 保存先のフォルダを指定
  },
  filename: (req, file, cb) => {
    // ファイル名の衝突を避けるためにタイムスタンプを付与
    const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
    cb(null, file.fieldname + '-' + uniqueSuffix + '.' + file.originalname.split('.').pop());
  }
});

const upload = multer({ storage: storage });

// POSTリクエストを受け取るエンドポイント
app.post('/upload', upload.single('myFile'), (req, res) => {
  if (!req.file) {
    return res.status(400).send('ファイルが選択されていません。');
  }
  res.send('ファイルのアップロードが成功しました:' + req.file.filename);
});

app.listen(3000, () => {
  console.log('Server started on http://localhost:3000');
});
実行結果
Server started on http://localhost:3000
ファイルのアップロードが成功しました:myFile-1715432100000-123456789.jpg

この実装では、upload.single('myFile')というミドルウェアがリクエストを解析し、ファイルを自動的に保存します。

req.fileオブジェクトを介して、ファイル名やサイズ、パスなどの詳細情報にアクセスすることが可能です。

バリデーションの実装:ファイル形式とサイズの制限

セキュリティとサーバーリソースを保護するために、アップロードされるファイルの制限を設定することは必須です。

Multerではlimitsオプションを使用して、ファイルサイズの上限を簡単に設定できます。

また、fileFilter機能を使うことで、特定の拡張子やMIMEタイプのみを許可するロジックを組み込めます。

JavaScript
const upload = multer({
  storage: storage,
  limits: {
    fileSize: 5 * 1024 * 1024 // 5MBを上限に設定
  },
  fileFilter: (req, file, cb) => {
    if (file.mimetype === 'image/jpeg' || file.mimetype === 'image/png') {
      cb(null, true);
    } else {
      cb(new Error('JPGまたはPNG形式の画像のみ許可されています。'), false);
    }
  }
});

このように制限を設けることで、不正な形式のファイル送信や、意図しないディスク容量の圧迫を防ぐことができます。

大容量ファイルを扱うためのStream(ストリーム)活用

Multerのデフォルトの挙動では、ファイル全体をメモリやディスクに一時的に保持してから後続の処理を行います。

しかし、数GBを超えるような巨大なファイルを扱う場合、メモリを過剰に消費しサーバーのパフォーマンスが著しく低下する恐れがあります。

そこで重要になるのが、Node.jsの強力な機能である「Stream(ストリーム)」の活用です。

Stream処理が有利な理由

Streamを利用すると、データを一括で読み込むのではなく、小さな断片(チャンク)ごとに分割して逐次処理することができます。

これにより、ファイルのサイズに関わらず、メモリ使用量を一定の低い水準に保つことが可能です。

動画共有プラットフォームや、膨大なログファイルを扱うシステムでは、Streamベースの処理が標準的に採用されています。

busboyを使用したStreamアップロードの実装

低レベルな制御が可能な「busboy」ライブラリを使用すると、アップロードされるデータを直接書き込みストリームへ流し込むことができます。

JavaScript
const http = require('http');
const busboy = require('busboy');
const fs = require('fs');
const path = require('path');

http.createServer((req, res) => {
  if (req.method === 'POST') {
    const bb = busboy({ headers: req.headers });

    bb.on('file', (name, file, info) => {
      const { filename, encoding, mimeType } = info;
      const saveTo = path.join(__dirname, 'uploads', filename);
      
      // ファイルシステムへの書き込みストリームを作成
      const writeStream = fs.createWriteStream(saveTo);
      
      // パイプ(pipe)を使ってデータを流し込む
      file.pipe(writeStream);

      writeStream.on('finish', () => {
        console.log(`ファイル ${filename} の保存が完了しました。`);
      });
    });

    bb.on('finish', () => {
      res.writeHead(200, { 'Connection': 'close' });
      res.end('アップロードが完了しました。');
    });

    req.pipe(bb);
  }
}).listen(3000);

この手法では、file.pipe(writeStream)によってデータが流れるように処理されるため、メモリのピーク消費を劇的に抑えることができます。

2026年のトレンド:クラウドストレージへの直接アップロード

2026年現在のモダンな構成では、サーバー自身のディスクに保存するのではなく、Amazon S3やGoogle Cloud Storageなどのオブジェクトストレージを活用することが一般的です。

特に、「署名付きURL(Presigned URL)」を利用したクライアントからの直接アップロードが推奨されています。

署名付きURLを利用するメリット

従来は「クライアント → アプリケーションサーバー → クラウドストレージ」という経路でデータを転送していました。

しかし、この経路ではアプリケーションサーバーの帯域幅を消費し、大きなボトルネックとなります。

署名付きURLを発行すれば、クライアントはサーバーを介さずストレージへ直接ファイルを送信できるため、サーバー負荷を極限まで減らせます。

Node.jsでの署名付きURL発行例

以下の例は、AWS SDK v3を使用してS3へのアップロード用URLを発行するイメージです。

JavaScript
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

const s3Client = new S3Client({ region: "ap-northeast-1" });

async function generateUploadUrl(fileName) {
  const command = new PutObjectCommand({
    Bucket: "my-app-bucket",
    Key: `uploads/${fileName}`,
  });

  // 1時間有効な署名付きURLを発行
  const url = await getSignedUrl(s3Client, command, { expiresIn: 3600 });
  return url;
}

このURLをフロントエンドに返し、フロントエンド側でPUTリクエストを行うことで、高効率なアップロードが実現します。

セキュリティと運用のベストプラクティス

ファイルアップロード機能は、攻撃者にとっても格好の標的となります。

悪意のあるスクリプトがアップロードされたり、サービス不能攻撃(DoS)を受けたりしないよう、対策を講じる必要があります。

ファイル名の正規化とパス・トラバーサル対策

ユーザーが指定したファイル名をそのまま保存に使用するのは非常に危険です。

../../etc/passwdのようなファイル名によって、サーバー内の重要なファイルを上書きされる「パス・トラバーサル」攻撃を招く恐れがあります。

保存時には必ずUUIDなどのランダムな文字列にリネームするか、記号を除去する正規化処理を行ってください。

ウイルススキャンとコンテンツ検証

アップロードされたファイルがウイルスに感染していないかチェックすることも検討すべきです。

Node.js環境であれば、アップロード完了後のイベントをトリガーに、ClamAVなどのウイルススキャンエンジンと連携させることが有効です。

また、ファイルの拡張子だけでなく、バイナリのヘッダを確認して実際のファイル形式が宣言通りであるかを確認するライブラリ(file-typeなど)の利用を推奨します。

アップロードサイズに応じたタイムアウト設定

大容量ファイルの送信は時間がかかるため、サーバー側のタイムアウト設定を適切に調整する必要があります。

デフォルトの短い設定では、アップロード途中で接続が切断されてしまう可能性があるからです。

一方で、タイムアウトを長くしすぎるとリソースを占有され続けるリスクがあるため、バランスの取れた設計が求められます。

まとめ

Node.jsでのファイルアップロード実装には、手軽なMulterから、効率を重視したStream処理、さらにはクラウドを活用したアーキテクチャまで幅広い選択肢があります。

小規模な社内ツールであればMulterのdiskStorageで十分ですが、不特定多数のユーザーが利用するサービスでは、Streamや署名付きURLの活用を検討しましょう。

また、利便性だけでなく、ファイルバリデーションやリネーム処理といったセキュリティ対策を常にセットで考えることが重要です。

2026年のWeb開発においては、パフォーマンスと安全性の両立が、質の高いサービスを提供する鍵となります。

本記事で紹介した手法を参考に、プロジェクトの要件に最適なファイルアップロード機能を構築してください。