【Node.js】Dockerにmysql環境を構築してmysql2で操作する

タイトルにもあるとおり、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()
  })

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などを使うとより開発しやすくなるかもしれません。

よかったらシェアしてね!
  • URLをコピーしました!
目次