Статьи

Использование MySQL с Node.js и JavaScript-клиентом mysql

Базы данных NoSQL довольно популярны среди разработчиков Node, причем MongoDB («M» в стеке MEAN) возглавляет пакет. Однако при запуске нового проекта Node вы не должны просто принимать Mongo в качестве выбора по умолчанию. Скорее, тип базы данных, который вы выбираете, должен зависеть от требований вашего проекта. Если, например, вам нужно динамическое создание таблиц или вставки в реальном времени, то решение NoSQL — это путь. Если ваш проект имеет дело со сложными запросами и транзакциями, с другой стороны, база данных SQL имеет гораздо больший смысл.

В этом уроке мы рассмотрим начало работы с модулем mysql — клиентом Node.js для MySQL, написанным на JavaScript. Я объясню, как использовать модуль для подключения к базе данных MySQL и выполнения обычных операций CRUD, прежде чем рассматривать хранимые процедуры и избегать ввода данных пользователем.

Эта популярная статья была обновлена ​​в 2020 году, чтобы отразить современные практики использования MySQL с Node.js.

Быстрый старт: как использовать MySQL в Node

Если вы прибыли сюда в поисках быстрого способа начать работу с MySQL в Node, мы поможем вам!

Вот как использовать MySQL в Node за пять простых шагов:

  1. Создайте новый проект: mkdir mysql-test && cd mysql-test .
  2. Создайте файл package.json : npm init -y .
  3. Установите модуль mysql: npm install mysql .
  4. Создайте файл app.js и скопируйте его во фрагмент app.js ниже (при необходимости app.js местозаполнители).
  5. Запустите файл: node app.js Обратите внимание на сообщение «Подключено!».
 const mysql = require('mysql'); const connection = mysql.createConnection({ host: 'localhost', user: 'user', password: 'password', database: 'database name' }); connection.connect((err) => { if (err) throw err; console.log('Connected!'); }); 

Установка модуля mysql

Теперь давайте подробнее рассмотрим каждый из этих шагов.

 mkdir mysql-test cd mysql-test npm init -y npm install mysql 

Прежде всего мы используем командную строку для создания нового каталога и перехода к нему. Затем мы создаем файл package.json с помощью команды npm init -y . Флаг -y означает, что npm будет использовать значения по умолчанию, не проходя интерактивный процесс.

Этот шаг также предполагает, что в вашей системе установлены Node и npm. Если это не так, ознакомьтесь с этой статьей SitePoint, чтобы узнать, как это сделать: Установите несколько версий Node.js с помощью nvm .

После этого мы устанавливаем модуль mysql из npm и сохраняем его как зависимость проекта. Зависимости проекта (в отличие от devDependencies) — это пакеты, необходимые для запуска приложения. Вы можете прочитать больше о различиях между ними здесь .

Если вам нужна дополнительная помощь по использованию npm, обязательно ознакомьтесь с этим руководством или спросите на наших форумах .

Начиная

Прежде чем мы перейдем к подключению к базе данных, важно, чтобы на вашем компьютере был установлен и настроен MySQL. Если это не так, пожалуйста, обратитесь к инструкции по установке на их домашней странице .

Следующее, что нам нужно сделать, — это создать базу данных и таблицу базы данных для работы. Вы можете сделать это с помощью
графический интерфейс, например Adminer , или с помощью командной строки. В этой статье я буду использовать базу данных с именем sitepoint и таблицу с именем authors . Вот дамп базы данных, так что вы можете быстро приступить к работе, если хотите следовать:

 CREATE DATABASE sitepoint CHARACTER SET utf8 COLLATE utf8_general_ci; USE sitepoint; CREATE TABLE authors ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(50), city varchar(50), PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=5 ; INSERT INTO authors (id, name, city) VALUES (1, 'Michaela Lehr', 'Berlin'), (2, 'Michael Wanyoike', 'Nairobi'), (3, 'James Hibbard', 'Munich'), (4, 'Karolina Gawron', 'Wrocław'); 

Использование MySQL с Node.js и JavaScript-клиентом mysql

Подключение к базе данных

Теперь давайте создадим файл с именем app.js в нашем app.js mysql-test и посмотрим, как подключиться к MySQL из Node.js.

 const mysql = require('mysql'); // First you need to create a connection to the database // Be sure to replace 'user' and 'password' with the correct values const con = mysql.createConnection({ host: 'localhost', user: 'user', password: 'password', }); con.connect((err) => { if(err){ console.log('Error connecting to Db'); return; } console.log('Connection established'); }); con.end((err) => { // The connection is terminated gracefully // Ensures all remaining queries are executed // Then sends a quit packet to the MySQL server. }); 

