
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);