LOGO Lionel Groulx

Site Web du cours « Programmation de Systèmes »

Node.js Chapitre 4
Usage du Framework Express avec le Template EJS :

Un framework (ou « structure logicielle », ou « cadre d’application », ou « cadriciel ») est une librairie qui facilite la construction d’applications web : elle fournit les bases (les fondations), et facilite l’élaboration des grandes lignes typiques que l’on retrouve souvent en conception d’applications web.

En effet, si on code sans elles, on se retrouve souvent à réécrire les mêmes lignes de codes, les mêmes fonctions. Leur objectif est donc de nous débarrasser des parties fastidieuses, rébarbatives et répétitives du codage. On peut donc dire qu’un framework nous fournit un « squelette » pour notre programme, et des « patrons » de code. L’objectif final est la standardisation du codage et des résultats, et l’accroissement des performances.

Choisir d’utiliser un framework pour développer une application est très commun. La plupart des frameworks sont libres de droit (pour être utilisés). Faire le choix d’un framework, c’est souvent aussi faire le choix d’une architecture de logiciel (voir plus bas « architecture MVC »).

Une des catégories de framework qui nous intéresse le plus, c’est celle des « middleware » (ou framework d’intégration intergicielle). Les middlewares sont des outils de programmation (librairies externes, par exemple) qui facilitent les échanges d’informations entre les différentes applications, ou traitements au sein de la même application (asynchrone). Avec Node.js, un middleware devient essentiel : il nous fournit des fonctions que nous pourrons enchaîner les unes aux autres.

Comme vous l’avez remarqué ci-dessus, lorsqu’on fait appel à un CALLBACK, on utilise souvent (au moins) 2 paramètres typiques : « request » et « response ». Enchaîner les callbacks facilite le développement : c’est créer un enchaînement de causes-conséquences, représentées par les paramètres « request » et « response ».

Un middleware va mettre à notre disposition des fonctions toutes faites, construites sur le modèle du CALLBACK. Une fois le middleware chargé, il ne nous reste plus qu’à appeler ses composantes.

Un enchaînement (ou pile) typique de callbacks de fonctions du middleware pourrait se schématiser comme ceci :

Les middlewares communiquent les paramètres grâce à Connect

On remarque que cette technique de codage s’appuie sur la circulation de 4 paramètres (au maximum) :

-          err

C’est le paramètre qui reflète une erreur éventuelle

-          req

Ce paramètre contient toutes les informations relatives à la « cause » (l’événement) source du déclenchement. C’est souvent l’ensemble des données de la requête du visiteur de la page.

-          res

C’est ce paramètre qui sera renvoyé, modifié dans le déclenchement du Callback.

-          next

C’est un événement qui pointe sur la prochaine fonction à se faire appeler.

Pour invoquer un middleware avec express, on utilise :

app.use();

Pour tester quelques exemples, vous pouvez vous référer à un des liens suivants :

https://medium.com/@viral_shah/express-middlewares-demystified-f0c2c37ea6a1

https://expressjs.com/fr/guide/using-middleware.html 

4.1.   Express.js comme module

Express.js est un middleware très utilisé sur Node.js. Il facilite la création et la gestion d’une application (traitement des routes, appels en post et get, etc…)

4.1.1.      Montage de route

Voici un exemple simple d’appel à express sans condition :

var app = express();

 

app.use(function (req, res, next) {

  console.log('Time:', Date.now());

  next();

});

Ici, le middleware affichera l’heure à n’importe quelle demande d’accès au site.

Voici un autre exemple AVEC montage sur une route :

app.use('/user', function (req, res, next) {

  console.log('Request Type:', req.method);

  next();

});

Ici, le middleware affichera le type de demande d’accès au site, seulement si la route (URL) demandée est du type : http://localhost/user

De plus, on remarque que les 2 codes se terminent par un next() pour sortir du app.use et pointer vers le middleware suivant (formation d’une pile).

4.1.2.      Requêtes get et post

