Node.jsからExpressへファイルのアップロードを処理する方法

November 03, 2021
レビュー担当者

Node.jsからExpressへファイルのアップロードを処理する方法

この記事はAshley Boucherこちらで公開した記事(英語)を日本語化したものです。

最近、Node.jsでMarkdownでの記事執筆のためのCLIツールを開発していました。ローカルの.mdファイルを解析して、書式設定し、.docxに変換してから、Google DocファイルとしてGoogleドライブにアップロードするというもので、このすべてをターミナルから1つのコマンドで実行します。

いくつかの方法でこのプロジェクトにアプローチしてみましたが、最初はサーバーレス関数を使用してバックエンドを処理できると考えました。この方法では何度も行き詰まり、最終的にHerokuにホストするExpressサーバーを構築することにしました。

CLIからセキュアなバックエンドのエンドポイント(ユーザーの認証済みGoogleドライブ)に.docxファイルの内容をアップロードするには、ブラウザを使用せずにNode.jsから直接multipart/form-dataPOSTする方法が必要でした。これにはいくつかのサードパーティのライブラリが必要ですが、それらをすべて連携させることは困難でした。役に立つ優れたリソースをインターネットで見つけることはできず、多くの試行錯誤を繰り返しました。この記事では、これを実現するにあたってたどり着いたNode.jsからExpressへファイルをアップロードする方法をご紹介します。

必要条件

