Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
44
Help us understand the problem. What is going on with this article?

More than 1 year has passed since last update.

@Nonta0605

Vue.js + FlaskでWebアプリケーション制作 - herokuにデプロイするまで -

はじめに

フロントエンドをVue.js,バックエンドをFlaskでWebアプリ(SPA)を作成したので,その際の手順をまとめておきました.作成したWebアプリのherokuでの公開方法までまとめます.
個人的に,フロントとバックのデータ連携の方法や,herokuへのデプロイ方法などで,ハマりポイントを量産したので,そのあたりを詳しくまとめたいと思います.

当記事の作成にあたり,以下の記事を参考にさせていただきました.

Vue.jsとは

WebアプリのUI作成用のjavascriptのフレームワーク.今回はVueのコマンドラインインターフェース(Vue CLI)を使って,開発を行っていきます.
レスポンシブなWebデザイン1にするために,Vuetifyというとっても便利なUIライブラリも使っていきます.

Flaskとは

Flaskは,プログラミング言語Python用の軽量なウェブアプリケーションフレームワーク.最小限の機能を標準で提供していて,導入も簡単.今回はflask restfulという追加パッケージも利用して,REST APIも実装していきます.

Herokuとは

アプリケーションの開発から実行,運用までのすべてをクラウドで完結できるプラットフォーム.無料プランでも結構色々なことができます.デプロイも割と簡単にできます.

事前準備

予め必要なもの

私の場合,以下のような環境で開発を進めていきます.

ディレクトリ構成

まず,以下のようにディレクトリを作成し,

myspa
  └ backend

myspa下で git init します.

$ cd myspa
$ git init

(注)ちなみに,最終的に作成するディレクトリの構成は次のようになります.一応,herokuにデプロイすることまで考慮したディレクトリ構成です.

myspa (ルートディレクトリ)
  ├ frontend (Vue.jsの諸々)
  ├ backend (Flaskの諸々)
  ├ dist (フロントエンド側でbuildして作成されるファイル)
  ├ Pipfile (pipenvのバージョン管理情報)
  ├ Pipfile.lock (pipenvのバージョン管理情報)
  ├ Procfile (herokuのデプロイに必要なファイル)
  ├ requirements.txt
  └ runtime.txt (herokuのデプロイに必要なファイル)

フロントエンドの準備

Veu CLIを使って,フロントエンドの準備をします.

Vue CLIのインストール

npmを使って,Vue CLIをグローバルにインストールします.2

$ npm install -g @vue/cli
$ vue --version
@vue/cli 4.2.3

Vueプロジェクトの作成

Vue CLIを使って新規にvueのプロジェクトを作成します.最終的なディレクトリ構造を踏まえて,frontendという名前で作成しておきます.vue create プロジェクト名を実行すると,対話形式で細かな設定を確認されます.
後々使うことになるので,2番目の質問 Check the features needed for your projectでBabel,Router,Vuex,Linterを追加しておきます.

myspa/
$ vue create frontend

Vue CLI v4.2.3
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, Linter
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a linter / formatter config: Standard
? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)Lint on save
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? No

ここまで実行すると,myspa下にfrontendというディレクトリが作成されているはずです.

Vuetifyの追加

Vueコマンド使って,Vuetifyを加えます.frontendディレクトリ下に移動して,以下のコマンドを実行します.インストール途中でプリセットを聞かれるのですが,Defaultを選択しておけば大丈夫です.

myspa/frontend/
vue add vuetify
? Choose a preset: Default (recommended)

configなどの設定

ここまで実行すると,frontendディレクトリは以下のようになっているはずです.

frontend
  ├ node_modules
  ├ public
  ├ package.json
  ├ vue.config.js
  ├ .gitignore
  └ その他諸々のファイル

ここで,package.jsonvue.config.js.gitignoreを以下のように編集します.

package.jsonの編集

元々のpackage.jsonscripts部分を以下のように変更します.

package.json
"scripts": {
    "serve": "vue-cli-service serve",
-    "build": "vue-cli-service build",
+    "build": "vue-cli-service build --dest ../dist",
    "lint": "vue-cli-service lint"
  },

これにより,npm run build(後で出てくる)を実行した際に,distディレクトリ(バックエンド側が読み込むファイル類が格納されるフォルダ)がmyspaの直下に生成されるようになります.

vue.config.jsの編集

元々のvue.config.jsを以下のように,変更します.

vue.config.js
module.exports = {
  transpileDependencies: [
    'vuetify'
  ],
+ assetsDir: 'static',
+ devServer: {
+   port: 8080,
+   host: '127.0.0.1'
+ }
}

devServerの設定を変更したことで, npm run serveを実行した際に http://127.0.0.1:8080/ に簡易サーバが立つようになります.

.gitignoreの編集

