Closed6
docker compose でExpress.js-React.js-PostgreSQL-TypeScriptの開発環境の構築
今回使用したソースコード
技術スタック
- TypeScript
- Express.js
- React.js
- PostgreSQL
- Prisma
- docker compose
- VSCode
要件
- 変更が即時反映される
- ブレイクポイントが使える
- などなど
最終的な構造
ワークディレクトリの配下にapi
とweb
のディレクトリがある両面TypeScriptの構成
API(Express.js)の環境を作っていく。
1.apiディレクトリの作成
mkdir api && cd api
2.必要なパッケージのインストール
npm init -y
npm i express
npm i -D typescript ts-node-dev prisma @types/node @types/express
3. packages.jsonとtsconfigをとりいそぎ
開発用サーバーはts-node-dev
を使用する。
{
"name": "api",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"start": "node dist/index.js",
"build": "tsc",
"dev": "ts-node-dev --respawn --transpile-only src/index.ts"
},
"dependencies": {
"express": "^4.19.2"
},
"devDependencies": {
"@types/express": "^4.17.21",
"@types/node": "^20.14.10",
"ts-node-dev": "^2.0.0",
"typescript": "^5.5.3"
}
}
{
"compilerOptions": {
"target": "ESNext",
"module": "commonjs",
"outDir": "dist",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
},
"include": ["src/**/*"]
}
4. エントリーポイント(src/indexc.ts)
import express from "express";
const app = express();
app.use("/hello", (req, res) => {
res.send("helloworld")
});
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
この時点でローカル環境での開発はできる。
コンテナ環境を作っていく。
開発だけならdocker-compose.ymlだけで事足りるので、Dockerfileは用意しないことにする。
1. .gitignore
dist
node_modules
2. docker-compose.yml
services:
api:
image: node:20
working_dir: /app
volumes:
- ./api:/app
- /app/node_modules # コンテナ内node_modulesをホストのファイルシステムから分離
ports:
- "3000:3000"
command: /bin/sh -c "npm i && npm run dev"
/api配下のコンテナ環境構築は一旦完了
docker compose up -d --build
変更も反映される。
docker compose logs -f api
vscodeでブレイクポイントを使う
1.launch.json(.vscode/launch.json)
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "attach",
"name": "Attach to ts-node-dev",
"port": 9229,
"restart": true, // ファイル変更時、デバッガも自動的に再接続させる
"address": "0.0.0.0",
"localRoot": "${workspaceFolder}/api",
"remoteRoot": "/app",
"skipFiles": [
"<node_internals>/**", // Node.js の内部コード(例:コアモジュール)にステップインするのを防ぐ
"${workspaceFolder}/api/node_modules/**"
]
}
]
}
その他のファイル修正
$ git diff --staged
diff --git a/api/package.json b/api/package.json
index 4193a27..543cb89 100644
--- a/api/package.json
+++ b/api/package.json
@@ -5,7 +5,7 @@
"scripts": {
"start": "node dist/index.js",
"build": "tsc",
- "dev": "ts-node-dev --respawn --transpile-only src/index.ts"
+ "dev": "ts-node-dev --inspect=0.0.0.0:9229 --respawn --transpile-only src/index.ts"
},
"dependencies": {
"express": "^4.19.2"
diff --git a/api/tsconfig.json b/api/tsconfig.json
index 1d08bdd..c5bfa51 100644
--- a/api/tsconfig.json
+++ b/api/tsconfig.json
@@ -6,7 +6,8 @@
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
- "skipLibCheck": true
+ "skipLibCheck": true,
+ "sourceMap": true
},
"include": ["src/**/*"]
}
diff --git a/docker-compose.yml b/docker-compose.yml
index 15a293f..c3f35cf 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -7,4 +7,5 @@ services:
- /app/node_modules # コンテナ内node_modulesをホストのファイルシステムから分離
ports:
- "3000:3000"
+ - "9229:9229" # デバッグポート
command: /bin/sh -c "npm i && npm run dev"
動作確認
PostgreSQLを起動してapiと連携する。
docker-compose.yml にPostgreSQLの定義を追加
$ git diff
diff --git a/docker-compose.yml b/docker-compose.yml
index c3f35cf..15958c9 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -9,3 +9,25 @@ services:
- "3000:3000"
- "9229:9229" # デバッグポート
command: /bin/sh -c "npm i && npm run dev"
+ depends_on:
+ db:
+ condition: service_healthy # db の起動が完了するまで待つ
+
+ db:
+ image: postgres:latest
+ container_name: db
+ environment:
+ POSTGRES_USER: postgres
+ POSTGRES_PASSWORD: password
+ ports:
+ - "5432:5432"
+ healthcheck:
+ test: pg_isready -U postgres
+ interval: 5s
+ timeout: 5s
+ retries: 10
+ volumes:
+ - db_volume:/var/lib/postgresql/data # データを永続化
+
+volumes:
+ db_volume:
Primsaのインストール
DB操作はPrismaを通して行う。
cd api
npm i -D prisma && npm i @prisma/client
npx prisma init
docker-compose.yml
の定義に合わせて.env
を修正
- DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"
+ DATABASE_URL="postgresql://postgres:password@db:5432?schema=public" # dbはdocker-compose.ymlで定義しているホスト名
Prismaの諸ファイルを用意(スキーマとシードとClient)
/api/prisma/scheme.prisma
にスキーマを定義
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
name String
email String @unique
}
スキーマ定義に則った@prisma/client
を生成
npx prisma generate
api/prisma/seed
にseedファイルを作成
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
async function main() {
// Seed data for the User model
const count = await prisma.user.count();
if (count < 1) {
const user1 = await prisma.user.create({
data: {
name: "John Doe",
email: "john.doe@example.com",
},
});
console.log({ user1 });
}
}
main()
package.json
にseedの定義する。
"build": "tsc",
"dev": "ts-node-dev --inspect=0.0.0.0:9229 --respawn --transpile-only src/index.ts"
},
+ "prisma": {
+ "seed": "ts-node-dev prisma/seed"
+ },
src/index.ts
をPrismaを使用してDBからデータを取得するように変更。
import express from "express";
+import { PrismaClient } from "@prisma/client";
const app = express();
-app.use("/hello", (req, res) => {
- res.send("helloworld")
+app.use("/hello", async (req, res) => {
+ const prisma = new PrismaClient();
+ const user = await prisma.user.findFirst();
+ res.send("helloworld: " + user?.name)
});
動作確認
apiコンテナの中でprisma関連のコマンドを実行
docker compose up -d --build
docker compose exec api bash
npx prisma db push
npx prisma db seed
curlを実行
React(vite)の環境を作っていく。
1.viteによるReactのセットアップ
npm create vite@latest web -- --template react-ts
cd web && npm install && npm run dev
一旦、動作確認
2. apiにcorsの設定を加える
viteのサーバーはlocalhost:5173
で立ち上がり、APIリクエストはクロスオリジンになってしまうので、それを許可する設定を入れる。
docker compose exec api bash -c "npm i cors && npm i -D @types/cors"
/appi/src/index.ts
import express from "express";
import { PrismaClient } from "@prisma/client";
import cors from "cors";
const app = express();
app.use(
cors({
origin: ["http://localhost:5173"],
})
);
app.use("/users", async (req, res) => {
const prisma = new PrismaClient();
const users = await prisma.user.findMany();
res.json(users);
});
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
3. webをapiからデータ取得するように変更
/web/src/App.tsx
をlocalhost:3000
からfetchするようなファイルに変更
import { useState, useEffect } from "react";
import "./App.css";
type User = {
id: number;
name: string;
email: string;
};
function App() {
const [users, setUsers] = useState<User[]>([]);
useEffect(() => {
const fetchUsers = async () => {
const response = await fetch('http://localhost:3000/users');
const data = await response.json();
setUsers(data);
};
fetchUsers();
}, []);
return (
<>
<h1>Vite + React</h1>
<h2>with Express.js in Docker</h2>
<div>
<h2>API response</h2>
<ul>
{users.map((user) => (
<li key={user.id}>
{user.name} - {user.email}
</li>
))}
</ul>
</div>
</>
);
}
export default App;
動作確認
このスクラップは4ヶ月前にクローズされました