Route53, GCE, React, FastAPIを用いて簡単なWebアプリを作製する 【中編】

# なんの記事？
以下の記事の中編にあたります。

https://qiita.com/izuna385/items/749dd0c552c88534d5b3

# 手順
残りの手順として

* フロントエンド
* 結合
* デプロイ

が存在します。が、その前に `docker-compose` でバックエンドとフロントエンドの環境を整えます。

# 構成
今回は以前に[backendのリポジトリ](https://github.com/izuna385/arxiv-checker-backend)を作成してしまったので、submoduleでバックエンドを引っ張ってきます。

```
.
├── README.md
├── docker-compose.prod.yml
├── docker-compose.yml
├── backend
│   ├── Dockerfile
│   ├── README.md
│   ├── app
│   ├── backend
│   ├── pytest.ini
│   ├── requirements.dev.txt
│   ├── requirements.txt
│   └── tests
└── frontend
    ├── Docekrfile.prod
    ├── Dockerfile.dev
    ├── README.md
    ├── node_modules
    ├── package.json
    ├── public
    ├── src
    └── yarn.lock
```

リポジトリは以下になります。

https://github.com/izuna385/arxiv-checker

## 開発環境用のdocker-compose.yml
最終的に以下のようになりました。

```Dockerfile:docker-compose.yml
services:
    backend:
        tty: true
        build:
            context: ./backend
            dockerfile: Dockerfile
        volumes:
            - ./backend/:/backend/
        command: uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 --log-level trace
        ports:
            - 8000:8000
    frontend:
        build:
            context: ./frontend
            dockerfile: Dockerfile.dev
        command: yarn start
        volumes:
            - ./frontend:/usr/src/app
        ports:
            - "3000:3000"
```

`docker-compose up --build`を行い`localhost:8000`, `localhost:3000` を見に行くことで、それぞれ起動していることが確認できます。
また、`volumes` でマウントを行っているので、`docker-compose up --build -d` で立ち上げたあと、開発しながら `localhost:3000` を見に行くことで、開発を進めることが可能です。
開発を終えるときは `docker-compose down`をしておきましょう。

## 本番環境用のdocker-compose.prod.yml
本番環境用ではnginxを用いてフロントが表示されるように変更します。

```text:docker-compose.prod.yml
services:
    backend:
        tty: true
        build:
            context: ./backend
            dockerfile: Dockerfile
        volumes:
            - ./backend/:/backend/
        command: uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 --log-level trace
        ports:
            - 8000:8000
    frontend:
        build:
            context: ./frontend
            dockerfile: Dockerfile.prod
        volumes:
            - ./frontend:/usr/src/app
        ports:
            - "80:80"
```

本番用のフロントは以下のようになります。

```Dockerfile:./frontend/Dockerfile.prod
FROM node:latest AS builder
WORKDIR /usr/src/app
COPY . .
RUN npm install
RUN npm run build

FROM nginx:latest
RUN rm -r -f /usr/share/nginx/html
COPY --from=builder /usr/src/app/build /usr/share/nginx/html
```
nginx経由の表示については、[こちら](https://marsquai.com/745ca65e-e38b-4a8e-8d59-55421be50f7e/f83dca4c-79db-4adf-b007-697c863b82a5/3956cdb7-44ac-410b-b9bc-508c341a45ca/)を参考にしました。

`docker-compose -f docker-compose.prod.yml up -d --build` を行うことで、 `localhost`でのフロント表示が確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357766/cc7d3fec-6793-c301-e515-022e6ba496dd.png)

# フロントエンド実装
フロントエンド実装に移ります。前回実装したAPIから、結果を取得する関数`fetchPapers`、また`paper`をレンダリングする`Paper`コンポーネントと、`Paper`一覧を表示する`Papers`を実装します。

```React:fetchPapers.js
import axios from 'axios';
import DEFAULT_API_LOCALHOST from '../urls';

export const fetchPapers =() => {
    return axios.get(DEFAULT_API_LOCALHOST)
    .then(res => {
      return res.data
    })
    .catch((e) => console.error(e))
  }
```
`DEFAULT_API_LOCALHOST` は前回実装したAPIのエンドポイント、すなわち`http://localhost:8000/api/papers/`になります。

```javascript:index.js
const DEFAULT_API_LOCALHOST = 'http://localhost:8000/api/papers/'

export default DEFAULT_API_LOCALHOST;
```

仮置で、`fetchPapers`関数を用いて、APIの結果が帰ってくるかどうかをconsole上で見てみます。

```React:src/pages/Papers.js
import React, { Fragment, useEffect } from 'react';

// apis
import Paper from '../components/Paper';
import { fetchPapers } from '../apis/fetchPapers';

export const Papers = () => {

    useEffect(() => {
        fetchPapers()
        .then((data) =>
        console.log(data)
        )
    }, [])

    return (
        <Fragment>
            Paper一覧
        </Fragment>
    )
}

export default Papers;
```
トップページで`Papers`をレンダし、ブラウザ上でログを見てみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357766/f2c7bfe0-b512-fd69-5ad4-b27651903cac.png)


