見出し画像

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もダウンロードしてパスワードを設定して…
という時代から環境構築は本当に楽になりましたね!

では、ありがとございました。


執筆者プロフィール:Hiro
SES、フリーランスを経て2023年01月にSHIFTに入社。
超インドア派で二ヶ月ぐらい家から出なくても平気。

お問合せはお気軽に
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/