Implémentation d'un panier en JavaScript avec Node.js

Marc AUTRAN

Suite à mon article implémentation d'un panier en JavaScript et HTML5 qui s'exécutait 100 % dans le navigateur, je vous propose dans cet article une implémentation d'un panier côté serveur avec Node.js et MongoDb. Commentez

- Titre : Implémentation d'un panier en JavaScript avec Node.js

- Auteur : Marc AUTRAN

- Parution : 19 août 2016

- Public visé :

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2016 Marc AUTRAN. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. Le cahier des charges

- III. L'architecture logicielle

- IV. La base de données.

- V. Les objets métier et la connexion à MongoDB

- VI. Les IHM

- VII. Gestion des routes et des URL

- VIII. Conclusions et remerciements

I - Introduction

Ce tutoriel s'adresse à des lecteurs qui connaissent déjà Node.JS, mais qui souhaiteraient comprendre comment développer basiquement une application web client-serveur.

Les bibliothèques et versions des logiciels utilisées pour les besoins de l'article :

- Node.js 4.2.3 ;

- MongoDb 3.2 ;

- navigateur Firefox (42.0).

Pour ce tutoriel, toutes les sources ont été écrites avec l'EDI Atom, mais un simple éditeur tel que SublimeText ou Notepad++ sera suffisant.

II - Le cahier des charges

L'objectif est de produire une application client-serveur de gestion de panier. Nous développerons bien entendu dans ce tutoriel une page d'authentification préalable d'un utilisateur et une page permettant l'enregistrement en temps réel du panier dans une base de données.
Tout sera réalisé côté serveur, donc pas de session ni cookies gérés dans le navigateur.

Les IHM à produire dynamiquement par le serveur seront de ce type :

III - L'architecture logicielle

Le serveur HTTP (serveur web) sera créé dynamiquement avec le Framework node.js comme l'illustre le tutoriel Node.js le livre du débutant.

L'application étant de très petite taille, je vais me permettre de développer le serveur d'application dans le fichier app.js qui prendra en charge :

- la création du serveur HTTP ;

- le mapping objet relationnel entre mes classes métier et la base de données. Je parle de classes, car nous développerons en ES6.
La gestion des routes. Une sorte de contrôleur pour multiplexer les requêtes du client ;

- quelques traitements métier pour l'authentification et les opérations élémentaires de gestion d'un panier.

À des fins pédagogiques, nous n'aurons qu'une classe métier Panier que nous développerons en ES6.

Pour la base de données, j'utiliserai MongoDB. Ça n'est pas une base de données, mais une base orientée documents. J'aurais pu choisir un vrai SGBD/R comme Postgresql. Mais pour une application de cette dimension, cela ne change pas grand-chose et l'on pourra assimiler une table d'une base de données à un document de MongoDB.
De plus MongoDB est le compagnon structurel de node.js au même titre que mysql l'est pour Apache/PHP.

Pour créer des IHM, nous utiliserons le module EJS qui est un moteur de « Templates ». De tels moteurs permettent de séparer les IHM du reste de l'application. Les templates permettent surtout de réduire le nombre de lignes de code nécessaires pour créer des pages HTML, ce qui est connu sous l'acronyme DRY : don't repeat yourself. SylvainPV a d'ailleurs publié un petit guide du templating côté client.

Node.js fonctionne avec des paquets qui accroissent ses capacités. Pour ce tutoriel nous utiliserons les paquets :

- Express qui fournit un jeu basique de capacités de traitement des routes et URL ;

- bodyparser pour parser les URL et les données Json ;

- ejs qui permet d'écrire des IHM à l'aide d'un langage de templating ;

- Mongoose qui permet de mapper les objets métier en JavaScript avec les documents de la base MongoDB.

Tous ces modules seront installés à l'aide de la commande npm install <nomDuModule>.

L'arborescence des répertoires pour le projet sera la suivante :

panierNode sera le répertoire racine de notre projet dans lequel on se placera pour lancer les commandes Node.js. Le répertoire views contiendra les IHM développées en HTML5 + EJS. Le répertoire node_modules contiendra tous les modules installés (Express, EJS, Mongoose…) ce répertoire sera créé automatiquement par la commande npm.

IV - La base de données.

