bhylu.com

Advertisements

Node.js MySQL Prepared Statements

A prepared execution separates a SQL statement from the values it receives. Validate domain input as well; parameterization does not decide which records a caller is authorized to read.

How it works

The statement below selects one kit by name using execute placeholders. The second input contains SQL-looking punctuation, but the driver sends it as a value rather than appending it to query source. Placeholders do not stand in for arbitrary table names, sort expressions or a whole WHERE clause. Those structural choices need a fixed application-authored query or a strict allowlist. The fixture uses only its disposable schema.

Run the code

Run this example locally with Node.js. The exact sources are reviewed on Node 22.15.1 and Node 24.22.1 unless a specialised lab states otherwise. Save the complete source as mysql-parameters.mjs in a separate empty exercises folder and run node mysql-parameters.mjs. There is no Node.js browser runner on Bhylu; the plain JavaScript playground does not provide these filesystem, server or process APIs. In that separate project, install the reviewed packages with npm install --save-exact mysql2@3.14.3. These are teaching dependencies, not changes to the Bhylu website package tree. This database example is reviewed on Node 22.15.1 against its named real database lab. Do not run it against an existing application database. Read the dedicated lab requirements below before execution.

Download the complete source. Ordinary Node scripts need no Apache, PHP or MySQL service. File examples own their temporary folders; network fixtures use loopback and available temporary ports.

Dedicated MySQL lab

Use Oracle MySQL 8.4.8 and mysql2 3.14.3 in a disposable instance with a loopback port other than 3306. The script requires BHYLU_NODE_DB_LAB=YES, BHYLU_MYSQL_PORT, BHYLU_MYSQL_USER and BHYLU_MYSQL_PASSWORD in its environment. Use a dedicated lab account permitted to create and remove only lab schemas on that disposable instance. Each script creates a random bhylu_node_ schema, seeds fictional records and drops that owned schema in finally. The review uses generated private credentials; none are included in the download. XAMPP MariaDB and your normal MySQL data are not the review target.

Example

mysql-parameters.mjs

import mysql from 'mysql2/promise';
import { randomBytes } from 'node:crypto';
const port = Number(process.env.BHYLU_MYSQL_PORT);
if (
  process.env.BHYLU_NODE_DB_LAB !== 'YES' ||
  !Number.isInteger(port) ||
  port < 1024 ||
  port > 65535 ||
  port === 3306 ||
  !process.env.BHYLU_MYSQL_USER ||
  !process.env.BHYLU_MYSQL_PASSWORD
)
  throw new Error('Use the dedicated disposable database lab');
const connection = await mysql.createConnection({
  host: '127.0.0.1',
  port,
  user: process.env.BHYLU_MYSQL_USER,
  password: process.env.BHYLU_MYSQL_PASSWORD,
  multipleStatements: false,
});
const schema = 'bhylu_node_' + randomBytes(8).toString('hex');
let created = false;
try {
  await connection.query('CREATE DATABASE ' + schema);
  created = true;
  await connection.query('USE ' + schema);
  await connection.query(
    'CREATE TABLE kits (id INT PRIMARY KEY, name VARCHAR(40) NOT NULL, available INT NOT NULL CHECK (available >= 0))',
  );
  await connection.execute('INSERT INTO kits VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?)', [
    701,
    'tripod',
    3,
    702,
    'binoculars',
    2,
    703,
    'torch',
    0,
  ]);
  const [good] = await connection.execute(
    'SELECT id FROM kits WHERE name = ? ORDER BY id',
    ['tripod'],
  );
  const [untrusted] = await connection.execute(
    'SELECT id FROM kits WHERE name = ? ORDER BY id',
    ["tripod' OR 1=1 --"],
  );
  console.log(good.map((row) => row.id).join(','));
  console.log(untrusted.length);
} finally {
  try {
    if (created) await connection.query('DROP DATABASE ' + schema);
  } finally {
    await connection.end();
  }
}

Expected result

701
0

The intended name matches one record and the SQL-looking text matches none. That is an actual database execution result, not a source-code pattern check or a promise that every query built elsewhere in the application is safe.

Use the Node.js API reference for the precise version contract.

Advertisements