Теперь откройте терминал и войдите в node app.js Как только соединение будет успешно установлено, вы увидите сообщение «Соединение установлено» в консоли. Если что-то идет не так (например, вы вводите неправильный пароль), вызывается обратный вызов, которому передается экземпляр объекта JavaScript Error ( err ). Попробуйте войти в консоль, чтобы увидеть, какую дополнительную полезную информацию она содержит.

Использование nodemon для просмотра файлов на предмет изменений

Запуск node app.js вручную каждый раз, когда мы вносим изменения в наш код, будет немного утомительным, поэтому давайте автоматизируем это. Эта часть не обязательна, чтобы следовать вместе с остальной частью учебника, но, безусловно, сэкономит вам несколько нажатий клавиш.

Давайте начнем с установки пакета nodemon . Это инструмент, который автоматически перезапускает приложение Node при обнаружении изменений файла в каталоге:

 npm install --save-dev nodemon 

Теперь запустите ./node_modules/.bin/nodemon app.js и внесите изменения в app.js nodemon должен обнаружить изменение и перезапустить приложение.

Примечание: мы запускаем nodemon прямо из папки node_modules . Вы также можете установить его глобально или создать скрипт npm, чтобы запустить его.

Выполнение запросов

чтение

Теперь, когда вы знаете, как установить соединение с базой данных MySQL из Node.js, давайте посмотрим, как выполнять SQL-запросы. Начнем с указания имени базы данных ( sitepoint ) в команде createConnection :

 const con = mysql.createConnection({ host: 'localhost', user: 'user', password: 'password', database: 'sitepoint' }); 

Как только соединение будет установлено, мы будем использовать переменную con для выполнения запроса к authors таблицы базы данных:

 con.query('SELECT * FROM authors', (err,rows) => { if(err) throw err; console.log('Data received from Db:'); console.log(rows); }); 

Когда вы запустите app.js (либо с помощью nodemon, либо введя node app.js в свой терминал), вы сможете увидеть данные, возвращенные из базы данных, записанной в терминал:

 [ RowDataPacket { id: 1, name: 'Michaela Lehr', city: 'Berlin' }, RowDataPacket { id: 2, name: 'Michael Wanyoike', city: 'Nairobi' }, RowDataPacket { id: 3, name: 'James Hibbard', city: 'Munich' }, RowDataPacket { id: 4, name: 'Karolina Gawron', city: 'Wrocław' } ] 

Данные, возвращаемые из базы данных MySQL, можно анализировать, просто зацикливая объект rows .

 rows.forEach( (row) => { console.log(`${row.name} lives in ${row.city}`); }); 

Это дает вам следующее:

 Michaela Lehr lives in Berlin Michael Wanyoike lives in Nairobi James Hibbard lives in Munich Karolina Gawron lives in Wrocław 

Создание

Вы можете выполнить запрос вставки к базе данных, например так:

 const author = { name: 'Craig Buckler', city: 'Exmouth' }; con.query('INSERT INTO authors SET ?', author, (err, res) => { if(err) throw err; console.log('Last insert ID:', res.insertId); }); 

Обратите внимание, как мы можем получить идентификатор вставленной записи, используя параметр обратного вызова.

обновление

Точно так же, при выполнении запроса на обновление, количество затронутых строк может быть получено с помощью result.affectedRows :

 con.query( 'UPDATE authors SET city = ? Where ID = ?', ['Leipzig', 3], (err, result) => { if (err) throw err; console.log(`Changed ${result.changedRows} row(s)`); } ); 

разрушающий

То же самое относится и к запросу на удаление:

 con.query( 'DELETE FROM authors WHERE id = ?', [5], (err, result) => { if (err) throw err; console.log(`Deleted ${result.affectedRows} row(s)`); } ); 

Расширенное использование

Я хотел бы закончить, посмотрев на то, как модуль mysql обрабатывает хранимые процедуры и экранирование пользовательского ввода.

Хранимые процедуры

Проще говоря, хранимая процедура представляет собой подготовленный код SQL, который можно сохранить в базе данных, чтобы его можно было легко использовать повторно. Если вам требуется освежить хранимые процедуры, ознакомьтесь с этим руководством .

