こんにちは、katです。
今回は「React(TS) × NestJS × PostgreSQL × docker-compose の開発環境」について、個人的に最も使いやすいと感じている構成を紹介してみたいと思います。
ちょうど1年前あたりから「docker-composeを用いたSPAの開発環境はどのような構成が使いやすいか…」と試行錯誤してきたのですが、ようやく満足のいくものが出来たため、本記事を投稿してみました。
※ 最初にお断りしておきますが、筆者はDockerを独学や学生時代の研究で2年近く使っていますが、実務で使ったことはありません。そのため、誤っている箇所や、改善したほうが良い点がございましたらご指摘していただけると幸いです。
前提
皆様の環境にDocker, docker-compose, Visual Studio Codeがインストールされている前提でお話します。
かつ、VSCodeにDev Containersという拡張機能がインストールされている必要があります(Remote Containerを使うため)。
また可能であれば、GNU Makeがインストールされているとなお良しです。
バージョンは、筆者の環境では以下のようになっております。
名称 | バージョン |
---|---|
Docker | 20.10.10 |
docker-compose | 1.29.2 |
VSCode | 1.73.1 |
Dev Containers | 0.262.3 |
GNU Make | 3.81 |
ソースコード解説
本記事作成時点でのGithubリポジトリはこちらになります。
ディレクトリ構成
ルート位置にdocker-compose.ymlを配置し、各フォルダ内にDoekerfileを配置しています。
よくある構成かと思います。
.
├── docker-compose.yml
├── Makefile
├── README.md
├── .gitignore
├── .env (各自作成してください)
├── front
│ └── Dockerfile
│ └── .devcontainer
│ │ └── devcontainer.json
│ └── Reactプロジェクト(初回起動時は空)
├── api
└── Dockerfile
└── .devcontainer
│ └── devcontainer.json
└── NestJSプロジェクト(初回起動時は空)
docker-compose.yml
各行にコメントアウトで解説を付けてあります。
なお、React, NestJS, PostgreSQL は以下のようなサービス名で立ち上がります。
名称 | サービス名 |
---|---|
React | front |
NestJS | api |
PostgreSQL | db |
version: "3.8"
services:
# React環境に関する設定
front:
build:
# frontディレクトリ内のDockerfileをビルドする
context: ./front
# コンテナ側起動時のディレクトリ位置を/workspace/frontとする
args:
- FRONT_WORKDIR=/workspace/front
# 下記2行はコンテナを起動させ続けるため記載
tty: true
stdin_open: true
# マウントに関する設定
# ホスト側のルート位置(docker-compose.ymlが格納されている階層)を、コンテナ側 /workspace にバインドマウント
# コンテナ側のReactプロジェクトのnode_modulesを、front_storeにボリュームマウント
volumes:
- .:/workspace
- front_store:/workspace/front/$FRONT_PROJ_NAME/node_modules
# React(vite)は5173番ポートで起動するため、ローカル側のものをポートフォワード
ports:
- "5173:5173"
# docker-compose起動時コマンド。プロジェクトディレクトリがある場合はアプリを起動し、ない場合はメッセージを出して待機
command: >
sh -c
"if [ -e $FRONT_PROJ_NAME/package.json ]; then
cd $FRONT_PROJ_NAME && yarn install && yarn dev --host
else
echo 'Project Directory is not found...' && tail -f /dev/null
fi"
# NestJSに関する設定
api:
build:
# apiディレクトリ内のDockerfileをビルドする
context: ./api
# コンテナ側起動時のディレクトリ位置を/workspace/apiとする
args:
- API_WORKDIR=/workspace/api
# 下記2行はコンテナを起動させ続けるため記載
tty: true
stdin_open: true
# マウントに関する設定
# ホスト側のルート位置(docker-compose.ymlが格納されている階層)を、コンテナ側 /workspace にバインドマウント
# コンテナ側のNestJSプロジェクトのnode_modulesを、api_storeにボリュームマウント
volumes:
- .:/workspace
- api_store:/workspace/api/$API_PROJ_NAME/node_modules
# NestJSは3000番ポートで起動するため、ローカル側のものをポートフォワード
# また、Prisma Studioを使う際は5555番ポートで起動するため、ローカル側のものをポートフォワード
ports:
- "3000:3000"
- "5555:5555"
# docker-compose起動時コマンド。プロジェクトディレクトリがある場合はアプリを起動し、ない場合はメッセージを出して待機
command: >
sh -c
"if [ -e $API_PROJ_NAME/package.json ]; then
cd $API_PROJ_NAME && yarn install && yarn start:dev
else
echo 'Project Directory is not found...' && tail -f /dev/null
fi"
# db(Postgres)が起動してから起動するようにする。
depends_on:
- db
db:
image: postgres:12-alpine
restart: always
environment:
TZ: Asia/Tokyo
POSTGRES_DB: $DB_NAME
POSTGRES_USER: $DB_USER
POSTGRES_PASSWORD: $DB_PASS
volumes:
- db_store:/var/lib/postgresql/data
ports:
- 5432:5432
# 今回使うボリューム
volumes:
db_store:
front_store:
api_store:
Dockerfile
Dockerfileについても、各行に解説コメントを付けました。
- front(React)側
FROM node:16-alpine
# docker-compose側から、コンテナ起動時のディレクトリ位置を引数として受け取り、そこで起動するよう設定
ARG FRONT_WORKDIR
WORKDIR ${FRONT_WORKDIR}
# 時刻は、日本時間のものに設定しておく
ENV TZ Asia/Tokyo
# dockerイメージのビルド時に実行されるコマンド
# パッケージマネージャapkのアップデート
RUN apk update
RUN apk upgrade
# gitはインストールしておきたい。また、git rebase等でvimも必要になるのでインストール
RUN apk add git vim
- api(Nest)側
# 基本的にはfrontのものと同じ
FROM node:16-alpine
ARG API_WORKDIR
WORKDIR ${API_WORKDIR}
ENV TZ Asia/Tokyo
RUN apk update
RUN apk upgrade
RUN apk add git vim
# NestJSをグローバルインストールしておく
RUN yarn global add @nestjs/cli
devcontainer.json
VSCode Remote Containerのための設定ファイルです。これにより、コンテナ内での作業をVSCodeで行うことが出来ます。
- front(React)側
{
"name": "front-vscode",
// docker-compose.ymlのfrontにアタッチする
"dockerComposeFile": "../../docker-compose.yml",
"service": "front",
// 起動ディレクトリの設定
"workspaceFolder": "/workspace/front",
// 拡張機能の設定(日本語設定、git、Prettier、ESLint、Reactに関する拡張機能を設定)
// 拡張機能の設定欄は、各自カスタマイズしてOKです。
"customizations": {
"vscode": {
"extensions": [
"MS-CEINTL.vscode-language-pack-ja",
"mhutchie.git-graph",
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"dsznajder.es7-react-js-snippets"
]
}
},
"shutdownAction": "none"
}
- api(Nest)側
// 基本的にはfrontのものと同じ
{
"name": "api-vscode",
"dockerComposeFile": "../../docker-compose.yml",
"service": "api",
"workspaceFolder": "/workspace/api",
// 拡張機能の設定(日本語設定、git、Prettier、ESLint、Prismaに関する拡張機能を設定)
// 拡張機能の設定欄は、各自カスタマイズしてOKです。
"customizations": {
"vscode": {
"extensions": [
"MS-CEINTL.vscode-language-pack-ja",
"mhutchie.git-graph",
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"Prisma.prisma"
]
}
},
"shutdownAction": "none"
}
使い方
envファイル準備
まず、プロジェクトのルート位置(docker-compose.ymlやMakefileがある位置)に、.envを作成します。
そこに、以下のように環境変数を記載します。
- FRONT_PROJ_NAME=frontのプロジェクト名
- API_PROJ_NAME=apiのプロジェクト名
- DB_NAME=dbで使用するデータベース名
- DB_USER=dbで使用するユーザー名
- DB_PASS=dbで使用するパスワード
例としてはこのような感じで良いと思います。
FRONT_PROJ_NAME=react_app
API_PROJ_NAME=nest_app
DB_NAME=postgres
DB_USER=kat
DB_PASS=P@ssw0rd
もちろん、.envは.gitignoreに含まれていますのでコミットされません。
dockerイメージのビルド
[GNU Makeあり] $ make build
[GNU Makeなし] $ docker-compose build
これにより、front/Dockerfile、api/Dockerfileをもとにイメージがビルドされます。
起動
[GNU Makeあり] $ make up
[GNU Makeなし] $ docker-compose up
で環境が起動します。
初回起動時は、NestJS、Reactのプロジェクトがまだ作成されていないため、ターミナル上に「Project Directory is not found...」という記載が出るはずです。この時点ではReact, Nestともにアクセスできません。
次に説明する「React, NestJSプロジェクト新規作成」後に起動した場合は、以下のようにlocalhostでアクセスできます。
React、NestJSプロジェクト新規作成
Reactは下記コマンドで作成できます。
[GNU Makeあり] $ make front-create-app
[GNU Makeなし] $ docker-compose exec front sh -c "yarn create vite <環境変数FRONT_PROJ_NAMEの値> --template react-ts"
Reactのプロジェクト作成の際は、「ターゲットディレクトリは空ではありません。続行しますか。」と聞かれますが、"y"と入力してください。
NestJSは下記コマンドで作成できます。
[GNU Makeあり] $ make api-create-app
[GNU Makeなし] $ docker-compose exec api sh -c "nest new <環境変数API_PROJ_NAMEの値> --package-manager yarn --skip-install --skip-git"
作業方法(Remote Container使用)
上記の手順でdocker-composeを立ち上げたら、VSCodeでRemote Containerに入ります。
手順としては、まず左下の「Remote Development」ボタン押下後「Open Folder in Container」を開きます。
次に、フォルダ選択のダイヤログが出てきますので、api もしくは front フォルダを開きます。
これにて、Remote Containerが立ち上がり、コンテナ内の作業をVSCodeで出来るようになります。
終了方法
[GNU Makeあり] $ make down
[GNU Makeなし] $ docker-compose down
で終了できます。
工夫したポイント
docker-compose.yml におけるコンテナ起動時のコマンド
React、NestJSプロジェクト(正確にはその中のpackage.json)がある場合は実行に移るが、無い場合は実行に移らず待機するよう場合分けしました。これにより、初回起動時に「まだプロジェクトが出来ておらず、実行できない」といったエラーを防止できますし、逐一docker-compose.ymlを書き換える必要もなくなります。
version: "3.8"
services:
front:
⋮
# docker-compose起動時コマンド。
# Reactディレクトリ内にpackage.jsonがある場合は、node_modulesがインストールされ、開発用サーバーが起動する。
# ない場合はメッセージを出した後、「tail -f /dev/null」コマンドで待機する(これが無いとコンテナが終了してしまう)。
command: >
sh -c
"if [ -e $FRONT_PROJ_NAME/package.json ]; then
cd $FRONT_PROJ_NAME && yarn install && yarn dev --host
else
echo 'Project Directory is not found...' && tail -f /dev/null
fi"
⋮
api:
⋮
# docker-compose起動時コマンド。
# NestJSディレクトリ内にpackage.jsonがある場合は、node_modulesがインストールされ、開発用サーバーが起動する。
# ない場合はメッセージを出した後、「tail -f /dev/null」コマンドで待機する(これが無いとコンテナが終了してしまう)。
command: >
sh -c
"if [ -e $API_PROJ_NAME/package.json ]; then
cd $API_PROJ_NAME && yarn install && yarn start:dev
else
echo 'Project Directory is not found...' && tail -f /dev/null
fi"
⋮
node_modulesのマウント方法
React、Nestといったnode.jsのフレームワークは、プロジェクト直下にnode_modulesディレクトリを作り、そこにnode.jsパッケージを保存するため、今回のボリュームマウントをする必要は必ずしもなく、他のts,tsxファイルと一緒にバインドマウントするというやり方も可能ではあります。
ですが、バインドマウントをnode_modulesに行ってしまうと、yarn add等で新しくパッケージをインストールする際にとても時間がかかってしまいます(普通なら数秒で終わるはずのインストールが5分以上かかってしまったりします)。
そのため、ボリュームマウントを取り入れ、インストール速度の問題をクリアしています。
React、NestJSプロジェクト作成時のひと工夫
今回のようにnode_modulesをボリュームマウントする場合、React・Nestのプロジェク作成時に工夫が必要になります。
初回起動(プロジェクト作成前)の時点でコンテナ内にnode_modulesディレクトリ(中身は空)が存在している状態になるのですが、そこにNestJS、Reactのプロジェクトをスタンダードなやり方で作成しようとすると、「node_modulesディレクトリが既に存在します」という旨のエラーになってしまいます。
そのため、プロジェクト作成の際、node.jsパッケージのインストールをスキップする方法を取る必要があります。
- React : ビルドシステムをcreate-react-appではなくViteを使うことで、パッケージのインストールをスキップできる。
- NestJS : nest new時に --skip-install オプションをつけ、パッケージのインストールをスキップする。
また、スキップしたnode.jsパッケージのインストールは、次回のdocker-compose up時のコマンドで行われます。
ReactのビルドシステムにViteを使用
筆者は当初、create-react-appで作成したReactで作業をしていたのですが、規模が大きくなるにつれ開発用サーバーがダウンする現象が多発しました。そのため、ビルドシステムの方を、動作が高速と評判なViteに変えたところ、開発用サーバーが落ちる問題が解決しました。
また、上でも述べたとおり、Viteではプロジェクトの新規作成の際にnodeパッケージのインストールをスキップでき、まさに一石二鳥でした。
さいごに
以上、見て頂きありがとうございました。
今現在この開発環境をベースに、PDCAサイクルのWebアプリを個人開発しています。そちらも完成したらぜひともQiitaで公開したいと考えています。
今後とも宜しくお願い致します。