このチュートリアルには、以下の項目が必要です。

  • マシンにインストールされているNode.jsとパッケージマネージャー(npm
  • テキストエディタ

アプリケーションの構造を設定する

ターミナルまたはコマンドプロンプトで、一般的なプロジェクトまたは開発用ディレクトリに移動し、次のコマンドを実行します。

mkdir multipart_demo
cd multipart_demo
mkdir node_app
npx express-generator express_app
cd express_app
npm install

これらのコマンドで、Node.jsアプリケーションとExpress APIの両方のプロジェクトを保持するディレクトリを作成します。また、Expressバックエンドの雛形をexpress-generatorで作成し、必要な依存パッケージを新しいExpressアプリケーションにインストールします。

Node.jsアプリケーションの雛形を作成する

ターミナルで、親ディレクトリのmultipart-demoに戻り、Node.jsアプリケーションに移

動して、次のコマンドを実行します。

cd ..
cd node_app

新しいNode.jsアプリケーションを初期化します。

npm init -y

このプロジェクトに必要なサードパーティの依存パッケージをインストールします。

npm install form-data axios os

form-dataライブラリを使用して、キーと値のペアが含まれる「フォーム」をNode.jsアプリケーションに作成します。ExpressアプリケーションにフォームデータをPOSTするには、axiosを使用します。

コードを記述してファイルをアップロードする

テキストエディタを使用し、node_appフォルダのindex.jsファイルを新規作成し、開きます。このファイル内で、次のコードを上部に追加します。

const fs = require('fs');
const axios = require('axios');
const FormData = require('form-data');

このコードでは、インストールした2つのサードパーティの依存関係をインポートし、ファイルシステムと対話できるようにするfsモジュールをインポートします。

これらの行の下に、次の関数と関数呼び出しを追加します。

const upload = async () => {
  try {
    const file = fs.createReadStream('./myfile.txt');
    const title = 'My file';
    
    const form = new FormData();
    form.append('title', title);
    form.append('file', file);
    
    const resp = await axios.post('http://localhost:3000/upload', form, {
      headers: {
        ...form.getHeaders(),
      }
    });
    
    if (resp.status === 200) {
      return 'Upload complete';
    } 
  } catch(err) {
    return new Error(err.message);
  }
}
  
upload().then(resp => console.log(resp));

upload()関数は非同期関数です。関数内にはtry/catchブロックがあります。これは、関数がtryブロック内で処理の実行を「試行」することを意味します。コードにエラーが発生した場合は、catchブロックをすぐに実行し、そこで問題のエラーにアクセスできます。

tryブロックで最初に試行することは、filetitleの2つの変数を作成することです。

3行目(file変数が作成されている)がハイライトされていますが、これは、表示されているファイルパスを、アップロードするファイルのパスに置き換える必要があるためです。この変数は、ファイルの読み取り可能なストリームを表します。

特定のファイルを念頭に置かずに処理を進めるには、myfile.txtファイルを新規作成し、そのファイルにランダムなテキストを記述して、index.jsの横にあるnode_appフォルダに保存します。

title変数では、アップロードするファイルのタイトルを保存しますが、任意の文字列値に変更できます。

これらの変数の下に、フォームが作成されます。ファイルとそのタイトル用の2つのキーと値のペアがフォームに追加されます。このように、フォームデータを表す任意の数のキーと値のペアをformオブジェクトに追加できます。

次に、axiosを使用して、フォーム全体がExpressバックエンドにポストされます。フォームをポストするエンドポイントはチュートリアルの後半で作成します。ヘッダー情報でオプションのオブジェクトをaxios.post()メソッドに渡すことに注意してください。form-dataライブラリでは、フォームデータに適したヘッダーを返す.getHeaders()メソッドを指定して、ヘッダーを簡単に正しく設定できます。

エラーがない場合は、「Upload complete」のメッセージがコンソールに記録されます。それ以外の場合は、エラーメッセージがログに記録されます。

フォームデータを処理する

ファイルを正常に送信したら、バックエンドで処理できるようにする必要があります。

ターミナルで、Expressアプリケーションに移動します。

cd ..
cd express_app

Multerは、multipart/form-dataを処理するNode.jsミドルウェアです。次のコマンドを実行し、multerパッケージをインストールします。

npm install multer

テキストエディターから、multipart_demo/express_app/routesフォルダにあるindex.jsファイルを開きます。ファイルの内容を削除し、次の内容に置き換えます。

var express = require('express');
var router = express.Router();
var os = require('os')
  
const multer  = require('multer');
const upload = multer({ dest: os.tmpdir() });
  
router.post('/upload', upload.single('file'), function(req, res) {
  const title = req.body.title;
  const file = req.file;
  
  console.log(title);
  console.log(file);
  
  res.sendStatus(200);
});
  
module.exports = router;

このコードでは、注意することがいくつかあります。特に重要な行はハイライトされています。

5行目では、multerパッケージがインポートされた後に、multer()関数を呼び出して、destキーを持つオプションオブジェクトを渡し、返されたオブジェクトをupload変数に保存して、Multerを使用する準備をしています。ディスクではなくメモリにファイルを保存するなど、Multerを設定する他の方法については、ドキュメントを参照してください。

この場合、Multerではアップロードしたファイルを別の場所に保存する必要があるため、dest値を指定しています。このアプリケーションでは、ファイルが一時的に必要なため、保存先をサーバーの/tmpフォルダに設定します。アップロードしたファイルを保持する必要がある場合、このオブジェクトのパスを指定すれば、サーバーの別のディレクトリに保存できます。

7行目では、APIエンドポイントのコールバック関数を実行する前に、uploadオブジェクトでsingle()メソッドを呼び出しています。uploadオブジェクトでは、さまざまなデータ構成に適した多くのメソッドを呼び出すことができます。この例では、ファイルを1つのみアップロードしているため、single()メソッドを使用します。

この時点で、エンドポイントのコールバック関数で使用可能な reqオブジェクトは、一般的なreqとは少し異なります。POSTリクエストを実行したときに、フォームにbodyの値を添付していないにもかかわらず、非ファイルフォームのフィールドの値はreq.bodyで使用可能になります。

アップロードしたファイルはreq.fileで使用可能になります。前述のコードでは、これらのオブジェクトの両方がコンソールに記録されます。以下では、これらの表示を確認するために、プロジェクトをテストします。

アプリケーションをテストする

ターミナルで、express_appディレクトリ内にいることを確認してから、次のコマンドを実行します。

npm start

ローカルサーバーがPORT 3000で起動します。

新しいターミナルウィンドウを開き、Node.jsプロジェクトに戻ります。

cd <あなたの親ディレクトリのパス>/multipart_demo/node_app

次のコマンドでスクリプトを実行します。

node index.js

POSTリクエストが成功すると、ターミナルに「Upload complete」のメッセージが表示され、失敗するとエラーメッセージが表示されます。

Screenshot showing upload complete message in console from node app

Expressアプリケーションを実行しているターミナルでもう一度確認します。ファイルのタイトルおよびファイルを表すオブジェクトとファイルのタイトルが表示されます。ファイルをディスクに保存すると、pathキーがあることが分かります。このパスを使用すると、(ファイルをGoogleドライブにアップロードするために必要だったように)別の読み取り可能なストリームを作成できます。

Screenshot showing file object and file title in terminal after posting to express app

この記事では、Node.jsアプリケーションからExpressにファイルを直接アップロードする方法について学びました。ご質問がある場合は、Twitterでお問い合わせください。

Ashleyは、TwilioブログのJavaScriptエディターです。Ashleyと協力し、Twilioにテクニカルストーリーを紹介するには、Twitter[@ahl389](https://twitter.com/ahl389)経由でご連絡ください。TwitterでAshleyが見つからない場合は、どこかのパティオでコーヒーを飲んでいることでしょう(ワインの時間かも)。