Pour l'installation de MongoDB et la création de la base, on suivra la procédure décrite dans le tutoriel Introduction à la base de données NoSQL MongoDB de Harry Wanki.
La base que j'ai créée s'appelle caddy.
J'ai créé deux collections : lignes et clients qui contiennent les données suivantes :

1. > db.clients.find(){ "_id" : ObjectId("568400bccf09d2b462bd3dd0"), "nom" : "autran", "prenom" : "marc", "email" : "marc.autran@datavalue.com", "password" : "123456" }

3. > db.lignes.find(){ "_id" : ObjectId("5686bead431b408024099bf5"), "proprietaire" : "john.rambo@boom.com", "code" : "3", "qte" : 53, "prix" : 9 }{ "_id" : ObjectId("56a36a52b4d39a30115a611f"), "proprietaire" : "marc.autran@datavalue.com", "code" : "1", "qte" : 10, "prix" : 20 }

La table clients sera utile pour authentifier les utilisateurs.
La table lignes représente le panier qui est une agrégation de lignes commandées par un propriétaire (client). Donc dans la table lignes, la colonne (propriété) propriétaire peut être considérée comme une clé étrangère qui pointe vers la table clients.

V - Les objets métier et la connexion à MongoDB

On trouvera deux objets Panier et LignePanier.
Mais ici, il s'agira de classes, car nous développerons en ES6.
Ces deux classes seront dans le fichier panier.js :

1. 'use strict';

2. class LignePanier

3. {

4. constructor(code, qte, prix)

5. {

6. this.codeArticle = code;

7. this.qteArticle = qte;

8. this.prixArticle = prix;

9. }

11. ajouterQte(qte)

12. {

13. this.qteArticle += qte;

14. }

15. getPrixLigne()

16. {

17. var resultat = this.prixArticle * this.qteArticle;

18. return resultat;

19. }

20. getCode()

21. {

22. return this.codeArticle;

23. }

24. getQte()

25. {

26. return this.qteArticle;

27. }

28. getPrix()

29. {

30. return this.prixArticle;

31. }

32. }

33. module.exports = class Panier

34. {

35. constructor()

36. {

37. this.liste = [];

38. }

40. ajouterArticle(code, qte, prix)

41. {

42. var index = this.getArticle(code);

43. if (index == -1) this.liste.push(new LignePanier(code, qte, prix));

44. else this.liste[index].ajouterQte(qte);

45. }

46. getPrixPanier()

47. {

48. var total = 0;

49. for(var i = 0 ; i < this.liste.length ; i++)

50. total += this.liste[i].getPrixLigne();

51. return total;

52. }

53. getArticle(code)

54. {

55. for(var i = 0 ; i <this.liste.length ; i++)

56. if (code == this.liste[i].getCode()) return i;

57. return -1;

58. }

59. supprimerArticle(code)

60. {

61. var index = this.getArticle(code);

62. if (index > -1) this.liste.splice(index, 1);

63. }

64. }

Seule la classe Panier sera exportée dans le reste de l'application. La classe LignePanier est utilisée par agrégation par la classe Panier.

Nous nous servirons de mongoose pour faire le lien entre les objets métier et la base de données MongoDB. Un tutoriel sur mongoose est proposé par Sébastien Chopin.
Voici le code source de la partie exploitation de mongoose.

1. 'use strict';

2. var mongoose = require('mongoose');

3. var Panier = require('./panier');

5. // initialisation de mongoose

6. mongoose.connect('mongodb://localhost/caddy', function(err) {

7. if (err) throw err;

8. });

10. // définition des schémas

11. var clientSchema = new mongoose.Schema({

12. nom : String,

13. prenom : String,

14. email : String,

15. password : String

16. }, {

17. versionKey: false

18. });

20. var ligneSchema = new mongoose.Schema({

21. proprietaire : String,

22. code : String,

23. qte : Number,

24. prix : Number

25. }, {

26. versionKey: false

27. });

29. // création des liens entre les tables et les schémas

30. var clientModel = mongoose.model('clients', clientSchema);

31. var ligneModel = mongoose.model('lignes', ligneSchema);

33. var monPanier;

34. var lignes;

35. var clients;

37. ligneModel.find({}, function (err, liste)

38. {

39. if (err)  console.log(err);

40. lignes = liste;

41. });

43. var chargerPanier = function(proprio)