Давайте создадим хранимую процедуру для нашей базы данных sitepoint которая извлекает все данные об авторе. Мы назовем это sp_get_authors . Для этого вам понадобится какой-то интерфейс к базе данных. Я использую Adminer . Выполните следующий запрос к базе данных sitepoint , чтобы sitepoint , что у вашего пользователя есть права администратора на сервере MySQL:

 DELIMITER $$ CREATE PROCEDURE `sp_get_authors`() BEGIN SELECT id, name, city FROM authors; END $$ 

Это создаст и сохранит процедуру в базе данных information_schema в таблице ROUTINES .

Создание хранимой процедуры в Adminer

Примечание: если синтаксис разделителя выглядит для вас странно, это объясняется здесь .

Затем установите соединение и используйте объект соединения для вызова хранимой процедуры, как показано:

 con.query('CALL sp_get_authors()',function(err, rows){ if (err) throw err; console.log('Data received from Db:'); console.log(rows); }); 

Сохраните изменения и запустите файл. После его выполнения вы сможете просматривать данные, возвращаемые из базы данных:

 [ [ RowDataPacket { id: 1, name: 'Michaela Lehr', city: 'Berlin' }, RowDataPacket { id: 2, name: 'Michael Wanyoike', city: 'Nairobi' }, RowDataPacket { id: 3, name: 'James Hibbard', city: 'Leipzig' }, RowDataPacket { id: 4, name: 'Karolina Gawron', city: 'Wrocław' }, OkPacket { fieldCount: 0, affectedRows: 0, insertId: 0, serverStatus: 34, warningCount: 0, message: '', protocol41: true, changedRows: 0 } ] 

Наряду с данными, он возвращает некоторую дополнительную информацию, такую ​​как количество затронутых строк, insertId и т. Д. Вам нужно insertId 0-й индекс возвращаемых данных, чтобы отделить детали сотрудника от остальной информации:

 rows[0].forEach( (row) => { console.log(`${row.name} lives in ${row.city}`); }); 

Это дает вам следующее:

 Michaela Lehr lives in Berlin Michael Wanyoike lives in Nairobi James Hibbard lives in Leipzig Karolina Gawron lives in Wrocław 

Теперь давайте рассмотрим хранимую процедуру, которая требует входной параметр:

 DELIMITER $$ CREATE PROCEDURE `sp_get_author_details`( in author_id int ) BEGIN SELECT name, city FROM authors where id = author_id; END $$ 

Мы можем передать входной параметр во время вызова хранимой процедуры:

 con.query('CALL sp_get_author_details(1)', (err, rows) => { if(err) throw err; console.log('Data received from Db:\n'); console.log(rows[0]); }); 

Это дает вам следующее:

 [ RowDataPacket { name: 'Michaela Lehr', city: 'Berlin' } ] 

В большинстве случаев, когда мы пытаемся вставить запись в базу данных, нам нужно, чтобы последний вставленный идентификатор был возвращен в качестве параметра out. Рассмотрим следующую хранимую процедуру вставки с параметром out:

 DELIMITER $$ CREATE PROCEDURE `sp_insert_author`( out author_id int, in author_name varchar(25), in author_city varchar(25) ) BEGIN insert into authors(name, city) values(author_name, author_city); set author_id = LAST_INSERT_ID(); END $$ 

Чтобы сделать вызов процедуры с параметром out, сначала нам нужно включить несколько вызовов при создании соединения. Итак, измените соединение, установив для выполнения нескольких операторов значение true :

 const con = mysql.createConnection({ host: 'localhost', user: 'user', password: 'password', database: 'sitepoint', multipleStatements: true }); 

Затем, при вызове процедуры, установите параметр out и передайте его:

 con.query( "SET @author_id = 0; CALL sp_insert_author(@author_id, 'Craig Buckler', 'Exmouth'); SELECT @author_id", (err, rows) => { if (err) throw err; console.log('Data received from Db:\n'); console.log(rows); } ); 

Как видно из приведенного выше кода, мы установили параметр @author_id out и передали его во время вызова хранимой процедуры. Как только вызов сделан, нам нужно выбрать параметр out для доступа к возвращенному идентификатору.

Запустите app.js При успешном выполнении вы должны увидеть выбранный параметр наряду с другой информацией. rows[2] должны дать вам доступ к выбранному параметру:

  [ RowDataPacket { '@author_id': 6 } ] ] 

