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 :
- ModÚle : GÚre les données et la logique métier.
- Vue : Affiche les données (dans notre cas, on utilise une API, donc pas de vue).
- 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ÚleBookManager. 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 fonctiongetAllBooks()pour récupérer tous les livres de la base de données. Cette fonction est asynchrone, donc on utiliseawaitpour attendre qu'elle termine son travail avant de passer à la ligne suivante. -
getAllBooks(): Cette fonction est dĂ©finie dansBookManager.js. Elle exĂ©cute une requĂȘte SQL pour rĂ©cupĂ©rer tous les livres de la tablelivres. -
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 fonctiongetAllBooks()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éecreateConnectiondepuis un fichierdb.js. Cette fonction est responsable de créer une connexion à la base de données MySQL. -
./../../db: Le chemin relatif indique que le fichierdb.jsse 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éegetAllBooks. 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 fonctioncreateConnection()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 tablelivres`. -
**
SELECT * FROM \livres`** : C'est la requĂȘte SQL. Elle signifie "sĂ©lectionne toutes les colonnes de la tablelivres`". -
await: On attend que la requĂȘte soit exĂ©cutĂ©e et que les rĂ©sultats soient retournĂ©s. -
const [results, fields]: La méthodequery()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 écrireconst [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 (commeid,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 bloctry, le code dans le bloccatchsera 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
getAllBooksest appelée :
- Elle établit une connexion à la base de données MySQL.
- Elle exĂ©cute une requĂȘte SQL pour rĂ©cupĂ©rer tous les livres de la table
livres. - Elle ferme la connexion à la base de données.
- Elle retourne les résultats (la liste des livres) à la fonction appelante.
- 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... đ