DockerでVue.jsとFlaskを連携するSPA開発

# はじめに
> ハッカソンでフロントエンドにVue.js、バックエンドにFlaskを採用してSPA開発する機会があったが、その両者をDockerで連携する記事が少なかったため、ここにまとめることにする。
なお、**今回はフロントエンドとバックエンドを1つのリポジトリで実装する。**

前提知識：Docker/Docker-composeの概念をある程度理解している
実行環境：Windows10, DockerHub
到達レベル：Dockerでフロントエンドとバックエンドを連携したSPA開発を理解できる

# Dockerfile
まず、フロントエンドのVue.jsとバックエンドのFlaskのDockerコンテナを立ち上げるために、それぞれのDockerfileを定義する。  
```dockerfile:Dockerfile_python
FROM python:3.9

WORKDIR /usr/src/app

COPY ./ /usr/src/app

RUN pip install --upgrade pip && \
    pip install flask && \
    pip install flask-cors
```
ここでは、バックエンドのDockerfile名はDocker_pythonにしているが、任意のファイル名でも問題ない。
また、今回はPython3.9のDockerイメージを基に作成し、後の開発のため```flask-cors```をインストールしている。

同様にフロントエンドのDockerfileも定義する。
```dockerfile:Dockerfile_node
FROM node:16.13.0

WORKDIR /usr/src/app

COPY ./ /usr/src/app

RUN npm install && \
    npm install -g @vue/cli && \
    npm install axios
```
node16.13.0のDockerイメージを基に作成した。
後の開発のために```axios```をインストールしている。

# docker-compose.yml
今回は2つのDockerコンテナを立ち上げるため、docker-compose.ymlファイルを記述し、複数個のコンテナを起動するように設定する。
```docker:docker-compose.yml
version: '3'
services:

  flask:
    build:
      context: .
      dockerfile: Dockerfile_python
    container_name: flask_container
    tty: true
    volumes:
      - ./:/usr/src/app
    ports:
      - "5000:5000"

  vue:
    build:
      context: .
      dockerfile: Dockerfile_node
    container_name: vue_container
    tty: true
    volumes:
      - ./:/usr/src/app
    ports:
      - "8080:8080"
```
```service```は起動するコンテナを記述する。（今回はflaskとvue）
この後にローカルサーバをフロントエンドとバックエンドから起動するが、両者のポート番号が重複しないように予め指定しておく。

~~flaskとvueのデフォルトポート番号は決まっているため、今回は必要ないが明確にするためにも...~~

# コンテナ起動
Dockerfileとdocker-compose.ymlを定義したのでさっそくコンテナを起動してみる。
```docker
docker-compose up -d
```
※ -dは--detachの略記

```-d```オプションをつけることでバックグラウンドでコンテナを起動できる。（デタッチモード）
```docker
docker-compose ps
```
でコンテナの起動状態を確認することができる。


# ディレクトリ構造
今の状態のディレクトリ構造は以下のようになる。
```
.
├── Dockerfile_node
├── Dockerfile_python
├── README.md
├── docker-compose.yml
```
今回はフロントエンド側とバックエンド側を１つのリポジトリで管理するため、それぞれのフォルダを作成する必要がある。

最終的に以下のディレクトリ構造になる。
```
.
├── Dockerfile_node
├── Dockerfile_python
├── README.md
├── backend
├── docker-compose.yml
├── frontend
├── node_modules
├── package-lock.json
└── package.json
```

## frontendディレクトリ
まずは、フロントエンド側から実装する。
```vue
vue create frontend
```
上記のコマンドでVueのプロジェクトを作成する。
対話形式で必要なモジュールなどを聞かれるが基本Yesで問題ない。
今回はSPA開発なのでVuexやRouterなどを追加した。

完了すると以下のようなディレクトリ構造になる。
```
.
├── Dockerfile_node
├── Dockerfile_python
├── README.md
├── docker-compose.yml
├── frontend
├── node_modules
├── package-lock.json
└── package.json
```
それではフロントエンド側のローカルサーバを起動してみよう。
```
cd frontend
```
frontendディレクトリに移動し、
```
npm run build
```
でサーバに反映するためにビルドする。
すると、```dist```ディレクトリが作成される。

それではローカルサーバを起動してみよう。
```
PS C:\Users\〇〇〇\〇〇〇〇\frontend> npm run serve

> frontend@0.1.0 serve 
> vue-cli-service serve

 INFO  Starting development server...


 DONE  Compiled successfully in 4126ms                                                                             19:17:14


  App running at:
  - Local:   http://localhost:8081/
  - Network: http://192.168.10.162:8081/

  Note that the development build is not optimized.
  To create a production build, run npm run build.
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2787207/52135d65-135f-04a3-53f2-cca650f9d397.png)
上記のようにVueが表示されれば起動成功である。

## backendディレクトリ
バックエンド側も実装する。
```
mkdir backend
```
でbackendディレクトリを作成し、その中に```app.py```ファイルを作成する。
```python:app.py
# render_template：参照するテンプレートを指定
# jsonify：json出力
from flask import Flask, render_template, jsonify

# CORS：Ajax通信するためのライブラリ
from flask_cors import CORS
from random import *

# static_folder：vueでビルドした静的ファイルのパスを指定
# template_folder：vueでビルドしたindex.htmlのパスを指定
app = Flask(__name__, static_folder="../frontend/dist/static", template_folder="../frontend/dist")

app.config.from_object(__name__)

CORS(app)

# 任意のリクエストを受け取った時、index.htmlを参照
@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def index(path):
    return render_template("index.html")

# app.run(host, port)：hostとportを指定してflaskサーバを起動
if __name__ == '__main__':
    app.run(host='0.0.0.0', debug=True)
```
Flaskでは、```render_template```でレンダリングするファイルを指定するが、今回はbackendディレクトリではなく、frontendディレクトリの内容をレンダリングする必要がある。
そのため、```static_folder="../frontend/dist/static", template_folder="../frontend/dist"```の記述を加えることで、デフォルトのレンダリングするディレクトリを変更する。

また、今回はSPA開発なので
```python
@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def index(path):
    return render_template("index.html")
```
とする。
詳細は以下の公式ドキュメントを参照されたい。

https://flask.palletsprojects.com/en/1.1.x/patterns/singlepageapplications/


static系のファイルを```static```フォルダに参照するように変更しているが、
デフォルトでは```static```フォルダは生成されないので、自分で作成して、その配下にstatic系のファイルを生成するように設定する必要がある。

そのため、```frontend```フォルダ直下に```vue.config.js```を作成する。
```javascript:vue.config.js
module.exports = {
  assetsDir: "static",
};
```
ディレクトリを構造を変更したので、
**```npm run build```を実行してサーバに反映させるのを忘れずに！**

それでは、バックエンド側でサーバを起動してみよう。
```
PS C:\Users\〇〇〇\〇〇〇〇\backend> flask run
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:5000
Press CTRL+C to quit
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2787207/dada9884-b0d6-8c29-46cf-be5ca5fb111b.png)

上記のようにVueが表示されれば連携完了である。

# 参考記事
https://tech-market.org/vue-flask-cooperation/

# まとめ
今回はVue.jsとFlaskを1つのリポジトリで連携させた。
以降の開発では、```cors```や```axios```を使用して、Ajax通信でフロントエンドとバックエンドを連携させ、SPA開発をする。