元々の.gitignoreから, /distの行を消します.

.gitignore
.DS_Store
node_modules
- /dist

# local env files
.env.local
.env.*.local

# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*

今回使用するherokuは,gitを使ってリモートレポジトリにプッシュします.なので,/dist.gitignoreに登録されていると,herokuにデプロイするときに,「distディレクトリが見つかりません!」というエラーを吐くようになってしまいます.

簡易サーバーの起動

ここまで,出来たら開発用の簡易サーバーが立ち上がるようになっています.frontend下で以下のコードを実行します.

myspa/frontend/
$ npm run serve

http://127.0.0.1:8080/ にアクセスすると,以下のような画面が立ち上がっているはずです.
SCS01.png
ちなみに,この画面はVuetifyをインストールしたときに作成される初期画面です.ここでフロントエンドの準備は一旦終わりです.

バックエンドの準備

pipenvで新規プロジェクトの初期化

myspa下で以下のコマンドを実行して,pipenvのプロジェクトの初期化を行います.

myspa/
$ pipenv --python 3.7.5

これを実行すると,myspa下にPipfileが生成されます.

flaskのインストール

flaskと追加パッケージであるflask restfulをインストール.myspa下で以下を実行します.

myspa/
$ pipenv install flask
$ pipenv install flask-restful

flaskアプリの作成

myspa下にbackendというディレクトリを作成します.

現在のディレクトリ構成
myspa
  ├ frontend
  ├ backend (今作成したディレクトリ)
  ├ Pipfile
  └ Pipfile.lock