Il peut exister différents types de requêtes d’un client (navigateur internet) vers le serveur :

-          Accès get

C’est le plus classique, il reflète une demande de page tapée en URL, ou en ayant cliqué sur un hyperlien.

-          Accès post

C’est celui qui s’active à la suite d’une validation de formulaire par exemple (Voir le chapitre Formulaires dans le référentiel HTML).

D’autres types d’accès existent, mais sortent du rayon de ce cours.

Un appel de type app.use s’active pour tout type de requête.

4.1.3.      Les Routes (simples et dynamiques) avec Express.Js

Voyons maintenant comment on « monte une route », c’est-à-dire comment répondre à une requête seulement du type get :

app.get('/user', function (req, res, next) {

  res.send('Welcome to user page');

});

On remarque ici l’absence du next(). En effet, suite à un accès get, souvent on termine notre action par le remplissage de la page (res.send ici pour écrire en texte brut dans la page).

Notez la différence entre res.send (avec Express) et res.end ou res.write (utilisé précédemment, sans Express, chapitres précédents 1 et 2).

Utiliser app.use se fait souvent AVANT la construction de la page, pour des traitements divers.

L’exemple ci-dessus correspond à une réponse sur une route dite « simple » : le callback qui contient le res.send ne s’activera que sur une demande d’URL du type :

http://www.localhost/user ou http://www.localhost/user/

Voyons un exemple de réponse à une route dite de type « dynamique » :

app.get('/user/:id', function (req, res, next) {

  res.send('USER ID is :'+req.params.id);

});

Dans l’exemple ci-dessus, notre middleware s’active si l’on demande une route du type :

http://www.localhost/user/1 Ou encore http://www.localhost/user/toto

Mais elle ne s’active pas sur les adresses suivantes :

http://www.localhost/user Ou http://www.localhost/user/

On remarquera de plus que le paramètre id est récupéré dans

req.params.id

4.1.4.      Exemple de serveur minimal avec Express.js

 

Voici le code complet qui peut servir de point de départ pour créer un serveur en Node.js avec Express.js :

Les lignes 4 à 6 s’occupent de « monter la route » de la page d’accueil du site (« / »).

Ce code de départ peut être complété par l’ajout de différentes « routes » à la ligne 7.

Les lignes 8 à 11 s’occupent de traiter l’erreur 404, si aucune des routes au-dessus ne s’active.

Des middlewares de traitement initial peuvent aussi être ajoutés à la ligne 3. 

4.1.5.      Les paramètres de GET

 Une requête GET peut être accompagnée de valeurs paramétrées, que le client envoie vers le serveur. Exemple d’une URL lançant une recherche google sur le mot « toulouse » :

Ici on remarque que l’URL contient comme à l’habitude un nom de site (« google.com »), et une page (« /search »). Mais également le caractère « ? » suivi du nom du paramètre « q » et de sa valeur « toulouse ».

Ceci signifie que la requête GET envoyée du client au serveur s’accompagne du paramètre « q » ayant pour valeur « toulouse ».

Comme vu au chapitre formulaire du Référentiel HTML, le passage de paramètre est une des techniques souvent utilisées pour que le client envoie des données au serveur (l’autre méthode courante est la requête POST).

Contrairement à la méthode POST, la méthode GET paramétrée est à privilégier si :

-          Les données sont peu nombreuses (quelques paramètres tout au plus)

-          Les données ne sont pas confidentielles

Pour davantage de détails, se rendre au chapitre 4.2.

Voici un exemple illustrant la syntaxe en Node.js pour récupérer les paramètres envoyés par le client au serveur. Exemple de requête GET du client :

Elle contient 2 paramètres, « t » et « u » ayant respectivement pour valeurs « 7 » et « 8 ». Et elle active la route « /toto ». Noter le symbole de séparation des paramètres dans l’URL : « & ».

Voici le code Node.js pour récupérer ces 2 valeurs dans la bonne route :