44. {

45. monPanier = new Panier();

46. var longueur = lignes.length;

47. for(var i = 0; i < longueur ; i++)

48. {

49. if(lignes[i].proprietaire == proprio)

50. monPanier.ajouterArticle(lignes[i].code, lignes[i].qte, lignes[i].prix);

51. }

52. }

54. clientModel.find({}, function (err, liste)

55. {

56. if (err)  console.log(err);

57. clients = liste;

58. });

60. var checkLogin = function(login, mdp)

61. {

62. for (var i = 0; i < clients.length; i++)

63. {

64. if (clients[i].email == login && clients[i].password == mdp)

65. return (clients[i]);

66. }

67. }

On remarquera que l'on ne s'est pas donné la peine de créer une classe métier pour la table client, car elle n'est utilisée qu'une fois dans la méthode checkLogin().

VI - Les IHM

Comme nous l'avons aperçu lors de la description de l'architecture logicielle, nos IHM seront écrites avec l'inclusion dans le code HTML5 de directives Jade. La syntaxe de Jade est assez proche de celle de la technologie de templating JEE : les JSP.

Le code EJS se trouvera incorporé au milieu du code HTML via les balises <% code EJS %>.

Les fichiers contenant du code en langage de templating EJS seront suffixés : .ejs Nous aurons deux IHM :

- une pour la page d'authentification (login.ejs) ;

- une pour la page de gestion du panier (index.ejs).

1. <html>

2. <head>

3. <title>Identification</title>

4. </head>

5. <body>

6. <h1>Identifiez vous</h1>

7. <form id = "loginForm" action = "/connecter" method = "post">

8. <table>

9. <tr>

10. <td><label> Login: </label></td>

11. <td><input type = "text" id = "login" name = "login" /></td>

12. </tr>

13. <tr>

14. <td><label> Mot de passe: </label></td>

15. <td><input type = "text" id = "mdp" name = "mdp" /></td>

16. </tr>

17. </table>

18. <input type = "submit" value = "Connecter" />

19. </form>

20. </body>

21. </html>

<html>
<head>
<title>Panier</title>
</head>
<body>
<h1>Mon panier</h1>
<h2> <%= client.prenom %>  <%= client.nom %></h2>
<h3>Email : <%= client.email%></h3>
<h2>Contenu</h2>
<table>
<thead>
<tr>
<th>code</th>
<th>quantité</th>
<th>prix(Euros)</th>
<th></th>
</tr>
</thead>
<tbody>
<%
for(var i = 0 ; i < nbre ; i++) {
%>
<tr>
<td><%= liste[i].codeArticle %></td>
<td><%= liste[i].qteArticle %></td>
<td><%= liste[i].prixArticle %></td>
<td>
<form id="supprimer" action="/supprimer" method = "post">
<input type = "submit" value = "Retirer"/>
<input type = "text" name = "ident" hidden value = <%= liste[i].codeArticle %> />
<input type = "text" name = "prenom" hidden value = <%= client.prenom %> />
<input type = "text" name = "nom" hidden value = <%= client.nom %> />
<input type = "text" name = "email" hidden value = <%= client.email %> />
</form>
</td>
</tr>
<% } %>
</tbody>
</table>
<h3>Prix Total : <%= total%></h3>
<form id="ajouter" action="/ajouter" method = "post">
<h2>Ajouter un article</h2>
<table>
<tr>
<td><label> Code: </label></td>
<td><input type = "text" id = "code" name = "code"/></td>
</tr>
<tr>
<td><label> Quantité: </label></td>
<td><input type = "number" id = "qte" name = "qte"/></td>
</tr>
<tr>
<td><label> Prix: </label></td>
<td><input type = "number" id = "prix" name = "prix"/></td>
</tr>
</table>
<input type = "text" id = "prenom" name = "prenom" hidden value = <%= client.prenom %> />
<input type = "text" id = "nom" name = "nom" hidden value = <%= client.nom %> />
<input type = "text" id = "email" name = "email" hidden value = <%= client.email %> />
<input type = "submit" value = "Ajouter"/>
</form>

<form id="sauvegarder" action="/sauvegarder" method = "post">
<input type = "text" id = "proprietaire" name = "proprietaire" hidden value = <%= client.email %> />
<input type = "submit" value = "Commander"/>
</form>
</body>
</html>

