1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

React+Fastapi+Mariadbで、Dockerを使ったフルスタックアプリを作る

Posted at

記事の概要

React+Fastapi+Mariadbで、Dockerを使ったフルスタックアプリを作るための記事。ローカル環境での動作確認はもちろん、サーバー(RaspberryPi)にデプロイをして動作確認も行った。

作るアプリはかなり単純で、ただ特定の文字をDBから取得してフロントに表示するというものである。

本記事の対象者

私自身がDockerを使ったアプリ開発の練習として作成したものであるため、Docker関連の勉強に重きを置いている。そのため、フルスタックアプリ開発自体や、React、Fastapiなどのフレームワークについての理解を深めたい方は、他の記事を参照されたい。

Screenshot 2025-01-24 at 18.37.56.png

環境などの前提条件

各種ツールのバージョンや開発環境

  • 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にアクセスして確認すると、以下のような画面が表示される。

Screenshot 2025-01-24 at 18.40.41.png

編集

フロント画面においては特別な記述は行わない。が、唯一バックエンドの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!と表示されれば成功である。

Screenshot 2025-01-24 at 18.37.56.png

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も用意したりと、開発環境をよくするためにできることは山ほどあるので、その辺りも考慮した上でのテンプレートを今後作成していきたい。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?