On obtiendra comme résultat en console :

Ainsi, dans cet exemple, les paramètres se récupèrent sous la forme d’un objet à 2 composantes

req.query.t

Et

req.query.u

4.1.6.      Le Template EJS

Le Template EJS est un outil utile pour appeler les pages HTML depuis Node.js. En effet, il permet d’appeler des fichiers HTML complets avec ou sans passage de paramètres depuis notre code Node.js, suite à une requête de route. En effet, écrire du HTML en chaîne de caractère dans les parenthèses du res.send ça devient vite lourd. Enfin, EJS nous simplifie aussi l’inclusion d’en-têtes ou pieds-de-page génériques.

On débute par l’installation du module EJS en ligne de commande, car c’est un module non-inclus par défaut, comme express. Noter les minuscules « ejs » dans la syntaxe.

npm install ejs

4.1.7.      Création d’une page en EJS

Par défaut, EJS stocke ses pages dans un sous-dossier « views » de votre projet. Donc il faut commencer par créer ce dossier, puis d’y créer les pages à l’intérieur, dans un fichier « accueil.ejs ». Voici un exemple de fichier « accueil.ejs » minimal (page HTML minimale) :

<!DOCTYPE html>

<html>

  <head>

    <meta charset="utf-8" />

    <title> TITRE </title>

  </head>

  <body>

    <h1> Bonjour et bienvenue à l'Accueil ! </h1>

  </body>

</html>

Ensuite, il faut faire appel à cette page dans le fichier Node.js avec la commande res.render() qui va remplacer le res.send :

var express = require('express');

var app = express();

app.set('view engine', 'ejs');

 

app.get('/', function(req, res) {

    res.render('accueil');

});

Notez aussi ci-dessus la présence de la configuration d’express pour reconnaître le format EJS comme format de vue (le app.set).

Ainsi, plus besoin de longues chaînes de caractères incluant du HTML dans le js lui-même. En utilisant cet outil, tout le HTML est à part, dans le dossier « views », sous forme de fichiers avec l’extension ejs.

4.1.8.      Utilisation de « Templates »

EJS nous permet aussi d’exploiter des fichiers HTML génériques (comme les en-têtes, pieds de page) identiques à toutes nos pages, pour éviter les redondances et les erreurs de copier-coller :

Afin de profiter de cette modularité, il est conseillé de réorganiser les dossiers d’un projet (Node.js + express.js + EJS) comme suit :

- Dans « public », les fichiers accessoires qui seront envoyés au client : les css, les images et les JavaScript client (comme JQuery par exemple)

- Dans « views », nos templates (fichiers) EJS, en 2 catégories :

- le dossier « partials » contiendra les fichiers génériques : les portions communes à toutes nos pages (en-tête, pied de page, menu, …),

- et le dossier « pages » contiendra les fichiers construisant les différentes pages (page d’accueil, login, …)

Voici un exemple de construction de projet modulaire :

1/ Dans « viewspartials », on crée 3 fichiers EJS :

Un fichier « head.ejs » qui servira de patron pour tous les débuts de nos pages :

<!-- views/partials/head.ejs -->

 

<meta charset="UTF-8">

<title>Super Awesome</title>

 

<!-- CSS (load bootstrap from a CDN) -->

<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">

<style>

    body    { padding-top:50px; }

</style>

Ici on rend générique le titre de notre site, les styles, et en particulier l’appel à un framework CSS en ligne, appelé « bootstrap ».

Un fichier « header.ejs » qui créera un patron d’en-tête de page :

<!-- views/partials/header.ejs -->

 

<nav class="navbar navbar-default" role="navigation">

    <div class="container-fluid">

 

        <div class="navbar-header">

            <a class="navbar-brand" href="#">

                <span class="glyphicon glyphicon glyphicon-tree-deciduous"></span>

                EJS Is Fun

            </a>

        </div>

 

        <ul class="nav navbar-nav">

            <li><a href="/">Home</a></li>

            <li><a href="/about">About</a></li>

        </ul>

      

    </div>

