はじめに
この記事では、Docker Compose を利用した NestJS / TypeScript / MySQL の開発環境構築手順について記載します。
開発環境
開発環境は以下の通りです。
- Windows11
- Docker Engine 26.1.1
- Docker Compose 2
- Node.js 20.15.1
- NestJS 10.0.0
- TypeScript 5.1.3
- MySQL 9.0.0
- MySQL2 3.11.0
- TypeORM 0.3.20\
NestJS / TypeScript の環境構築
まずは NestJS アプリケーションの開発支援ツールである Nest CLI をインストールします。
npm i -g @nestjs/cli
次に以下のコマンドでプロジェクトを作成します。
nest-mysql
はプロジェクト名です。また、--strict
を追加することで、TypeScript の strict 系のコンパイラオプションが有効になります。
nest new nest-mysql --strict
パッケージマネージャーは npm を利用します。
⚡ We will scaffold your app in a few seconds..
? Which package manager would you ❤️ to use? npm
CREATE nest-mysql/.eslintrc.js (688 bytes)
CREATE nest-mysql/.prettierrc (54 bytes)
CREATE nest-mysql/nest-cli.json (179 bytes)
CREATE nest-mysql/package.json (2018 bytes)
CREATE nest-mysql/README.md (3413 bytes)
CREATE nest-mysql/tsconfig.build.json (101 bytes)
CREATE nest-mysql/tsconfig.json (562 bytes)
CREATE nest-mysql/src/app.controller.ts (286 bytes)
CREATE nest-mysql/src/app.module.ts (259 bytes)
CREATE nest-mysql/src/app.service.ts (150 bytes)
CREATE nest-mysql/src/main.ts (216 bytes)
CREATE nest-mysql/src/app.controller.spec.ts (639 bytes)
CREATE nest-mysql/test/jest-e2e.json (192 bytes)
CREATE nest-mysql/test/app.e2e-spec.ts (654 bytes)
✔ Installation in progress... ☕
🚀 Successfully created project nest-mysql
👉 Get started with the following commands:
$ cd nest-mysql
$ npm run start
Thanks for installing Nest 🙏
Please consider donating to our open collective
to help us maintain this package.
🍷 Donate: https://opencollective.com/nest
プロジェクトを作成したディレクトリに移動します。
cd nest-mysql
ローカルサーバーを起動します。
npm start
プロジェクト作成時に自動生成されたコードの中に 'Hello World!' を返す getHello
メソッドがあるので、動作確認します。
NestJS の Dockerfile 作成
次は NestJS の Dockerfile を作成します。
# Use the official Node.js image
FROM node:20-alpine3.19
# Create and set the working directory
WORKDIR /app
# Copy package.json and package-lock.json
COPY package*.json ./
# Install dependencies
RUN npm install
# Expose the application port
EXPOSE 3000
# Run the application
CMD ["npm", "start"]
動作確認のため、作成した Dockerfile を利用したイメージのビルドとコンテナの起動確認をします。
まずはイメージをビルドします。
docker image build --tag nest-ts:1.0.0 .
イメージがビルドできたか確認します。
docker image ls nest-ts
ビルドしたイメージを使ってコンテナを起動します。また、コンテナ起動時にホストマシンのファイルをバインドマウントします。
docker container run `
--name nest-ts `
--rm `
--publish 3000:3000 `
--mount type=bind,source="$(pwd)",target=/app `
nest-ts:1.0.0
NestJS のプロジェクトファイル作成時と同じく getHello
メソッドを呼び出して動作確認をします。
compose.yaml と DB 初期データ作成ファイルの作成
以下の要件を満たす compose.yaml
を作成します。
- NestJS
- コンテナ名:
app-container
- ビルド:Dockerfile
- ポート:3000
- ソースコード:ホストマシンで実装
- コンテナ名:
- MySQL
- コンテナ名:
db-container
- ビルド:MySQL 公式イメージのタグ 9.0.0
- ポート:3306
- 初期データを用意
- データはコンテナを削除しても残す
- ホストマシンからDB接続可能
- コンテナ名:
- コンテナ間通信可能
services:
app:
container_name: 'app-container'
build: . # Dockerfile path
ports:
- '3000:3000'
volumes:
- type: bind
source: ./
target: /app
environment:
DB_HOST: db
DB_USER: user
DB_PASSWORD: userpassword
DB_NAME: sampledb
db:
container_name: 'db-container'
image: mysql:9.0.0
ports:
- '3306:3306'
volumes:
- type: bind
source: ./db
target: /docker-entrypoint-initdb.d
- type: volume
source: db-volume
target: /var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_DATABASE: sampledb
MYSQL_USER: user
MYSQL_PASSWORD: userpassword
volumes:
db-volume:
DB の初期データ作成ファイルのバインドマウント先の値 /docker-entrypoint-initdb.d
は、Docker Hub の MySQL リポジトリの Initializing a fresh instance に記載があります。1
DB のボリュームマウント先の /var/lib/mysql
は、MySQL サーバーがデータを保存するデフォルトのディレクトリになります。MySQL 設定ファイル(/etc/my.cnf
)で定義されています。
docker container run --rm mysql:9.0.0 cat /etc/my.cnf
[mysqld]
...
datadir=/var/lib/mysql
...
バインドマウント元の DB 初期データ作成ファイルを作成します。
CREATE TABLE users(id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(32) NOT NULL, email VARCHAR(32) NOT NULL);
INSERT users (name, email) VALUES ("Wyatt", "wyatt@example.com");
INSERT users (name, email) VALUES ("Billy", "billy@example.com");
ここまでできたら一度動作確認をします。
docker compose up --build
コンテナ一覧を確認します。
docker container ls
compose.yaml
で定義した名前通りのコンテナが作成されています。
App コンテナの動作確認として、今回も getHello
メソッドを呼び出して動作確認をします。
DB コンテナの動作確認として、初期データが登録されているか確認します。
まずは MySQL に接続します。
mysql --host=127.0.0.1 --port=3306 --user=user --password=userpassword
データベース一覧に環境変数として指定したデータベースが存在することを確認します。
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| performance_schema |
| sampledb |
+--------------------+
3 rows in set (0.08 sec)
初期データ作成時に指定したテーブル及びユーザーデータがあることを確認します。
mysql> use sampledb;
Database changed
mysql> select * from users;
+----+-------+-------------------+
| id | name | email |
+----+-------+-------------------+
| 1 | Wyatt | wyatt@example.com |
| 2 | Billy | billy@example.com |
+----+-------+-------------------+
2 rows in set (0.04 sec)
この時点では、AppコンテナとDBコンテナがそれぞれ起動しているだけで、コンテナ間の通信は発生していません。
DB 接続処理の実装
App コンテナ(NestJS)から DB コンテナへの接続処理を実装します。
パッケージのインストール
必要なパッケージをインストールします。
-
MySQL2
: MySQL クライアント -
TypeORM
: TypeScript / JavaScript 向けの ORM(DB とプログラミング言語のデータ変換) -
@nestjs/typeorm
: NestJS 向けの TypeORM モジュール
npm install --save @nestjs/typeorm typeorm mysql2
TypeORM の設定
先ほどインストールした TypeORM の設定ファイルを作成します。
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
const config: TypeOrmModuleOptions = {
type: 'mysql',
host: 'db',
port: 3306,
username: 'user',
password: 'userpassword',
database: 'sampledb',
entities: [],
synchronize: true,
};
export default config;
作成した設定値を src/app.module.ts
の imports
に TypeORMModule.forRoot
の引数として渡します。
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import config from './ormconfig';
@Module({
imports: [TypeOrmModule.forRoot(config)], // Add
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
アプリ初期化時にクエリを実行する
設定ができたので、実際 DB に接続する処理を追加します。
今回はアプリ初期化時に簡単なクエリを実行します。
import { Module, OnModuleInit } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { InjectDataSource, TypeOrmModule } from '@nestjs/typeorm';
import config from './ormconfig';
import { DataSource } from 'typeorm';
@Module({
imports: [TypeOrmModule.forRoot(config)],
controllers: [AppController],
providers: [AppService],
})
export class AppModule implements OnModuleInit {
constructor(@InjectDataSource() private readonly dataSource: DataSource) {}
async onModuleInit() {
try {
await this.dataSource.query('SELECT 1');
console.log('Database connection is successful');
} catch (error) {
console.error('Error connecting to the database', error);
}
}
}
動作確認
Docker を起動して動作確認をします。
docker compose up --build
先ほど実装したログ Database connection is successful
が出力されます。