Express.js ファイルアップロード (multer 編)


#はじめに
[multer](https://www.npmjs.com/package/multer) はファイルアップロードでよく使われる Express.js ミドルウェアです。サンプルや関連記事も多くて安定した人気です。

multer ではファイルアップロードに関して次のことが可能です。

* 単一ファイルのアップロード
* 単一エレメントからの複数ファイルのアップロード
* 複数エレメントからの複数ファイルのアップロード

[npmjs.com](https://www.npmjs.com/package/multer) のサンプルや説明は簡単な物しかなかったので、上の3つのケースについて簡単に試してみました。

その前に multer の基本ですが次のようにします。

- multer モジュールをロードする。
 <br>const multer = require('multer');
- multer オブジェクトを初期化する。
 <br>const upload = multer({dest:updir}); // updir はアップロード先のフォルダ(フルパス)。省略した場合は、OS の一時フォルダが使用される。
- リクエストハンドラの第2引数には multer オブジェクトのオプションを設定する。
 <br>router.post('/single', upload.single('file1'), (req, res) => { .. });


#multer の初期化

multer を使う場合、アップロード先のフォルダを指定しておく必要があります。ただし、これは省略可能で省略すると OS で決まる一時フォルダが使用されます。

```js
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 属性です。

```js:
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: "エラー：アップロードできませんでした。"});
    }
});
```

この時のフォームは次のような感じなります。

```HTML
<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 オブジェクトです。

```js
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 キーを押しながら複数ファイルを選択できます。

```HTML
<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"] の指定とアップロードできるファイルの最大数の一覧です。

```js
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 キーを押しながら複数ファイルを選択できます。

```HTML
<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-form-data 編もあります。

終わり