Пошук уроків, статей та іншого контенту
Виконуватимете SELECT, INSERT, UPDATE і DELETE-запити з Node.js та передаватимете параметри до SQL.
Node.js не виконує SQL-запити самостійно. Для підключення до конкретної бази даних потрібен драйвер. У цьому уроці використаємо пакет mysql2 для роботи з MySQL.
Встановіть пакет у проєкті:
npm init -y
npm install mysql2Також потрібен запущений сервер MySQL і створена база даних. Наприклад, створимо базу node_queries:
CREATE DATABASE node_queries;Далі Node.js підключатиметься до цієї бази та виконуватиме чотири основні операції:
SELECT — отримання даних;
INSERT — додавання даних;
UPDATE — оновлення даних;
DELETE — видалення даних.
Для асинхронної роботи використаємо API mysql2/promise. Він дає змогу застосовувати async і await.
const mysql = require('mysql2/promise');
const connection = await mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'your_password',
database: 'node_queries'
});Замість your_password потрібно вказати пароль користувача MySQL.
Підключення краще закривати після завершення роботи:
await connection.end();Якщо застосунок обробляє багато запитів протягом тривалого часу, зазвичай використовують пул з'єднань. Для прикладів цього уроку достатньо одного з'єднання.
Перед виконанням CRUD-запитів створимо таблицю users:
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
age INT NOT NULL
);У Node.js цей SQL можна виконати так:
await connection.execute(`
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
age INT NOT NULL
)
`);Метод execute() повертає масив із двох елементів:
результат запиту;
службову інформацію про поля або запит.
Тому часто використовують деструктуризацію:
const [result] = await connection.execute(sql);Дані не потрібно вставляти безпосередньо в рядок SQL. Замість цього використовують плейсхолдер ?:
const [rows] = await connection.execute(
'SELECT * FROM users WHERE age >= ?',
[18]
);Значення з масиву [18] передається на місце ?.
Такий підхід має дві переваги:
код простіше читати;
значення правильно екрануються, що захищає від SQL-ін'єкцій.
Кількість параметрів має відповідати кількості плейсхолдерів:
const [rows] = await connection.execute(
'SELECT * FROM users WHERE name = ? AND age = ?',
['Олена', 25]
);Не слід формувати SQL за допомогою конкатенації рядків:
// Небезпечно: дані користувача вставляються безпосередньо в SQL
const sql = `SELECT * FROM users WHERE name = '${name}'`;SELECT отримує записи з таблиці.
const [users] = await connection.execute(
'SELECT id, name, email, age FROM users'
);
console.log(users);Змінна users містить масив об'єктів:
[
{
id: 1,
name: 'Олена',
email: 'olena@example.com',
age: 25
}
]const userId = 1;
const [users] = await connection.execute(
'SELECT id, name, email, age FROM users WHERE id = ?',
[userId]
);
if (users.length === 0) {
console.log('Користувача не знайдено');
} else {
console.log('Користувач:', users[0]);
}Навіть якщо очікується один запис, результат SELECT усе одно є масивом. Саме тому для отримання першого запису використовують users[0].
const minimumAge = 18;
const [adults] = await connection.execute(
'SELECT id, name, email, age FROM users WHERE age >= ? ORDER BY age DESC',
[minimumAge]
);
console.log(adults);Параметри можна використовувати в умовах WHERE, але не для назв таблиць або стовпців. Наприклад, WHERE id = ? є правильним використанням параметра.
INSERT додає новий запис до таблиці.
const [result] = await connection.execute(
'INSERT INTO users (name, email, age) VALUES (?, ?, ?)',
['Олена', 'olena@example.com', 25]
);
console.log('Створено запис з id:', result.insertId);Для запиту INSERT важливими властивостями результату є:
insertId — ідентифікатор нового запису;
affectedRows — кількість доданих записів.
Дані можна спочатку зберегти в окремі змінні:
const name = 'Андрій';
const email = 'andrii@example.com';
const age = 30;
const [result] = await connection.execute(
'INSERT INTO users (name, email, age) VALUES (?, ?, ?)',
[name, email, age]
);
console.log({
id: result.insertId,
affectedRows: result.affectedRows
});Якщо стовпець email уже містить таке значення, MySQL поверне помилку через обмеження UNIQUE.
UPDATE змінює дані наявних записів.
const newAge = 31;
const userId = 2;
const [result] = await connection.execute(
'UPDATE users SET age = ? WHERE id = ?',
[newAge, userId]
);
console.log('Оновлено записів:', result.affectedRows);У запиті є два параметри:
нове значення age;
ідентифікатор запису в умові WHERE.
Уважно перевіряйте наявність WHERE. Запит без WHERE змінить значення в усіх записах:
-- Змінить вік кожного користувача
UPDATE users SET age = 18;Також можна оновити кілька стовпців одночасно:
const [result] = await connection.execute(
'UPDATE users SET name = ?, age = ? WHERE id = ?',
['Андрій Коваль', 32, 2]
);
console.log('Оновлено записів:', result.affectedRows);Якщо запису з таким id немає, affectedRows буде 0.
DELETE видаляє записи з таблиці.
const userId = 2;
const [result] = await connection.execute(
'DELETE FROM users WHERE id = ?',
[userId]
);
console.log('Видалено записів:', result.affectedRows);Як і для UPDATE, умова WHERE дуже важлива. Без неї буде видалено всі записи:
-- Видалить усі записи з таблиці
DELETE FROM users;Перед видаленням конкретного запису можна перевірити, чи він існує:
const userId = 2;
const [users] = await connection.execute(
'SELECT id FROM users WHERE id = ?',
[userId]
);
if (users.length === 0) {
console.log('Користувача не знайдено');
} else {
await connection.execute(
'DELETE FROM users WHERE id = ?',
[userId]
);
console.log('Користувача видалено');
}Нижче наведено повний файл app.js, який:
підключається до MySQL;
створює таблицю;
додає користувача;
отримує його через SELECT;
змінює його через UPDATE;
видаляє його через DELETE;
закриває з'єднання.
const mysql = require('mysql2/promise');
async function main() {
const connection = await mysql.createConnection({
host: process.env.DB_HOST || 'localhost',
port: Number(process.env.DB_PORT || 3306),
user: process.env.DB_USER || 'root',
password: process.env.DB_PASSWORD || 'your_password',
database: process.env.DB_NAME || 'node_queries'
});
try {
await connection.execute(`
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
age INT NOT NULL
)
`);
const email = `user_${Date.now()}@example.com`;
// Додаємо нового користувача
const [insertResult] = await connection.execute(
'INSERT INTO users (name, email, age) VALUES (?, ?, ?)',
['Олена', email, 25]
);
const userId = insertResult.insertId;
console.log('Створено користувача з id:', userId);
// Отримуємо користувача за id
const [selectedUsers] = await connection.execute(
'SELECT id, name, email, age FROM users WHERE id = ?',
[userId]
);
console.log('Отриманий користувач:', selectedUsers[0]);
// Оновлюємо вік користувача
const [updateResult] = await connection.execute(
'UPDATE users SET age = ? WHERE id = ?',
[26, userId]
);
console.log('Оновлено записів:', updateResult.affectedRows);
// Перевіряємо оновлені дані
const [updatedUsers] = await connection.execute(
'SELECT id, name, email, age FROM users WHERE id = ?',
[userId]
);
console.log('Оновлений користувач:', updatedUsers[0]);
// Видаляємо користувача
const [deleteResult] = await connection.execute(
'DELETE FROM users WHERE id = ?',
[userId]
);
console.log('Видалено записів:', deleteResult.affectedRows);
} finally {
// Закриваємо з'єднання навіть у разі помилки
await connection.end();
}
}
main().catch((error) => {
console.error('Помилка під час роботи з базою даних:', error.message);
});Запустити програму можна командою:
node app.jsПароль та інші параметри підключення можна передати через змінні середовища:
DB_PASSWORD=secret DB_NAME=node_queries node app.jsSQL-запит може завершитися помилкою. Наприклад:
база даних недоступна;
неправильні дані підключення;
порушено обмеження UNIQUE;
передано неправильний тип даних;
SQL містить синтаксичну помилку.
Для обробки таких помилок використовують try...catch:
try {
const [result] = await connection.execute(
'INSERT INTO users (name, email, age) VALUES (?, ?, ?)',
['Олена', 'olena@example.com', 25]
);
console.log('Новий id:', result.insertId);
} catch (error) {
console.error('Не вдалося додати користувача:', error.message);
}У повному прикладі try...finally використовується для того, щоб з'єднання закрилося незалежно від результату виконання запитів. Обробник catch для функції main виводить помилку, яка не була оброблена всередині функції.
Порядок значень у масиві має відповідати порядку плейсхолдерів у SQL:
await connection.execute(
'UPDATE users SET name = ?, age = ? WHERE id = ?',
['Олена', 26, 1]
);Тут:
'Олена' передається для name;
26 — для age;
1 — для id.
Перед виконанням таких запитів перевіряйте, чи справді потрібна операція над усіма записами:
// Оновить усіх користувачів
await connection.execute(
'UPDATE users SET age = ?',
[18]
);Для зміни одного запису потрібна умова:
await connection.execute(
'UPDATE users SET age = ? WHERE id = ?',
[18, userId]
);Не вставляйте значення користувача безпосередньо в SQL-рядок:
// Небезпечно
const sql = `DELETE FROM users WHERE id = ${userId}`;Використовуйте параметр:
// Безпечніше та правильно
await connection.execute(
'DELETE FROM users WHERE id = ?',
[userId]
);Результат SELECT є масивом, навіть якщо запит повертає один запис:
const [users] = await connection.execute(
'SELECT * FROM users WHERE id = ?',
[userId]
);
const user = users[0];Якщо запису немає, users[0] матиме значення undefined, тому перед використанням варто перевірити довжину масиву.
Для роботи Node.js з MySQL можна використовувати пакет mysql2.
connection.execute(sql, values) виконує параметризований SQL-запит.
SELECT повертає масив рядків.
INSERT повертає insertId нового запису.
UPDATE і DELETE повертають affectedRows.
Параметри передаються через плейсхолдери ? і масив значень.
Значення не слід вставляти в SQL за допомогою конкатенації рядків.
Для UPDATE і DELETE потрібно уважно перевіряти умову WHERE.
З'єднання з базою даних потрібно закривати після завершення роботи.