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側のファイル作成
先の構成に従ってそれぞれファイルを作成していきます。
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に記述されており、内容は次の通りとなっています。
fastapi
uvicorn[standard]
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は次のように作成します。
# 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を作成します。
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リクエストを使用してサーバからデータ取得、送信を行うことができます。参考サイト
### 中略 ###
# vueサーバーの起動(vueアプリを作成した後、有効化する)。コンテナが起動してから30秒後くらいに起動。
CMD yarn serve #ここのコメントアウトをはずす。
Vueアプリの修正
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アプリ構築時にインストールした、axios
、vue-axios
と、先ほど作成したaxios.jsをVueアプリに読み込ませています。
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 にアクセスした際に表示される画面のファイルです。
※ この画面が必要な方はバックアップしてから書き換えてください。
<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に変わります。
// ・・・中略・・・
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