Docker-compose3でnode.js&express&mongodb&nginxの環境を整える[完全版]

# はじめに
　これはある記事を参考にnode.jsとmongodbとnginxのDocker環境をcomposeでめっちゃお手軽に作ったろ、とか思ったらエラーしたため案外悪戦苦闘してしまった人の後日談です。
　記事を書くとしたら、以下のURLをモロパクな感じになっちゃうと思って遠慮していた(というかダメ)なんですが、久しぶりに環境を整えようとしてみたら思ったよりもメンドクサかったので、備忘録という免罪符を元にモロパクしようと思います。
　モロパクしようと思ったのですが、ついでにexpress-generatorを使用してテンプレートを利用してみようと思って、少し本家様の流れから改変しています。また、この記事はCentOS7をインストールした直後を想定していたりいなかったりしています。

[引用]

https://zenn.dev/cizneeh/articles/nginx-node-mongo-docker-example#docker-compose.yml

# 環境
**2022/06/17**
・CentOS7
・Docker 1.13.1
・Docker-compose 1.18.0
・npm 8.5.5

# インストール方法

### Dockerのインストール

 Dockerのインストールはこれをコピペ。

```
sudo yum -y install docker
```

### Docker-composeのインストール

 Docker-composeのインストールはこれをコピペ。

```
sudo yum -y install docker-compose
```

### npmのインストール

　npmのインストールはこれをコピペ。
　これはexpress-generatorをインストールするためのものです。

```
sudo yum -y install npm
```

### express-generatorのインストール

 express-generatorをnpmでインストールします。

```
sudo npm -g install express-generator
```

# ディレクトリ構成

```
.
├── docker-compose.yml
├── mongo
│   ├── init.js # DB初期化用スクリプト
│   └── mongo-data # データ保存用ディレクトリ（バインドマウント）
├── nginx
│   ├── Dockerfile
│   └── conf
│       └── proxy.conf
└── server # express-generator の　pug テンプレート
```

　serverのファイル作成には次のコマンドを使用してください。

```
express server --view pug
```

　これはexpress-generatorによって使用できるようになるコマンドです。sudoをつけると実行できなくなったりする可能性もあるので要注意です。
　因みにですがテンプレートエンジンに今回使用するつもりなのはpugなのですが、express-generatorに初期設定されているjadeとpugは商標の都合で名前を変更しただけのものそうです。
　htmlとはかなり表記が違うので、htmlで作りたいという人はpugをejsなどに変更するといいかもしれません。

# Mongo

　公式のイメージを使用します。
　Dockerfileは用意せず、公式イメージをそのまま使います。
　MongoDBはデフォルトだと認証が無効になっているので、command: [mongod, --auth]で認証を有効にして起動しています。
　また、以下のような初期化用のスクリプトを用意します。(引用)

### init.js

```./mongo/init.js
db.createUser({
  user: 'username',
  pwd: 'password',
  roles: [
    {
      role: 'readWrite',
      db: 'test',
    },
  ],
})
```

　このスクリプトの動作や原理など、詳しいところは参考にしているページ様をご覧ください。

https://zenn.dev/cizneeh/articles/nginx-node-mongo-docker-example#mongodb

# nginx
 nginxディレクトリのconfディレクトリにあるproxy.confという設定ファイル(のコピー元)を設定します。

### proxy.conf

```./nginx/conf/proxy.conf
server {
  listen 80;
  location / {
    proxy_pass http://server:3000/;
  }
}
```

### Dockerfile

```./nginx/Dockerfile
FROM nginx

# デフォルトの設定を削除
RUN rm /etc/nginx/conf.d/default.conf

# 作成した設定ファイルをコピー
COPY conf/proxy.conf /etc/nginx/conf.d
```

　これによって何が起き何を期待しての変更なのか、詳しいところは参考にしているページ様をご覧ください。

https://zenn.dev/cizneeh/articles/nginx-node-mongo-docker-example#nginx

# Node.js
 ここからターニングポイントです。パクリ元サイト様から大きく変わります。　
 serverディレクトリでMongoDB公式のNode.jsドライバーをインストールします。