backendに移動し,main.pyを作成します.中身は,公式ドキュメント( https://a2c.bitbucket.io/flask/quickstart.html#id2 )に詳しく書いてあります.日本語でも書かれてるので,かなりわかりやすいと思います.
static_folder=...template_folder=...のところは,npm run buildを実行して作成されるdistディレクトリに合わせます.フロントエンドの準備をする際に,distmyspa直下に生成されるように設定したので,今回はtemplate_folder='../dist'としてあります.

backend/main.py
from flask import Flask, render_template
import os


app = Flask(__name__, static_folder='../dist/static', template_folder='../dist')

@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def index(path):
    return render_template('index.html')

if __name__ == '__main__':
    app.run()

ここで,バックエンドの準備も一旦終わりです.

フロントエンドとバックエンドを繋げる

frontend/内のディレクトリの構成を少し変更します.現在は,

今のディレクトリ構成
frontend/
  ├ public
  │  ├ favicon.ico
  │  └ index.html
  └ その他諸々

となっていますが,

変更後のディレクトリ構成
frontend/
  ├ public
  │  ├ static
  │  │  └ favicon.ico
  │  └ index.html
  └ その他諸々

に変更します.これに伴って,public内のindex.htmlの参照箇所も変更しておきます.

frontend/public/index.html
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
-   <link rel="icon" href="<%= BASE_URL %>favicon.ico">
+   <link rel="icon" href="<%= BASE_URL %>static/favicon.ico">
    <title><%= htmlWebpackPlugin.options.title %></title>
    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css">
  </head>

フロントエンドのコードをBuildする

frontendに移動し,以下を実行.ビルドに成功すると,myspa直下にdistディレクトリが生成されます.

frontend/
$ npm run build

FlaskのWebサーバを起動

次にバックエンド側でWebサーバを立てて,フロントエンドで作成したWebページが読み込みます.まず,Pipfileにサーバ起動用のスクリプトを登録しておきます.

myspa/Pipfile
+ [scripts]
+ start = "python backend/main.py"

これをしておくと,いちいちpipenv run python backend/main.pyを打たずに,pipenv run startを実行するだけで,Flaskサーバが立ち上がります.

myspa下でサーバを立ち上げると,vueで作成したWebページが見れるようになります.

myspa/
$ pipenv run start

http://127.0.0.1:5000/ にアクセスすると,先程フロントエンドで読み込んだページと同じものが表示されています.ひとまず,これでフロントエンドとバックエンドが連携した開発環境の構築が出来ました.
SCS02.png

Webアプリの改造

せっかくローカルで色々といじれる環境が出来たので,少しアプリに手を加えます.あまり複雑なものを作成するのもアレなので,「ユーザーがブラウザ上で入力した文字列をカウントする」という超単純な機能を実装しようと思います.
フロントエンドとバックエンドのデータ連携もしたいので,

  1. 入力された文字列をbackendに送る
  2. backendで文字列の長さをカウント
  3. frontendに結果を送る

というような構造にします(こんなのわざわざbackendで処理しなくてもいいんですけどね...笑).
ただ,ここは本筋じゃないのでざっくりと説明します.

フロントエンドの変更

今回はfrontendとbackendのデータ連携をREST APIで行います.なので,HTTP通信を実装するためにaxiosをローカルにインストールします.

frontend/
$ npm install -D axios

次に,frontend/src/下のApp.vueを以下のように変更します.Vue.jsでは,この.vue拡張子のファイルに手を加えて,見た目を変えていきます.

frontend/src/App.vue
<template>
  <v-app>
    <v-container fluid>
      <v-row align="start" justify="center">
        <v-col cols="10">
          <v-textarea
          outlined
          name="input-7-4"
          label="テキストを入力してください"
          v-model="InputText"
        ></v-textarea>
        </v-col>
        <v-col cols="2">
          <v-btn outlined @click="SendData"> 文字数をカウント </v-btn>
        </v-col>
      </v-row>

      <v-row align="start" justify="center">
        <v-col cols="6">
        <v-card
          max-width="450"
          class="mx-auto"
        >
          <v-toolbar
            dark
          >
            <v-toolbar-title>Result</v-toolbar-title>
          </v-toolbar>

          <v-list three-line>
            <template v-for="(item, index) in items">
              <v-list-item
                :key="item.title"
              >
                <v-list-item-content>
                  <v-list-item-title >{{ item.count }}文字です</v-list-item-title>
                  <v-list-item-subtitle> {{ item.text }} </v-list-item-subtitle>
                </v-list-item-content>
              </v-list-item>
              <v-divider
                :key="index"
                :inset="item.inset"
              ></v-divider>
            </template>
          </v-list>
        </v-card>
        </v-col>
      </v-row>
    </v-container>
  </v-app>
</template>

<script>
import axios from 'axios'

export default {
  name: 'App',

  data () {
    return {
      // 入力データ
      InputText: '',
      TextLength: null,
      items: []
    }
  },

  methods: {
    SendData: function () {
      var data = { text: this.InputText }

      axios
        .post('/api/post', data)
        .then(response => {
          this.items.push(response.data)
        })
        .catch(err => {
          alert('APIサーバと接続できません')
          err = null
        })
    }
  }
}
</script>

frontend下でnpm run buildをして,distディレクトリを更新.

バックエンド側の変更

まず,以下のapi.pyを新たに作成します.

backend/api.py
from flask import Blueprint, jsonify, request, session
from flask_restful import Api, Resource
import json

# postされたテキストをカウントするapi(POSTメソッド)
text_count_bp = Blueprint('text_count', __name__, url_prefix='/api/post')
class TextCount(Resource):
    def post(self):

        # postされたデータを読み込み
        input_data = request.json

        # 入力文字列の文字数をカウント
        result_data = {'text':input_data['text'], 'count':len(input_data['text'])}

        return jsonify(result_data)

text_count = Api(text_count_bp)
text_count.add_resource(TextCount, '')

次に,main.pyを次のように変更します.

backend/main.py
from flask import Flask, render_template
from api import text_count_bp


app = Flask(__name__, static_folder='../dist/static', template_folder='../dist')
app.register_blueprint(text_count_bp)

@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def index(path):
    return render_template('index.html')

if __name__ == '__main__':
    app.run()

ここで,myspa下でpipenv run startを実行して, http://127.0.0.1:5000/ にアクセスすると,ページが変更されているはずです.
SCS03.png

Herokuへのデプロイ

最後に,ここまでで完成したアプリをHerokuにデプロイします.アプリのデプロイの手順は大まかに以下のようになります.

  1. Herokuのアカウントを取得
  2. gunicornのインストール
  3. デプロイに必要な設定ファイル(Procfile,runtime.txt,requirements.txt)の作成
  4. heroku cliをインストール
  5. herokuアプリの作成
  6. herokuのリモートにプッシュ

herokuのアカウント取得

herokuの公式ページ( https://jp.heroku.com/ )に行き,アカウントを作成します.アカウント作成にクレジットカードの情報などは必要ありません.ほんとありがたいですね.

gunicornのインストール

gunicornはWSGIサーバ3の1つです.これを利用することで,heroku上でも作成したアプリケーションを動かせるようになります.

flaskと同様に,gunicornもpipenvでインストールします.

myspa/
pipenv install gunicorn

設定ファイルの作成

myspa下にProcfileを作成します.今回は,backend下のmain.pyにflaskアプリを記述しているので,backend.main:appとしてます.ここで,必ず--pythonpathmain.pyが置かれているディレクトリに設定してください.このオプションが無いと,api.pyなどの自作モジュールを読み込んでくれなくなります.

myspa/Procfile
web: gunicorn -b 0.0.0.0:$PORT --pythonpath backend backend.main:app

次に,runtime.txtを作成します.ここには,使用しているpythonのバージョンを記述しておきます.

myspa/runtime.txt
python-3.7.5

最後に,requirements.txtを作成します.ここには,インストールしたpythonパッケージのバージョン情報を記載します.手書きしてもいいですが,以下のコマンドで作成してしまったほうが楽です.

myspa/
$ pipenv run pip freeze > requirements.txt

実行すると,以下のようなファイルが作成されます.

myspa/requirements.txt
aniso8601==8.0.0
click==7.1.1
Flask==1.1.1
Flask-RESTful==0.3.8
gunicorn==20.0.4
itsdangerous==1.1.0
Jinja2==2.11.1
MarkupSafe==1.1.1
pytz==2019.3
six==1.14.0
Werkzeug==1.0.0

heroku cliのインストール

heroku cliはコマンドラインからheroku関連の操作をすることができるツールです.brewコマンドが使える方は,以下のコマンドインストールしてください.

$ brew tap heroku/brew && brew install heroku

brewコマンドが使えない方は,公式のダウンロードページ( https://devcenter.heroku.com/articles/heroku-cli#download-and-install )からインストールしてください.

herokuアプリの作成

コマンドライン上でherokuにログインします.

$ heroku login

次に,herokuアプリを作成します.ここで付けた名前は,アプリのURLにも使われます.

$ heroku create 好きな名前

私の場合は,test-app-nontaという名前でアプリを作成したので,

$ heroku create test-app-nonta
Creating ⬢ test-app-nonta... done
https://test-app-nonta.herokuapp.com/ | https://git.heroku.com/test-app-nonta.git

のように,アプリがデプロイされるURLとgitのリモートレポジトリを返してくれます.
最後に,herokuが返してくれたリモートレポジトリの設定をgitの設定に加えておきます.

$ git remote add heroku https://git.heroku.com/アプリ名.git

これで,デプロイの準備は終了です.

Herokuへプッシュ

最後にremoteにプッシュして,デプロイ完了です.まず,コミットします.

myspa/
$ git add .
$ git commit -m "コミットメッセージ"

herokuリモートにプッシュ

myspa/
$ git push heroku master

デプロイ先のURLをブラウザに打ち込むか,

$ heroku open

を実行することで,デプロイしたアプリが開きます.ちゃんと動いてることが確認できますね.
SCS04.png

おわりに

割と簡単に,Vue.js + Flask + HerokuでWebアプリを作れたのではないかと思います.今回は簡単な動作のアプリケーションしか実装しませんでしたが,少しいじるだけで色々な機能を追加で実装することができると思います.
今後は,Vue+flaskアプリにgoogle firebaseのAuthenticationを使ったログイン機能や,leaflet.jsでの地図描画機能を追加する方法なんかもまとめていこうと思います.

個人的にハマった・詰まったポイント

最後に私自身がハマって,時間を取られたポイントをまとめておこうと思います.

1. distディレクトリの位置

herokuは,gitの機能を使ってデプロイを行います.vue cliでプロジェクト作成すると,自動で.gitignoreも作成され,その中に/distも含まれてしまっています.このまま何もしないと,herokuのリモートレポジトリにdistディレクトリはプッシュされないので,.gitignoreから/distを外すことと,distをルートディレクトリに生成するように設定を変更することは忘れないようにしてください.

2. Procfileのpythonpath

ここには,かなりハマりました.Heroku(Procfile)の仕様というより,gunicornの仕様によるものです.今回のように複数ファイルに分けてを記述してる場合,--pythonpathの設定をしないと自作モジュールを正しく読み込んでくれなくなります.
一応,herokuにデプロイする前に,ローカルでgunicornサーバを立てて,動作するか確認しておくといいかもしれないですね.以下のコマンドでgunicornのサーバをローカルでて立てれます.( http://127.0.0.1:8000 にサーバが立ってるようですね)

myspa/
$ pipenv run gunicorn --pythonpath backend backend.main:app
[2020-03-30 02:42:23 +0900] [93950] [INFO] Starting gunicorn 20.0.4
[2020-03-30 02:42:23 +0900] [93950] [INFO] Listening at: http://127.0.0.1:8000 (93950)

  1. 端末の画面サイズを判別することで,PCからアクセスしても,スマホやタブレットからアクセスしても,綺麗にUIを表示させることができるデザイン. 

  2. 最初はグローバルではなくローカルにVue CLIをインストールしようとしたのですが,脆弱性の警告が出てきてしまい上手くいきませんでした.なので,今回はグローバルにインストールしておきます. 

  3. WSGI(ウィズギー)とは,Web Server Gateway Interfaceという「アプリケーションとWebサーバをつなぐインタフェースをPythonで定義したもの」です.WSGIサーバは,このWSGIの仕様に則って作成されたアプリケーションを動かすことのできるサーバを指します.WSGIやWSGIサーバについては, https://www.youtube.com/watch?v=S-InxJA5NOg&t=1578s で非常にわかりやすく説明しています. 

44
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
44
Help us understand the problem. What is going on with this article?