Примечание. Чтобы удалить хранимую процедуру, необходимо выполнить команду DROP PROCEDURE <procedure-name>; против базы данных, для которой вы ее создали.

Экранирование ввода пользователя

Чтобы избежать атак SQL-инъекций, вы всегда должны избегать любых данных, которые вы получаете от пользователей, прежде чем использовать их в запросе SQL. Давайте продемонстрируем, почему:

 const userSubmittedVariable = '1'; con.query( `SELECT * FROM authors WHERE id = ${userSubmittedVariable}`, (err, rows) => { if(err) throw err; console.log(rows); } ); 

Это кажется достаточно безопасным и даже возвращает правильный результат:

  { id: 1, name: 'Michaela Lehr', city: 'Berlin' } 

Однако попробуйте изменить userSubmittedVariable следующим образом:

 const userSubmittedVariable = '1 OR 1=1'; 

У нас внезапно появляется доступ ко всему набору данных. Теперь измените это на это:

 const userSubmittedVariable = '1; DROP TABLE authors'; 

Теперь у нас проблемы!

Хорошей новостью является то, что помощь под рукой. Вам просто нужно использовать метод mysql.escape :

 con.query( `SELECT * FROM authors WHERE id = ${mysql.escape(userSubmittedVariable)}`, (err, rows) => { if(err) throw err; console.log(rows); } ); 

Вы также можете использовать заполнитель вопросительного знака, как мы это делали в примерах в начале статьи:

 con.query( 'SELECT * FROM authors WHERE id = ?', [userSubmittedVariable], (err, rows) => { if(err) throw err; console.log(rows); } ); 

Почему бы просто не использовать ORM?

Прежде чем мы перейдем к плюсам и минусам этого подхода, давайте на секунду рассмотрим, что такое ORM. Следующее взято из ответа о переполнении стека :

Объектно-реляционное отображение (ORM) — это метод, который позволяет запрашивать и манипулировать данными из базы данных с использованием объектно-ориентированной парадигмы. Говоря об ORM, большинство людей ссылаются на библиотеку, которая реализует технику объектно-реляционного отображения, отсюда и фраза «ORM».

Таким образом, это означает, что вы пишете логику своей базы данных на языке предметной области ORM, в отличие от ванильного подхода, который мы использовали до сих пор. Чтобы дать вам представление о том, как это может выглядеть, вот пример использования Sequelize , который запрашивает базу данных для всех авторов и записывает их в консоль:

 const sequelize = new Sequelize('sitepoint', 'user', 'password', { host: 'localhost', dialect: 'mysql' }); const Author = sequelize.define('author', { name: { type: Sequelize.STRING, }, city: { type: Sequelize.STRING }, }, { timestamps: false }); Author.findAll().then(authors => { console.log("All authors:", JSON.stringify(authors, null, 4)); }); 

То, имеет ли смысл использовать ORM для вас, будет во многом зависеть от того, над чем вы работаете и с кем. С одной стороны, ORMS, как правило, делают разработчиков более продуктивными, частично абстрагируя большую часть SQL, так что не всем в команде нужно знать, как писать суперэффективные запросы к базе данных. Также легко перейти на другое программное обеспечение базы данных, потому что вы развиваетесь до абстракции.

С другой стороны, однако, можно написать какой-то действительно грязный и неэффективный SQL в результате непонимания того, как ORM делает то, что делает. Производительность также является проблемой, поскольку гораздо проще оптимизировать запросы, которые не должны проходить через ORM.

Какой бы путь вы ни выбрали, зависит от вас, но если это решение, которое вы принимаете, проверьте этот поток переполнения стека: Почему вы должны использовать ORM? , Также проверьте этот пост на SitePoint: 3 JavaScript ORM, которые вы, возможно, не знаете .

Вывод

В этом руководстве мы установили клиент mysql для Node.js и настроили его для подключения к базе данных. Мы также увидели, как выполнять операции CRUD, работать с подготовленными операторами и избегать ввода данных пользователем, чтобы смягчить атаки с использованием SQL-инъекций. И тем не менее, мы только поцарапали поверхность того, что предлагает клиент MySQL. Для более подробной информации рекомендую ознакомиться с официальной документацией .

И имейте в виду, что модуль mysql — не единственное шоу в городе. Есть и другие варианты, такие как популярный узел-mysql2 .