docker-composeでNode.js(Express)とPostgresSQLを連携したサービスを作る勉強会

# 概要

先日行われた勉強会にて簡易ではありますが表題のサービスを作りました。Dockerの勉強の延長で取り組みました。この記事ではそのときの勉強会の内容を記載します。

# どんなモノをつくるのか？

- チャットのようなアプリケーション
- docker-composeによるデータベースとの連携
    - Node.js(Express)でフロントエンド＆バックエンド
    - データベースはPostgresSQL

# 完成したソースコード

https://gitlab.com/tamoco-mocomoco/study-docker-compose

# 参考にさせていただいた記事

https://qiita.com/ryo-ohnishi/items/b54e649b14b51694ef77

# 作ったモノの説明

## docker-compose


### サービス：app

```yaml
  app:
    build:
      context: ./      # Dockerfile保存場所
    depends_on: 
      - database
    image: n-app                  # イメージ名
    container_name: n-app         # コンテナ名
    ports:                          # ポート接続
      - 3000:5000
    environment:
      PORT: 5000
      DB_USER: postgres
      DB_HOST: database
      DB_PORT: 5432
      DB_NAME: test_db
```

`app`は`Node.js(Express)`がメインとなるサービスになります。ここでは学習のために敢えてデフォルトのポートを`5000`に変更しています。これは参考にさせていただいたExpressの環境変数で`PORT`が使用されており、それを`docker-compose`で`5000`に上書きしています。コンテナ内の`5000`をホスト側の`3000`に転送しています。ちなみに`Express`で環境変数が使用されている箇所は`app/bin/`の`www`というファイルで確認できます。

#### app/bin/www

```javascript
#!/usr/bin/env node

/**
 * Module dependencies.
 */

var app = require('../app');
var debug = require('debug')('myapp:server');
var http = require('http');

/**
 * Get port from environment and store in Express.
 */

var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
// ～以下省略～
```

### サービス：database

```yaml
  database:
    image: postgres:12.3
    volumes:
      - ./init-sql:/docker-entrypoint-initdb.d:ro
    environment:
      POSTGRES_DB: test_db
      TZ: "Asia/Tokyo"
      POSTGRES_HOST_AUTH_METHOD: trust
```

`database`は`PostgresSQL`を使用しています。`docker-compose`で環境変数である`POSTGRES_DB`を`test_db`に設定することで自動的に`test_db`という名前のデータベースが作成されます。

`volumes`の設定は`init-db`というフォルダにSQLファイルを格納しておくことで、SQLファイルに書かれているクエリを自動的に実行してくれます。
初期スキーマの設定（テーブル構造の作成）やダミーデータなどを挿入するときに利用します。

実行順序はファイル名の昇順で実行されるので、ファイル名に番号などをつけるとよさそうです。

今回、作成したデータベースには`postgres`というユーザーが作成されますが、パスワードが設定されていません。そのため`POSTGRES_HOST_AUTH_METHOD`を`trust`に設定する必要があります。

あと、`TZ`でタイムゾーンを設定しています。タイムゾーンの変更については以下のクエリでも可能です。

```bash
ALTER DATABASE test_db SET timezone TO 'Asia/Tokyo';
SELECT pg_reload_conf();
```

`init-db`にクエリをファイルとして格納しておけばタイムゾーンも変更可能です。ただし、どこでタイムゾーンが設定されているか後追いしづらいので、環境変数で設定できるなら環境変数を使った方が良いと思います。


# Dockerfile：Node.js(Express)

今回のアプリケーションの部分（サービス：app）は`app`フォルダ配下
に`Express`のソースが格納されています。それらのソースを元に`Dockerfile`でコンテナを作成します。

## Dockerfile

```dockerfile
# ベースイメージを指定
FROM node:10.12

# 環境変数設定
ENV NODE_ENV="development"

# 作業ディレクトリ作成&設定
WORKDIR /src

COPY ./app /src

RUN npm install

CMD npm run start
```

ここで重要なのでコンテナの中に`src`というフォルダを作成し、そこにホスト（自分の端末）の`app`フォルダの中身をコピーします。`src`は`WORKDIR`に設定されているので`src`でコピーしたソースファイルを元に`npm install`などを実行できます。

今回は`Dockerfile`でソースをコピーしましたが`docker-compose`でも同じようなことが可能です。

## Node.js(Express)

ここは別記事にしてもよいくらい説明する箇所が多いので抜粋して記載します。今回の`Express`のソースでは`app/routes`フォルダのファイルでエンドポイントを設定して、表示内容を`app/views`フォルダのテンプレートのファイルに渡してコンテンツを表示する流れになっています。

```
処理の流れ
app/routes → app/views
```

### localhost:3000 にアクセス

```
app/routes/index.js
↓
app/views/index.ejs
```


`index.js`では`PostgresSQL`の`chat`テーブルをセレクトした結果を`index.ejs`に渡しています。

### localhost:3000/insert?msg=SEND_TEST

```
app/routes/insert.js
↓
app/views/index.ejs
```
`insert.js`ではURLパラメーターで`msg`を受け取ることができます。`msg`の値を`chat`テーブルにインサートします。もしパラメーターがない場合は「ふにょふにょ」という値がインサートされます。

表示する内容はindex.ejsと同じですがインサートされた後のテーブルの情報を表示します。`app/routes`のファイルと`app/views`のファイルは必ず１対１にする必要はなく、同じテンプレートでも渡す内容で表示内容を切り替えるなどの制御も可能だったりします。

### データベース（PostgresSQL）への接続：db_client.js

```javascript
module.exports = {
    pg_client: function () {
        const { Client } = require('pg')

        const client = new Client({
            user: process.env.DB_USER,
            host: process.env.DB_HOST,
            port: process.env.DB_PORT,
            database: process.env.DB_NAME
        })
        return client
    },
    pupupu: function () {
      console.log('pupupupu')
    }
  };
```
`db_client.js`でデータベースの接続処理を共通で使用できるようにしています。`PostgresSQL`に必要なユーザー情報などは`docker-compose`から環境変数で受け取りそれを元に接続を行います。

# 総評

ある程度コンパクトにまとまっており、構成もそこまで複雑ではないかなと思いました。ただ開発するときに少し難があり`Express`は個別で`npm install`などで`package.json`などを更新する必要があります。これは`docker-compose build`は`app`フォルダをコピーしているので、アプリの`node_module`などは手動で更新しないとコンテナで動作してくれません。逆に`npm start`などで開発すればよい話になりますが、データベースとの接続ができないので困ったところです。
しかしながら、今回の勉強会ではいろいろと`docker-compose`について理解を深めることができたのでよかったかなと思います。