Node.js + TypeScript + MySQL2 によるクエリ送信ラッパークラスの作成

Node.js + TypeScript + MySQL2 によるクエリ送信ラッパークラスの作成

TypeScript において MySQL2 を使用し、MySQL DB へのクエリ送信処理と結果取得を行うシンプルなラッパークラスを作成しました。

開発環境
$ npm --version
10.9.2
$ node --version
v22.13.1
$ npm list --depth=0
mysql_utils@1.0.0 C:\work\mysql_utils
+-- @types/mysql@2.15.26
`-- mysql2@3.12.0

開発環境の作成

$ npm install mysql2
$ npm install --save-dev @types/mysql

MySQL ユーティリティクラスの作成

クエリ送信をラップするクラスを作成しました。

import * as mysql from "mysql2"

// 今回の解説ではここが本質ではないためハードコーディングしてしまっていますが、本来は .env などで定義するのが適切と思います。
const DB_HOST = "localhost"
const DB_USER = "user"
const DB_PASSWORD = "****"
const DB_NAME = "test"

/**
 * MySQL データベースにアクセスするためのユーティリティクラス
 *
 * @example
 * import {MySqlUtil} from "./mySqlUtil";
 * const db = new MySqlUtil();
 *
 * // クエリを実行する
 * db.query("SELECT * FROM users WHERE id = :id", { id: 1 })
 *
 * // クエリを実行して結果を取得する
 * const result = await db.query("SELECT * FROM users WHERE id = :id", { id: 1 });
 *
 * // 結果をオブジェクトの配列に変換する
 * const users: User[] = Array.isArray(result) ? result.map((row: any) => {
 *    return new User(row.id, row.name, row.email);
 *    }) : [];
 */
export class MySqlUtil {
    // SQL インジェクション対策としてエスケープ処理を行う関数です。今回は mysql2 の escape をラップします。
    private readonly escape: (value: any) => string = mysql.escape;

    // クエリとは別に Values が与えられたときに、 Values をクエリに埋め込むための関数です。
    private readonly queryFormat: (query: string, values: any) => string = (query: string, values: any) => {
        if (!values) {
            return query;
        }
        return query.replace(/:(\w+)/g, (txt, key) => {
            if (values.hasOwnProperty(key)) {
                return this.escape(values[key]);
            }
            return txt;
        });
    }

    // コネクションを取得してコールバックを実行します。
    private connect(callback: (connection: mysql.Connection) => Promise<mysql.QueryResult>): Promise<mysql.QueryResult> {
        return new Promise((resolve, reject) => {
            // コネクションを作成
            const connection = mysql.createConnection({
                host: DB_HOST,
                user: DB_USER,
                password: DB_PASSWORD,
                database: DB_NAME,
            });

            // 接続
            connection.connect((err) => {
                if (err) {
                    console.error("error connecting: " + err.stack);
                    return;
                }

                // クエリフォーマット関数を設定
                connection.config.queryFormat = this.queryFormat;

                // コールバックを実行
                callback(connection).then(result => resolve(result))
                    .catch(err => reject(err))
                    .finally(() => {
                        // 処理が終わったらコネクションを切断
                        connection.end();
                    });
            });
        });
    }

    /**
     * クエリを実行します
     * @param sql クエリ
     * @param values クエリのパラメータ
     * @returns クエリの実行結果
     */
    public query(sql: string, values?: any): Promise<mysql.QueryResult> {
        return this.connect((connection) => {
            return new Promise((resolve, reject) => {
                connection.query(sql, values, (err, result) => {
                    if (err) {
                        reject(err);
                    } else {
                        resolve(result);
                    }
                });
            });
        });
    }
}

解説

            // 接続
            connection.connect((err) => {
                if (err) {
                    console.error("error connecting: " + err.stack);
                    return;
                }

                // クエリフォーマット関数を設定
                connection.config.queryFormat = this.queryFormat;

                // コールバックを実行
                callback(connection).then(result => resolve(result))
                    .catch(err => reject(err))
                    .finally(() => {
                        // 処理が終わったらコネクションを切断
                        connection.end();
                    });
            });

データベースに接続しコールバックを実行します。別で定義したクエリ送信処理をコールバックとして渡すことで接続→クエリ送信→切断の流れが可能になります。

    /**
     * クエリを実行します
     * @param sql クエリ
     * @param values クエリのパラメータ
     * @returns クエリの実行結果
     */
    public query(sql: string, values?: any): Promise<mysql.QueryResult> {
        return this.connect((connection) => {
            return new Promise((resolve, reject) => {
                connection.query(sql, values, (err, result) => {
                    if (err) {
                        reject(err);
                    } else {
                        resolve(result);
                    }
                });
            });
        });
    }

先に作成した connection 関数にクエリ送信の処理を渡してクエリ送信を実行します。この関数のみ公開し、他の mysql2 周りの処理をラップします。

テストの作成

クエリを投げるテストを実行し、動作を確認しました。

import {MySqlUtil} from "../mySqlUtil";

const db = new MySqlUtil();

const testDbUtil = (sql: string) => {
    (async () => {
        return await db.query(sql);
    })().then((result) => {
        console.log(result);
    }).catch((err) => {
        console.error(err);
    });
}

// 存在するテーブルへのアクセス
const sql = "SELECT * FROM test";
testDbUtil(sql);

// 存在しないテーブルへのアクセス
const sqlInvalid = "SELECT * FROM invalid";
testDbUtil(sqlInvalid);

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.