React + Express + Docker の環境構築

Reactで作成した画面からExpressで作成したAPIを呼び、取得したデータを画面表示するところまでを構築していきます。
React/Expressはそれぞれ別のDockerコンテナで動作させます。

# ディレクトリ
```
app/
　├── docker/
　│   ├── docker-compose.yml
　│   └── Dockerfile
　├── backend/
　└── frontend/
```
`docker/` の2ファイルはホスト側で作成しますが、その他の `backend/` と `frontend/` 内のファイルは全てコンテナ側で作成していきます。

# Docker
`Dockerfile` と `docker-compose.yml` を作成し、コンテナを起動します。

## ファイル作成
React、Express で共通で使う Dockerfile を1つ作成します。

```:Dockerfile
FROM node:14
```
今回はひとまずnodeが動けば良いので何も設定していません。

次に docker-compose.yml を作成します。

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

networks:
  react_express:
    driver: bridge

services:
  frontend:  # React用のコンテナ
    build: .
    container_name: front
    tty: true
    volumes:
      - ../frontend:/frontend
    working_dir: "/frontend"
    ports:
      - 3001:3000
    networks:
      - react_express
  backend:  # Express用のコンテナ
    build: .
    container_name: back
    tty: true
    volumes:
      - ../backend:/backend
    working_dir: "/backend"
    ports:
      - 3002:3000
    networks:
      - react_express
```

* `network` : 2つのコンテナを同じネットワーク内に配置します。
* `volumes` : 開発の便宜上、ホストとコンテナでファイル共有しています。
* `ports` : frontend、backendの各サーバ用のポート 3000 を、それぞれホストのポート 3001、3002 に転送しています。

## コンテナの起動
コンテナを起動します。

```
$ cd docker/
$ docker-compose up -d
...

$ docker-compose ps
back    docker-entrypoint.sh node   Up      0.0.0.0:3002->3000/tcp,:::3002->3000/tcp
front   docker-entrypoint.sh node   Up      0.0.0.0:3001->3000/tcp,:::3001->3000/tcp
```
`Up`となっていればOKです。

# Express (Backend)
## プロジェクト作成
`docker exec` を使って、Express用のコンテナ (back) に入ります。

```
$ docker exec -u node -it back /bin/bash
```

package.json を作成します。

```
$ npm init -y
```

Expressをインストールします。

```
$ npm install express
```

## APIの作成
index.js を作成します。

```javascript:index.js
const express = require('express');
const app = express();

const port = 3000;

app.get('/user', (req, res) => {
  res.json([{
    id: 1,
    name: "Taro"
  }, {
    id: 2,
    name: "Jiro"
  }]);
});

app.listen(port, () => console.log(`Server running on port ${port}`));
```

`/user` にリクエストが来たときに、jsonデータを返すようにしています。

## Expressサーバの起動
準備ができたので、Expressサーバを起動します。


```
$ node index.js
Server running on port 3000
```

# React (Frontend)
## プロジェクト作成
別のターミナルを立ち上げ、`docker exec` を使って、React用のコンテナ (front) に入ります。

```
$ docker exec -u node -it front /bin/bash
```

`create-react-app` コマンドを使って、プロジェクトを作成します。
（ドットを付けることで、ディレクトリ直下に作成できます）

```
$ npx create-react-app .
```

## Proxyの設定
Dockerのネットワーク環境における Front/Backend の各オリジン ( `http://xxx:yy` )は、

* Frontend : `http://frontend:3000`
* Backend : `http://backend:3000`

のように異なるため、CORS (Cross-Origin Resource Sharing) の設定をする必要があります。
ここでは、Front側に Proxy を使う方法で回避します。以下2パターンのどちらかを使います。

### 方法1: package.json に proxy を設定する
package.json に以下を追記します。

```json:pacage.json
  "proxy": "http://backend:3000",
```
この設定があると、text/html 以外のヘッダのリクエストを proxy のアドレスを origin としてリクエストするようになるため、Same-Origin として認識されます。

### 方法2: http-proxy-middleware を使う
複数の proxy を設定したい場合にはこちらの方法を使います。

`src/setupProxy.js` を作成します。

```javascript:src/setupProxy.js
const proxy = require('http-proxy-middleware')
 
module.exports = function(app) {
  app.use(proxy('/user', { target: 'http://backend:3000' }))
}
```

この設定により、Reactアプリに対して `/user` にリクエストが来たときに `http://backend:3000` にリクエストが飛ぶことになり、方法1と同様に Same-Origin として通信できます。
`app.use(proxy())` は複数記述できるため、リクエストのパスにより別の proxy に分けて設定することもできます。

ちなみに `create-react-app` は、`npm start` の実行時に `setupProxy.js` を自動で読み込んで処理をするようになっているため、他のファイルにimportをする必要はありません。（ただし、`npm run build`では機能しません。）

###

## 画面の作成
`src/App.js` を以下に編集します。

```react:src/App.js
import { useEffect, useState } from 'react';

const App = () => {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    fetch('/user')
    .then(res => res.json())
    .then(data => {
      setUsers(data);
    }).catch(err => {
      console.log(err)
    });
  }, []);

  return (
    <div>
      {users.map((user, index) =>
        <p key={index}>{ user.name }</p>
      )}
    </div>
  );
}

export default App;
```
画面読み込み時に `fetch('/user')` が実行され、Proxyの設定によりExpressサーバ ( `http://backend:3000/user` ) にリクエストが送られます。
このAPIから取得したデータを `users` ステートに格納し、画面に表示します。

## Reactサーバの起動
Reactサーバを起動します。

```
$ yarn start
```

ホスト側のブラウザで `http://localhost:3001/` にアクセスすると、
`Taro`
`Jiro`
が画面に表示されることが確認できました。