Aller au contenu

MySQL avec Express.js

Installation

Pour nous permettre d'interragir avec une base de données MySQL nous allons installer un nouveau module avec npm: mysql2.

npm install mysql2

Création du pool de connexions

Ensuite nous allons créer le fichier ./src/config/db.js où nous allons déclarer un pool de connexions à notre BD. Notez aussi que j'utilise le module dotenv pour enregistrer les informations de connexion à la base de données

Note

On aurait aussi pu créer une connexion au lieu d'un pool, mais ce dernier est mieux adapté pour gérer de multiples utilisateurs. Pour plus de détail, cet article (anglais) résume bien la situation : Why is Connection Pooling better than Single Connection

.env
MYSQL_HOST="localhost"
MYSQL_USER="root"
MYSQL_PASSWORD=""
MYSQL_DATABASE="<nom_bd>"
MYSQL_CONNECTION_LIMIT = 10

Mysql

Le mot de passe par défaut pour l'utilisateur root dans devilbox est une chaîne vide.

.src/config/db.js
// Version du module mysql qu'on peut utiliser avec async/await
import mysql from "mysql2/promise";
import dotenv from "dotenv";
dotenv.config();

const pool = mysql.createPool({
    connectionLimit: process.env.MYSQL_CONNECTION_LIMIT,
    host: process.env.MYSQL_HOST,
    user: process.env.MYSQL_USER,
    password: process.env.MYSQL_PASSWORD,
    database: process.env.MYSQL_DATABASE
});

export default pool;

Utilisation

Maintenant pour lancer des requêtes à notre base de données MySQL il suffit d'importer le module db qu'on vient de créer et d'utiliser la fonction query()

script.js
import db from './config/db.js';

let id = 1;

const requete = `SELECT nom, prenom FROM professeurs WHERE id = ?`;
// Les paramètres doivent toujours être passés sous forme de tableau, 
// même quand il n'y en a qu'un.
const params = [id]

try {
    // Attention: mysql2/promise retourne un tableau avec deux éléments : les résultats et 
    //  les informations sur les champs
    // Nous n'avons besoin que des résultats ici (déstructuration en ignorant le second élément)
    const [resultats] = await db.query(requete, params);
    console.log(resultats);
} catch (erreur) {
    console.log(`Erreur, code: ${erreur.code} sqlState ${erreur.sqlState} : ${erreur.sqlMessage}`);
}

Attention: l'objet resultats est toujours un tableau d'objets, même s'il n'y a qu'une ligne de résultats.

Si on veut accéder à la valeur d'une colonne de la première ligne de résultats on peut faire resultat[0].nomColonne. Dans l'exemple plus haut resultats[0].prenom me retournerait le prénom du professeur de la première ligne de résultats.

Sur erreur (ex: problème de connexion à la base de données, erreur dans la requête SQL, etc.) une exception est levée et on peut la capturer avec un bloc try...catch.

Dans l'objet erreur vous pouvez récupérer des informations sur l'erreur comme le code d'erreur SQL (erreur.sqlState) et un message explicatif (erreur.sqlMessage).

script.js
console.log(`Erreur SQL (${erreur.sqlState}) : ${erreur.sqlMessage}`);

L'appel à db.query retourne une promesse (Promise). C'est pourquoi on utilise await pour attendre la résolution de cette promesse. Pour plus de détail sur les promesses, voir la page sur l'asynchronisme.

Si on utilise await dans une fonction, il faut que cette fonction soit déclarée avec le mot-clé async. Ici, nous sommes dans le contexte d'un module ES6 (fichier avec extension .js et utilisant des instructions import et export), donc on peut utiliser await directement au niveau supérieur du module.

Exemple Express.js + MySQL

Voici un exemple complet d'une requête SELECT avec le modèle, la route et le controleur.

professeurs.model.js
import pool from '../config/db.js';

const getProfesseur = async (id) => {

    // On spécifie LIMIT 1 pour s'assurer de ne récupérer qu'un seul enregistrement
    const requete = `SELECT nom, prenom FROM professeurs WHERE id = ? LIMIT 1`;
    const params = [id]

    try {
        // Attention: mysql2 retourne un tableau avec deux éléments : les résultats et 
        //      les informations sur les champs
        // Nous n'avons besoin que des résultats ici (décomposition du tableau en ignorant 
        //      le second élément)
        const [resultats] = await pool.query(requete, params);
        // Retourne le premier élément du tableau ou null si vide
        return resultats[0] ?? null;
    } catch (erreur) {
        console.log(`Erreur, code: ${erreur.code} sqlState ${erreur.sqlState} : 
                    ${erreur.sqlMessage}`);
        throw erreur;
    }
};

export default {
    getProfesseur
}
professeurs.controller.js
import professeursModel from "../models/professeurs.model.js";

const trouverUnProfesseur = async (req, res) => {

    // Teste si le paramètre id est présent et valide
    if(!req.params.id || parseInt(req.params.id) <= 0){
        res.status(400);
        res.send({
            message: "L'id du professeur est obligatoire et doit être supérieur à 0"
        });
        return;
    }

    try {
        // Appel à la fonction getProfesseur dans le modèle
        const professeur = await professeursModel.getProfesseur(req.params.id);

        // On retourne un message d'erreur avec le code 404 si aucun professeur n'a été trouvé
        // ** à faire en exercice **

        // OK - on retourne l'objet professeur
        res.send(professeur);

    } catch (erreur) {
        // S'il y a eu une erreur au niveau de la requête, on retourne un erreur 500 car 
        //  c'est du serveur que provient l'erreur.
        console.log('Erreur : ', erreur);
        res.status(500)
        res.send({
            message: "Erreur lors de la récupération du professeur avec l'id " + req.params.id
        });
    };
};

export {
    trouverUnProfesseur
}
professeurs.route.js
import { trouverUnProfesseur  } from '../controllers/professeurs.controller.js';

// Nous avons besoin d'importer le module express pour utiliser la classe Router
import express from 'express';
// Nous créons un objet router qui va nous permettre de gérer les routes
const router = express.Router();

router.get('/:id', trouverUnProfesseur);

export default router;

Exercice 3b: MySQL et Express.js

L'exercice consiste à compléter l'exemple ci-dessus en ajoutant la gestion du cas où aucun professeur n'est trouvé dans la base de données. Dans ce cas, le serveur doit retourner un code 404 avec un message d'erreur approprié.

Étapes

  1. Créez une base de données MySQL (conteneur sous devilbox) avec une table professeurs contenant au moins les colonnes id, nom, et prenom.
  2. Insérez quelques enregistrements dans la table professeurs.
  3. Créez un nouveau projet Node.js avec la structure de dossiers appropriée (controllers, models, routes, config)
  4. Installez les dépendances nécessaires (express, mysql2, dotenv, etc.)
  5. Complétez la fonction trouverUnProfesseur dans le contrôleur professeurs.controller.js pour gérer le cas où aucun professeur n'est trouvé.
  6. Testez votre API en utilisant Postman
    1. GET avec un code valide (200 OK)
    2. GET avec un code inexistant (doit retourner 404)

Références et autres exemples