Koneksi Raw SQL Pool & Normalisasi Parameter
Lapisan eksekusi query database langsung menggunakan connection pool mysql2/promise tanpa ORM, dilengkapi fungsi normalisasi tipe parameter otomatis.
Membaca sekitar 2 menit
Lapisan eksekusi query database langsung menggunakan connection pool mysql2/promise tanpa ORM, dilengkapi fungsi normalisasi tipe parameter otomatis.
Seluruh endpoint API memanggil helper query() dan queryOne() yang membungkus pool.execute() serta menormalisasi parameter angka dari URL sebelum dikirim ke prepared statement MySQL.
Tanpa normalisasi tipe parameter, prepared statement MySQL kerap gagal saat menerima nilai paginasi (LIMIT dan OFFSET) yang terbaca sebagai string dari URL query params.
Masalahnya dulu
Banyak laporan di MyPocket membutuhkan query agregasi yang cukup spesifik—seperti menggabungkan 5 pemasukan dan 5 pengeluaran terakhir dengan UNION ALL, atau membentuk deret 6 bulan ke belakang menggunakan derived table di SQL. Menggunakan ORM sering kali membuat query semacam ini berbelit-belit atau justru memicu N+1 query. Karena itu, project ini memilih menulis SQL murni di atas mysql2/promise.
Mengapa butuh normalisasi parameter sebelum pool.execute()
Saat menggunakan prepared statements dengan pool.execute(sql, params) di mysql2, driver mengirimkan tipe parameter secara ketat ke MySQL. Masalah klasik muncul ketika parameter angka yang berasal dari URL query string (seperti halaman, batas baris, atau tahun) dikirim sebagai string ke klausa yang mengharapkan integer murni. Untuk mengatasinya di satu pintu, modul database menyertakan fungsi normalisasi yang merapikan seluruh parameter sebelum dieksekusi.
Menulis SQL langsung memberi kendali penuh atas performa agregasi, asalkan pintu masuk parameternya dijaga tetap konsisten.
Implementasi di ngodingan ini
Seluruh komunikasi database di aplikasi ini melewati helper bertipe generik query<T> dan queryOne<T> yang membungkus connection pool:
import mysql from "mysql2/promise";
const pool = mysql.createPool({
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT) || 3306,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
waitForConnections: true,
connectionLimit: 10,
timezone: "+07:00",
dateStrings: true,
});
export async function query<T = any>(sql: string, params?: any[]): Promise<T[]> {
const [rows] = await pool.execute(sql, normalizeParams(params));
return rows as T[];
}
export async function queryOne<T = any>(sql: string, params?: any[]): Promise<T | null> {
const rows = await query<T>(sql, params);
return rows[0] ?? null;
}Jebakan dan detail yang harus dijaga
- Mengaktifkan opsi dateStrings: true dan timezone: '+07:00' pada konfigurasi pool. Tanpa ini, kolom DATE di MySQL akan diubah otomatis oleh driver menjadi objek Date JavaScript dalam zona waktu UTC yang sering menggeser tanggal transaksi mundur satu hari.
- Memastikan nilai desimal (seperti jumlah unit investasi pecahan) tetap dipertahankan presisinya saat melewati lapisan parameter sebelum disimpan ke kolom DECIMAL di database.
- Mengeksekusi query independen secara paralel dengan Promise.all. Contohnya pada endpoint dashboard, sembilan query agregasi (saldo total, pemasukan bulan ini, bulan lalu, transaksi terbaru, grafik 6 bulan, hingga portofolio investasi) dijalankan serentak dalam satu round-trip konkuren.