Skip to main content

API REST avec Express et MySQL en utilisant le modùle MVC 🚀

Les codeurs en herbe ! Aujourd'hui, on va plonger dans le monde d'Express.js et créer une API REST pour gérer une bibliothÚque de livres. On va utiliser MySQL comme base de données et suivre le modÚle MVC (ModÚle-Vue-ContrÎleur) pour structurer notre projet. Préparez-vous, car on va coder comme des pros (ou presque) !


Étape 1 : Initialisation du projet đŸ› ïžâ€‹

1.1 CrĂ©er un nouveau projet​

Ouvrez votre terminal et créez un nouveau dossier pour votre projet :

mkdir ma-bibliotheque
cd ma-bibliotheque

Ensuite, initialisez un nouveau projet Node.js :

npm init -y

Cela va créer un fichier package.json qui contiendra toutes les informations sur votre projet et ses dépendances.

1.2 Installer les dĂ©pendances​

On a besoin de quelques packages pour notre projet :

  • Express : Le framework web pour Node.js.
  • MySQL2 : Un client MySQL pour Node.js.
  • Nodemon : Pour redĂ©marrer automatiquement le serveur quand on fait des changements.

Installez-les avec la commande suivante :

npm install express mysql2 nodemon

1.3 Configurer package.json​

Ouvrez le fichier package.json et ajoutez un script pour démarrer le serveur avec Nodemon :

"scripts": {
"start": "nodemon index.js"
}

Maintenant, vous pouvez démarrer votre serveur avec :

npm start

Étape 2 : Structurer le projet avec le modĂšle MVC đŸ—ïžâ€‹

Le modÚle MVC (ModÚle-Vue-ContrÎleur) est une façon de structurer votre application en trois parties :

  1. ModÚle : GÚre les données et la logique métier.
  2. Vue : Affiche les données (dans notre cas, on utilise une API, donc pas de vue).
  3. ContrĂŽleur : GĂšre les requĂȘtes et les rĂ©ponses.

2.1 CrĂ©er les dossiers​

Créez les dossiers suivants dans votre projet :

