Catégories
Astuces et Design

Utilisation de MySQL avec Node.js et le client JavaScript mysql – SitePoint

Les bases de données NoSQL sont plutôt populaires parmi les développeurs de nœuds, avec MongoDB (le «M» dans la pile MEAN) en tête du peloton. Cependant, lorsque vous démarrez un nouveau projet Node, vous ne devez pas simplement accepter Mongo comme choix par défaut. Le type de base de données que vous choisissez doit plutôt dépendre des exigences de votre projet. Si, par exemple, vous avez besoin de création de table dynamique ou d'insertions en temps réel, alors une solution NoSQL est la solution. Si votre projet traite de requêtes et de transactions complexes, en revanche, une base de données SQL est beaucoup plus logique.

Dans ce tutoriel, nous allons voir comment démarrer avec le module mysql – un client Node.js pour MySQL, écrit en JavaScript. Je vais vous expliquer comment utiliser le module pour vous connecter à une base de données MySQL et effectuer les opérations CRUD habituelles, avant d'examiner les procédures stockées et d'échapper aux entrées utilisateur.

Cet article populaire a été mis à jour en 2020 pour refléter les pratiques actuelles d'utilisation de MySQL avec Node.js. Pour en savoir plus sur MySQL, lisez Jump Start MySQL.

Démarrage rapide: Comment utiliser MySQL dans le nœud

Si vous êtes arrivé ici à la recherche d'un moyen rapide de démarrer avec MySQL dans Node, nous avons ce qu'il vous faut!

Voici comment utiliser MySQL dans Node en cinq étapes simples:

  1. Créez un nouveau projet: mkdir mysql-test && cd mysql-test.
  2. Créer un package.json fichier: npm init -y.
  3. Installez le module mysql: npm install mysql.
  4. Créé un app.js fichier et copie dans l'extrait ci-dessous (en modifiant les espaces réservés selon le cas).
  5. Exécutez le fichier: node app.js. Observez un «Connecté!» message.
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!');
});

Installer le module mysql

Voyons maintenant de plus près chacune de ces étapes.

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

Tout d'abord, nous utilisons la ligne de commande pour créer un nouveau répertoire et y accéder. Ensuite, nous créons un package.json fichier à l'aide de la commande npm init -y. le -y flag signifie que npm utilisera les valeurs par défaut sans passer par un processus interactif.

Cette étape suppose également que Node et npm sont installés sur votre système. Si ce n'est pas le cas, consultez cet article SitePoint pour savoir comment procéder: Installez plusieurs versions de Node.js à l'aide de nvm.

Après cela, nous installons le module mysql à partir de npm et l'enregistrons en tant que dépendance de projet. Les dépendances du projet (par opposition à devDependencies) sont les packages requis pour l'exécution de l'application. Vous pouvez en savoir plus sur les différences entre les deux ici.

Si vous avez besoin d'aide supplémentaire pour utiliser npm, assurez-vous de consulter ce guide ou de demander dans nos forums.

Commencer

Avant de nous connecter à une base de données, il est important que MySQL soit installé et configuré sur votre machine. Si ce n'est pas le cas, veuillez consulter les instructions d'installation sur leur page d'accueil.

La prochaine chose que nous devons faire est de créer une base de données et une table de base de données avec lesquelles travailler. Vous pouvez le faire en utilisant un
interface graphique, comme Adminer, ou en utilisant la ligne de commande. Pour cet article, j'utiliserai une base de données appelée sitepoint et une table appelée authors. Voici un vidage de la base de données, afin que vous puissiez être rapidement opérationnel si vous souhaitez suivre:

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

Utiliser MySQL avec Node.js et le client JavaScript mysql

Connexion à la base de données

Maintenant, créons un fichier appelé app.js dans notre mysql-test répertoire et voir comment se connecter à MySQL à partir de 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.
});

Ouvrez maintenant un terminal et entrez node app.js. Une fois la connexion établie avec succès, vous devriez pouvoir voir le message «Connexion établie» dans la console. Si quelque chose ne va pas (par exemple, vous entrez le mauvais mot de passe), un rappel est déclenché, auquel est transmise une instance de l'objet Erreur JavaScript (err). Essayez de vous connecter à la console pour voir les informations supplémentaires utiles qu'elle contient.

Utiliser nodemon pour surveiller les fichiers pour les changements

Fonctionnement node app.js à la main chaque fois que nous apporterons une modification à notre code, cela deviendra un peu fastidieux, alors automatisons cela. Cette partie n'est pas nécessaire de suivre le reste du didacticiel, mais vous permettra certainement d'économiser quelques touches.

