2022.02.26  

VueとFastAPIを連携させ画面表示を行う(Docker利用)

VueとFastAPIが連携する処理のバグ修正をする機会があり、その際VueとFastAPIの仕組みが分からず苦戦しました。

そこで、ロジックの理解のために、Vueで画面表示を行い、表示に必要なデータをFastAPIから取得する処理をDocker-Composeで作成してみました。

githubにコードをアップしているので、すぐに動きを確認してみたい方はご利用ください。https://github.com/ruruyuki/Docker_FastAPI_Vue

ディレクトリ構成

始めに次のような構成でファイルとディレクトリを作成します。

.
├── backend
│     ├── app
│     │   └── main.py
│     ├── requirements.txt
│     └── Dockerfile
├── frontend
│     ├── app
│     └── Dockerfile
│
└── docker-compose.yml

frontend/appについては、コンテナ作成後にVueアプリを追加作成していきます。

FastAPI側のファイル作成

先の構成に従ってそれぞれファイルを作成していきます。

backend/Dockerfile
FROM python:3.9.2-buster

ENV LANG C.UTF-8
ENV TZ Asia/Tokyo

# pip installs
COPY ./backend/requirements.txt requirements.txt
RUN pip install -r requirements.txt

WORKDIR /app

# FastAPIの起動
CMD ["python3.9", "/app/main.py"]

backend/Dockerfileの中身は単純で、
pythonのインストール、言語、タイムゾーンの設定、必要パッケージのインストール、最後にbackend/appになるmain.pyを実行するといった内容です。

インストールするパッケージについては、requirements.txtに記述されており、内容は次の通りとなっています。

backend/requirements.txt
fastapi
uvicorn[standard]

main.pyについては、次のように記述します。

backend/app/main.py
from fastapi import FastAPI
from starlette.middleware.cors import CORSMiddleware
import uvicorn

app = FastAPI()

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


@app.get("/api/hoge")
def index1():
    return {"message": "hogehoge"}


@app.get("/api/fuga")
def index2():
    return {"message": "fugafuga"}


# Dockerfileからuvicorn(FastAPIサーバー)を起動する
if __name__ == "__main__":
    uvicorn.run(
        app="main:app",
        host="0.0.0.0",
        reload=True,
        port=3000,
        log_level="debug",)

app = FastAPI()はFastAPIをインスタンス化する処理です。

app.add_middleware()では、CORS (オリジン間リソース共有)の設定を行なっています。CORSとは、ウェブアプリケーションに、異なるオリジン (ドメイン、プロトコル、ポート番号)にあるリソースへのアクセス権を与えるようブラウザに指示するための仕組みです。詳細はFastAPIマニュアルをご確認ください。

@app.get("/api/hoge")では、URLの/api/hogeにアクセスがあった際に返す情報を設定しています。今回は{"message": "hogehoge"}を返すようにしています。

同様に@app.get("/api/fuga"){"message": "fugafuga"}を返します。

これらをVueから呼び出し、画面に表示するようにしていきます。

uvicorn.run()はFastAPIのサーバーである uvicornを実行するための処理です。
先ほど作成したDockerfileから実行されます。

Vue側のファイル作成

Vue側のDockerfileは次のように作成します。

frontend/Dockerfile
# Node.js(17.5.0)は2022‑01‑18リリース
# vue createなどでエラーとなった場合は最新版に設定しなおす
FROM node:17.5.0-stretch-slim

ENV LANG C.UTF-8
ENV TZ Asia/Tokyo

WORKDIR /app

RUN yarn global add @vue/cli @vue/cli-init

# vueアプリが作成されいない状態(初期構築時)は手動で以下のコマンドを実行する
# docker exec -i -t frontend bash
# vue create .
# yarn add axios vue-axios

# vueサーバーの起動(vueアプリを作成した後、有効化する)。コンテナが起動してから30秒後くらいに起動。
# CMD yarn serve

Vueの実行に必要な環境を構築する処理を記述しています。

Vueアプリが作成されいない状態ではVueサーバーの起動が行えないため、コンテナ作成後、アプリを手動で作成します。