</nav>

On remarque ici l’utilisation générique d’une barre de menu.

Et enfin un fichier de pied-de-page, « footer.ejs »

<!-- views/partials/footer.ejs -->

<p class="text-center text-muted">© Copyright 2014 The Awesome People</p>

On remarquera que nos 3 fichiers EJS ne contiennent QUE du HTML et du CSS.

2/ Une fois ces 3 patrons créés il ne nous reste plus qu’à les « coller » ensemble pour créer notre page d’accueil « index.ejs », dans le dossier « views – pages » :

<!-- views/pages/index.ejs -->

 

<!DOCTYPE html>

<html lang="fr">

<head>

    <%- include('../partials/head') %>

</head>

<body class="container">

 

    <header>

        <%- include('../partials/header') %>

    </header>

 

    <main>

        <div class="jumbotron">

            <h1>This is great</h1>

            <p>Welcome to templating using EJS</p>

        </div>

    </main>

 

    <footer>

        <%- include('../partials/footer') %>

    </footer>

   

</body>

</html>

On remarque ici l’utilisation de <%- include('../partials/nomdufichier') %>

L’utilisation des balises <% %> nous permet dans un fichier EJS d’inclure du JavaScript. Ainsi, dans ce dernier exemple, le fichier EJS contient du HTML ET du JavaScript.
Noter également que de rajouter le "-" derrière le "<%" fait simplement appel à la fonction INCLUDE qui insère une portion de page « générique » dans mes template (dossier partials) dans la page finale.

3/ On peut visualiser dans un navigateur le résultat de notre res.render :

La page est un assemblage des 3 portions, et elle fait également appel au BOOTSTRAP pour une mise en page agréable : tous les paramètres « class » présents dans le code HTML font appel à du formatage CSS préparé par BOOTSTRAP. Pour plus de détails, consulter les références.

4.1.9.      Passage de paramètres dans les template EJS

On va maintenant imaginer que le contenu de notre page doit exploiter des données du fichier Node.js. Prenons par exemple dans notre server.js :

app.get('/canards/:nbcanards', function(req, res) {

  res.render('./pages/canards.ejs', {qte: req.params.nbcanards});

});

Notez dans l’exemple ci-dessus que dorénavant la fonction res.render a un second paramètre, un objet JSON (association clé-valeur) qui servira à transmettre une valeur au code EJS de ma page. Ainsi, on peut récupérer le nombre de canards dans le fichier « canards.ejs » avec le paramètre « qte » comme cela :

<p> Vous avez <%= qte %> Canards. </p>

Noter la balise spéciale <% %> et le signe = juste derrière la balise ouvrante, qui incruste une valeur de variable JavaScript au milieu du HTML.

4.1.10. Mettre du code JavaScript dans EJS

On peut aller plus loin : si on veut interpréter les paramètres reçus dans la page EJS pour faire de la mise en forme de page conditionnelle, on peut utiliser les boucles « for » et les « if » :

<p>

  <% for (var i = 1; i <= qte; i++) { %>

    <% if (i == 1) { %>

        <p> Voici <%= i%> canard. </p>

    <% } else { %>

        <p> Voici <%= i%> canards. </p>

    <% } %>

  <% } %>

</p>

Remarquez bien la présence des balises spéciales <% %> mais sans le signe =. Remarquez aussi que CHAQUE ligne doit être SOIT dans ces balises spéciales SOIT en dehors. Pas de mélange. Remarquez enfin la présence des accolades fermantes SEULES sur leur ligne, mais dans les balises spéciales. Exemple de résultat du code précédent avec comme URL http://localhost/canards/3 :

4.2.   Utilisation des formulaires et récupération des résultats

Nous savons déjà comment faire des formulaires en HTML (Voir référentiel HTML, Chapitre 5). Cependant la transmission de données depuis un formulaire en Node.js ne fonctionne pas de la même façon qu’en PHP (voir Référentiel PHP, Chapitre 6).

