Node.js WebアプリケーションのDocker環境構築まとめ

#1.この記事の内容

ブラウザ経由で操作するWebアプリの実装が必要となり，Node.jsをDocker環境で動かすサンプルを実装しましたので紹介します．
Webページ上のフォームに入力されたデータをサーバ上にjsonファイルとして保存するプログラムです．

##1-1.使用環境

* Windows Subsystem for Linux上のUbuntu 20.04
* Docker Imageはnode:12

##1-2.ソースコード
本記事の内容は下記リポジトリの「01_docker_nodejs」～「03_file_access」までです．
本記事には概要のみを記載します．詳細はgithub内のソースコードを参照ください．

* https://github.com/ryoma-jp/samples/tree/master/web_app

#2.実装概要

##2-1.Dockerfile
今回実装したサンプルはlocalhostアクセスを前提としていて，かつ，WSL2上に構築したDockerコンテナからアクセスするため，0.0.0.0でLISTENし，ポートは8080を使用します．
サーバの起動はnpmを使用しますが，サーバ上のファイルを保存するため，bashでコンテナを起動し「node server.js」または「npm start」を実行してサーバを起動します．

サーバ起動後はブラウザで「[http://localhost:8080](http://localhost:8080)」へアクセスする．
なお，Dockerfile内の「CMD [ "/bin/bash" ]」を「CMD [ "npm", "start" ]」とすると「docker run」でサーバを起動することができます．


```dockerfile
FROM node:12

# アプリケーションディレクトリを作成する
WORKDIR /usr/src/app

# アプリケーションの依存関係をインストールする
# ワイルドカードを使用して、package.json と package-lock.json の両方が確実にコピーされるようにします。
# 可能であれば (npm@5+)
COPY package*.json ./

RUN npm install express axios --save
# 本番用にコードを作成している場合
# RUN npm install express axios --only=production

# アプリケーションのソースをバンドルする
COPY . .

EXPOSE 8080
CMD [ "/bin/bash" ]
```

##2-2.Webページのフォーム

index.htmlに自由記述のテキストボックスとSubmitボタンを配置し，Submitボタンがクリックされたらテキストボックスに入力されたデータがPOST送信される仕組みです．

```html
<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>POST sample</title>
    <style>
      body { font-family: sans-serif; }
      input { padding: 5px; font-size: 1rem; }
      button { padding: 5px; width: 70px; font-size: 1rem; }
    </style>
  </head>
  <body>
    <form action='/' method='POST'>
      <table>
        <tr>
          <td>Name:</td>
          <td><input type='text' name='name' value='' placeholder='Input your name'></td>
        </tr>
        <tr>
          <td>Age:</td>
          <td><input type='text' name='age' value='' placeholder='Input your age'></td>
        </tr>
        <tr>
          <td>Phone:</td>
          <td><input type='text' name='phone' value='' placeholder='Input your phone number'></td>
        </tr>
        <tr>
          <td></td>
          <td><button type='submit'>Submit</button></td>
        </tr>
      </table>
    </form>
  </body>
</html>
```

##2-3.POST送信データの保存
Webフォームから入力されたnameフィールド名でjson保存先のディレクトリを生成し，Webフォームに入力されたname，age，phoneフィールドの内容をjson形式で保存します．

mkdir実行後，ファイルシステム上にディレクトリが生成される前にjsonファイル作成処理が実行される状況が生じたため，mkdir実行後にディレクトリ生成確認を行うようにしました．setIntervalを用いて1ms間隔でチェックし，100回チェックしてなお，ディレクトリ未生成状態が続けばタイムアウトする仕様で実装しました．

jsonファイル保存後はトップページに戻る仕様とし，GETリクエストを発行してページを取得し，ブラウザへ送信する．
以前はrequestモジュールが使用できたが，deprecatedだったので，[代替手段のディスカッション](https://github.com/request/request/issues/3143)の中で多そうだった[axios](https://www.npmjs.com/package/axios)モジュールを使用することとしました．

```javascript
// App: Post
app.use(bodyParser.urlencoded({
	extended: true
}));
app.use(bodyParser.json());
app.post('/', (req, res) => {
	console.log(req.body);
	var user_dir = path.join(user_dir_root, req.body.name);
	var user_info_file = path.join(__dirname, user_dir, user_info);
	
	// create user directory
	console.log(user_dir);
	console.log(user_info_file);
	console.log(__dirname);
	fs.mkdir(user_dir, {recursive: true}, (err) => {
		if (err) throw err;
	});

	// create user information file after user directory is created
	var ms = 1;
	var count = 0;
	var count_th = 100;	// timeout threshold: 100ms
	var interval_id = setInterval(function () {
		if (fs.existsSync(user_dir)) {
			clearInterval(interval_id);
			fs.writeFile(user_info_file, JSON.stringify(req.body, null, '\t'), (err)=> {
				if (err) throw err;
			});
			console.log('[INFO] save user_info done');

			// back to top page after submit
			const options = {
				method: 'GET',
				url: `http://${HOST}:${PORT}`,
			};
			axios.get(`http://${HOST}:${PORT}`)
				.then(function (response) {
//					console.log(response.data);	// for debug
					res.send(response.data);
				});
		} else {
			count++;
			if (count > count_th) {
				// timeout
				console.log('[ERROR] Create directory failed: ' + user_dir);
				throw Error('[ERROR] Timeout');
			}
		}
	}, ms);
});
```

#3.さいごに
環境立ち上げとしてjsonファイルの保存までを記事にしました．内容は基礎的な部分のみだと思いますが，どなたかの参考になりそうな情報が含められていれば幸いです．

#4.関連リンク

* [Node.js Web アプリケーションを Docker 化する](https://nodejs.org/ja/docs/guides/nodejs-docker-webapp/)
* [node.js + expressでPOSTを受け取る & POSTパラメータをJSONで取得する](https://qiita.com/ktanaka117/items/596febd96a63ae1431f8)
* [body-parser で フォームからの POST データを受け取る方法](https://nodejs.keicode.com/nodejs/body-parser-form.php)
* [Request’s Past, Present and Future](https://github.com/request/request/issues/3142)
* [Alternative libraries to request](https://github.com/request/request/issues/3143)
* [axios](https://www.npmjs.com/package/axios)