devcontainerを使ってフロントからDBまで(react × express × postgres)
はじめに
こんにちは。
株式会社SHIFTの洋です。
開発や調査などをしていると、環境を用意して動かしたくなることが多々あると思います。
そんな時はdevcontainerを使用することが多いのですが、意外とフロントからデータベースまで用意しようとすると癖があるので、今回はその方法を紹介しようかと思います。
前提
Visual Studio Codeに以下の拡張機能が追加されていて、コンテナが起動できるようになっている方が対象です。
windowsだとDocker Desktopや、WSL2上でDocker起動するなど色々手段があるのですが、本筋ではないので省略します。
Dev Containers
フォルダ構成
以下のような形になります。
それぞれのディレクトリに存在している.devcontainer内のファイルがキモ。 詳しくはこちらを参照ください。
.
├── .devcontainer
│ └── docker-compose.yml
├── app
│ ├── .devcontainer
│ │ └── devcontainer.json
│ └── Dockerfile
├── client
│ ├── .devcontainer
│ │ └── devcontainer.json
│ └── Dockerfile
├── db
└── docker-compose.yml
./.devcontainer/docker-compose.yml
version: '3.8'
services:
app:
volumes:
- ..:/workspaces:cached
command: /bin/sh -c "while sleep 1000; do :; done"
client:
volumes:
- ..:/workspaces:cached
command: /bin/sh -c "while sleep 1000; do :; done"
./app/.devcontainer/devcontainer.json
{
"name": "container app",
"dockerComposeFile": [
"../../docker-compose.yml",
"../../.devcontainer/docker-compose.yml"
],
"service": "app",
"workspaceFolder": "/app",
"shutdownAction": "none"
}
./client/.devcontainer/devcontainer.json
{
"name": "container client",
"dockerComposeFile": [
"../../docker-compose.yml",
"../../.devcontainer/docker-compose.yml"
],
"service": "client",
"workspaceFolder": "/client",
"shutdownAction": "none"
}
./docker-compose.yml
version: '3.8'
services:
db:
image: postgres:latest
container_name: ae-db
volumes:
- ./db/data:/var/lib/postgresql/data
- ./db/init:/docker-entrypoint-initdb.d
environment:
TZ: Asia/Tokyo
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- "5432:5432"
app:
container_name: ae-app
build:
context: ./app
dockerfile: Dockerfile
working_dir: /app
volumes:
- ./app:/app
environment:
TZ: Asia/Tokyo
depends_on:
- db
client:
container_name: ae-client
build:
context: ./client
dockerfile: Dockerfile
working_dir: /client
volumes:
- ./client:/client
environment:
TZ: Asia/Tokyo
depends_on:
- app
今回はフロントもバックもnodeを使う想定なので、 Dockerfileの中身はいずれも以下でOKです!
FROM node:18-alpine
起動
次は実際に起動していきましょう。
まず新しいウィンドウを2つ開いておくとよいです。
File > New Window
それぞれ新しく開いたウィンドウから、
F1 > Dev Containers: Open Folder in Container...
を実行し、./appと./clientを選択します。
しばらく待っているとコンテナが起動しCLIENTとAPPをルートにしたワークスペースが表示されるかと思います。
さて、これで土台となるコンテナは用意できました。
DBの準備
環境だけ用意してもつまらないので、中身を用意していきます。 お好みのDBクライアントを使用してください。
VSCの拡張機能などで、APPやCLIENTから接続する場合hostはae-dbにしてください。
逆にdockerを起動しているホストOSから接続する場合はlocalhostでよいです。 これはDockerの仕様ですが、忘れがちなので気を付けましょう。
例として、APPからVSCの拡張機能であるDatabase Clientを使用して接続する場合の例を載せておきます。
※Passwordはpostgresです
テーブルとデータ
テーブルとレコードを追加しておきます。
CREATE TABLE users(
id SERIAL NOT NULL,
name text,
age integer,
PRIMARY KEY(id)
);
insert into
users (
name,
age
)
values
(
'山田 太郎',
20
);
expressでバックエンドを構築
APP内のターミナルから以下のコマンドを実行しまう。
npx express-generator my-express-app
実行すると以下のようなディレクトリになりますが、
app直下にmy-express-appの中身を展開してしまってもOKです。
.
├── .devcontainer
│ └── devcontainer.json
├── my-express-app
│ └── ...
└── Dockerfile
APPからDB接続
my-express-app直下で以下のコマンドを実行
yarn add pg
my-express-app直下に以下のdb.jsファイルを配置します。
本来ポート番号などを設定するhostにコンテナ名しています。
./my-express-app/db.js
const { Pool } = require('pg');
const pool = new Pool({
user: 'postgres',
host: 'ae-db',
database: 'postgres',
password: 'postgres',
port: 5432,
});
module.exports = pool;
users.jsの中身を以下のように書き換えておきます。
./my-express-app/routes/users.js
var express = require('express');
var router = express.Router();
const pool = require('../db');
router.get('/', async function(req, res, next) {
try {
const result = await pool.query('SELECT * FROM users');
res.json(result.rows);
} catch (error) {
res.status(500).json({ error: 'データベースからユーザー情報を取得できませんでした。' });
}
});
module.exports = router;
動作確認
以下のコマンドをmy-express-app直下で実行してブラウザから確認してみましょう。
yarn start
よさそうですね。
フロントの用意
次はフロントを用意します。
clientのコンテナを起動したVSCのコンソールから、以下のコマンドを実行します。 今回はReactのTypeScript + SWCを選択します。
yarn create vite
以下のようなディレクトリになりますが、例のごとくvite-projectの中身を直下CLIENT直下に展開してしまってもよいです。
.
├── .devcontainer
│ └── devcontainer.json
├── vite-project
│ └── ...
└── Dockerfile
フロントとバックエンドの疎通
以下のコマンドをvite-project直下で実行します。
yarn add axios
App.tsxを以下に書き換えます。
./vite-project/src/App.tsx
import "./App.css";
import { useState, useEffect } from "react";
import axios from "axios";
function App() {
interface User {
id: number;
name: string;
age: number;
}
const [users, setUsers] = useState([]);
useEffect(() => {
axios
.get("http://localhost:3000/users" )
.then((response) => {
setUsers(response.data);
})
.catch((error) => {
console.error("データの取得に失敗しました。", error);
});
}, []);
return (
<div>
<h1>User List</h1>
<ul>
{users.map((user) => (
<li>ID:{user.id} 名前:{user.name} 年齢:{user.age}</li>
))}
</ul>
</div>
);
}
export default App;
動作確認
以下のコマンドをvite-project直下で実行します。
yarn dev
ブラウザ上から確認してみましょう。
CORSエラーが発生してしまいました。 せっかくなので直しておきましょう。
APPコンテナのmy-express-app直下から以下のコマンドを実行
yarn add cors
app.jsに以下の行を追加
./my-express-app/app.js
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var cors = require('cors'); // 行追加
~~~
app.use(cors()); // 行追加
app.use(logger('dev'));
app.use(express.json());
expressを再起動して、ブラウザをリロードします。
うまくいきました。
DBの値を取得して画面に表示できていることも確認できます。
おわりに
devcontainerを使ってフロントからDBまで疎通する環境を用意することができました。
この構成のまま運用する…には少し厳しいものがありますが、githubなどで共有すれば勉強会などで即動く環境を共有できるのでオススメです。
またフロント、バック、DBのimageを切り替えれば自由に構成を変えることができるので、作っては消しを繰り返しながら自学習や調査でよく使用しています。
JREをインストールして環境変数を通して…
あとDBもダウンロードしてパスワードを設定して…
という時代から環境構築は本当に楽になりましたね!
では、ありがとございました。
お問合せはお気軽に
https://service.shiftinc.jp/contact/
SHIFTについて(コーポレートサイト)
https://www.shiftinc.jp/
SHIFTのサービスについて(サービスサイト)
https://service.shiftinc.jp/
SHIFTの導入事例
https://service.shiftinc.jp/case/
お役立ち資料はこちら
https://service.shiftinc.jp/resources/
SHIFTの採用情報はこちら
https://recruit.shiftinc.jp/career/