Si la commande app.get nous permet de créer une route, la commande app.post en revanche nous permet de décider ce qu’il va se passer si un formulaire est transmis. Voici un exemple simple de formulaire et de récupération de ses données SANS l’usage d’EJS :

 

Outre la lourdeur de la syntaxe d’envoi d’une page HTML sans exploiter EJS, on note dans cet exemple qu’on utilise un nouveau module : « body-parser », qui est là pour récupérer les données issues d’un POST, et de les structurer. La ligne 5 enclenche ce module, et le configure afin que les données soient structurées telles qu’utilisées en ligne 26.

On remarque que l’événement conséquence de l’envoi du formulaire se situe dans un app.post, comme expliqué précédemment, et comme le montre la ligne 24.

On remarque que la structure de données récupérées est telle que nous le montrera le console.log de la ligne 25 (« req.body »), et qu’accéder à un membre de cette structure se fait tel qu’en ligne 26.

On remarque aussi qu’un formulaire pour Node.js en HTML débute par une balise form, avec seulement le paramètre « method = post » (ligne 13), contrairement à la version en PHP (voir HTML chapitre 5 et PHP chapitre 6).

On peut étayer ce simple exemple pour combiner formulaires, réception de leurs données, et EJS pour un effet optimal. 

4.3.   express-generator

Il existe une méthode encore plus performante de démarrer une nouvelle application, en suivant un « canevas » tout prêt, basée sur le framework express. On s’appuie pour cela sur un nouveau module, « express-generator », qui met à disposition un exécutable.

Donc, pour l’installer, il ne faut pas oublier l’option « -g » de npm :

npm install express-generator -g

Une fois ceci fait, on dispose dans la console Node.js d’une nouvelle commande :

express

Pour obtenir toutes les options de cette nouvelle commande, taper :

express -h

L’option qui nous intéresse principalement est celle qui dit à express-generator quel moteur de page on veut utiliser. Pour nous, c’est EJS :

express --view=ejs

Et enfin, si l’on veut spécifier un nom particulier à l’application que l’on veut créer :

express --view=ejs monApplication

(Pour plus de détails : voir Références)

Une fois cette commande lancée, express-generator a automatisé la création d’une application de base avec toutes les dépendances « classiques » utilisées, le « package.json » qui va bien.

Voici (principalement) ce qu’express-generator a déjà fait pour nous :

-          Un « package.json » est déjà tout garni, mais AUCUN module n’est installé par défaut. Il faudra donc lancer un NPM INSTALL avant d’exploiter notre application de base.

-          Un dossier « routes » qui contient des sous-fichiers js. Chacun des sous-fichiers ici s’occupera de sa propre route en get (voir les 2 exemples initiaux fournis)

-          Un dossier « public » qui contiendra les fichiers utiles à la page : les images (sous-dossier « image »), les scripts JavaScript du côté client (sous-dossier « javascript »), et les CSS (sous-dossier « stylesheets »).

-          Le fichier de base app.js est déjà prêt à fournir un serveur web « vide » de base.

o   Il initialise express.js et body-parser (ainsi que d’autres modules) comme vu précédemment.

o   Il s’appuie sur des sous-fichiers js dans le dossier « routes » pour 2 routes de base : « / » et « /users », ce qui fait un code mieux structuré.

o   Il enclenche l’usage d’EJS, et fournit dans le sous-dossier « views » 2 pages EJS de base.

o   Il s’occupe de la gestion du 404

-          Et enfin un dossier « bin » qui contient un script de démarrage du serveur (« www »), qui s’exécute lorsqu’on lance en ligne de commande :

npm start

Entre autres, ce script :

-          Gère les erreurs et redirige les messages d’erreur pour qu’ils s’affichent dans la page.

-          Gère les ports système, et ouvre le port 3000 (par défaut) et affichera des traces détaillées sur la console de l’activité client.