NetWorkErrorで怒られてしまっています。networkのログを見てみると

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357766/ded64e70-cd88-f3c2-3ae9-2f2d6be6fe47.png)

バックエンド側のCORS設定に問題があるようです。

https://qiita.com/satto_sann/items/0e1f5dbbe62efc612a78

を参考に、バックエンドを修正します。

```python:backend/app/main.py
import uvicorn
from fastapi import FastAPI
from starlette.middleware.cors import CORSMiddleware
from backend.arxiv_api import ArxivApiClass
from backend.extractor import Extractor

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"]
)

arxiv_api = ArxivApiClass()
extractor = Extractor()
# ...
```

submodule側で更新を行い、mainブランチにマージしたので、その反映を行います。

```text
(base) ➜  arxiv-checker git:(feature/createPaperComponent) ✗ git submodule foreach git pull origin main
Entering 'backend'
remote: Enumerating objects: 12, done.
remote: Counting objects: 100% (12/12), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 8 (delta 4), reused 7 (delta 4), pack-reused 0
Unpacking objects: 100% (8/8), done.
From github.com:izuna385/arxiv-checker-backend
 * branch            main       -> FETCH_HEAD
   c37561e..6f5d886  main       -> origin/main
Updating c37561e..6f5d886
Fast-forward
 README.md   |  4 ++--
 app/main.py | 10 ++++++++++
 2 files changed, 12 insertions(+), 2 deletions(-)
(base) ➜  arxiv-checker git:(feature/createPaperComponent) ✗ 
```
` docker-compost up --build` で再度開発環境をビルドした後、`localhost:3000`番を見に行きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357766/dad31807-d977-38d4-d6b5-b6fccd9285ef.png)

バックエンドから論文一覧を取得できていることが分かります。

## useState, useEffectを用いたレンダリング
バックエンドからの結果を実際にレンダリングする実装を次に行います。

```React:Papers.js
import React, { Fragment, useEffect, useState } from 'react';

// apis
import Paper from '../components/Paper';
import { fetchPapers } from '../apis/fetchPapers';

export const Papers = () => {

    const[allPapersData, setAllPapersData] = useState([]);

    useEffect(() => {
        fetchPapers()
        .then(data => setAllPapersData(data.papers))
    }, [])

    return (
        <Fragment>
            {console.log(allPapersData)}
            <ul>
                {allPapersData.map((data) => (
                    <Paper paperData={data}/>
                ))}
            </ul>
        </Fragment>
    )
}

export default Papers;
```
`allPapersData`を空の初期値で定義し、そこにバックエンドからの結果を格納しています。

ブラウザ上では、バックエンドの返却結果のリストの各要素が、`Paper`コンポーネントとして表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357766/e6432260-066d-0618-c24c-33c2c25cf495.png)

ここまでで、バックエンドとフロントエンドの結合までを完了しました。
動作を確認する場合は、リポジトリからcloneし、`docker-compose up --build`で確認可能です。

また、UIの調整についてはここでは省略しますが、今回はreact-bootstrapを用いました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357766/cd2fe855-5baa-a875-4692-40b484cdda88.png)


# 次回

https://qiita.com/izuna385/items/3bf79497dee8c1faa693

# リポジトリ

https://github.com/izuna385/arxiv-checker

# 参考文献
[React+Docker+DockerComposeの開発環境の構築について説明
](https://marsquai.com/745ca65e-e38b-4a8e-8d59-55421be50f7e/f83dca4c-79db-4adf-b007-697c863b82a5/3956cdb7-44ac-410b-b9bc-508c341a45ca/)