作成した後はCMD yarn serveのコメントアウトを削除することで、コンテナ起動時にVueサーバーも自動起動するようになります。

RUNとCMDの違い

RUNはビルド時にコンテナ内で実行され、イメージの作成に利用される。
CMDは完成したイメージからコンテナを作成するときに実行される。

ちなみにビルドとは、ベースイメージ(python:3.9.2-busterやnode:17.5.0-stretch-slimなど)にDockerfileの内容を設定し、独自のイメージを作成することを言います。

参考1: RUNとCMD
参考2: Dockerイメージビルドの仕組み

docker-compose.ymlの作成

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

docker-compose.yml
version: '3.8'

services:
  frontend:
    container_name: frontend
    build:
      context: .
      dockerfile: ./frontend/Dockerfile
    ports:
      - 80:8080
    restart: always
    tty: true
    volumes:
      - ./frontend/app:/app

  backend:
    container_name: backend
    build:
      context: .
      dockerfile: ./backend/Dockerfile
    ports:
      - 3000:3000
    restart: always
    tty: true
    volumes:
      - ./backend/app:/app

portsを80:8080とすることで、Vueサーバーにブラウザでアクセスするポートを80番とししています。(http://localhost:80)

restart: alwaysは明示的に stop がされない限り、終了ステータスに関係なくコンテナの再起動が行われる設定です。restartを設定しない場合、再起動を行いません。参考

tty: trueは設定たコンテナに仮想端末のプロセスを起動させます。
コンテナはなんらかのプロセスがフォアグランドで実行されていないと正常終了してしまうので、それを防ぐための設定となります。参考

ちなみに、DockerfileでCMDで処理を実行していると、その処理がフォアグランドで実行されるためtty: trueがなくてもコンテナが終了しなくなります。

volumesでは、ローカルにあるファイルをコンテナの指定領域配置する設定を行なっています。(- <ローカルディレクトリ>:<コンテナのディレクトリ>

Docker-comoseのビルドと起動

docker-compose.yml が配置されているディレクトリで次のコマンドを実行します。

docker-compose build 
docker-compose up -d

コンテナが起動すると次のように表示されます。

Creating network "fastapi_project_default" with the default driver
Creating frontend ... done
Creating backend  ... done

Vueコンテナの構築

コンテナが起動したら、次のコマンドでVueコンテナ(frontend)に入ります。

docker exec -i -t frontend bash

コンテナ内の/app ディレクトリでVueアプリを作成するコマンドを実行します。

vue create .

対話式でアプリ作成を行います。
始めの質問はYを入力し、Enterを押します。

次は一番上のDefault [Vue 3]を選んで、Enterを押します。

Use Yarnを選択して、Enterを押します。

Vueの構築が始まるのでしばらく待ちます。

構築が終わるとyarn serveが表示されます。

yarn serveコマンドを実行すると、vueサーバーが起動します。
起動したらブラウザからhttp://localhost:80 にアクセスします。

※ 画面上では、http://localhost:8080/でアプリが起動していると表示されますが、 docker-compose.ymlでコンテナの8080に接続するには80を使用するように設定しているので接続先は80ポートとなります。

上手くいっていれば次の画面が表示されます。

余談ですがyarn serveは開発用のコマンドであり、本番環境でアプリを起動する場合はyarn buildを使用します。

また、frontendのDockerfileに記述しているCMD yarn serveを有効にしておくと次回から自動でvueサーバーが起動するようになります。

http://localhost:80 にアクセスできることを確認したら、一旦Ctrl + C でサーバーを停止たあと、次のコマンドを実行します。

yarn add axios vue-axios

axiosはPromiseベースのHTTP Clientライブラリで、GETやPOSTのHTTPリクエストを使用してサーバからデータ取得、送信を行うことができます。参考サイト

frontend/Dockerfile
### 中略 ###

# vueサーバーの起動(vueアプリを作成した後、有効化する)。コンテナが起動してから30秒後くらいに起動。
CMD yarn serve #ここのコメントアウトをはずす。 

Vueアプリの修正

frontend/appにpluginsディレクトリを作成します。
作成したら、そのディレクトリの中にaxios.jsファイルを作成します。

frontend/app/plugins/axios.js
export let axios;

export default {
    install(app) {
        // base url バックエンド(FastAPI) のURL:port を指定する
        app.config.globalProperties.$http.defaults.baseURL = 'http://localhost:3000'

        // request interceptor
        app.config.globalProperties.$http.interceptors.request.use(config => {
            config.headers.Accept = 'application/json';
            return config;
        })

        // response interceptor
        app.config.globalProperties.$http.interceptors.response.use(response => {

            return response;
        }, function(error) {

            return Promise.reject(error);
        })

        axios = app.config.globalProperties.$http;
    },
    get(url) {
        return axios.get(url);
    },
    post(url) {
        return axios.post(url);
    }
}

ここにはaxiosの共通処理を記述します。

app.config.globalProperties.$http.defaults.baseURL =
バックエンド(FastAPI) のURL:port を指定するところがポイントです。

次に frontend/app/src/main.pyを以下のように書き換えます。

ここではVueアプリ構築時にインストールした、axiosvue-axiosと、先ほど作成したaxios.jsをVueアプリに読み込ませています。

frontend/app/src/main.py
import { createApp } from 'vue'
import App from './App.vue'
import axios from 'axios'
import VueAxios from 'vue-axios'
import AxiosPlugin from './plugins/axios'

createApp(App).use(VueAxios, axios)
                           .use(AxiosPlugin)
                           .mount('#app')

最後にHelloWorld.vueを以下のように書き換えます。
これは http://localhost:80 にアクセスした際に表示される画面のファイルです。
※ この画面が必要な方はバックアップしてから書き換えてください。

frontend/app/src/components/HelloWorld.vue
<template>
  <div class="hello">
    <h1>Hello World {{ message }}</h1>
  </div>
</template>

<script>

import { axios } from '/app/plugins/axios'

export default {
    components: {
    },
    data() {
      return {
        message : ''
      }
    },
    mounted() {
      //get_hoge()の実行結果を変数messageに渡す
      this.get_hoge().then((response) =>{
        this.message = response.data.message
      })
    },
    methods: {
      //FastAPI(http://localhost:3000/api/hoge)にgetをリクエスト
      get_hoge() {
        return axios.get('api/hoge')
      }
    },
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1 {
  color: #42b983;
}
</style>

<script>で囲まれた部分でFastAPIから情報を取得する処理を実行しています。
get_hello() {return axios.get('api/hoge')}
mounted()はVueが起動、更新された時に実行される処理(らしい)。参考
この中でget_hello()を記述することで、画面表示時に変数messageへget_hello()の実行結果が格納されます。

動作確認

docker-compose down --volumes
docker-compose up -d
reating network "fastapi_project_default" with the default driver
Creating frontend ... done
Creating backend  ... done

Creating frontend ... doneが表示されても、Vueサーバーの起動には30~60秒かかります。

それぐら時間が経ったらブラウザからhttp://localhost:80 にアクセスします。
以下の画面が表示されていれば成功です。

また、HelloWorld.vueのaxios.get('api/hoge')の部分をaxios.get('api/fuga')に変更すると画面の表示内容がfugafugaに変わります。

frontend/app/src/components/HelloWorld.vue
// ・・・中略・・・
    methods: {
      //FastAPI(http://localhost:3000/api/hoge)にgetをリクエスト
      get_hoge() {
        return axios.get('api/fuga') // api/hoge を api/fuga に書き換える
      }

参考サイト

https://buzz-server.com/tech/vuejs-fastapi/
https://it-web-life.com/vue_environment_setup/
https://reffect.co.jp/vue/vue-axios-learn#axios-2
https://junchang1031.hatenablog.com/entry/2016/05/18/000605
https://zenn.dev/hohner/articles/43a0da20181d34
https://qiita.com/YusukeHigaki/items/044164837daa5e845d50
https://reffect.co.jp/vue/vue-js-created-mounted-diffrence

コメント
現在コメントはありません。
コメントする
コメント入力

名前 (※ 必須)

メールアドレス (※ 必須 画面には表示されません)

送信