docker composeで手軽にReact+FastAPI+MySQLお試しローカル環境を作る
動機
- Reactを使った静的ホスティングなるものをやってみたい。
- 動的なコンテンツはFastAPIで作って適宜呼び出したい。
- FastAPIとデータベースはつなげておいて、FastAPIサーバーは将来的にサーバーレスみたいにしたい。
この記事の目的
Reactで生成したページから、FastAPIで作ったAPIを利用して、MySQLに入っているデータをブラウザ上で確認する。
最初のディレクトリ構成
- docker-compose.yml
- client
- Dockerfile
- server
- Dockerfile
- requirements.txt
- code
- db.py
- main.py
- model.py
- mysql
- conf.d
- my.cnf
- initdb.d
- schema.sql
- testdata.sql
- log
- mysql
- conf.d
Docker系ファイル
docker-compose.yml
docker-compose.yml
version: "3"
services:
db:
container_name: back_db
image: mysql:5.7
restart: always
tty: true
environment:
MYSQL_DATABASE: sample_db
MYSQL_USER: user
MYSQL_PASSWORD: password
MYSQL_ROOT_PASSWORD: password
ports:
- 3306:3306
command: --port 3306
volumes:
- ./mysql/initdb.d:/docker-entrypoint-initdb.d
- ./mysql/conf.d:/etc/myaql/conf.d
- ./mysql/log/mysql:/var/log/mysql
server:
links:
- db
build: ./server/
tty: true
container_name: back_fastapi
working_dir: /usr/src/server
ports:
- 8080:8080
volumes:
- ./server/code/:/usr/src/server
client:
build: ./client/
container_name: front_react
ports:
- 3000:3000
volumes:
- ./server/:/var/www/server/
- ./client/:/var/www/client/
tty: true
stdin_open: true
command: sh -c "cd test_app && npm start"
- ポイント
- clientのcommandで、コンテナたちを立ち上げるたびにReactのサーバーを起動している。
- serverの方も本当はcommandで起動できるのかもしれないが、今回はのちに出てくるFastAPIの方のDockerfileで起動の処理を行った。
- MySQLの環境変数の設定の項目は、おそらく本来は.envか何かで外に出して、gitignoreさせるのがよろしいとは思う。
server/Dockerfile
Dockerfile
FROM python:3.8
WORKDIR /usr/src/server
ADD ./requirements.txt .
RUN pip install --trusted-host pypi.python.org -r requirements.txt
CMD ["uvicorn", "main:app", "--reload", "--host", "0.0.0.0", "--port", "8080"]
- ポイント
- 地味にWORKDIRを記しておくことが大切そう。
- requirements.txtをコンテナ内に追加して、環境構築をする。
- CMDコマンドはおそらくdocker-composeのcommandと一緒(確証なし)
client/Dockerfile
Dockerfile
FROM node:latest
WORKDIR /var/www/client/
RUN npm install -g create-react-app
RUN npm install --save react-router-dom
RUN npm install --save prop-types
- ポイント
- こちらも環境構築のためにDockerfileを構築した
serverの中身
requirements.txt
requirement.txt
mysqlclient
sqlalchemy
uvicorn
fastapi
- ポイント
- 上二つはSQLとつなげるため
- 下二つはFastAPIサーバーを立てるため
code/main.py
main.py
from fastapi import FastAPI
from typing import List # ネストされたBodyを定義するために必要
from starlette.middleware.cors import CORSMiddleware # CORSを回避するために必要
from db import session # DBと接続するためのセッション
from model import UserTable, User # 今回使うモデルをインポート
app = FastAPI()
# CORSを回避するために設定
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# ----------APIの実装------------
@app.get("/")
def read_root():
return {"Hello": "World!!!!"}
# テーブルにいる全ユーザ情報を取得 GET
@app.get("/users")
def read_users():
users = session.query(UserTable).all()
return users
- ポイント
- ルートでFastAPIをたたくと、{"Hello": "World!!!!"}を返す。
- /usersでたたくと、DBに入っているusersのキークエリを返す。
code/db.py
db.py
# -*- coding: utf-8 -*-
# DBへの接続設定
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, scoped_session
# 接続したいDBの基本情報を設定
user_name = "user"
password = "password"
host = "db" # docker-composeで定義したMySQLのサービス名
database_name = "sample_db"
DATABASE = 'mysql://%s:%s@%s/%s?charset=utf8' % (
user_name,
password,
host,
database_name,
)
# DBとの接続
ENGINE = create_engine(
DATABASE,
encoding="utf-8",
echo=True
)
# Sessionの作成
session = scoped_session(
# ORM実行時の設定。自動コミットするか、自動反映するか
sessionmaker(
autocommit=False,
autoflush=False,
bind=ENGINE
)
)
# modelで使用する
Base = declarative_base()
# DB接続用のセッションクラス、インスタンスが作成されると接続する
Base.query = session.query_property()
code/model.py
model.py
# -*- coding: utf-8 -*-
# モデルの定義
from sqlalchemy import Column, Integer, String
from pydantic import BaseModel
from db import Base
from db import ENGINE
# userテーブルのモデルUserTableを定義
class UserTable(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(30), nullable=False)
age = Column(Integer)
# POSTやPUTのとき受け取るRequest Bodyのモデルを定義
class User(BaseModel):
id: int
name: str
age: int
def main():
# テーブルが存在しなければ、テーブルを作成
Base.metadata.create_all(bind=ENGINE)
if __name__ == "__main__":
main()
mysqlの中身
conf.d/my.cnf
my.cnf
[mysqld]
character-set-server=utf8
skip-character-set-client-handshake=utf8
default-storage-engine=INNODB
explicit-defaults-for-timestamp=1
general-log=1
general-log-file=/var/log/mysql/mysqld.log
[mysqldump]
default-character-set=utf8
[mysql]
default-character-set=utf8
[client]
default-character-set=utf8
- ポイント
- 主に文字コードの設定
initdb.d/schema.sql
schema.sql
CREATE TABLE user (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(30) NOT NULL,
age INT,
PRIMARY KEY (id)
);
initdb.d/testdata.sql
testdata.sql
INSERT INTO user (name, age) VALUES ("saburo", 15);
INSERT INTO user (name, age) VALUES ("jiro", 18);
INSERT INTO user (name, age) VALUES ("taro", 20);
起動コマンド
docker compose run --rm client sh -c "create-react-app test_app"
docker compose up -d --remove-orphans
1行目は最初だけ。reactの初期フォルダーを作成するためのコマンド。
二回目以降は2行目だけでOK
最初は
localhost:3000でReactの初期画面
localhost:8080で{"Hello": "World!!!!"}
localhost:8080/usersでtestdata.sqlで設定したuserの中身が見えるはず。
ReactからAPIを呼び出す
モジュールのインストール
docker compose exec client bash
root@xxxxxxxxxxxx:/var/www/client# cd app_test
root@xxxxxxxxxxxx:/var/www/client/app_test# npm install --save axios
App.jsの更新
client/test_app/App.js
import React from "react";
import axios from "axios";
function App() {
const [data, setData] = React.useState();
const url = "http://127.0.0.1:8080";
const GetData = () => {
axios.get(url).then((res) => {
setData(res.data);
});
};
const url_users = "http://127.0.0.1:8080/users";
const GetData_users = () => {
axios.get(url_users).then((res) => {
setData(res.data);
});
};
return (
<div>
{/* <div>8080</div>
{data ? <div>{data.Hello}</div> : <button onClick={GetData}>データを取得 Hello</button>} */}
<div>8080/users</div>
{data ? <div>{data[1].name}</div> : <button onClick={GetData_users}>データを取得 Users</button>}
</div>
);
}
export default App;
- ポイント
- jiroが出てくればOK。目的達成
- React詳しい方はもっとうまいことできるはず
終了コマンド
docker compose stop
参考記事
https://qiita.com/KWS_0901/items/684ac71e728575b6eab0
https://qiita.com/ikeikeda/items/eeed5abb2230bf031ba5
https://qiita.com/A-Kira/items/d2f9c8cef9346cb32229