ma-bibliotheque/
├── src/
│ ├── controller/
│ ├── model/
│ └── views/ (on n'en aura pas besoin pour une API)
├── index.js
└── package.json

Étape 3 : CrĂ©er le serveur Express 🌐​

3.1 Le fichier index.js​

Ouvrez le fichier index.js et ajoutez le code suivant :

const express = require('express');
const app = express();
const port = 3555;

// Middleware pour parser le JSON
app.use(express.json());

// Importer le contrĂŽleur des livres
const BookController = require('./src/controller/BookController');

// Utiliser le contrĂŽleur pour les routes /api/books
app.use('/api/books', BookController);

// Démarrer le serveur
app.listen(port, () => {
console.log(`Serveur en écoute sur le port ${port}`);
});

Explication :

  • express() : CrĂ©e une instance d'Express.
  • app.use(express.json()) : Ce middleware permet Ă  Express de comprendre le JSON envoyĂ© dans les requĂȘtes.
  • app.use('/api/books', BookController) : On utilise le contrĂŽleur pour gĂ©rer toutes les routes qui commencent par /api/books.
  • app.listen(port) : Le serveur Ă©coute sur le port 3555.

Étape 4 : CrĂ©er le contrĂŽleur des livres 📚​

4.1 Le fichier BookController.js​

Dans le dossier src/controller/, créez un fichier BookController.js :

const express = require('express');
const app = express();
const {
getAllBooks,
getBookById,
updateBook,
createBook,
deleteBook
} = require('./../model/BookManager');

// Récupérer tous les livres
app.get('/', async (req, res) => {
const results = await getAllBooks();
res.json(results);
});

// Récupérer un livre par son ID
app.get('/:id', async (req, res) => {
const id = req.params.id;
const results = await getBookById(id);
res.json(results);
});

// Modifier un livre par son ID
app.put('/:id', async (req, res) => {
const id = req.params.id;
const results = await updateBook(id, req.body);
res.json(results);
});

// Créer un livre
app.post('/', async (req, res) => {
const results = await createBook(req.body);
res.json(results);
});

// Supprimer un livre par son ID
app.delete('/:id', async (req, res) => {
const id = req.params.id;
const results = await deleteBook(id);
res.json(results);
});

module.exports = app;

Explication dĂ©taillĂ©e du code 🧐​

On va dĂ©cortiquer cette partie du code pour bien comprendre ce qui se passe. C'est le cƓur de notre contrĂŽleur, oĂč on dĂ©finit les routes pour gĂ©rer les livres. PrĂ©parez-vous, car on va rentrer dans les dĂ©tails !


1. Importation des modules et des fonctions​

const express = require('express');
const app = express();
const {
getAllBooks,
getBookById,
updateBook,
createBook,
deleteBook
} = require('./../model/BookManager');

Explication :

  • const express = require('express'); : On importe le module Express. C'est notre framework web qui va nous permettre de crĂ©er des routes et de gĂ©rer les requĂȘtes HTTP.

  • const app = express(); : On crĂ©e une instance d'Express. Cette instance (app) va nous permettre de dĂ©finir des routes et de dĂ©marrer le serveur.

  • const { getAllBooks, getBookById, updateBook, createBook, deleteBook } = require('./../model/BookManager'); : On importe les fonctions du modĂšle BookManager. Ces fonctions sont responsables de l'interaction avec la base de donnĂ©es. Elles vont nous permettre de rĂ©cupĂ©rer, crĂ©er, mettre Ă  jour et supprimer des livres.


2. DĂ©finition de la route pour rĂ©cupĂ©rer tous les livres​

// Récupérer tous les livres
app.get('/', async (req, res) => {
const results = await getAllBooks();
res.json(results);
});

Explication :

  • app.get('/', async (req, res) => { ... }); : On dĂ©finit une route GET pour l'URL racine (/). Quand un client (comme un navigateur ou Postman) fait une requĂȘte GET Ă  cette URL, cette fonction sera exĂ©cutĂ©e.

  • app.get : C'est une mĂ©thode d'Express pour dĂ©finir une route qui rĂ©pond aux requĂȘtes HTTP de type GET.

  • '/' : C'est l'URL de la route. Ici, c'est la racine, donc quand on accĂšde Ă  /api/books, cette route sera dĂ©clenchĂ©e.

  • async (req, res) => { ... } : C'est une fonction asynchrone qui prend deux paramĂštres :

  • req (request) : Contient les informations sur la requĂȘte HTTP (comme les paramĂštres, les headers, etc.).

  • res (response) : UtilisĂ© pour envoyer une rĂ©ponse au client.

  • const results = await getAllBooks(); : On appelle la fonction getAllBooks() pour rĂ©cupĂ©rer tous les livres de la base de donnĂ©es. Cette fonction est asynchrone, donc on utilise await pour attendre qu'elle termine son travail avant de passer Ă  la ligne suivante.

  • getAllBooks() : Cette fonction est dĂ©finie dans BookManager.js. Elle exĂ©cute une requĂȘte SQL pour rĂ©cupĂ©rer tous les livres de la table livres.

  • res.json(results); : Une fois qu'on a rĂ©cupĂ©rĂ© les livres, on les envoie au client sous forme de JSON.

  • res.json() : C'est une mĂ©thode d'Express qui envoie une rĂ©ponse au client au format JSON. C'est parfait pour une API REST, car les clients (comme les applications frontend) peuvent facilement interprĂ©ter ce format.


3. Pourquoi utiliser async et await ?​

Dans ce code, on utilise async et await pour gérer les opérations asynchrones, comme les appels à la base de données.

  • async : On dĂ©clare la fonction comme asynchrone. Cela signifie qu'elle peut contenir des opĂ©rations qui prennent du temps (comme une requĂȘte SQL).

  • await : On l'utilise pour attendre qu'une opĂ©ration asynchrone se termine avant de passer Ă  la ligne suivante. Par exemple, await getAllBooks() attend que la fonction getAllBooks() termine son travail (c'est-Ă -dire qu'elle rĂ©cupĂšre les livres de la base de donnĂ©es) avant de continuer.

Exemple sans await :

Si on n'utilisait pas await, le code essaierait d'envoyer la réponse (res.json(results)) avant que getAllBooks() ait fini de récupérer les livres. Cela causerait une erreur, car results serait undefined.

  • app.get('/') : Route pour rĂ©cupĂ©rer tous les livres.
  • app.get('/:id') : Route pour rĂ©cupĂ©rer un livre par son ID.
  • app.put('/:id') : Route pour modifier un livre.
  • app.post('/') : Route pour crĂ©er un nouveau livre.
  • app.delete('/:id') : Route pour supprimer un livre.

Étape 5 : CrĂ©er le modĂšle pour gĂ©rer les livres 📖​

5.1 Le fichier BookManager.js​

Dans le dossier src/model/, créez un fichier BookManager.js :

const createConnection = require('./../../db');

// Récupérer tous les livres
const getAllBooks = async () => {
try {
const mysqlConnection = await createConnection();
const [results] = await mysqlConnection.query('SELECT * FROM livres');
await mysqlConnection.end();
return results;
} catch (err) {
console.error(err);
return 'Error 500';
}
};

// Récupérer un livre par son ID
const getBookById = async (id) => {
try {
const mysqlConnection = await createConnection();
const [results] = await mysqlConnection.query(
`SELECT l.*, a.nom as auteur_nom, a.pays as auteur_pays, a.email as auteur_email
FROM livres l
LEFT JOIN auteurs a ON auteur_id = a.id
WHERE l.id = ?`, [id]
);
await mysqlConnection.end();
return results[0] || null;
} catch (err) {
console.error(err);
throw new Error("Erreur serveur");
}
};

// Modifier un livre
const updateBook = async (id, updateData) => {
try {
const mysqlConnection = await createConnection();
const [result] = await mysqlConnection.query(
`UPDATE livres SET titre = ?, auteur_id = ?, annee_publication = ?
WHERE id = ?`, [updateData.titre, updateData.auteur_id, updateData.annee_publication, id]
);
await mysqlConnection.end();
if (result.affectedRows === 0) return null;
return await getBookById(id);
} catch (e) {
console.error(e);
}
};

// Créer un livre
const createBook = async (bookData) => {
try {
const mysqlConnection = await createConnection();
const [result] = await mysqlConnection.query(
`INSERT INTO livres (titre, auteur_id, annee_publication) VALUES (?, ?, ?)`,
[bookData.titre, bookData.auteur_id, bookData.annee_publication]
);
await mysqlConnection.end();
if (result.insertId) return await getBookById(result.insertId);
return null;
} catch (e) {
console.log(e);
}
};

// Supprimer un livre
const deleteBook = async (id) => {
try {
const mysqlConnection = await createConnection();
const [result] = await mysqlConnection.query(
'DELETE FROM livres WHERE id = ?', [id]
);
await mysqlConnection.end();
return result.affectedRows > 0;
} catch (err) {
console.error(err);
throw new Error('Erreur serveur');
}
};

module.exports = {
getAllBooks,
getBookById,
updateBook,
deleteBook,
createBook
};

Explication dĂ©taillĂ©e du code 🧐​

On va maintenant dĂ©cortiquer cette partie du code, qui se trouve dans le fichier BookManager.js. Cette fonction, getAllBooks, est responsable de rĂ©cupĂ©rer tous les livres de la base de donnĂ©es. C'est une fonction asynchrone qui interagit avec MySQL pour exĂ©cuter une requĂȘte SQL. PrĂ©parez-vous, car on va rentrer dans les dĂ©tails techniques !


1. Importation du module de connexion Ă  la base de donnĂ©es​

const createConnection = require('./../../db');

Explication :

  • const createConnection = require('./../../db'); : On importe une fonction appelĂ©e createConnection depuis un fichier db.js. Cette fonction est responsable de crĂ©er une connexion Ă  la base de donnĂ©es MySQL.

  • ./../../db : Le chemin relatif indique que le fichier db.js se trouve deux niveaux au-dessus du dossier actuel. Ce fichier contient probablement la configuration pour se connecter Ă  la base de donnĂ©es (comme l'hĂŽte, l'utilisateur, le mot de passe, etc.).


2. DĂ©finition de la fonction getAllBooks​

const getAllBooks = async () => {
try {
// Connexion MySQL
const mysqlConnection = await createConnection();
// RequĂȘte MySQL
const [results, fields] = await mysqlConnection.query(
'SELECT * FROM `livres`'
);

// Fermer la connexion aprĂšs utilisation
await mysqlConnection.end();

return results;
} catch (err) {
// Gestion des erreurs
console.error(err);
return 'Error 500';
}
}

Explication :

  • const getAllBooks = async () => { ... } : On dĂ©finit une fonction asynchrone appelĂ©e getAllBooks. Cette fonction est asynchrone car elle interagit avec une base de donnĂ©es, ce qui est une opĂ©ration qui prend du temps.

  • async : Cela indique que la fonction contient des opĂ©rations asynchrones (comme des appels Ă  une base de donnĂ©es).

  • () => { ... } : C'est une fonction flĂ©chĂ©e (arrow function), une syntaxe moderne pour dĂ©finir des fonctions en JavaScript.


3. Connexion Ă  la base de donnĂ©es​

const mysqlConnection = await createConnection();

Explication :

  • const mysqlConnection = await createConnection(); : On utilise la fonction createConnection() pour Ă©tablir une connexion Ă  la base de donnĂ©es MySQL.

  • await : On attend que la connexion soit Ă©tablie avant de passer Ă  la ligne suivante. C'est nĂ©cessaire car la connexion Ă  une base de donnĂ©es est une opĂ©ration asynchrone.

  • mysqlConnection : Une fois la connexion Ă©tablie, on stocke l'objet de connexion dans cette variable. Cet objet nous permet d'exĂ©cuter des requĂȘtes SQL.


4. ExĂ©cution de la requĂȘte SQL​

const [results, fields] = await mysqlConnection.query(
'SELECT * FROM `livres`'
);

Explication :

  • **await mysqlConnection.query('SELECT * FROM \livres`');** : On exĂ©cute une requĂȘte SQL pour rĂ©cupĂ©rer tous les livres de la table livres`.

  • **SELECT * FROM \livres`** : C'est la requĂȘte SQL. Elle signifie "sĂ©lectionne toutes les colonnes de la table livres`".

  • await : On attend que la requĂȘte soit exĂ©cutĂ©e et que les rĂ©sultats soient retournĂ©s.

  • const [results, fields] : La mĂ©thode query() retourne un tableau avec deux Ă©lĂ©ments :

  • results : Contient les rĂ©sultats de la requĂȘte (ici, la liste des livres).

  • fields : Contient des mĂ©tadonnĂ©es sur les colonnes de la table (comme les noms des colonnes, les types de donnĂ©es, etc.). Dans ce cas, on ne l'utilise pas, donc on pourrait aussi Ă©crire const [results].


5. Fermeture de la connexion​

await mysqlConnection.end();

Explication :

  • await mysqlConnection.end(); : Une fois que la requĂȘte est exĂ©cutĂ©e et que les rĂ©sultats sont rĂ©cupĂ©rĂ©s, on ferme la connexion Ă  la base de donnĂ©es.

  • end() : Cette mĂ©thode ferme la connexion. C'est une bonne pratique de toujours fermer la connexion aprĂšs l'avoir utilisĂ©e pour Ă©viter des fuites de ressources.

  • await : On attend que la connexion soit fermĂ©e avant de continuer.


6. Retour des rĂ©sultats​

return results;

Explication :

  • return results; : On retourne les rĂ©sultats de la requĂȘte SQL (c'est-Ă -dire la liste des livres) Ă  la fonction qui a appelĂ© getAllBooks.

  • results : Contient les lignes retournĂ©es par la requĂȘte SQL. Dans ce cas, ce sera un tableau d'objets, oĂč chaque objet reprĂ©sente un livre avec ses propriĂ©tĂ©s (comme id, titre, auteur_id, etc.).


7. Gestion des erreurs​

} catch (err) {
console.error(err);
return 'Error 500';
}

Explication :

  • catch (err) { ... } : Si une erreur se produit dans le bloc try, le code dans le bloc catch sera exĂ©cutĂ©.

  • err : Contient l'objet d'erreur, qui donne des informations sur ce qui s'est mal passĂ© (par exemple, une erreur de connexion Ă  la base de donnĂ©es ou une requĂȘte SQL invalide).

  • console.error(err); : On affiche l'erreur dans la console pour le dĂ©bogage. C'est utile pendant le dĂ©veloppement pour comprendre ce qui a Ă©chouĂ©.

  • return 'Error 500'; : On retourne une chaĂźne de caractĂšres 'Error 500' pour indiquer qu'une erreur serveur s'est produite. Dans une application rĂ©elle, on pourrait retourner un objet d'erreur plus dĂ©taillĂ© ou utiliser un mĂ©canisme de gestion d'erreurs plus sophistiquĂ©.


8. RĂ©sumĂ© de ce que fait cette fonction​

  • Quand getAllBooks est appelĂ©e :
  1. Elle établit une connexion à la base de données MySQL.
  2. Elle exĂ©cute une requĂȘte SQL pour rĂ©cupĂ©rer tous les livres de la table livres.
  3. Elle ferme la connexion à la base de données.
  4. Elle retourne les résultats (la liste des livres) à la fonction appelante.
  5. Si une erreur se produit, elle affiche l'erreur dans la console et retourne 'Error 500'.

Pourquoi cette fonction est-elle importante ?​

Cette fonction est cruciale car elle permet Ă  l'application de rĂ©cupĂ©rer des donnĂ©es de la base de donnĂ©es. Sans elle, notre API ne pourrait pas renvoyer la liste des livres aux clients (comme un navigateur ou une application mobile). C'est un bon exemple de la couche ModĂšle dans le modĂšle MVC, oĂč la logique mĂ©tier et l'accĂšs aux donnĂ©es sont gĂ©rĂ©s.


Étape 6 : Tester l'API đŸ§Ș​

Maintenant que tout est en place, vous pouvez tester votre API avec des outils comme Postman ou directement avec des requĂȘtes HTTP.

  • GET /api/books : RĂ©cupĂšre tous les livres.
  • GET /api/books/1 : RĂ©cupĂšre le livre avec l'ID 1.
  • POST /api/books : CrĂ©e un nouveau livre.
  • PUT /api/books/1 : Met Ă  jour le livre avec l'ID 1.
  • DELETE /api/books/1 : Supprime le livre avec l'ID 1.

Conclusion 🎉​

FĂ©licitations ! Vous avez créé une API REST avec Express et MySQL en utilisant le modĂšle MVC. Vous ĂȘtes maintenant prĂȘt Ă  conquĂ©rir le monde du dĂ©veloppement web backend. 🚀

N'oubliez pas de garder votre code propre et bien documentĂ©, et surtout, amusez-vous en codant ! 😄


Prochaine Ă©tape : Ajouter une authentification pour protĂ©ger votre API. Mais ça, c'est une autre histoire... 😉