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
[email protected] C:\work\mysql_utils
+-- @types/[email protected]
`-- [email protected]

開発環境の作成

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

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

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

import * as mysql from "mysql2"

const DB_HOST = "localhost"
const DB_USER = "user"
const DB_PASSWORD = "****"
const DB_NAME = "test"

class MySQLUtil {
    // エスケープ関数
    private readonly escape: (value: any) => string = mysql.escape;
    // クエリフォーマット関数
    private readonly queryFormat: (query: string, values: any) => string = (query: string, values: any) => {
        // values がないならそのまま query を返してよい
        if (!values) {
            return query;
        }

        // values があるなら query に values を展開して返す
        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();
                    });
            });
        });
    }

    // クエリを実行する
    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);
                    }
                });
            });
        });
    }
}

export default new MySQLUtil();

解説

    // エスケープ関数
    private readonly escape: (value: any) => string = mysql.escape;

MySQL2 のエスケープ関数をラップしています。クエリフォーマット関数の中で使用します。

    // クエリフォーマット関数
    private readonly queryFormat: (query: string, values: any) => string = (query: string, values: any) => {
        // values がないならそのまま query を返してよい
        if (!values) {
            return query;
        }

        // values があるなら query に values を展開して返す
        return query.replace(/:(\w+)/g, (txt, key) => {
            if (values.hasOwnProperty(key)) {
                return this.escape(values[key]);
            }
            return txt;
        });
    }

クエリのフォーマット設定を定義しています。クエリの平文とは別に Values が与えられたときにクエリに埋め込むための関数です。

            // 接続
            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();
                    });
            });

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

    // クエリを実行する
    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 関数にクエリ送信の処理を渡してクエリ送信を実行します。この関数のみ公開し、データベース周りの処理をラップします。

テストの作成

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

import db from './db';

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 SQL2 = "SELECT * FROM invalid";
testDbUtil(SQL2);

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.