Express.js ファイルアップロード (multer 編)
Node.js,Express.js,ファイルアップロード
はじめに
multer はファイルアップロードでよく使われる Express.js ミドルウェアです。サンプルや関連記事も多くて安定した人気です。
multer ではファイルアップロードに関して次のことが可能です。
- 単一ファイルのアップロード
- 単一エレメントからの複数ファイルのアップロード
- 複数エレメントからの複数ファイルのアップロード
npmjs.com のサンプルや説明は簡単な物しかなかったので、上の3つのケースについて簡単に試してみました。
その前に multer の基本ですが次のようにします。
- multer モジュールをロードする。
const multer = require(‘multer’); - multer オブジェクトを初期化する。
const upload = multer({dest:updir}); // updir はアップロード先のフォルダ(フルパス)。省略した場合は、OS の一時フォルダが使用される。 - リクエストハンドラの第2引数には multer オブジェクトのオプションを設定する。
router.post(‘/single’, upload.single(‘file1’), (req, res) => { .. });
multer の初期化
multer を使う場合、アップロード先のフォルダを指定しておく必要があります。ただし、これは省略可能で省略すると OS で決まる一時フォルダが使用されます。
const path = require('path');
const multer = require('multer');
const updir = path.dirname(__dirname).replace(/\\/g, "/") + "/tmp"; // アプリケーションフォルダのサブディレクトリ "./tmp" をアップロード先にしている。
const upload = multer({dest:updir});
単一ファイルのアップロード
フォームに 1 つの input[type=”file”] エレメントがあり、multiple 属性がない場合の例です。POST ハンドラの第二引数に upload.single(‘file1’) を指定します。ただし、”file1″ は input[type=”file”] エレメントの name 属性です。
router.post('/single', upload.single('file1'), (req, res) => {
const path = req.file.path.replace(/\\/g, "/");
if (path) {
const dest = updir + "/" + req.file.originalname;
fs.renameSync(path, dest); // 長い一時ファイル名を元のファイル名にリネームする。
res.render('upload', {message: `${dest} にアップロードされました。`});
}
else {
res.render('upload', {message: "エラー:アップロードできませんでした。"});
}
});
この時のフォームは次のような感じなります。
<form id="form1" method="POST" enctype="multipart/form-data" action="/upload/single">
<fieldset class="form-group">
<label for="file1">アップロードファイル (1)</label>
<input type="file" id="file1" name="file1" class="form-control" />
</fieldset>
<fieldset class="form-group">
<input type="submit" id="submitButton" name="submitButton" class="btn btn-primary" value=" 送信 " />
</fieldset>
</form>
<p class="message" id="message"><%= message %></p>
複数ファイルのアップロード
複数ファイルをアップロードする場合は、post ハンドラの第二引数に upload.array(‘file1′, MAXFILES) が必要です。’file1’ はフォームの input[type=”file”] の name 属性、MAXFILES はアップロードできるファイルの最大数です。upload は multer オブジェクトです。
const MAXFILES = 3;
router.post('/multiple', upload.array('file1', MAXFILES), (req, res) => {
const n = req.files.length;
try {
for (let i = 0; i < n; i++) {
const path = req.files[i].path.replace(/\\/g, "/");
const dest = updir + "/" + req.files[i].originalname;
fs.renameSync(path, dest); // 長い一時ファイル名を元のファイル名にリネームする。
}
res.render('upload', {message: `${n} 個のファイルがアップロードされました。`});
}
catch (err) {
res.render('upload', {message: "エラー:アップロードできませんでした。"});
}
});
この時のフォームは次のような感じなります。input[type=”file”] タグに multiple 属性が付与されています。multiple 属性を設定すると、ファイル選択ダイアログで Ctrl キーを押しながら複数ファイルを選択できます。
<form id="form1" method="POST" enctype="multipart/form-data" action="/upload/multiple">
<fieldset class="form-group">
<label for="file1">アップロードファイル (1)</label>
<input type="file" id="file1" name="file1" class="form-control" multiple />
</fieldset>
<fieldset class="form-group">
<input type="submit" id="submitButton" name="submitButton" class="btn btn-primary" value=" 送信 " />
</fieldset>
</form>
<p class="message" id="message"><%= message %></p>
複数エレメントからのアップロード
フォームには複数の input[type=”file”] がある場合がありますが、そういう場合は post ハンドラの第二引数に upload.fields(namedOption) が必要です。upload は multer オブジェクトです。namedOption は、複数 input[type=”file”] の指定とアップロードできるファイルの最大数の一覧です。
const MAXFILES = 3;
const namedOption = [
{'name':'file1', maxCount:1}, {'name':'file2', maxCount:MAXFILES}
];
router.post('/named', upload.fields(namedOption), (req, res) => {
// file1
const path1 = req.files['file1'][0].path.replace(/\\/g, "/");
if (path1) {
const dest1 = updir + "/" + req.files['file1'][0].originalname;
fs.renameSync(path1, dest1); // 長い一時ファイル名を元のファイル名にリネームする。
}
else {
res.render('upload', {message: "エラー:アップロードできませんでした。(file1)"});
return;
}
// file2
const n = req.files['file2'].length;
for (let i = 0; i < n; i++) {
let path2 = req.files['file2'][i].path.replace(/\\/g, "/");
let dest2 = updir + "/" + req.files['file2'][i].originalname;
fs.renameSync(path2, dest2);
}
res.render('upload', {message: `${n+1} 個のファイルがアップロードされました。`});
});
この時のフォームは次のような感じなります。input[type=”file”] (name=”file2″) タグに multiple 属性が付与されています。multiple 属性を設定すると、ファイル選択ダイアログで Ctrl キーを押しながら複数ファイルを選択できます。
<form id="form1" method="POST" enctype="multipart/form-data" action="/upload/multiple">
<fieldset class="form-group">
<label for="file1">アップロードファイル (1)</label>
<input type="file" id="file1" name="file1" class="form-control" />
</fieldset>
<fieldset class="form-group">
<label for="file1">アップロードファイル (2)</label>
<input type="file" id="file2" name="file2" class="form-control" multiple />
</fieldset>
<fieldset class="form-group">
<input type="submit" id="submitButton" name="submitButton" class="btn btn-primary" value=" 送信 " />
</fieldset>
</form>
<p class="message" id="message"><%= message %></p>
Express.js ファイルアップロード (express-form-data 編)
Node.js,Express.js,FormData,ファイルアップロード
はじめに
Express.js でファイルアップロードする場合、multer と呼ばれるミドルウェアがしばしば使用されます。
しかし、ファイルアップロードに対応するミドルウェアは他にもいろいろあって express-form-data もその1つです。
express-form-data は単なるファイルアップロードのためのミドルウェアではなく、JavaScript の FormData オブジェクトを Express.js で扱うためのミドルウェアです。
Express.js では POST データはフォームから submit されたデータと JSON にのみ標準で対応しています。このため、Fetch API で FormData を POST してもリクエストボディが undefined になってしまい処理が続行できません。
そこで、express-form-data をミドルウェアにすることで、FormData も正しく扱えるようになります。
インストール
express-form-data は次のようにしてインストールできます。
npm install express-form-data
単一ファイルのアップロード
express-form-data では multer のように単一ファイル、複数ファイル、複数エレメントでコードを変える必要はなく、すべて req.files に保存されています。下の例では、input[type=”file”] の name 属性が “file1” によりアップロードされたファイルを取得して、そのファイルの長い一時ファイル名を元の名前に変更するものです。
このケースでは、express-form-data をミドルウェアとして使用するので、モジュールの最初の方で次のようなコードを追加しておく必要があります。これは他の例でも同様です。
formData.parse() の引数は省略可能ですが、その場合、アップロードファイルの保存先が OS の一時フォルダになります。
const path = require('path');
const formData = require('express-form-data');
const updir = path.dirname(__dirname).replace(/\\/g, "/") + "/tmp"; // アップロード先のフォルダ
router.use(formData.parse({uploadDir:updir, autoClean:true}));
下のコードが post ハンドラの例です。multer のような第二引数は不要です。
router.post('/single', (req, res) => {
const path1 = req.files.file1.path; // アップロードされたファイルのフルパス名
const name = req.files.file1.name; // 元のファイル名
if (path1) {
const dest = path.dirname(path1).replace(/\\/g, "/") + "/" + name;
fs.renameSync(path1, dest); // 一時ファイル名を元のファイル名に変更する。
res.render('upload2', {message: dest + " にアップロードされました。"});
}
else {
res.render('upload2', {message: "エラー:アップロードできませんでした。"});
}
});
このときのフォームは次のような感じです。
<form id="form1" method="POST" enctype="multipart/form-data" action="/upload2/single">
<fieldset class="form-group">
<label for="file1">アップロードファイル </label>
<input type="file" id="file1" name="file1" class="form-control" />
</fieldset>
<fieldset class="form-group">
<input type="submit" class="btn btn-primary" value=" 送信 " />
</fieldset>
<br />
</form>
<br />
<p class="message" id="message"><%= message %></p>
複数ファイルのアップロード
express-form-data を使用する場合、アップロードファイルが単一か複数かの違いはなく req.files にそのデータが入っています。この例では input[type=”file”] の name 属性が “file1” の場合、アップロードファイルの情報が req.files.file1 に入っていますが、これが配列になっており、そこからアップロードファイルの情報を取り出します。
(注意) express-form-data の初期化が必要。単一ファイルのアップロード の説明参照。
router.post('/multiple', (req, res) => {
const n = req.files.files1.length; // アップロードされたファイル数
for (let i = 0; i < n; i++) {
let src = req.files.files1[i].path.replace(/\\/g, "/");
let dest = path.dirname(req.files.files1[i].path).replace(/\\/g, "/") + "/" + req.files.files1[i].name;
fs.renameSync(src, dest); // 長い一時ファイル名から元の名前に変更する。
}
if (n > 0) {
res.render('upload2Multi', {message: n + " 個のファイルがアップロードされました。"});
}
else {
res.render('upload2Multi', {message: "エラー:アップロードできませんでした。"});
}
});
この例でのフォームは次のようになっています。
<form id="form1" method="POST" enctype="multipart/form-data" action="/upload2/multiple">
<fieldset class="form-group">
<label for="file1">アップロードファイル </label>
<input type="file" id="files1" name="files1" class="form-control" multiple />
</fieldset>
<fieldset class="form-group">
<input type="submit" class="btn btn-primary" value=" 送信 " />
</fieldset>
<br />
</form>
<br />
<p class="message" id="message"><%= message %></p>
Fetch API と FormData を使用してのアップロード
express-form-data を使うと FormData オブジェクトとして input[type=”file”] を含むフォームを丸ごとサーバへ送信できるので、クライアント側のコードが簡単になります。
(注意) express-form-data の初期化が必要。単一ファイルのアップロード の説明参照。
router.post('/formdata', (req, res) => {
const path1 = req.files.file1.path.replace(/\\/g, "/"); // アップロードされたファイルのフルパス名
const name = req.files.file1.name; // 元のファイル名
if (path1) {
const dest = path.dirname(path1).replace(/\\/g, "/") + "/" + name;
fs.renameSync(path1, dest); // 一時ファイル名を元のファイル名に変更する。
res.send(dest + " にアップロードされました。");
}
else {
res.send("エラー:アップロードできませんでした。");
}
});
次のコードは、input[type=”file”] エレメントを含むフォームの例です。アップロードの結果は id=”message” で指定される p タグの内部文字列として表示されます。
<form id="form1" method="POST" enctype="multipart/form-data">
<fieldset class="form-group">
<label for="file1">アップロードファイル </label>
<input type="file" id="file1" name="file1" class="form-control" />
</fieldset>
<fieldset class="form-group">
<input type="button" class="btn btn-primary" value=" 送信 " onclick="javascript:submitClick()" />
</fieldset>
<br />
</form>
<br />
<p class="message" id="message"></p>
次がクライアント側のコードで、送信ボタンをクリックしたときのハンドラです。fetch 関数のパラメータが FormData オブジェクトなのですっきりします。
function element(id) {
return document.getElementById(id);
}
function submitClick() {
const formData = new FormData(form1);
fetch('/upload2/formdata', {method:"POST", body:formData})
.then(res => res.text())
.then(text => element('message').innerText = text)
.catch(err => element('message').innerHTML = err.message);
}
FormData を使った複数エレメントのファイルアップロード
この例は複数 input[type=”file”] エレメント (name=”file1″, name=”file2″) を持つフォームからのファイルアップロードのコードです。基本的に単一ファイルの場合と同じで、name=”file1″, name=”file2″ について同じコードを書くだけです。
router.post('/formdata2', (req, res) => {
const path1 = req.files.file1.path.replace(/\\/g, "/"); // アップロードされたファイルのフルパス名 (1)
const path2 = req.files.file2.path.replace(/\\/g, "/"); // アップロードされたファイルのフルパス名 (2)
const name1 = req.files.file1.name; // 元のファイル名
const name2 = req.files.file2.name; // 元のファイル名
if (path1) {
var dest1 = path.dirname(path1).replace(/\\/g, "/") + "/" + name1;
fs.renameSync(path1, dest1); // 一時ファイル名を元のファイル名に変更する。
if (path2) {
var dest2 = path.dirname(path2).replace(/\\/g, "/") + "/" + name2;
fs.renameSync(path2, dest2); // 一時ファイル名を元のファイル名に変更する。
}
res.send(`"${dest1}", "${dest2}" にアップロードされました。`);
}
else {
res.send("エラー:アップロードできませんでした。");
}
});
次のコードは、この場合のフォームの例です。
<form id="form1" method="POST" enctype="multipart/form-data">
<fieldset class="form-group">
<label for="file1">アップロードファイル (1)</label>
<input type="file" id="file1" name="file1" class="form-control" />
</fieldset>
<fieldset class="form-group">
<label for="file1">アップロードファイル (2)</label>
<input type="file" id="file2" name="file2" class="form-control" />
</fieldset>
<fieldset class="form-group">
<input type="button" class="btn btn-primary" value=" 送信 " onclick="javascript:submitClick()" />
</fieldset>
<br />
</form>
<br />
<p class="message" id="message"></p>
次のコードは、クライアント側の JavaScript コードの例です。
function element(id) {
return document.getElementById(id);
}
// 送信ボタンがクリックされたとき
function submitClick() {
const formData = new FormData(form1); // form1 を元に FormData オブジェクトを作成する。
fetch('/upload2/formdata2', {method:"POST", body:formData})
.then(res => res.text())
.then(text => element('message').innerText = text)
.catch(err => element('message').innerHTML = err.message);
}
express-form-data を使うと multer より簡単にファイルアップロードを行えます。特に、Fetch API を使う場合は、FormData が使えるので非常に簡単になります。
Express.js でのファイルアップロードについて、multer と express-form-data をそれぞれ使った記事を Qiita にも投稿しました。
multer 編
express-form-data 編