DockerでPostgres、Node、Typescript の環境構築(サンプルコード付き)
はじめに
こんにちは。
full-stack developerを目指しているShenです。
DockerでPostgres、Node、Typescriptの基本な環境構築方法を共有したいと思います。
サンプルコードのrepoはこちらから取得できます。🙇♂️
前提条件
dockerがインストールされて、起動していること。
ちなみに、私はdocker20.10.14を使っていました。
ディレクトリ構成
BookManagement/
├── db
│ └── setup.sql
├── dist
│ └── app.js
├── src
│ └── app.ts
├── docker-compose.yml
├── Dockerfile
├── node_modules
├── package-lock.json
├── package.json
└── tsconfig.json
npmパッケージ
該当プロジェクトのruntime用パッケージをインストールする
npm install express pg uuid
- express: Node.jsの開発フレームワーク
- pg: Node.js用のPostgreSQLクライアント
- uuid: ランダムのIDを発行するため
TypeScriptを使うため、@typeがないとエラーが出てくるので、それぞれの@typeも入れましょう。
npm install @types/express @types/pg @types/uuid --save-dev
開発用のパッケージもインストールする。
npm install dotenv nodemon typescript --save-dev
- dotenv: 環境変数用
- nodemon: ファイルの編集したことを監視して、サーバを立ち直さなくても修正を反映できる
package.json
"scripts": {
"dev": "nodemon -L -e ts --exec \"npm run build && npm start\"",
"start": "node ./dist/app.js",
"build": "tsc"
}
- build: typescriptをビルドして、拡張子を
.ts
から.js
に変換する。 - start: nodeで
./dist/app.js
を実行する - dev:
-
-L
はファイル監視機能がサポートされていないファイルシステムが必要です。(例:一部Linuxベースのコンテナが必要です) Docker for MacやDocker for Windowsなどは付けなくても良いです -
-e
は監視するファイルの拡張子を指定する -
--exec
引数のコマンドを実行する
-
tsconfig.json
{
"compilerOptions": {
"target": "es6" /* 生成されるJavaScriptのバージョンを設定し、互換性のあるライブラリ宣言を含める*/,
"module": "commonjs" /* 生成されるモジュールコードを指定する */,
"typeRoots": ["./node_modules/@types"] /* 依存パッケージ用のtypeフォルダを指定する */,
"resolveJsonModule": true /* .jsonがインポートできるようにする */,
"outDir": "./dist" /* tscでビルドしたソースの出力先 */,
"esModuleInterop": true /* CommonJSモジュールのインポートをサポートする */
"forceConsistentCasingInFileNames": true /* インポート時に大文字小文字が正しいことを確認します。例:macは大小文字区別しないですが、linuxが区別ありなので、設定した方がソースが移植しやすくなる */,
"strict": true /* すべての厳格な型チェックオプションを有効にします。 */,
"skipLibCheck": true /* .d.tsファイルの型チェックをスキップします。 */
}
postgresの設定
まずは.env
でデータベース用の必要な情報を作成する
DB_HOST='db'
DB_PORT=5432
DB_NAME='db_name'
DB_USER='postgres'
DB_PASSWORD='password'
次は、docker-compose.yml
ファイルを作成する
docker-composeは複数コンテナを管理しやすくために使っています
version: '3.8'
services:
db:
container_name: postgres
image: postgres
ports:
- 5433:${DB_PORT}
volumes:
- data:/data/db
- ./db:/docker-entrypoint-initdb.d
environment:
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_DB=${DB_NAME}
volumes:
data: {}
- image: Docker Hubから
postgres
のイメージを利用する - ports: ${DB_PORT}を
localhost
のport:5433にマウントする - volumes:
- /data/db: コンテナをシャットダウンしてもデータがなくならないようにする
- docker-entrypoint-initdb.d: dbの初期化sqlを実行するため
- environment: 環境変数を設定する
さらに、初期化用の./db/setup.sqlを作成する
docker-compose.yml
に./db:/docker-entrypoint-initdb.d
をすると、データベースを初期化する際に、setup.sql
が実行される。つまり、postgres
コンテナが起動後、booksというテーブルはすでに存在している状態になる
set client_encoding = 'UTF8';
CREATE TABLE IF NOT EXISTS books (
"id" VARCHAR(100) NOT NULL,
"title" VARCHAR(100) NOT NULL,
"author" VARCHAR(100) NOT NULL,
"pages" INTEGER NOT NULL,
PRIMARY KEY ("id")
)
最後にapp.ts
からpostgres
との接続処理を追加する
import { Pool } from 'pg';
dotenv.config();// .envの環境変数をprocess.envからアクセスできるようにする
const pool = new Pool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
database: process.env.DB_NAME,
password: process.env.DB_PASSWORD,
port: parseInt(process.env.DB_PORT || '5432'),
});
const connectToDB = async () => {
try {
await pool.connect();
} catch (err) {
console.log(err);
}
};
connectToDB();
appのサーバーをimage化にする
まずは、appのサーバーポートを.env
に追加する。
PORT=3000
次は、Dockerfile
を生やす。
FROM node:16.18.1
WORKDIR /usr/src/app
COPY ["package.json", "package-lock.json", "tsconfig.json", ".env", "./"]
COPY ./src ./src
RUN npm install
CMD npm run dev
- FROM node:16.18.1: nodeのベースイメージをインストールする
- WORKDIR /usr/src/app: appのサーバーは該当パスで実行される
- COPY:localからファイルコンテナにコピーする
- RUN npm install: 依存パッケージをインストールする
- CMD npm run dev:
package.json
を参照すると、"nodemon -L -e ts --exec \"npm run build && npm start\""
が実行される
Dockerfile
を作成した後、appはイメージとして、コンテナで実行できるようになる。docker-compose.yml
のservices
配下に以下を追加する。
api:
container_name: api
restart: always
build: .
ports:
- ${PORT}:${PORT}
depends_on:
- db
volumes:
- .:/usr/src/app
- restart:
always
を指定すると、コンテナを自動起動できるようになる - build: Dockerfile のあるディレクトリのパスを指定する
- ports: コンテナの
port:3000
をlocalhost:3000
にマウントする - depends_on: dbコンテナがスタート完了した後、該当コンテナを実行する
expressでエンドポイントを生やす
次はapp.ts
にエンドポイントを作成する。(postgres
のコードも含まれる)
import express from 'express';
import dotenv from 'dotenv';
import { v4 } from 'uuid';
import { Pool } from 'pg';
dotenv.config();
const pool = new Pool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
database: process.env.DB_NAME,
password: process.env.DB_PASSWORD,
port: parseInt(process.env.DB_PORT || '5432'),
});
const connectToDB = async () => {
try {
await pool.connect();
} catch (err) {
console.log(err);
}
};
connectToDB();
const app = express();
app.use(express.json());
app.get('/books', async (req, res) => {
const query = {
text: `SELECT * FROM books`,
};
const records = await pool.query(query);
res.status(200).send(records.rows);
});
app.get('/books/:bookId', async (req, res) => {
const { bookId } = req.params;
console.log('aabbb');
const query = {
text: `SELECT * FROM books WHERE id = $1`,
values: [bookId],
};
const records = await pool.query(query);
res.status(200).send(records.rows[0]);
});
app.post('/books', async (req, res) => {
const { id, title, author, pages } = req.body;
const bookId = id ?? v4();
try {
const query = {
text: 'INSERT INTO books(id, title, author, pages) VALUES($1, $2, $3, $4)',
values: [bookId, title, author, pages],
};
await pool.query(query);
res.status(200).send({ id: bookId });
} catch (error) {
res.status(500).send(error);
}
});
app.listen(process.env.PORT, () => {
console.log(`Server is listening on port ${process.env.PORT}`);
});
起動
ターミナルでBookManagement
配下で以下のコマンドを実行する。
docker-compose up
動作確認
三つのエンドポイントを生やしたので、リクエストして正しく動作できていることを確認する。
- booksの基本情報を取得用(全件)
GET /books
curl --location 'http://localhost:3000/books'
レスポンスは[]
であるはず。statusは200
であることは、postgres
の初期化sqlが無事に実行されたことがわかる。
- bookの基本情報を登録用(一件)
POST /books
curl --location 'http://localhost:3000/books' \
--header 'Content-Type: application/json' \
--data '{
"id": "testId",
"title": "The Time Traveler'\''s wife",
"author": "Audrey Niffenegger",
"pages": 1203
}'
これでデータ1件登録完了。
- bookの基本情報を取得用(一件)
GET /books/{bookId}
curl --location 'http://localhost:3000/books/testId'
レスポンスは以下のようになれば、動作確認完了です。
{
"id": "testId",
"title": "The Time Traveler's wife",
"author": "Audrey Niffenegger",
"pages": 1203
}
終わりに
最後までご覧いただいて、ありがとうございます!
以上はDockerでPostgres、Node、Typescript の環境構築でした。
結構雑なソースですが、次回から上記のソースを使って、オニオンアーキテクチャの紹介ベースとして、リファクタリングの記事に書こうと思っています。
Discussion