Commençons par installer le package nodemon. Il s'agit d'un outil qui redémarre automatiquement une application Node lorsque des modifications de fichier dans un répertoire sont détectées:

npm install --save-dev nodemon

Maintenant, lancez ./node_modules/.bin/nodemon app.js et modifiez app.js. nodemon devrait détecter le changement et redémarrer l'application.

Remarque: nous exécutons nodemon directement depuis le node_modules dossier. Vous pouvez également l'installer globalement ou créer un script npm pour le lancer.

Exécution de requêtes

En train de lire

Maintenant que vous savez comment établir une connexion à une base de données MySQL à partir de Node.js, voyons comment exécuter des requêtes SQL. Commençons par spécifier le nom de la base de données (sitepoint) dans le createConnection commander:

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

Une fois la connexion établie, nous utiliserons le con variable pour exécuter une requête sur la table de base de données authors:

con.query('SELECT * FROM authors', (err,rows) => {
  if(err) throw err;

  console.log('Data received from Db:');
  console.log(rows);
});

Quand tu cours app.js (en utilisant nodemon ou en tapant node app.js dans votre terminal), vous devriez pouvoir voir les données renvoyées par la base de données enregistrée sur le terminal:

( 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' } )

Les données renvoyées par la base de données MySQL peuvent être analysées en faisant simplement une boucle sur le rows objet.

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

Cela vous donne les éléments suivants:

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

Créer

Vous pouvez exécuter une requête d'insertion sur une base de données, comme ceci:

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

Notez comment nous pouvons obtenir l'ID de l'enregistrement inséré à l'aide du paramètre de rappel.

Mise à jour

De même, lors de l'exécution d'une requête de mise à jour, le nombre de lignes affectées peut être récupéré à l'aide de 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)`);
  }
);

Détruire

La même chose vaut pour une requête de suppression:

con.query(
  'DELETE FROM authors WHERE id = ?', (5), (err, result) => {
    if (err) throw err;

    console.log(`Deleted ${result.affectedRows} row(s)`);
  }
);

Utilisation avancée

Je voudrais terminer en regardant comment le module mysql gère les procédures stockées et l'échappement des entrées utilisateur.

Procédures stockées

En termes simples, une procédure stockée est un code SQL préparé que vous pouvez enregistrer dans une base de données, afin qu'il puisse être facilement réutilisé. Si vous avez besoin d'un rappel sur les procédures stockées, consultez ce didacticiel.

Créons une procédure stockée pour notre sitepoint base de données qui récupère tous les détails de l'auteur. Nous l'appellerons sp_get_authors. Pour ce faire, vous aurez besoin d'une sorte d'interface avec la base de données. J'utilise Adminer. Exécutez la requête suivante sur le sitepoint base de données, garantissant que votre utilisateur dispose des droits d'administrateur sur le serveur MySQL:

DELIMITER $$

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

Cela va créer et stocker la procédure dans le information_schema base de données dans le ROUTINES table.

Création d'une procédure stockée dans Adminer

Remarque: si la syntaxe du délimiteur vous semble étrange, elle est expliquée ici.

Ensuite, établissez une connexion et utilisez l'objet de connexion pour appeler la procédure stockée comme indiqué:

con.query('CALL sp_get_authors()',function(err, rows){
  if (err) throw err;

  console.log('Data received from Db:');
  console.log(rows);
});

Enregistrez les modifications et exécutez le fichier. Une fois qu'il est exécuté, vous devriez pouvoir visualiser les données renvoyées par la base de données:

( ( 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 } )

Avec les données, il renvoie des informations supplémentaires, telles que le nombre de lignes concernées, insertId etc. Vous devez parcourir le 0e index des données renvoyées pour séparer les détails de l'employé du reste des informations:

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

Cela vous donne les éléments suivants:

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

Examinons maintenant une procédure stockée qui nécessite un paramètre d'entrée:

DELIMITER $$

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

Nous pouvons passer le paramètre d'entrée lors d'un appel à la procédure stockée:

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

Cela vous donne les éléments suivants:

( RowDataPacket { name: 'Michaela Lehr', city: 'Berlin' } )

La plupart du temps, lorsque nous essayons d'insérer un enregistrement dans la base de données, nous avons besoin que le dernier ID inséré soit renvoyé en tant que paramètre out. Considérez la procédure stockée d'insertion suivante avec un paramètre 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 $$