``` ./server
sudo npm install mongodb
```

 express-generator で作成したserverのディレクトリにあるrouteディレクトリにある、index.jsをいじってみましょう。せめてちょっとだけmongodbのドライバを使用したプログラムを仕立ててみます。
　便利にするために、元サイトと同じくTop level awaitを設定したかったのですが、めんどくさいエラーが発生したためここではTop level awaitを設定していません。

　因みに、Top level awaitとは、node.jsでは基本的にはasyncを使わないとawaitが使えないのを、asyncなしでもawaitが使えるようになる便利な設定です。

### index.js

```./server/routes/index.js
var express = require('express');
var MongoClient = require('mongodb').MongoClient;
var ObjectId = require('mongodb').ObjectID;
var router = express.Router();

const URL = 'mongodb://username:password@mongo/test?authSource=admin';
const client = new MongoClient(URL);

(async() => {
  try {
    await client.connect();
    console.log('Succesfully connected to mongo');
  } catch (e) {
    console.error(e);
  }
})();

const db = client.db();
const col = db.collection('test-collection');
id = new ObjectId();
col.insertOne({_id:id,n:0});

/* GET home page. */
router.get('/', async function(req, res, next) {
  col.updateOne({_id:id},{$inc:{n:1}});
  result = await col.findOne({_id:id});
  console.log(result);
  res.render('index', { title: 'Express' +result['n'] });
});

module.exports = router;
```

　express-generatorのejsテンプレートのroutes/index.jsを改変した形です。
　mongodbでタイトルの'Express'に付け足してページviewsを表示します。

### Dockerfile

```./server/Dockerfile
FROM node

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

# キャッシュを利用するために、package.jsonとpackage-lock.jsonのみをコピーし、
# 依存関係を先にインストール
COPY package*.json ./
RUN npm install

# アプリケーションコードをコンテナにコピー
COPY . .

EXPOSE 3000
CMD [ "node", "bin/www" ]
```

node_modules等を無視するために、上のDockerfileと同じディレクトリに.dockerignoreを以下の内容で作成します。(引用)

### .dockerignore

```server/.dockerignore
node_modules
npm-debug.log
```

# docker-compose.yml

　docker-compose.ymlを準備します。
　これも元サイト様と多少変えて、nginxのportsを変えています。
　この場合はnginxが80ポートから受け取ることが出来る、つまりhttpで通信できるということです。

```docker-compose.yml
version: '3'

services:
   nginx:
     build: ./nginx
     container_name: nginx
     depends_on:
       - server
     ports:
       - 80:80

   server:
     build: ./server
     container_name: server
     depends_on:
       - mongo

   mongo:
     image: mongo
     container_name: mongo
     environment:
       MONGO_INITDB_DATABASE: admin
     volumes:
       - ./mongo/init.js:/docker-entrypoint-initdb.d/init.js:ro
       - ./mongo/mongo-data:/data/db
     # Start mongo with authentication enabled
     command: [mongod, --auth]
```

# 起動

　一回普通に起動してみます。
　この起動の仕方ではdockerコンテナはバックグラウンドで起動するのではないため、ログを逐一確認することができ、node.jsでもnginxでもどのようなエラーが出たのかを確認することが出来ます。

```
docker-compose up
```

　また、当然といえば当然なのですが、バックグラウンドで起動したい場合の方が多いでしょう。そういう場合は以下のように-dオプションをつけて起動してください。

```
docker-compose up -d
```

　アクセスするにはこのサーバのIPを指定したhttpでOKです。portを80に設定しないのであれば、:portと後ろに設定する必要が出てきますが、80なのでhttpで十分です。

```
http://IP
```

# 最後に

　最後に、node.jsがエラーを起こすたびにコンテナごと止まってたんじゃやってられない！という場合にはnode.jsのforeverモジュールなどを採用することをお勧めします。まぁ、dockerコンテナでの挙動は確認してないので、どうなるかは知りませんけどね！