タイトルにもあるとおり、Node.js + MySQL + Dockerな環境で開発を行う機会がありました。
Node.jsからMySQL操作を行うためのライブラリはmysql2というものを使用しましたが、
- 接続処理
- コネクションプール
- CRUD
といった情報が意外とまとまっておらず困りました。
またDockerにMySQL環境を構築する方法についても調べるのが少し面倒だったので、本記事でまるっとまとめておこうと思います。
- DockerにMySQL環境を構築する
- Docker上のMySQLにホスト環境からアクセスする
- mysql2を使用してMySQLへ接続する
- mysql2のコネクションプールを使用する
- mysql2を用いてCRUD(INSERT, SELECT, UPDATE, DELETE)操作を行う
MySQL環境の作成
まずはDocker上にMySQL環境を作成していきましょう。
Docker公式イメージを使えば簡単に作成できます。
docker-compose設定ファイルの作成
本記事ではDocker Composeを使用して環境を構築しようと思います。
そのため設定ファイルを以下のように作成します。(名前やパスワードなどは任意の値でOK)
version: '3.8'
services:
mysql:
container_name: sample-mysql
hostname: sample-mysql
image: mysql:latest
command:
- --default-authentication-plugin=mysql_native_password # パスワード方式でログインする
- --default-time-zone=Asia/Tokyo # タイムゾーンは日本時間
restart: always
environment:
- MYSQL_ROOT_PASSWORD=password
- MYSQL_DATABASE=sample
- MYSQL_USER=sample
- MYSQL_PASSWORD=sample
- LANG=C.UTF-8 # とりあえずでマルチバイト文字対応させるための設定
- LANGUAGE=en_US # とりあえずでマルチバイト文字対応させるための設定
ports:
- '3306:3306' # 3306ポートでホストからDockerのMySQL環境へ接続できるようにする
DBの初期データ
MySQL環境を立ち上げる際には初期データを入れておきたいです。
その場合は/docker-entrypoint-initdb.d
にホスト側のフォルダをマウントさせましょう。
コンテナを初めて起動した際に該当フォルダ内にあるsqlファイルが実行されます。
- 実行されるファイルは
.sh
,.sql
,.sql.gz
の3種類です。 - ファイルはアルファベット順に実行されるのでリレーションなどには注意が必要です。
- より詳細な情報は公式イメージの”Initializing a fresh instance“をご覧ください。
フォルダのマウント設定
前述のとおり/docker-entrypoint-initdb.d
へホスト側フォルダをマウントさせます。
Docker Compose設定ファイルに以下のように設定を追加しましょう。
version: '3.8'
services:
mysql:
container_name: sample-mysql
hostname: sample-mysql
image: mysql:latest
command:
- --default-authentication-plugin=mysql_native_password # パスワード方式でログインする
- --default-time-zone=Asia/Tokyo # タイムゾーンは日本時間
restart: always
environment:
- MYSQL_ROOT_PASSWORD=password
- MYSQL_DATABASE=sample
- MYSQL_USER=sample
- MYSQL_PASSWORD=sample
- LANG=C.UTF-8
- LANGUAGE=en_US
ports:
- '3306:3306' # 3306ポートでホストからDockerのMySQL環境へ接続できるようにする
volumes:
- ./initdb.d:/docker-entrypoint-initdb.d
上記の場合は./initdb.d
がホスト側のフォルダで、/docker-entrypoint-initdb.d
がDocker側のフォルダを示します。
SQLファイルの作成
あとは必要なテーブルやデータを用意するための.sh
や.sql
ファイルを作りましょう。
今回は簡単なテーブルのみあれば良いのでsample_user.sql
をホスト側の./initdb.d
フォルダに作成します。
CREATE TABLE `sample`.`sample_user`
(
`id` INT AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(30) NOT NULL,
`age` TINYINT NULL,
`isActive` BOOLEAN,
`createdAt` DATETIME DEFAULT CURRENT_TIMESTAMP
);
起動
準備ができたら後はdocker-compose up
するだけで良い感じにMySQL環境が作成されて立ち上がります。
以下にならってコマンドを実行しましょう。
docker-compose -f ./database.yml up
Node.jsからの接続処理
MySQL環境の準備ができたのでNode.jsからMySQLへの操作を実装していきます。
使用ライブラリ
mysql2というライブラリを使用します。
mysqlというライブラリもありますがこちらは非同期処理(async/await)に対応していません。
インストール
まずはmysql2をインストール
npm i mysql2
本ライブラリは非同期サポートがあるため以降の処理は非同期のほうをimportして使っていきます。
接続処理
コネクションプールの使用有無で接続処理が若干変わります。
コネクションプールを使用する場合
import * as mysql from 'mysql2/promise'
const pool = mysql.createPool({
database: 'sample',
host: '127.0.0.1',
password: 'sample',
port: 3306,
timezone: 'Z',
user: 'sample',
})
await pool.query(
...
)
コネクションプールを利用する場合は明示的に接続終了処理を実行する必要はありません。
コネクションプールからコネクションを拝借→クエリ実行→コネクションをリリースといった定型プロセスを内部的にやってくれているようです。
詳しくは以下を参照。
より詳しくはもととなったライブラリを参照したほうがよい
コネクションプールを使用しない場合
import * as mysql from 'mysql2/promise'
const connection = await mysql.createConnection({
database: 'sample',
host: '127.0.0.1',
password: 'sample',
port: 3306,
timezone: 'Z',
user: 'sample',
});
await connection
.query(
...
)
.finally(() => {
connection.end()
})
データ操作
DBへの接続ができましたので、データ操作に関する処理を見ていきましょう。
プレースホルダ
mysql2ではプレースホルダを使用したクエリに対応しています。
使用できるプレースホルダは2種類でそれぞれ以下のような実装となります。
通常のプレースホルダを使用する場合
const pool = mysql.createPool({
database: 'sample',
host: '127.0.0.1',
password: 'sample',
port: 3306,
timezone: 'Z',
user: 'sample',
})
await pool.query<RowDataPacket[]>({
sql: 'SELECT * FROM sample_user WHERE id = ?', // クエリ
values: [12345], // パラメータ
})
名前付きプレースホルダを使用する場合
const pool = mysql.createPool({
database: 'sample',
host: '127.0.0.1',
password: 'sample',
port: 3306,
timezone: 'Z',
user: 'sample',
namedPlaceholders: true, // 名前付きプレースホルダ
})
await pool.query<RowDataPacket[]>({
sql: 'SELECT * FROM sample_user WHERE id = :id', // クエリ
values: {
id: 12345, // パラメータはオブジェクト形式。プロパティ名をプレースホルダ名と一致させる
},
})
CRUDサンプル
DBへのCRUD操作のサンプルです。
特筆すべきことはほぼありませんのでコードだけさらっとおいておきます。
Read
await pool.query<RowDataPacket[]>({
sql: 'SELECT * FROM sample_user WHERE id = :id',
values: {
id: 12345,
},
})
Create
await pool.query<ResultSetHeader>({
sql: 'INSERT INTO sample_user SET :insertValues',
values: {
insertValues: {
name: 'dummy user',
age: 99,
isActive: true,
},
},
})
.then(([result]) => {
return result.insertId
})
mysql2を利用したINSERTではMySQLのINSERT INTO テーブル名 SET フィールド名 = 値, フィールド名 = 値...
の構文が便利です。
上記のサンプルコードでもINSERT INTO ... SET ...
の構文を利用してクエリをスッキリさせています。
Update
await pool.query<ResultSetHeader>({
sql: 'UPDATE sample_user SET :updateValues WHERE id = :id',
values: {
id: 12345,
updateValues: {
name: 'updated user',
age: -123,
isActive: false,
},
},
})
.then(([result]) => {
return result.changedRows
})
Delete
await pool.query<ResultSetHeader>({
sql: 'DELETE FROM sample_user WHERE id = :id',
values: {
id: 12345,
},
})
.then(([result]) => {
return result.affectedRows
})
最後に
接続まわりに気をつけておけばデータ操作処理はそんなに難しいことはありませんね。
必要に応じてORMなどを使うとより開発しやすくなるかもしれません。