Pour effectuer un appel de procédure avec un paramètre out, nous devons d'abord activer plusieurs appels lors de la création de la connexion. Modifiez donc la connexion en définissant l'exécution de plusieurs instructions sur true:

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

Ensuite, lorsque vous appelez la procédure, définissez un paramètre out et transmettez-le:

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

Comme vu dans le code ci-dessus, nous avons défini un @author_id out et l'a transmis lors d'un appel à la procédure stockée. Une fois l'appel effectué, nous devons sélectionner le paramètre out pour accéder à l'ID retourné.

Courir app.js. En cas d'exécution réussie, vous devriez pouvoir voir le paramètre de sortie sélectionné ainsi que diverses autres informations. rows(2) devrait vous donner accès au paramètre out sélectionné:

 ( RowDataPacket { '@author_id': 6 } ) )

Remarque: pour supprimer une procédure stockée, vous devez exécuter la commande DROP PROCEDURE ; par rapport à la base de données pour laquelle vous l'avez créé.

Échapper l'entrée utilisateur

Afin d'éviter les attaques par injection SQL, vous devez toujours échapper toutes les données que vous recevez des utilisateurs avant de les utiliser dans une requête SQL. Montrons pourquoi:

const userSubmittedVariable = '1';

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

Cela semble assez inoffensif et renvoie même le résultat correct:

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

Cependant, essayez de changer le userSubmittedVariable pour ça:

const userSubmittedVariable = '1 OR 1=1';

Nous avons soudainement accès à l'ensemble des données. Maintenant, changez-le en ceci:

const userSubmittedVariable = '1; DROP TABLE authors';

Nous avons maintenant des ennuis!

La bonne nouvelle est que l'aide est à portée de main. Il vous suffit d'utiliser la méthode mysql.escape:

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

Vous pouvez également utiliser un espace réservé de point d'interrogation, comme nous l'avons fait dans les exemples au début de l'article:

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

Pourquoi ne pas simplement utiliser un ORM?

Avant d'entrer dans les avantages et les inconvénients de cette approche, prenons une seconde pour voir ce que sont les ORM. Ce qui suit est tiré d'une réponse sur Stack Overflow:

Le mappage relationnel objet (ORM) est une technique qui vous permet d'interroger et de manipuler les données d'une base de données à l'aide d'un paradigme orienté objet. Quand on parle d'ORM, la plupart des gens font référence à une bibliothèque qui implémente la technique de cartographie relationnelle objet, d'où l'expression «un ORM».

Cela signifie donc que vous écrivez la logique de votre base de données dans le langage spécifique au domaine de l'ORM, contrairement à l'approche vanille que nous avons adoptée jusqu'à présent. Pour vous donner une idée de ce à quoi cela pourrait ressembler, voici un exemple utilisant Sequelize, qui interroge la base de données pour tous les auteurs et les enregistre sur la console:

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

Le fait que l'utilisation ou non d'un ORM ait du sens pour vous dépendra beaucoup de ce sur quoi vous travaillez et avec qui. D'une part, ORMS a tendance à rendre les développeurs plus productifs, en partie en faisant abstraction d'une grande partie du SQL afin que tout le monde dans l'équipe n'ait pas besoin de savoir comment écrire des requêtes spécifiques aux bases de données super efficaces. Il est également facile de passer à différents logiciels de base de données, car vous développez vers une abstraction.

D'un autre côté cependant, il est possible d'écrire du SQL vraiment désordonné et inefficace du fait de ne pas comprendre comment l'ORM fait ce qu'il fait. Les performances sont également un problème dans la mesure où il est beaucoup plus facile d'optimiser les requêtes qui n'ont pas à passer par l'ORM.

Quel que soit le chemin que vous choisissez, mais si c'est une décision que vous êtes en train de prendre, consultez ce fil Stack Overflow: Pourquoi devriez-vous utiliser un ORM?. Consultez également cet article sur SitePoint: 3 ORM JavaScript que vous ne connaissez peut-être pas.

Conclusion

Dans ce didacticiel, nous avons installé le client mysql pour Node.js et l'avons configuré pour se connecter à une base de données. Nous avons également vu comment effectuer des opérations CRUD, travailler avec des instructions préparées et échapper les entrées des utilisateurs pour atténuer les attaques par injection SQL. Et pourtant, nous n'avons fait qu'effleurer la surface de ce que propose le client mysql. Pour des informations plus détaillées, je vous recommande de lire la documentation officielle.

Et n'oubliez pas que le module mysql n'est pas le seul spectacle en ville. Il existe également d'autres options, comme le populaire node-mysql2.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *