Mis à jour le mardi 2 janvier 2018
  • 10 heures
  • Difficile

Ce cours est visible gratuitement en ligne.

Ce cours existe en livre papier.

Vous pouvez obtenir un certificat de réussite à l'issue de ce cours.

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

TP : la todo list

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Je crois qu'il est grand temps de pratiquer un peu ! Nous avons fait le tour d'un bon nombre de fonctionnalités de base de Node.js et nous avons même appris à nous servir du micro-framework Express.

Pourtant, on n'a vraiment le sentiment d'avoir compris que lorsqu'on a pratiqué et réalisé une première vraie application. C'est ce que je vous propose de faire dans ce TP. :)

Nous allons réaliser ici une todo list (une liste de tâches). Le visiteur pourra simplement ajouter et supprimer des tâches.
Nous ferons donc quelque chose de très simple pour commencer, que vous pourrez ensuite améliorer comme bon vous semble !

Voici à quoi ressemble la page que nous devons créer :

L'application que nous allons créer. Superbe non ? ;o)
L'application que nous allons créer. Superbe non ? ;o)
  • On peut ajouter des éléments à la todolist via le formulaire.

  • On peut supprimer des éléments en cliquant sur les croix dans la liste.

  • La liste est stockée dans la session du visiteur. Si quelqu'un d'autre se connecte, il aura sa propre liste.

Les instructions sont simples et le code final ne sera pas très long. En revanche, si c'est votre première app un peu complète avec Node.js vous allez sûrement tâtonner et avancer doucement. Ne prenez pas peur, c'est parfaitement normal ! Persévérez et, si vraiment vous avez besoin d'aide, lisez la section "Un peu d'aide" avant de comparer votre travail avec le mien. ;)

Vous avez toutes les infos, à vous de jouer !

Besoin d'aide ?

Allons bon, comme ça vous avez besoin d'aide ? Vous êtes perdus et vous ne savez pas par où commencer ? :p

Je rappelle à quoi ressemble l'application que nous devons créer. Je vous ai présenté cette capture en introduction du TP :

L'application que nous allons créer. Toujours aussi belle !
L'application que nous allons créer. Toujours aussi belle !

Les modules et le package.json

Prenons les choses dans l'ordre en respirant un grand coup. De quoi a-t-on besoin ?

  • Express : sans le framework Express la mission sera vraiment très difficile. Maintenant que nous savons utiliser ce framework, ce serait dommage de s'en priver.

  • EJS (ou un autre système de template) : cela nous permettra de construire facilement la page HTML qui affiche la liste des tâches et le formulaire.

A priori ces modules suffisent. Une première bonne pratique serait de se créer un fichier package.json dans le dossier de notre projet :

{
    "name": "ma-todolist",
    "version": "0.1.0",
    "dependencies": {
        "express": "~4.11.0",
        "ejs": "~2.1.4",
        "cookie-session": "~1.1.0",
        "body-parser": "~1.10.1"
    },
    "author": "Mateo21 <mateo21@email.com>",
    "description": "Un gestionnaire de todolist ultra basique"
}

Vous pouvez remplacer le nom, l'auteur et la description comme bon vous semble bien sûr. ;)

Pour les numéros de version d'express et d'ejs, je me suis basé sur les versions disponibles au moment de la rédaction de ce tutoriel. Comme l'univers de Node.js avance vite, vous aurez sûrement des versions plus récentes. Le petit tilde ~ permet d'autoriser les futurs patchs de ces modules mais pas les nouvelles versions mineures ou majeures, ce qui nous garantit que leur API ne changera pas, et donc que notre code continuera à fonctionner même avec ces mises à jour.

Maintenant que vous avez votre package.json, installez les dépendances avec un simple :

npm install

Nous sommes prêts à travailler !

Les routes

Je vous avais dit que bien définir ses routes était important quand on construit une application web. Si vous hésitez et ne savez pas par où commencer, je vous invite à lister les routes de votre application. Que doit-elle faire ?

  • Lister les tâches

  • Ajouter une tâche

  • Supprimer une tâche

A priori, on peut associer une route à chacune de ces fonctionnalités :

  • /todo : liste les tâches

  • /todo/ajouter : ajoute une tâche

  • /todo/supprimer/:id : supprime la tâche n°id

Vous allez donc écrire les routes comme ceci dans votre fichier app.js :

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

});

// ...

En revanche, il faudrait qu'on se penche un instant sur la question du formulaire. En général, les formulaires envoient les données avec la méthode POST et non la méthode GET. L'ajout d'une tâche va donc se faire sur la route /todo/ajouter mais avec la méthode POST. C'est pour ça qu'au lieu de faire appel à .get() on devra faire appel à .post() pour cette route :

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

})

Bien chaîner les appels aux middlewares

Nous savons que nous aurons besoin d'un système de sessions. La doc du middleware cookie-session dont je vous ai parlé nous indique comment utiliser les sessions.

Autre chose : nous aurons besoin de récupérer les données du formulaire dans /todo/ajouter. Nous avons appris à récupérer des paramètres depuis l'URL mais pas depuis les formulaires. En fait, vous avez ici besoin du middleware body-parser. Après un peu de configuration, vous aurez ensuite accès à req.body.nomDuChamp.

Du coup, le début de notre code JavaScript devrait ressembler à quelque chose comme ça :

var express = require('express');
var session = require('cookie-session'); // Charge le middleware de sessions
var bodyParser = require('body-parser'); // Charge le middleware de gestion des paramètres
var urlencodedParser = bodyParser.urlencoded({ extended: false });

var app = express();


/* On utilise les sessions */
app.use(session({secret: 'todotopsecret'}))


/* Gestion des routes en-dessous
   ....                         */

Le paramètre secret envoyé au module de session est obligatoire : il permet de sécuriser les cookies de session. Envoyez la valeur de votre choix. Notez que vous pouvez envoyer d'autres options, comme la durée de vie du cookie de session (par défaut, la session durera tant que le navigateur restera ouvert).

Allez, je vous en ai trop donné, à vous de coder ! :)

Correction

Allez hop hop hop, c'est l'heure de la correction ! Ne lisez cette section que si vous avez réussi ou que toutes vos tentatives ont échoué et que vous êtes désespérés. :p

Le code n'est pas si difficile à lire au final. Il n'est pas très long non plus, mais il fallait bien réfléchir pour trouver quoi écrire exactement.

La solution

Ci-dessous, vous avez le code JavaScript que j'ai produit dans le fichier principal app.js. Notez bien que c'est une façon de faire et que vous n'êtes pas obligés d'avoir produit un code strictement identique au mien !

var express = require('express');
var session = require('cookie-session'); // Charge le middleware de sessions
var bodyParser = require('body-parser'); // Charge le middleware de gestion des paramètres
var urlencodedParser = bodyParser.urlencoded({ extended: false });

var app = express();


/* On utilise les sessions */
app.use(session({secret: 'todotopsecret'}))


/* S'il n'y a pas de todolist dans la session,
on en crée une vide sous forme d'array avant la suite */
.use(function(req, res, next){
    if (typeof(req.session.todolist) == 'undefined') {
        req.session.todolist = [];
    }
    next();
})

/* On affiche la todolist et le formulaire */
.get('/todo', function(req, res) { 
    res.render('todo.ejs', {todolist: req.session.todolist});
})

/* On ajoute un élément à la todolist */
.post('/todo/ajouter/', urlencodedParser, function(req, res) {
    if (req.body.newtodo != '') {
        req.session.todolist.push(req.body.newtodo);
    }
    res.redirect('/todo');
})

/* Supprime un élément de la todolist */
.get('/todo/supprimer/:id', function(req, res) {
    if (req.params.id != '') {
        req.session.todolist.splice(req.params.id, 1);
    }
    res.redirect('/todo');
})

/* On redirige vers la todolist si la page demandée n'est pas trouvée */
.use(function(req, res, next){
    res.redirect('/todo');
})

.listen(8080);

Il y a aussi un fichier de template qui va avec. Le fichier todo.ejs :

<!DOCTYPE html>

<html>
    <head>
        <title>Ma todolist</title>
        <style>
            a {text-decoration: none; color: black;}
        </style>
    </head>

    <body>
        <h1>Ma todolist</h1>

        <ul>
        <% todolist.forEach(function(todo, index) { %>
            <li><a href="/todo/supprimer/<%= index %>">✘</a> <%= todo %></li>
        <% }); %>
        </ul>

        <form action="/todo/ajouter/" method="post">
            <p>
                <label for="newtodo">Que dois-je faire ?</label>
                <input type="text" name="newtodo" id="newtodo" autofocus />
                <input type="submit" />
            </p>
        </form>
    </body>
</html>

Et avec ça, l'indispensable package.json qui permet d'installer les dépendances avec un simplenpm install:

{
    "name": "ma-todolist",
    "version": "0.1.0",
    "dependencies": {
        "express": "~4.11.0",
        "ejs": "~2.1.4",
        "cookie-session": "~1.1.0",
        "body-parser": "~1.10.1"
    },
    "author": "Mateo21 <mateo21@email.com>",
    "description": "Un gestionnaire de todolist ultra basique"
}

Les explications qui vont bien

Bon, déjà mon code est assez commenté, ce qui devrait vous permettre de vous y retrouver.

Vous remarquerez que je me suis permis de rediriger le visiteur vers la liste (/todo) après un ajout ou une suppression d'élément, avec res.redirect('/todo').

La liste des tâches est stockée dans un array (tableau). C'est d'ailleurs lui qui est la cause de la petite subtilité de ce programme : comme JavaScript n'apprécie pas qu'on essaie de parcourir des arrays qui n'existent pas, j'ai créé un middleware qui crée un array vide si le visiteur n'a pas de todolist (parce qu'il vient de commencer sa session par exemple). C'est le rôle de ce code :

.use(function(req, res, next){
    if (typeof(req.session.todolist) == 'undefined') {
        req.session.todolist = [];
    }
    next();
})

Cette fonction middleware reçoit la requête, la réponse et la prochaine fonction à exécuter. J'ai donc écrit moi-même un middleware à l'instar de cookie-parser ou cookie-session. Son rôle est très simple : il vérifie s'il y a une todolist dans la session et, si ce n'est pas le cas, il crée un array vide (d'où les []). Ca nous évite beaucoup d'erreurs par la suite.

Pourquoi avoir créé un middleware ? Parce que c'est le seul moyen à ma disposition pour exécuter des fonctionnalités avant le chargement de n'importe quelle page. Et pour que le middleware "passe le bébé à son voisin", je dois finir impérativement par un appel à next() (la fonction suivante). Dans le cas présent, next() fait référence à .get('/todo', function() {}).

A part ça, rien de vraiment particulier. J'ajoute des éléments à la fin du tableau avec .push() et je retire des éléments avec .splice(), mais ça c'est du JavaScript de base. ;)

Du côté du template, qu'est-ce qu'il y a de particulier ? Hmm, pas grand chose... Si ce n'est que je parcours la todolist avec un forEach (là encore, c'est du JavaScript) :

<% todolist.forEach(function(todo, index) { %>
    <li><a href="/todo/supprimer/<%= index %>">✘</a> <%= todo %></li>
<% }); %>

Télécharger le projet

Je vous ai donné tout le code du projet, mais si vous insistez pour télécharger le tout dans un fichier .zip, alors suivez le lien ci-dessous :

Télécharger le projet Node.js ma-todolist.zip

N'oubliez pas de faire unnpm installpour installer les dépendances avant de l'exécuter !

Allez plus loin !

Ma petite todolist est très basique. Vous pouvez lui ajouter de nombreuses fonctionnalités :

  • Modification des noms des tâches

  • Réagencement des tâches entre elles

  • Exportation CSV

  • Attribution d'une priorité et d'une date limite

  • Persistence de la todolist (stockage dans une base de données ou une base NoSQL)

  • Partage d'une todolist entre plusieurs personnes

  • Synchronisation de la todolist en temps réel entre les personnes sans avoir besoin de recharger la page

Certaines de ces fonctionnalités sont plus faciles à réaliser que d'autres. Pour d'autres, il vous faudra découvrir et utiliser de nouveaux modules.

Vous avez de quoi vous amuser pendant un bon petit moment, bon courage ! :)

Exemple de certificat de réussite
Exemple de certificat de réussite