Node.js・Expressでmulterを使って、ファイルをアップロードする方法

はいさい、ちゅらデータぬオースティンやいびーん！んな、がんじゅー？（みんな、元気？）

# 概要
本記事では、Node.jsのexpressフレームワークで、multerというミドルウェアを使って、FormDataからファイルを取って、アップロード機能をバックエンドに追加する方法を紹介します。

## 事前準備
### Node.jsのローカルインストール
https://nodejs.org/en/download/

## 目次
1. Expressのセットアップ
1. \<form>の作成
1. multerの導入

# Expressのセットアップ

## パッケージをインストールする
今回は、TypeScriptを使ったExpressプロジェクトを元に作っていくので、まずは、新規フォルダーを作って、`package.json`を作ります。

また、筆者は<a href="https://classic.yarnpkg.com/lang/en/">yarn</a>推奨派なので、全てのコマンドはyarnを使います。
```
mkdir express-multer-upload
cd express-multer-upload
yarn init
yarn add express body-parser
yarn add -D typescript @types/express @types/node nodemon ts-node
```
すると、以下のような`package.json`が出来上がります！
```json:package.json
{
  "name": "express-multer-upload",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "body-parser": "^1.20.0",
    "express": "^4.18.1"
  },
  "devDependencies": {
    "@types/express": "^4.17.13",
    "@types/node": "^18.0.3",
    "nodemon": "^2.0.19",
    "ts-node": "^10.8.2",
    "typescript": "^4.7.4"
  }
}
```
そこに、以下の`scripts`コマンドを入れます。
```json
...
  "scripts": {
    "dev": "nodemon src/index.ts"
  }
}
```
### TypeScriptの設定
次は、tsconfig.jsonを作成し、設定を入れます。筆者の推奨設定は以下の通りです。
```json:tsconfig.json
{
  "compilerOptions": {
    "module": "commonjs",
    "target": "esnext",
    "moduleResolution": "node",
    "esModuleInterop": true,
    "sourceMap": true,
    "outDir": "dist",
    "allowJs": true,
    "lib": ["dom", "es6"],
    "baseUrl": "./src",
  },
  "exclude": ["node_modules", "dist"],
  "include": ["src/**/*.ts"],
}
```
### `src/index.ts`を作成し、expressの基本ロジックを入れる
そして、`src`のフォルダーを作り、そこに`index.ts`を置きます。

そこに以下のように基本的なロジックを入れます。
```typescript:src/index.ts
import bodyParser from "body-parser";
import express from "express";
import { createServer } from "http";
import path from "path";

const app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use("/public", express.static(path.resolve(__dirname, "../public")));

app.get("/", (req, res) => {
  res.sendFile(path.join(__dirname, "index.html"));
});

app.post("/", (req, res) => {
  res.redirect("/");
});

const server = createServer(app);
const port = 4000;
server.listen(port, () => console.log("Listening on port: ", port));
```
これでExpressの基本的なセットアップはOKです！

# `index.html`と\<form>の作成
`index.html`をGETで指定しているのですが、それを作っていないので、作っておきましょう。

ここに、ファイルをアップロードするフォームも入れます。
```html:public/index.html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Express Multer Uploads</title>
</head>

<body>
  <style>
    form {
      display: flex;
      flex-direction: column;
      max-width: 500px;
      margin: 1rem auto;
    }
    input {
      margin-bottom: 1rem;
    }
  </style>
  <h1>Express Multer Uploads!</h1>
  <form action="/" method="POST" enctype="multipart/form-data">
    <label for="name">Name</label>
    <input type="text" name="name" id="name" minlength="1" maxlength="64" required>
    <label for="file">File</label>
    <input type="file" name="file" id="file" accept="video/mp4 image/png image/jpeg audio/mpeg">
    <button type="submit">Send</button>
  </form>
</body>

</html>
```
\<form>には必ず、`enctype="multipart/form-data"`を入れるようにしましょう！こうしないと、multerは拾ってくれません。

また、\<input type="file">に、アップロードしてもいいファイルのMIMEタイプを指定しています。これは`multer`でも指定します。

### 試してみよう！
ここまでの作業がうまくいったのか、確認してみましょう。

ターミナルで以下のコードを実行して、expressを立ち上げます。
```
yarn dev
```
ターミナルに以下のような出力があったらOK!
![スクリーンショット 2022-07-12 10.18.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2344956/e079a329-7035-39b3-b504-d87b9f42fb9a.png)
また、ページをアクセスしてみると以下のような画面が出るはず！
![スクリーンショット 2022-07-12 10.19.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2344956/33d29016-fc42-fb28-be76-0ff6a25f94d1.png)
これで`multer`の説明に入りましょう！