On remarque que le fichier login.ejs ne comporte pas d'inclusion EJS. Il s'agit d'un banal fichier HTML porteur d'un tout aussi banal formulaire d'authentification.

Quant au fichier index.ejs, on remarque que le code EJS permet de manipuler des objets en lien avec le contenu du panier que ce langage de templating permet d'échanger avec l'application.

VII - Gestion des routes et des URL

Nous gérerons cinq routes :

- / : oriente par défaut vers la page du formulaire de connexion ;

- connecter : à la validation du formulaire ;

- ajouter : ajoute un article au panier ;

- supprimer : supprime un article du panier ;

- enregistrer : sauvegarde le panier en base de données.

Voici le code source correspondant à la gestion de ces routes :

1. var express = require('express');

2. var app = express();

3. var bodyParser = require('body-parser');

4. app.get("/", function(req, res)

5. {

6. ligneModel.find({}, function (err, liste)

7. {

8. if (err)  console.log(err);

9. lignes = liste;

10. });

11. res.render('login.ejs', {});

12. })

13. .post('/connecter', function(req, res)

14. {

15. var client = checkLogin(req.body.login, req.body.mdp)

16. if (client)

17. {

18. chargerPanier(req.body.login);

19. res.render('index.ejs', {

20. client: client,

21. liste : monPanier.liste,

22. nbre : monPanier.liste.length,

23. total : monPanier.getPrixPanier()});

24. }

25. else res.redirect('/');

26. })

27. .post('/ajouter', function(req, res) {

28. if (req.body.code != '' &amp;&amp; req.body.qte != ''&amp;&amp; req.body.prix != '')

29. {

30. var client = new Object();

31. client.nom = req.body.nom;

32. client.prenom = req.body.prenom;

33. client.email = req.body.email;

34. monPanier.ajouterArticle(req.body.code, parseInt(req.body.qte), parseInt(req.body.prix));

35. res.render('index.ejs', {

36. client: client,

37. liste : monPanier.liste,

38. nbre : monPanier.liste.length,

39. total : monPanier.getPrixPanier()});

40. }

41. else res.redirect('/');

42. })

43. .post('/supprimer', function(req, res) {

44. if (req.body.ident != '')

45. {

46. var client = new Object();

47. client.nom = req.body.nom;

48. client.prenom = req.body.prenom;

49. client.email = req.body.email;

50. monPanier.supprimerArticle(req.body.ident);

51. res.render('index.ejs', {

52. client: client,

53. liste : monPanier.liste,

54. nbre : monPanier.liste.length,

55. total : monPanier.getPrixPanier()});

56. }

57. else res.redirect('/');

58. })

59. .post('/sauvegarder', function(req, res)

60. {

61. ligneModel.remove({ proprietaire : req.body.proprietaire }, function (err)

62. {

63. if (err) { throw err; }

64. console.log("lignes détruites");

65. var maLigne;

66. var longueur = monPanier.liste.length;

67. for (var i = 0; i < longueur; i++)

68. {

69. maLigne = new ligneModel({proprietaire : req.body.proprietaire, code : monPanier.liste[i].getCode() , qte : parseInt(monPanier.liste[i].getQte()) , prix : parseInt(monPanier.liste[i].getPrix())});

70. maLigne.save(function (err) {

71. if (err) throw err;

72. console.log('ligne panier ajoutée avec succès !');

73. });

74. }

75. res.redirect('/');

76. });

77. });

79. app.listen(5000);

Grâce au module Express, la gestion d'un serveur web est grandement facilitée. En effet, var app = express(); permet d'instancier un serveur HTTP.

La méthode listen fera écouter le serveur HTTP sur le port 5000 grâce à l'instruction : app.listen(5000);.

La méthode post(url, callback()) permet de déterminer la fonction de callback à appliquer suivant l'URL reçue par le serveur (gestion des routes).

VIII - Conclusions et remerciements

Nous avons donc vu comment écrire en JavaScript une application sur un serveur. Néanmoins il convient de garder à l'esprit que cet exemple est une première approche des possibilités de ce langage côté serveur et que cette application reste très modeste. En effet une application professionnelle doit aujourd'hui implémenter le pattern MVC (modèle - vue - contrôleur). Mais cette technique sera vue dans un prochain tutoriel.

Nous tenons à remercier Claude Leloup pour la relecture orthographique de cet article.