記事の概要
React+Fastapi+Mariadbで、Dockerを使ったフルスタックアプリを作るための記事。ローカル環境での動作確認はもちろん、サーバー(RaspberryPi)にデプロイをして動作確認も行った。
作るアプリはかなり単純で、ただ特定の文字をDBから取得してフロントに表示するというものである。
本記事の対象者
私自身がDockerを使ったアプリ開発の練習として作成したものであるため、Docker関連の勉強に重きを置いている。そのため、フルスタックアプリ開発自体や、React、Fastapiなどのフレームワークについての理解を深めたい方は、他の記事を参照されたい。
環境などの前提条件
各種ツールのバージョンや開発環境
- PC: MacBook Air M2
- OS: macOS Sonoma 14.5
- VSCode: 1.96.3
- Docker: 24.0.5
- Vite: 6.0.5
- React: 18.3.1
- python: 3.12
- Fastapi: 0.115.6
- Mariadb: 11.4.4
以下のようなファイル構成で作成する
.
├── backend
│ ├── Dockerfile
│ ...
├── db
│ ├── Dockerfile
│ ...
│── frontend
│ ├── Dockerfile
│ ...
└── docker-compose.yml
フロントエンドの作成
起動
任意のアプリ名のフォルダを作成し、その中でfrontendという名前でReactアプリを作成する。(viteの使い方についてはこちら)
npm create vite@latest
- ProjectName: frontend
- framework: React
- variant: TypeScript + SWC
cd frontend
npm install
npm run dev
これで起動画面が立ち上がればOK。http://localhost:5173にアクセスして確認すると、以下のような画面が表示される。
編集
フロント画面においては特別な記述は行わない。が、唯一バックエンドのAPIを叩く必要はあるので、その部分と文字の表示部分だけここで実装してしまう。
App.tsx
を以下のように編集する。
import { useEffect, useState } from 'react';
const DEFAULT_BASE_API_URL = 'http://localhost:8080';
function App() {
const [testWord, setTestWord] = useState<string>('');
useEffect(() => {
const fetchTestWord = async () => {
const response = await fetch(`${DEFAULT_BASE_API_URL}/get-test-word`);
const data = await response.json();
setTestWord(data.data);
};
fetchTestWord();
}, []);
return <>Test word: {testWord}</>;
}
export default App;
その他、index.css
, App.css
など、不要なファイルやディレクトリは削除しても良い。
バックエンドの作成
起動
frontendと同じ階層に、backendという名前でFastapiアプリを作成する(Fastapiの使い方についてはこちら)。
また、pythonを使った開発ではpoetryを使うことが多いので、ここでもpoetryを使って環境構築を行ってみる(poetryの使い方についてはこちら)。pythonのバージョンは3.12を使う(補足だが、pythonのバージョン管理はpyenvがおすすめ)。
poetry new backend
cd backend
poetry add fastapi uvicorn
こうすると、backend
ディレクトリの中にbackend
というディレクトリができてしまってわかりにくいので、app
ディレクトリに変更する
.
├── backend
│ ├── README.md
│ ├── backend <-これをappに変更した
│ ├── poetry.lock
│ ├── pyproject.toml
│ └── tests
└── frontend
...
そしたら、appディレクトリの中にmain.py
を作成し、以下のように記述する。
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/get-test-word")
async def root():
return {"status": 200, "errorMsg": [], "data": "Congratulation!"}
以下のコマンドでサーバーを起動する。
uvicorn app.main:app --reload --port 8080
そしたら、http://localhost:8080/get-test-wordにアクセスして、以下のようなJSONを取得できればOK。
{
"status": 200,
"errorMsg": [],
"data": "Congratulation!"
}
Dockerコンテナの作成
ここまで作ってしまえば、frontendとbackendをどちらも起動させてhttp://localhost:3000にアクセスすれば、バックエンドからの文字を得ることができる。ここではDockerfileをそれぞれで作成して、docker-composeで起動させることで、他のサーバーでも同じように動作できるようにする。
Dockerについての基本的な使い方について、私は以下の本を参考にした。
また、Linuxについての基礎知識も必要になるので、以下の本も参考にした。
frontend
frontendディレクトリの中にDockerfile
を作成し、以下のように記述する。
# Build Stage
FROM node:18-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# Production Stage
FROM nginx:stable-alpine AS production
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
概要を簡単に説明する。まずnodeのバージョンを指定して、frontendのアプリをビルドする。その後nginxのコンテナにビルドしたファイルをコピーして、80番ポートで起動するということである。
backend
同様にbackendディレクトリの中にDockerfile
を作成し、以下のように記述する。
FROM python:3.12.0-bullseye
ENV PATH /root/.local/bin:$PATH
ENV PYTHONPATH=/app
WORKDIR /app
COPY pyproject.toml poetry.lock ./
COPY ./app ./app
RUN apt-get update
RUN curl -sSL https://install.python-poetry.org | python3 -
RUN poetry config virtualenvs.create false
RUN poetry install --no-root
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
こちらも簡単に説明すると、pythonのバージョンを指定して、pythonの起動ディレクトリを指定して、poetryを使って環境構築を行い、uvicornでサーバーを起動するということである。hostは0.0.0.0にしておくことで、外部からアクセスできるようにしている。
また、pyproject.toml
は以下のように記述しておく。readme = "README.md"の部分を記述していると、dockerのpoetry install時にエラーが出るので、削除しておく。
[tool.poetry]
name = "app"
version = "0.1.0"
description = ""
authors = ["ユーザー名 <ユーザーのメールアドレス>"]
# readme = "README.md" <- これは削除する
[tool.poetry.dependencies]
python = "^3.12"
fastapi = "^0.115.7"
uvicorn = "^0.34.0"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
docker-compose.yml
あとは同時に起動できた方が便利なので、アプリのルートディレクトリにdocker-compose.yml
を作成し、以下のように記述する。
version: '3.9'
services:
frontend:
container_name: docker_test_app_frontend
build: ./frontend
ports:
- '3000:80'
restart: always
backend:
container_name: docker_test_app_backend
build: ./backend
ports:
- 8080:80
restart: always
これでルートディレクトリでdocker compose up -d
を実行すると、frontendとbackendが同時に起動される。http://localhost:3000にアクセスして、またCongratulation!と表示されれば成功である。
DBの作成
最後にDBを作成する。ここではMariadbを使う。マイグレーションなど便利なツールを使う方法もあるが、今回はテーブルなどは手作業で作成する。DB自体はconfファイルとdocker-compose.ymlを使って作成できる。
ディレクトリ構成は以下のようになる。
.
├── backend
│ ├── Dockerfile
│ ├── README.md
│ ├── app
│ ├── poetry.lock
│ ├── pyproject.toml
│ └── tests
├── db
│ ├── Dockerfile
│ └── my.cnf
├── docker-compose.yml
└── frontend
├── Dockerfile
...
confファイル
まずはDBの設定ファイルを作成する。db
ディレクトリの中にmy.cnf
を作成し、以下のように記述する。これはMariadbの設定ファイルとなる。
[mysqld]
default-time-zone = 'Asia/Tokyo'
character-set-server=utf8mb4
log-bin=mysql-bin
max_binlog_size=256M
expire_logs_days=7
slow_query_log=1
[mysql]
default-character-set = utf8mb4
[client]
default-character-set=utf8mb4
Dockerfile
次にDBのコンテナを作成する。db
ディレクトリの中にDockerfile
を作成し、以下のように記述する。
FROM mariadb:lts
COPY ./my.cnf /etc/mysql/conf.d/my.cnf
RUN chmod 644 /etc/mysql/conf.d/my.cnf
やっているのは、Mariadbのイメージを使って、設定ファイルをコピーしているだけである。
docker-compose.yml
次にdocker-compose.yml
を以下のように記述する。
version: '3.9'
services:
frontend:
container_name: docker_test_app_frontend
build: ./frontend
ports:
- '3000:80'
restart: always
backend:
container_name: docker_test_app_backend
build: ./backend
ports:
- 8080:80
restart: always
+ mariadb:
+ container_name: docker_test_app_db
+ build: ./db
+ ports:
+ - 3306:3306
+ environment:
+ MYSQL_ROOT_PASSWORD: password
+ MYSQL_USER: docker_test_user
+ MYSQL_PASSWORD: password
+ MYSQL_DATABASE: docker_test_db
+ volumes:
+ - ./db/data:/var/lib/mysql
+ restart: always
これでDB自体は起動できるようになったが、テーブルも何もないので、手動で作成する必要がある。以下のコマンドでDBに接続できる。
私はDBeaverを使ってDBに接続してテーブルを作成した。DBeaverの使い方についてはこちらを参考にしてほしい。
テーブル定義としては至極シンプルなもので、以下のようになる。正直コマンドで作成する方が早い気もする。
そして、データは以下を入れるだけで良い
- id: 1
- test_word: "Congratulation!"
これでDBの準備は完了である。
バックエンドとDBの接続
最後にバックエンドとDBを接続するために、バックエンドに移動して編集を行う。ここではpymysql
を使って接続する。backendディレクトリに移動してから、以下のコマンドを実行して、pymysql
をインストールする。
poetry add pymysql
そして、main.py
を以下のように編集する。
+ import pymysql.cursors
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/get-test-word")
async def root():
+ connect = pymysql.connect(
+ host="mariadb",
+ user="docker_test_user",
+ password="password",
+ db="docker_test_db",
+ charset="utf8mb4",
+ cursorclass=pymysql.cursors.DictCursor,
+ )
+
+ test_word = ""
+
+ with connect.cursor() as cursor:
+ cursor.execute("SELECT * FROM docker_test_table")
+ result = cursor.fetchall()
+ for row in result:
+ test_word = row["test_word"]
- return {"status": 200, "errorMsg": [], "data": "Congratulation!"}
+ return {"status": 200, "errorMsg": [], "data": test_word}
cursorclassの設定だけ解説すると、DictCursorを指定することで、取得したデータを辞書型で取得できるようになる。
DictCursor設定しない場合は返ってくる値が以下のようにタプルになる
(1, "Congratulation!")
しかし、DictCursorを設定することで以下のように辞書型で取得できるようになる
{"id": 1, "test_word": "Congratulation!"}
これで全ての準備が整ったので、リビルドするためにdocker compose down
してからdocker compose up --build -d
を実行して、http://localhost:3000にアクセスすれば、無事にDBから取得した文字がフロントに表示されるはずである。
補足
フロントで設定したバックエンドのURLは、http://localhost:8080
になっているが、たとえばサーバーにあげた際などは、サーバーのホスト名に変更すれば同じような動作を確認できる。
終わりに
今回はDockerを使って、RDBを使ったフルスタックアプリの開発を行なった。エラーハンドリングやテストなどは何も考えなかったので、実際の開発現場に応用する際には、それらの部分も考慮した上で実装しなければならない。
また、devcontainerを使った開発ができるようにもしたり、AWSなどの仮想環境にデプロイするCICDも用意したりと、開発環境をよくするためにできることは山ほどあるので、その辺りも考慮した上でのテンプレートを今後作成していきたい。