# `multer`の導入
ここから、`multer`の使い方を解説していきます。
### `multer`のパッケージを追加する
まずは、multerとTypeScriptの関連型をインストールします。

先ほど動かしたサーバーを一旦止めましょう。
```
yarn add multer
yarn add -D @types/multer
```
これで、`package.json`は以下のようになります。
```json:package.json
{
  "name": "express-multer-upload",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "body-parser": "^1.20.0",
    "express": "^4.18.1",
    "multer": "^1.4.5-lts.1"
  },
  "devDependencies": {
    "@types/express": "^4.17.13",
    "@types/multer": "^1.4.7",
    "@types/node": "^18.0.3",
    "nodemon": "^2.0.19",
    "ts-node": "^10.8.2",
    "typescript": "^4.7.4"
  },
  "scripts": {
    "dev": "nodemon src/index.ts"
  }
}
```

## `multer`の設定を追加する
次は、expressでmulterを読み込み、設定します。
### diskStorageでサーバー上の保存設定を決める
最初に、multerがFormDataからファイルを受け取った時に、サーバーのどこにそれを保存すればいいかの設定を持つ`storage`という定数を作ります。
```typescript
import multer from "multer";

const storage = multer.diskStorage({
  destination(req, file, callback) {
    callback(null, path.resolve(__dirname, "../uploads"));
  },
  filename(req, file, callback) {
    const uniqueSuffix = Math.random().toString(26).substring(4, 10);
    callback(null, `${Date.now()}-${uniqueSuffix}-${file.originalname}`);
  },
});
```
ここでサーバー上の`/uploads`というフォルダーに、アップロードされたファイルにアップロードされた時間と元々付いていた名前を使って保存するように指定しています。

`/uploads`フォルダーがなかったら、multerは作ってくれます。
### multerの初期設定
次は上記の`storage`定数も使って、multerのインスタンスを作ります。
```typescript
const upload = multer({
  storage,
  fileFilter(req, file, callback) {
    console.log(file.mimetype)
    if (["video/mp4", "image/png", "image/jpeg", "audio/mpeg"].includes(file.mimetype)) {
      callback(null, true);
      return;
    }
    callback(new TypeError("Invalid File Type"));
  },
});
```
fileFilterでは、ファイルのMIMEタイプを見て、アップロードしてもいいものかどうかを判断しています。ここで、もっと厳しいチェックをすることもできます。

実際、MIMEタイプを偽ることができるので、セキュリティ上、MIMEタイプのみに頼るのはお勧めしません。

## ミドルウェアとして、指定のパスにmulterを使う
最後に、ファイルのアップロードをさせたいパスに、上記の`upload`の定数を使います。

```typescript:src/index.ts
import bodyParser from "body-parser";
import express from "express";
import { createServer } from "http";
import path from "path";
import multer from "multer";

const storage = multer.diskStorage({
  destination(req, file, callback) {
    callback(null, path.resolve(__dirname, "../uploads"));
  },
  filename(req, file, callback) {
    const uniqueSuffix = Math.random().toString(26).substring(4, 10);
    callback(null, `${Date.now()}-${uniqueSuffix}-${file.originalname}`);
  },
});

const upload = multer({
  storage,
  fileFilter(req, file, callback) {
    console.log(file.mimetype)
    if (["video/mp4", "image/png", "image/jpeg", "audio/mpeg"].includes(file.mimetype)) {
      callback(null, true);
      return;
    }
    callback(new TypeError("Invalid File Type"));
  },
});

const app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use("/public", express.static(path.resolve(__dirname, "../public")));

app.get("/", (req, res) => {
  res.sendFile(path.join(__dirname, "index.html"));
});

// "file"とは、<input name="file">の"file"というフィールド名に当たります
app.post("/", upload.single("file"), (req, res) => {
  console.log(req.file);
  res.redirect("/");
});

const server = createServer(app);
const port = 4000;
server.listen(port, () => console.log("Listening on port: ", port));
```
### 試してみよう、アップロード！
またサーバーを立ち上げて、何かをアップロードしてみましょう！
![ezgif.com-gif-maker (8).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2344956/34109c7b-9442-47b8-5d7b-3472284776f6.gif)
ばっちり！

# まとめ
これでmulterを使ってFormDataについているファイルの処理をする方法を紹介しましたが、いかがでしょうか？

次の記事では、アップロードされたファイルに対して、`child_process`を用いて、何らかの処理を施す方法を紹介します！