logoHE logoHE

Programmation Web : les bases

MPA / JavaScript / Node.js / Express / MVC
BINV1051-Web1

Semaine 1 - Objectifs de la semaine

  • Prise en main de l’environnement de développement (Node.js / VScode)
  • Savoir utiliser les éléments de base du langage JavaScript (condition, boucle, tableaux, …)
  • Comprendre l’architecture Web Client-Serveur et son application avec Node.js/Express
  • Renvoyer une page HTML à une requête GET

Semaine 1 - Infos générales

Syllabus - Slides de ce cours

Ces slides ont été fait avec R studio et revealjs. Revealjs est framework de présentation basé sur du JavaScript !

Hormis le côté fun de RevealJS, plusieurs fonctionnalités vous seront utiles lors des exercices :

  • Vous avez un menu hiérarchique pour accéder directement à un chapitre (en bas à gauche)
  • Vous pouvez rechercher un mot dans les slides via le champ recherche en haut à gauche
  • Les flèches de navigation vers la gauche et la droite permettent de passer d’un chapitre à l’autre
  • Les flèches de navigation vers le bas et le haut permettent de naviguer à l’intérieur d’un chapitre

Autres fonctionnalités RevealJS

  • la touche ‘o’ (overview) vous donnera une vue globale de tous les slides
  • la touche ‘espace’ pour un parcours séquentiel (normal des slides)

Objectifs du cours

  • Compétences visées
    • Savoir écrire une Multi-Page Application (MPA) utilisant Node.js/Express
    • Gérer le contexte d’un utilisateur selon le mécanisme de session
    • Respecter l’architecture MVC lors de l’élaboration d’un site Web
  • Compétences prérequises
    • Base HTML et CSS
    • Base en anglais (commentaires de code en anglais)
    • Bases d’un langage de programmation (structures conditionnels, boucles, fonctions…)

Engagement pédagogique (1)

  • Détails des engagements pédagogiques : Fiche UE
  • Evaluation UE :
    • HTML : 10%
      • Q1
      • Projet
      • pas représentable ni en juin, ni en septembre
    • JavaScript : 90%
      • Q2
      • Examen
      • Ecrit sur PC (juin et représentable en septembre)

Attention le projet web ne fait pas partie de cette UE.

Voir Fiche UE Projet Web

Supports du cours

Supports principaux :

  • Théorie :
    • Ces slides
    • Vidéos
  • Exercices : Enoncés des exercices
  • Solutions : Solutions des exercices via Git disponibles le vendredi

Tous ces supports sont accessibles et regroupés sur MooVin

Autres supports

Autres supports très utiles !

Mozilla Network

Remarques très importantes (1)

  • Cours Web 1 très important
    • Cours métier
    • Rythme soutenu (6h pendant 6 semaines)
    • Prépare au Projet Intégrateur Web
    • Les exercices de la dernière séance sont moins nombreux ou en BONUS !

Pour ces raisons, investissez-vous dans ce cours maintenant ! Terminez les exercices qui sont prévus durant la semaine. Tout retard sera difficile à rattraper car la matière avance chaque semaine.

Remarques très importantes (2)

  • Comprenez ce que vous faites !
    • Copier/coller du code sans comprendre n’est pas une bonne méthode
    • Lisez le message d’erreur avant d’appeler le professeur et essayez de comprendre
    • Posez vos questions au prochain cours théorique ou en séances

Pour ces raisons, investissez-vous dans ce cours maintenant ! Terminez les exercices qui sont prévus durant la semaine. Tout retard sera difficile à rattraper car la matière avance chaque semaine.

Semaine 1 - Environnement de travail

Environnement de travail (1)

  • Systèmes d’exploitation possibles : Windows, Linux, MacOS

    L’examen se déroulera sur les machines de l’école sous l’environnement Windows.

  • Editeur recommandé : Visual Studio Code avec extensions

    • JavaScript (ES6) code snippets
    • Handlebars
    • npm Intellisense
    • Path Intellisense
    • Material Icon Theme

    TRES IMPORTANT : activer Autosave dans Vscode (voir image slide suivant)

  • Navigateur recommandé : Firefox

Environnement de travail (3)

Environnement de travail (4)

Vous pouvez travailler pour ce cours :

  • sous le système d’exploitation de votre choix (Windows, Linux, MacOS)
  • sur la machine de votre choix (machine de l’école, votre propre portable)

Sur les machines de l’école, tout est déjà installé.

Si vous souhaitez travailler sur votre propre machine, consultez les slides suivants sur les installations nécessaires

Environnement de travail (5)

Où enregistrer vos fichiers ?

  • OneDrive : Déconseillé
  • Machine école - disque U : OK
  • Machine école - Documents : OK
    • attention à recopier votre projet JS sur U ou clé USB en fin de séance
  • Machine personnelle - Local : OK
  • Clé USB : OK
    • faire un backup de temps en temps

Installations nécessaires (1)

  • Node.js LTS :

    Il est important de bien choisir la version stable de Node.js à savoir la version LTS (Long Time Support) 64 bits !!!

    sudo apt install nodejs && sudo apt install npm

    Laissez les choix par défaut si des questions (case à cocher, …) sont posées pendant l’installation.

    Testez votre installation Node.js dans une console/terminal :

    node -v 

Installations nécessaires (2)

  • Visual Studio Code :

    sudo snap install --classic code

    Sous Windows, cochez “Ajouter VS code” au menu contextuel lors de l’installation

  • Firefox/Chrome/Safari

Nous vous conseillons vivement d’utiliser Firefox comme navigateur !
En effet, Firefox est le plus respectueux des normes. Donc ce qui fonctionne sous Firefox a de fortes chances de fonctionner sur un autre navigateur, l’inverse n’est pas vrai.

Installations nécessaires (3)

Extensions à installer sous VsCode :

Semaine 1 - Langage JavaScript

Types de langages de programmation

  • Compilé

    • Transformation du code source en binaire par un compilateur
    • Ex : .java -> .class
    • Exécution du code binaire
    • Souvent langage statiquement typé
  • Interprété

    • Exécution directe du code source
    • Ex : Javascript
    • Souvent langage dynamiquement typé

Eléments du langage JavaScript (JS)

Le code JavaScript(JS) se compose de :

  • Instructions (déclaration de variables, …)
  • Structure de contrôle (for, if, …)
  • Commentaires

Les instructions sont normalement séparées par des “;”

Les “;” ne sont pas obligatoires en JS mais vivement conseillés

console.log

console.log vous sera très utile en JS notamment pour le débogage. Elle permet d’afficher un message ou encore le contenu d’une variable dans la console.

console.log("coucou");
console.log(x);
console.log("contenu de la variable x : + " + x);

Variables

  • Sensibles à la casse
  • Type de la variable déterminé à l’exécution (langage dynamiquement typé)
  • Déclarations de variables :
    • let : portée associée à un bloc
    • var : portée globale. A ne pas utiliser pour ce cours !
    • const : portée associée à un bloc et immuable (la valeur ne peut pas être changée)
  • Il est possible également d’utiliser directement une variable sans utiliser let, var, const !

let

if (true) {
  let blockScope = "Hello";
  console.log(blockScope); // Hello
}
console.log(blockScope); // Uncaught ReferenceError: blockScope is not 
                        // defined

const

if (true) {
  const constVar = "Hello";
  console.log(constVar); // Hello
  const SITE_URL = "http://MyCMS.org";
  console.log(SITE_URL); // http://MyCMS.org
  constVar = "Hi";
  console.log(constVar); // Uncaught TypeError: Assignment to constant                                  
                         //    variable.
}

Variables - Remarque importante

Nous avons présenté ici les différentes possibilités pour déclarer ou non les variables.

Dans le cadre de ce cours, nous nous obligerons à respecter la convention suivante :

  • Toutes les variables seront déclarées !
  • Les variables seront déclarées avec let ou const !
  • Aucune variable sans déclaration !
  • Aucune variable déclarée avec var !

Commentaires

  • Commentaire sur une ligne : //
  • Commentaire sur plusieurs lignes : /* */
function raiseAlert(message) {
  // Single line comment
  console.log(message);
  /* Regular comment
    on multiple lines
    */
  console.log("An alert has been raised.");
}

Types des variables

Rappel : JS est un langage dynamiquement typé !

  • Number (Nombre) : un seul type pour les entiers, réels, doubles.
  • String (Chaîne) : comprise entre guillemets simples ou doubles.
  • Bool (Booléen) : true / false
  • Array (Tableau) :
let students = ["studentInfo", "studentDiet"]; 
  • Object (Objet) :
let person = { name: "Choquet", firstname : "olivier"}; 

Types des variables - Type String

// simple declaration of strings
const welcome = "Hello";
const world = "World";
// string concats
const concat = welcome + " " + world +" !";
// string type have many functions
const maj = welcome.toUpperCase();
// string interpolation -> use backstick (`)
const number = 7;
const message = `The number is ${number}`;

Types des variables - typeof

console.log(typeof 12); // number
console.log(typeof "I love JS"); // string
console.log(typeof true); // boolean
console.log(typeof undeclaredVariable); // undefined

Opérateurs d’égalité

  • Stricte sans conversion de type : === ou !==
  • Avec conversion de type : == ou !=
1 === 1; // true
"1" === 1; // false
1 == 1; // true
"1" == 1; // true
0 == false; // true
0 == null; // false
let object1 = { key: "value" },
object2 = { key: "value" };
object1 == object2; //false

Préférons !== et === quand c’est possible pour éviter les pièges du dynamiquement typé !

Opérateurs logiques

  • && : ET logique
  • || : OU logique
  • ! : NOT logique

Plus de détails : MDN Network

Conditions

  • if … else
let isAuthenticated = false;
if (isAuthenticated) {
  console.log("Render the HomePage.");
  console.log("You are authenticated.");
} else {
  console.log("Render the Login Page."); // Render the Login Page.
  console.log("You are not authenticated."); // You are not authenticated.
}

Objets

  • Création d’objets - notation littérale
// create objects without class -- LITTERAL NOTATION
// theses objects are simply a collection of properties 
let vegetable = { name : "carrot", color: "orange" };
  • Accès à un champ objet
let vegetable = { name : "carrot", color: "orange" };
// show/access field name
const nameofvegetable = vegetable.name;
  • Affichage d’un objet en console - Débogage
// Show object with all fields in litteral notation
console.log(JSON.stringify(vegetable));

Tableaux

  • []
  • propriété length
  • push / pop
let vegetables = ["onion","garlic"];
vegetables.push("carrot"); // array vegetables now  : ["onion", "garlic", "carrot"]
  • Affichage d’un tableau en console - Débogage
// Show array with all fields in litteral notation
console.log ( JSON.stringify(vegetables));

Toujours créer un tableau(même vide) avec l’opérateur [] !

Semaine 1 - Node.js/Express (MPA)

Introduction Node.js/Express

  • Node.js est une plateforme logicielle écrite en JS embarquant un serveur HTTP
  • Express est un framework facilitant l’écriture d’application Web utilisant Node.js
  • Node.js permet l’installation facile de modules via la commande :
npm install <module> 

MPA signifie Multi-Page Application par opposition aux SPA (Single Page Application) que vous verrez en Bloc 2. Une application MPA renverra(“render”) à chaque requête une nouvelle page/vue.

Remarque : Node.js n’est pas qu’un serveur web mais dans le cadre de ce cours nous nous limiterons à cet aspect.

Architecture Web (1)

Architecture Web (2)

  • Le protocole HTTP ou HTTPS permet l’envoi de requêtes (request) et la réception de réponses (response).
  • La réponse est généralement une page HTML !
  • Le protocole HTTP dispose de plusieurs méthodes pour effectuer une requête.
    • GET : consulter de l’information, afficher qqch
    • POST : modifier des données ou données sensibles (mot de passe par ex.)
  • Le protocole HTTP renvoie un code de retour avec la réponse
    • 200 OK
    • 404 Ressource Not found

Architecture Web (3)

On peut consulter la requête HTTP via les outils de développement présents dans les navigateurs via l’onglet Réseau.

Utilisation Node.js

Dans Visual Studio Code, ajoutez la fenêtre “Terminal” (Terminal -> New Terminal).

Utilisation Node.js

Lancez votre application via :

npm start

Ensuite allez sur l’adresse : http://localhost:3000 via votre navigateur

Pour arrêter votre application dans Node.js -> CTRL-C

Tester votre application via :

(Ceci n’est pas obligatoire mais peut vous aider à déboguer)

npm run test

Introduction Express (MPA)

Site Express
Express est un framework facilitant le développement d’application sous Node.js. Express se charge de récupérer et vous présenter les requêtes HTTP sous un format simple. Grâce à Express, vous ne devez écrire que le code de gestion(App-Specific Middleware) de votre site.

Architecture Express

Coder avec Express (Intro)

Nous verrons les répertoires et fichiers d’Express au fur et à mesure des semaines.

Cette semaine, nous nous intéressons à :

  • routes : routes accessibles pour le site Web (Ex: /, /forum, /products)
  • views : fichiers handlebars(hbs) pour afficher une réponse. Cette réponse est essentiellement une page HTML
  • public : fichiers statiques. C’est ici que vous trouverez les images dont le site a besoin et les feuilles de styles (CSS)

Coder avec Express (Répertoires)

Coder avec Express (ROUTES)

// index.js in routes directory
const express = require('express');
const router = express.Router();

// when i receive a request GET on / 
// '/'  is the root of the siteWeb -> lhttp://localhost:3000 
router.get('/', (req, res) => {
  // send a response with render -> response in HBS
  // res.render search a file named index.hbs in views dir !
  res.render('index.hbs');
});

// when i receive a request GET on /brol 
// http://localhost:3000/brol 
router.get('/brol', (req, res) => {
  // send a response with render -> response in HBS
    // res.render search a file named brol.hbs in views dir !
  res.render('brol.hbs');
});

module.exports = router;

Coder avec Express (VUES/VIEWS)

<!-- index.hbs code -->
<section>
    <h1>Bienvenue sur le site d'apprentissage du Javascript et dédié aux ExoPlanètes.</h1>

    <h3>Qu'est-ce qu'une exoplanète?</h3>
    <p>
        Une exoplanète est une planète située en-dehors du Système solaire, autrement dit :
        elle orbite autour d'une étoile autre que le Soleil. <br>
        La plupart des exoplanètes découvertes à ce jour sont situées à moins de 400 années-lumière de notre Système
        solaire.
        Une année-lumière équivaut approximativement à 9.460 milliards de km.
    </p>
    <img src="images/exoplanet.jpg" alt="illustration exoplanète" class="img" />
</section>

Coder avec Express (Intro HBS)

  • layout.hbs : modèle de base du site.
    • Eviter la redondance de code HTML
    • variable {{{body}}} : affichage code de la vue demandée
router.get('/', (req, res) => {
  // send a response with render -> response in HBS
  res.render('index.hbs');
});

Donc dans le code ci-dessus res.render va afficher layout.hbs avec {{{body}}} qui sera index.hbs

Coder avec Express (Variable simple HBS)

Variable HBS simple :

router.get('/', (req, res) => {
  // send a response with render -> response in HBS
  res.render('index.hbs', { varhbs : "handlebars"});
});
<section>
  <p> Bonjour {{varhbs}} </p>
</section>

Coder avec Express - (Variable liste HBS)

Variable HBS tableau/liste :

router.get('/', (req, res) => {
  const tableOfVegetables = ["tomato", "banana"];
  res.render('index.hbs', { simpleStringVar : "handlebars", vegetablesList : tableOfVegetables });
});
<section>
  <p> Bonjour {{simpleStringVar}} </p>
  <ul>
  {{#each vegetablesList}}
    <li> {{this}} </li> 
  {{/each}}
  </ul>
</section>

Pour éviter une erreur de syntaxe, je vous conseille de taper each dans votre fichier vue. VsCode vous aidera à créer le bloc #each

Semaine 2 - Objectifs de la semaine

  • Langage JavaScript : boucles, fonctions
  • Traitement de formulaire - réception de paramètres GET / POST
  • render et redirect
  • Handlebars #if, #exists

Semaine 2 - Langage JavaScript

Boucles (1)

  • for … of / for … in
// for ... of pour les tableaux
let vegetables = ["onion","garlic"];
for (let vege of vegetables)
{
  console.log(vege);
}

// for ... in pour les objets JSON
const student = { name: 'Monica', age: 12 }
for (let key in student ) {
    // display the properties
    console.log(key + " => " + student[key]);
}
// expected output:
// name => Monica
// age => 12

Boucles (2)

Parcours d’un tableau d’objets …

 let students = [{name:'Monica', age: 12}, {name:'Sandra', age: 13} ];
  for (let stud of students) {
    for(let key in stud) {
      console.log("Key : " + key + " Value : " + stud[key]);
    }
  }

Boucles (3)

  • for, while

// for traditionnel si besoin d'un index
for (let index = 0; index < 5; index++) {
  console.log(index); // 0 1 2 3 4 
}
// while existe aussi
let vegetables = ["onion","garlic"];
let i = 0;
while(i<vegetables.length)
{
  console.log(vegetables[i]);
  i++;
}

Utilisons de préférence le for … in et for … of !

Fonctions

function welcomeMessage(message) {
  return "Message : " + message;
}

let myMessage = welcomeMessage("Welcome to everyone!");
console.log(myMessage); // Message : Welcome to everyone!

Fonctions comme valeur de variable

En JS, il est possible (et c’est utilisé fréquemment) d’assigner une fonction comme valeur de variable.

function welcomeMessage(message) {
  return "Message : " + message;
}

let x = welcomeMessage;
let myMessage = x("Hi");
console.log(myMessage); // Message : Hi

Fonctions anonymes

En JS, les fonctions ne doivent pas avoir nécessairement un nom. Ceci est également fortement utilisé !

const welcome = function (message) {
  return "Message : " + message;
};

let myMessage = welcome("Hello world ; )");
console.log(myMessage); // Message : Hello world ; )

Fonctions fléchées

Les fonctions fléchées sont souvent anonymes et ont une syntaxe plus courte.

const welcome2 = (message) => {
  return "Message : " + message;
};

let myMessage = welcome2("Hello world...");
console.log(myMessage); // Message : Hello world...

// OTHER EXAMPLE
const higher = n => n + 1;
console.log(higher(1)); // 2

C’est ce type de fonction que vous rencontrerez le plus souvent et que l’on vous demandera de réaliser !

Paramètres des fonctions

  • Portée locale au sein de la fonction
  • Passage d’argument par valeur, sauf pour les objets
  • Passage d’un objet par référence

Paramètres par valeur

function print(myMessage) {  
    console.log(myMessage); // Hello
    myMessage = "Good bye";
    console.log(myMessage); // Good Bye
}

let myMessage="Hello";
print(myMessage);
console.log(myMessage); // Hello

Paramètres par référence

let myMessage = { content: "Hello" };
consolePrint(myMessage);

function consolePrint(myMessage) {
  console.log(myMessage.content); // Hello
  myMessage.content = "Good bye";
  console.log(myMessage.content); // Good Bye
}

console.log(myMessage.content); // Good bye

Semaine 2 - Routing

Routing - Paramètres (1)

  • Récupération des paramètres d’un formulaire HTML :
    • GET : req.query.”input name”
    • POST : req.body.”input name”
<form method="post" action="/register">
     <input class="form-control" id="email" type="text" name="email" />
     <input type="submit">
</form>
/* POST new user */
router.post("/register", function (req, res, next) {
  console.log("POST /register:",req.body.email) ;
  res.render("index.hbs");
});

Routing - Paramètres (2)

Les paramètres lors d’un GET sont présent dans l’URL. Ils peuvent être placés directement comme ceci : http://localhost:3000?id_student=45

Ils peuvent être placés également comme ceci :

<form method="get" action="/">
     <input class="form-control" type="text" name="id_student" />
     <input type="submit">
</form>

Dans les 2 cas, on récupère le(s) paramètre(s) :

/* GET id_student */
router.get("/", function (req, res, next) {
  console.log("GET /:",req.query.id_student) ;
  res.render("index.hbs");
});

Routing - Réponse

Le but dans une application MPA est de renvoyer une réponse à une requête. La réponse sera renvoyée via l’une des 4 instructions suivantes :

  • res.render : renvoyer une page HTML rendue via un moteur de template (handlebars)
  • res.send : renvoyer une réponse (une string, un objet JSON, …)
  • res.sendFile : renvoyer un fichier
  • res.redirect : rediriger vers une autre route

En gras, les actions les plus courantes pour une MPA.

Routing - res.send / res.sendFile

app.get('/', (req, res) => {
  res.send("Bonjour");
});

app.get('/index.html', (req, res) => {
  // __dirname return the root path of the project
  res.sendFile(__dirname + "/index.html");
});

Routing - res.render

res.render renverra une vue c’est-à-dire un fichier hbs présent dans le répertoire views. La fonction peut prendre en argument des paramètres.

router.get('/', (req, res) => {
  // send a response with render -> response in HBS
  const table = ["january", "february", "march"];
  res.render('index.hbs', { param1: "bonjour", param2: table });
});

Routing - res.redirect

res.redirect renverra vers une route.

router.get('/', (req, res) => {
  // redirect to route /students
  res.redirect('/students');
});

Attention la route à fournir se fait toujours à partir de la racine du site !!!

Routing - res.render vs res.redirect

res.render res.redirect
renvoie une vue redirige vers une route
chemin vers le fichier hbs à partir du répertoire views route à partir de la racine (localhost:3000)
ne commence pas par un / commence par un /
on peut passer des paramètres à la vue directement le passage de paramètre doit se faire via la route et est limité !
Ex : res.render(“students/index.hbs”, { studentList }); Ex: res.redirect(“/students?id=7”);

Semaine 2 - Handlebars

Handlebars - Rendu conditionnel

#if : si la condition est vraie le bloc est affiché, sinon c’est le bloc du else, s’il y en a un, qui sera affiché.

{{#if isAuthenticated}}
  <a class="nav-item nav-link" href="/">Home</a>
  <a class="nav-item nav-link" href="/list">List users</a>
  <a class="nav-item nav-link" href="/logout">Logout</a>
  <a class="nav-item nav-link" href="/">{{user}}</a>
{{else}}
  <a class="nav-item nav-link" href="/">Home</a>
  <a class="nav-item nav-link" href="/register">Register</a>
  <a class="nav-item nav-link" href="/login">Login</a>
{{/if}}

Dans vscode, vous pouvez tapez “if” dans un fichier hbs et vscode vous aidera à créer un bloc if handlebars.

La condition utilisée dans un #if peut être une variable non booléenne (string, tableau, variable null ou undefined). Dans ce cas une string vide, un tableau vide ou une variable nulle ou undefined seront considérés comme faux.

Handlebars - Rendu conditionnel

#unless : si la condition est fausse le bloc est affiché, sinon c’est le bloc du else, s’il y en a un, qui sera affiché.

{{#unless isAuthenticated}}
  <a class="nav-item nav-link" href="/">Home</a>
  <a class="nav-item nav-link" href="/register">Register</a>
  <a class="nav-item nav-link" href="/login">Login</a>
{{else}}
  <a class="nav-item nav-link" href="/">Home</a>
  <a class="nav-item nav-link" href="/list">List users</a>
  <a class="nav-item nav-link" href="/logout">Logout</a>
  <a class="nav-item nav-link" href="/">{{user}}</a>
{{/unless}}

unless est donc un if inversé !

La condition utilisée dans un #unless peut être une variable non booléenne (string, tableau, variable null ou undefined). Dans ce cas une string vide, un tableau vide ou une variable nulle ou undefined seront considérés comme faux.

Handlebars - Rendu liste/table (1)

#each : itération pour afficher les éléments d’un tableau

<ul class="list-group list-group-horizontal-lg">
    {{#each userList}}
    <li class="list-group-item">{{this}}</li>
    {{/each}}
</ul>

Dans vscode, vous pouvez tapez “each” dans un fichier hbs et vscode vous aidera à créer un bloc each handlebars.

Ceci ne peut fonctionner qu’avec une variable de type liste /tableau !

Handlebars - Rendu liste/table (2)

#each : itération pour afficher les éléments d’un tableau avec alias

<ul class="list-group list-group-horizontal-lg">
    {{#each userList as |userItem|}}
    <li class="list-group-item">{{userItem}}</li>
    {{/each}}
</ul>

Remarquez l’alias créé via as |userItem| et l’emploi de celui-ci {{userItem}}.

Handlebars - Test d’égalité

eq permet de faire un test d’égalité dans une vue handlebars.

<section>
  <p>
  {{#if (eq user.email "olivier.choquet@vinci.be") }} 
    Bonjour Olivier ! 
  {{/if}}
  </p>
</section>

les parenthèses autour de l’instruction eq sont nécessaires !

L’instruction eq ne fait pas partie des instructions standard du langage hbs. Il s’agit d’un ajout des professeurs de Web1.

Handlebars - Test d’existence

#exists permet de tester si une variable est définie ou pas.

 {{#exists var}}
     <p> la variable est bien définie </p>
 {{else}}
    <p> la variable est undefined </p>
 {{/exists}}

L’instruction #exists ne fait pas partie des instructions standard du langage hbs. Il s’agit d’un ajout des professeurs de Web1.

Semaine 3 - Objectifs de la semaine

  • Développer une application Node.js/Express respectant l’architecture MVC
    • Utilisation d’un modèle
    • Utilisation d’un contrôleur/routeur
  • Utiliser et comprendre les modules node.js

Semaine 3 - MVC

Model - View - Controller

MVC c’est quoi ?

  • Technique de découpe du code
  • Modèle == les données (persistance des données)
  • Vue == affichage (HTML)
  • Contrôleur == chef d’orchestre entre le modèle et la vue

Explications MVC

  • Les vues : contiendront le code de présentation. Il s’agira dans ce cours des vues handlebars.
  • Les données : contiendront le code d’accès aux données (données statiques ou SELECT, INSERT dans la base de données).
  • Les contrôleurs : contiendront le code de traitement des requêtes. Ils utiliseront le modèle et renverront vers une vue.

Architecture Express avec MVC (1)

Voici comment le paradigme MVC est appliqué avec Express :

  • Les vues : il s’agit du répertoire views avec les fichiers .hbs
  • Les données : il s’agit du répertoire models. A créer par nos soins
  • Les contrôleurs : il s’agit du répertoire routes. Les contrôleurs sont appelés routeurs dans Express.

Architecture Express avec MVC (2)

Voir explications sur le slide précédent !

MVC - Créer un Routeur/Contrôleur

Un routeur est un fichier .js présent dans le dossier routes.

  • Utilisation d’un routeur pour rassembler les routes ayant un point commun

Il commence toujours par les 2 lignes suivantes :

const express = require('express');
const router = express.Router();

et se termine toujours par la ligne suivante :

module.exports = router;

MVC - Créer un Routeur/Contrôleur - Exemple

// file routes/users.js
const express = require('express');
const router = express.Router();
/* GET /users/  */
router.get('/', function(req, res) {
  res.render('users/index.hbs');
});
module.exports = router;

MVC - Activer un Routeur/Contrôleur

  • Définition d’un routeur dans app.js :
// file app.js
const studentsRouter = require('./routes/students.js’);
... 
// route for this router begin with /students
app.use('/students', studentsRouter);

Il est important d’ajouter ces lignes au bon endroit !!!

Créer toujours votre variable routeur en dessous de celui déjà présent - studentsRouter dans l’exemple ci-dessus.

Faites toujours l’activation du routeur en dessous des activations déjà présentes - app.use(‘/students’, studentsRouter) dans l’exemple ci-dessus.

MVC - Remarques importantes (1)

On crée donc un routeur dans le but de rassembler des routes ayant qqch en commun. Souvent il s’agit d’un objet.

Par exemple pour un site de gestion d’une école, je vais créer un routeur students (/students) pour gérer les utilisateurs étudiants de mon site. Ce routeur contiendra des routes pour ajouter(/add), supprimer(/delete), rechercher(/search) un étudiant et une route pour voir son bulletin de notes (/notes).

Je vais également créer un routeur teachers (/teachers) pour gérer les professeurs de mon site. Ce routeur contiendra des routes pour ajouter(/add), supprimer(/delete), rechercher(/search) un professeur et encoder une note pour ses étudiants (/addnotes).

MVC - Remarques importantes (2)

A l’intérieur d’un routeur, les routes commencent automatiquement déjà par la route renseignée.

// file app.js
...
app.use('/students', studentsRouter);
// file routes/students.js
const express = require('express');
const router = express.Router();
/* GET /students  --> already /students because in studentsRouter  */
router.get('/', function(req, res) {
  ...
});
/* GET /students/search  --> already /students because in studentsRouter  */
router.get('/search', function(req, res) {
  ...
});
module.exports = router;

MVC - Remarques importantes (3)

L’instruction redirect redirige vers une route toujours depuis la racine

// file routes/students.js
const express = require('express');
const router = express.Router();
router.get('/', function(req, res) {
  // KO
  // redirect to root racine of the website, not redirect to /students
  res.redirect('/');
});
router.post('/add', function(req, res) {
  // OK
  // redirect to /students
  res.redirect('/students')
});
module.exports = router;

MVC - Les vues (views)

Concernant les vues, celles-ci seront comme vu jusqu’ici des fichiers handlebars (.hbs) présents dans le dossier views.

MVC - Les données (models)

Les données seront gérées par des modèles présents dans le dossier models. Il s’agit de fichiers .js regroupant les données et les opérations sur celles-ci.

Au début les modèles contiendront des données statiques (Ex: tableau d’objets en mémoire). Ensuite les modèles contiendront des opérations ayant pour but d’aller rechercher/insérer les données dans une base de données.

MVC - Les données (models) - Exemple

//file models/User.js

// the user model is simply a collection of objects users
let usersList = [ { email : "laurent.leleux@vinci.be", pseudo : "lle"}, 
                  { email : "olivier.choquet@vinci.be", pseudo : "och"}];
                  
// function list 
// Pay attention to modules.export to give access 
// to list function outside (in controller for example)
module.exports.list = () => {
  return usersList;
};

module.exports.add = (email, pseudo) => {
// data must be an object like the other in the table
// something like this { email:"truc@vinci.be", pseudo:"truc"}
  const user = {email: email, pseudo: pseudo}
  usersList.push(user);
};

MVC - Les données (models) - Utilisation

Les modèles seront utilisés dans les routeurs :

// import model file at the beginning of the file
const User = require('../models/User.js');
...

// Call list function 
let lst = User.list();
...

// Call add function 
//add(email, pseudo)
User.add("stephanie.ferneeuw@vinci.be","sfe");

MVC - Conventions d’organisation

  • Les fichiers modèles commencent par une majuscule et sont au singulier.
  • Les fichiers routeurs portent le nom de la route. (Ex : users.js -> /users)
    • exception à cette règle : la route par défaut (index.js -> /)
  • Les vues spécifiques à un contrôleur peuvent être regroupées dans un dossier portant le nom du contrôleur
    • attention ce dossier doit être dans le dossier views

Conventions d’organisation - Exemple

Semaine 3 - Modules Node.js

Node.js permet l’installation de modules. Cela permet d’utiliser le code d’autres développeurs ou encore de la communauté. L’objectif de ces modules est de faciliter la réutilisation du code et ainsi d’accélérer le développement d’applications.

Au fil des séances, nous utiliserons de plus en plus de modules.

Voyons maintenant comment cela fonctionne !

Modules - Node Package Manager (NPM)

  • Commandes NPM :

Création d’un projet Node.js :

npm init 

Dans le cadre de ce cours, nous vous donnerons toujours un projet de base donc la commande npm init ne sera pas utilisée.

Installation d’un module :

npm install <nom_module> 

Installation d’un module uniquement pour le développement :

npm install <nom_module> --save-dev

Dans les 2 lignes de commandes précédentes <nom_module> est à remplacer par le nom d’un module node.js.

Modules - Package.json

Tous les installations de modules du projet sont reprises dans le fichier package.json

Quand vous récupérez un projet Node.js (les solutions du prof via Git par ex.), il est important d’exécuter la commande
 npm install 

afin que les modules nécessaires au projet soient installés suivant son fichier package.json.

Format JSON

Le format JSON(JavaScript Object Notation) correspond simplement à la notation littérale des objets en JS. Ce format est très utilisé car simple et très compréhensible.

package.json l’utilise notamment pour décrire les modules constituant un projet.

// JSON.stringify convertit une valeur JavaScript en une string JSON
// Pour afficher/déboguer un objet JSON
console.log(JSON.stringify(object)); 

Modules - Nodemon

Le module Nodemon est très utile pour recharger automatiquement le projet dans Node.js dès qu’une modification a été faite. Sans cela, vous devez à chaque fois arrêter et relancer votre application.

  • Installation :
     npm install nodemon --save-dev   
  • Lancement application :
     nodemon app.js 

Remarquez le - -save-dev pour installer nodemon uniquement pour l’environnement de développement. C’est en effet un module uniquement utile pour les développeurs, pas pour la production.

nodemon a déjà été renseigné dans le package.json par les professeurs. Allez jeter un coup d’oeil ! Remarquez également que le script npm start lance “nodemon app.js”

Modules Express

Express est un framework facilitant le développement d’application sous Node.js.

  • Installation :
     npm install express 
  • Lancement application :
     node app.js 

Express a déjà été renseigné dans le package.json par les professeurs. Allez jeter un coup d’oeil !

Semaine 4 - Objectifs de la semaine

  • Manipuler une base de données SQL via le logiciel Datagrip
  • Utiliser une base de données SQL dans une application Node.js/Express
  • Meilleure compréhension de l’architecture Express, Débogage et Formatage du code

Semaine 4 - Base de données

Introduction

La majorité des sites Web stockent leurs informations dans une base de données. Divers types et variétés de base de données existent.

Dans le cadre de cours, nous utiliserons une base de données SQL à savoir SQLite.

Les slides suivants montrent comment créer une base de données SQLite et la gérer avec le logiciel Datagrip. Ensuite, nous verrons comment utiliser cette base de données avec une application Express.

SQLite

  • Base de données légère (pas d’installation, ni login, ni mot passe, …)
  • Convient bien pour le développement
  • Pas de gestion des utilisateurs, des accès concurrents, …
  • Autres SGBDR (Système de Gestion de Base de Données Relationnel) : MariaDB, PostgreSQL, SQL Server

Création d’une base de données SQLite (1)

Pour créer une base de données SQLite : New -> Data Source -> SQLite

Création d’une base de données SQLite (2)

Sur l’écran suivant, appuyez sur le +

Création d’une base de données SQLite (3)

Sur l’écran suivant, donnez un nom à votre base de données(FileName) et choisissez l’endroit où l’enregister. Consultez le slide suivant pour savoir où enregistrer correctement votre DB !

Création d’une base de données SQLite (4)

TRES IMPORTANT !!!

Où enregistrer votre Base de données SQLite ?

  • Enregistrer votre fichier DB sur le disque U si vous travaillez sur les PC de l’école
  • Enregistrer votre fichier DB où vous le souhaitez si vous travaillez sur votre propre machine
  • Ce n’est pas une bonne pratique d’enregistrer votre fichier DB dans votre projet car elle sera utilisée par plusieurs exercices/projets
  • Donner un nom clair à votre base de données !
  • Mémoriser bien l’endroit où vous l’avez enregistrée !
  • Assurez-vous que le chemin vers votre base de données ne contient pas d’espace, ni de caractères spéciaux($,%, …) surtout si vous êtes sous MacOS

Ouvrir une base de données existante

Pour ouvrir une base de donnée : File -> Open File -> aller rechercher votre fichier db.

Connexion à SQLite via Datagrip (1)

Dans l’onglet avancé -> ajoutez la gestion des contraintes des clés étrangères !

Utilisation de la console Datagrip

La console Datagrip permet d’interagir avec la base de données SQL via des instructions SQL. Elle permet notamment :

  • d’effectuer des créations de table SQL (CREATE TABLE)
  • de tester des query SQL avant de les intégrer dans le code JavaScript

Création d’une table via la console Datagrip

Voici un exemple de création d’une table professeur et d’une table cours

CREATE TABLE teachers (
    -- autoincrement allow to have an id incremented automatically by the db engine
    id_teacher INTEGER PRIMARY KEY AUTOINCREMENT,
    name VARCHAR(255) NOT NULL,
    firstname VARCHAR(255) NOT NULL,
    -- active is a boolean. By default a teacher is active (1)
    active BOOLEAN NOT NULL DEFAULT 1
)
CREATE TABLE courses (
    -- autoincrement allow to have an id incremented automatically by the db engine
    id_course INTEGER PRIMARY KEY AUTOINCREMENT,
    title VARCHAR(255) NOT NULL,
    number_of_hours INTEGER NOT NULL,
    -- only one teacher have the responsability of the course
    teacher_manager INTEGER NOT NULL,
    -- FOREIGN KEY TO TABLE teachers
        FOREIGN KEY(teacher_manager) REFERENCES teachers(id_teacher)
)

Commandes SQL - Conseils et Astuces

  • Reportez-vous à votre cours SQL pour l’utilisation des commandes SQL !
  • N’oubliez pas les clés primaires, clés étrangères et NOT NULL !
  • Testez vos commandes SQL dans Datagrip avant de les intégrer dans votre code !
    • Clic droit sur une table -> Edit Data
    • Clic sur l’outil query pour créer et tester une nouvelle requête

Base de données & Express

Dans une application Express MVC, la base de données SQL fera partie du modèle.

Pour réaliser ceci, les points suivants sont nécessaires :

  • Installation d’un module pour communiquer avec la DB (module better-sqlite3)
  • Création d’un fichier db_conf.js contenant les paramètres de connexion à la DB
  • Utilisation des Prepared Statements

Ces points sont détaillés ci-après.

Module better-sqlite3 - Installation

  • Installation du module better-sqlite3 pour dialoguer avec SQLite
npm install better-sqlite3

Remarques importantes si vous travaillez sous Mac OS :

  • Vous devez installez xcode : xcode-select --install
  • Vous devez installez node-gyp de manière globale : sudo npm install node-gyp -g
  • Le chemin vers votre projet ne doit pas contenir d’espaces, ni de caractères spéciaux
  • Le chemin vers votre base de données ne doit pas contenir d’espaces, ni de caractères spéciaux

Création du fichier db_conf.js

// file db_conf.js in models
// /home/olivier/user.db -> path to the db file !
// it shoud be c:/users/olivier/users.db on a windows system
const db = require('better-sqlite3')('/home/olivier/users.db', { verbose: console.log });

module.exports = db;

Utilisation des prepared statements (1)

Pour envoyer une requête SQL à un SGBDR(Système de Gestion de Base de Données Relationnel), on envoie une string avec les ordres SQL.

Mais quid des paramètres ?

On peut concaténer les paramètres dans la string mais cela pose plusieurs soucis :

  • les simples guillements (’) sont utilisés en SQL, le développeur doit donc être prudent lors de ces concaténations
  • un problème de sécurité courant sont les injections SQL (le hacker remplit les champs d’un formulaire avec du code SQL)

Utilisation des prepared statements (2)

Pour éviter ces soucis, la majorité des langages de programmation propose des Prepared Statements.

Dans ce cas, le query se fera en 2 temps :

  • l’écriture du query, les paramètres étant simplement identifiés par un ?
  • l’attribution des paramètres dans un second temps (les paramètres sont convertis en string pour éviter toute injection SQL)

Méthodes better-sqlite3

  • all(params) :
    • renvoie tous les enregistrements sous la forme d’un tableau d’objets
    • s’utilise quand on fait un SELECT renvoyant plus d’une ligne
  • get(params) :
    • renvoie le premier enregistrement sous la forme d’un objet
    • s’utilise quand on fait un SELECT renvoyant une seule ligne
  • run(params) :
    • exécute la commande SQL / s’utiliser avec INSERT, UPDATE, ….

Dans les 2 cas, on fera un prepare avant l’une de ces 3 instructions. Voir exemple slide suivant

Exemple SQL-JS

const stmt_all = db.prepare("SELECT * FROM users");
// all() -> return alls rows in a table of objects like below
// [ {id:1, name:'user1', pseudo:'oli'}, {id:2, name:'user2', pseudo:'stef'}]
return stmt_all.all();

// ? is a parameter, the value of this parameter will be give by the call of get
const stmt_one = db.prepare("SELECT * FROM users u WHERE u.id=?");
// get(2) -> return one row where u.id=2 in an object like below
// {id:2, name:'user2', pseudo:'stef'}
return stmt_one.get(2);

// two parameters (?) for the insert 
const stmt_insert = db.prepare('INSERT INTO users (name, pseudo) VALUES (?, ?)');
//run -> return infos about changes made
const info = stmt_insert.run(name, pseudo);

Modification du modèle

// file User.js
const db = require('../models/db_conf.js'); 

module.exports.list = () => {
  // use of prepared statement
  const stmt = db.prepare("SELECT * FROM USERS");
  // all() -> return alls rows like [ {name:'user1', pseudo:'oli'}, {name:'user2', pseudo:'stef'}]
  return stmt.all();
};
  
module.exports.save = (name, pseudo) => {
  // use of prepared statement with parameters
  const stmt = db.prepare('INSERT INTO USERS (name, pseudo) VALUES (?, ?)');
  const info = stmt.run(name, pseudo);
  console.log("users model save" + info.changes);
};

Semaine 4 - Compréhension code Express & Débogage & Formatage

Introduction

Nous allons ici revoir le code généré par le framework Express afin de mieux comprendre son fonctionnement.

Cette compréhension facilitera également le débogage.

Lancement de l’application Express (1)

  1. npm start : la commande npm start lance le script start situé dans le package.json à savoir nodemon app.js
  2. nodemon app.js : lance le fichier app.js pour qu’il réponde aux requêtes HTTP. (C’est +/- l’équivalent du main en Java)
  3. Sélection du routeur : app.js suivant la requête reçue sélectionnera le routeur approprié
    • Si une route /users/… est envoyée il sélectionnera le usersRouter si app.js contient ceci : app.use(‘/users’, usersRouter)
    • Si aucun routeur correspondant n’est trouvé pour la route de base, le routeur par défaut (indexRouter sur la route / sera utilisé).

Lancement de l’application Express (2)

  1. Sélection de la route : recherche dans le routeur de la route demandée
    • N’oubliez pas que les routeurs contiennent déjà leur route de base. Ex: app.use(‘/users’, usersRouter) -> le usersRouter commence déjà toutes ses routes par /users
  2. Appel à d’autres morceaux de code : le routeur fait éventuellement appel à d’autres morceaux de code (models par ex.)
  3. Renvoi d’une réponse : le routeur renvoie une réponse (cela se termine toujours par un et un seul render même si vous redirigez(redirect) vers une autre route)

Débogage du code (1)

Voici quelques éléments qui vous permettront d’améliorer votre technique de débogage

  1. NotFoundError: la route demandée via le navigateur n’a pas été trouvée.
    • Reparcourez la liste des points du slide précédent pour découvrir où cela coince
  2. Vérification que l’on arrive/passe bien par une route
    • Ajoutez un message avec console.log comme première instruction de la route
  3. Vérification des données que vous envoyez à hbs.
    • Faites un console.log ou un console.log (JSON.stringify(table)) pour les objets et tableaux.

Débogage du code (2)

  1. better-sqlite3 affiche dans la console les queries SQL que vous faites
    • Regardez si le query envoyé correspond bien à ce que vous voulez. En particulier, regardez si vous n’avez pas des NULL qui sont envoyés pour certains paramètres.
  2. npm test peut vous aider à découvrir certains problèmes

Formatage du code

Le travail d’un développeur ne consiste pas seulement à réaliser un code fonctionnel. Son code doit également être clair et bien structuré pour faciliter la maintenance de l’application.

Que signifie avoir un code bien structuré ?

Cela signifie notamment que :

  1. le code est indenté pour faciliter la lecture
  2. les noms des variables et fonctions sont explicites
  3. utilisation des standards de programmation (MVC)

npm test peut vous aider dans cette tâche.

Dans Visual Studio Code, vous avez dans menu Affichage la palette de commandes et vous pouvez rechercher “Format” pour indenter correctement votre code.

Semaine 5 - Objectifs de la semaine

  • Utiliser des sessions
  • Chiffrer des informations (password)
  • Contexte handlebars

Semaine 5 - Sessions

Introduction (1)

Le protocole HTTP est sans état (stateless) ce qui veut dire que chaque requête est indépendante l’une de l’autre.

Comment faire alors pour conserver le fait qu’un utilisateur soit connecté et puisse accéder à différentes pages sur un site MPA sans devoir lui redemander son login/mdp à chaque page?

Les sessions résolvent ce souci. Le serveur Web peut créer une session pour chaque nouvel utilisateur. L’id de cette session est ensuite stocké chez le client sous forme de cookie et réenvoyé avec chaque requête ce qui permet de suivre l’utilisateur.

Introduction (2)

  • Un nouvel utilisateur en terme de session correspond à un même navigateur, système d’exploitation et même adresse IP.
  • Les sessions constituent un moyen de conserver/transmettre des variables sur toutes les pages de votre site.

Express et Sessions (1)

Express dispose d’un module pour utiliser les sessions.

npm install express-session 

Express et Sessions (2)

Configurer le module express-sessions : création d’une variable globale session

//app.js
const createError = require('http-errors');
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const logger = require('morgan');

// Use of sessions
// YOU MUST ADD THIS LINE BEFORE YOURS ROUTERS !
const session = require('express-session');

const indexRouter = require('./routes/index');
const usersRouter = require('./routes/users');

const app = express();
...

Express et Sessions (3)

Configurer le module express-sessions : définir quelques paramètres obligatoires

  • secret : ce secret sera utilisé pour chiffrer le cookie de session stocké chez le client
  • resave : forcer la sauvegarde de la session pour chaque nouvelle requête
  • saveUninitialized : forcer la sauvegarde des nouvelles sessions qui n’ont pas encore été modifiées

Express et Sessions (4)

Laissez ces paramètres comme dans le code ci-dessous

...
app.use(express.static(path.join(__dirname, 'public')));


// use of sessions
// YOU MUST ADD THESES LINES BEFORE APP.USE ROUTERS !
app.use(session({secret: "Your secret key", resave: false, saveUninitialized:false}));
// use of session variables in views via res.locals
app.use(function (req, res, next) {
  res.locals.session = req.session;
  next();
});


app.use('/', indexRouter);
app.use('/users', usersRouter);
...

Express et Sessions (5)

Créer/récupérer une variable dans la session : req.session.”variablename”

router.post('/login', (req, res, next) => {
  console.log("LOGIN");
  req.session.connected = true;
  req.session.login = "olivier";
  
});

Express et Sessions (6)

Détruire la session

router.post('/logout', (req, res, next) => {
  console.log("LOGOUT");
  req.session.destroy();
  
});

Express et Sessions (7)

Utiliser une variable de session dans les vues grâce à res.locals

<section>
  <h2> Profil Utilisateur </h2>
  <!-- use of session variable : req.session.login -->
  Login de l'utilisateur connecté : {{session.login}}
</section>

Chiffrement de données (1)

Installation module bcrypt :

npm install bcrypt

Chiffrement de données (2)

Création d’une empreinte d’une donnée :

// import module bcrypt 
const bcrypt = require('bcrypt');
// mandatory variable for bcrypt
const saltRounds = 10;

.....

const encryptedData = bcrypt.hashSync("ma donnée à protéger/encrypter", saltRounds);

Chiffrement de données (3)

Pour vérifier une donnée chiffrée (mot de passe par ex.), bcrypt dispose d’une méthode de comparaison du hash avec un texte en clair.

const bcrypt = require('bcrypt');

...

if (bcrypt.compareSync(dataInClear, hash))
{
  console.log("dataInClear == hash, OK c'est bon");
} else {
  console.log ("dataInClear != hash, KO");
}

Handlebars contexte (1)

Quand vous utilisez des instructions handlebars, un contexte de base est associé.

Dans ce contexte de base se trouve toutes les variables que vous avez données à la méthode render. Cependant certaines instructions handlebars modifie ce contexte de base.

L’instruction #each modifie ce contexte de base, le contexte devient un élément du tableau. Vous n’avez donc plus accès aux variables du contexte de base dans un #each.

Pour accéder à une variable du contexte de base à l’intérieur d’un #each, utilisez @root ou ../ devant le nom de votre variable

Handlebars contexte (2)

Exemple :

router.get('/', (req, res) => {
  const tableOfMeals = ["Burger", "Steack"];
  res.render('index.hbs', { tableOfMeals, userVegan : true });
});
<section>
  <p> {{title}} </p>
  <ul>
  {{#each tableOfMeals}}
    <li> 
      <!-- use of @root or ../ to access the root context -->
      {{this}} {{#if @root.userVegan}} Une alternative à la viande est le seitan {{/if}}     
    </li> 
  {{/each}}
  </ul>
</section>

Semaine 6 - Objectifs de la semaine

  • Envoi et traitement de fichiers
  • Validation des inputs
  • Examen

Semaine 6 - Envoi et traitement de documents

Introduction

Il est fort fréquent qu’un site Web permette l’envoi de documents(image, PDF, …) . Il existe en HTML l’input suivant pour réaliser ceci :

<input type="file"> 

Cependant il sera nécessaire de modifier le formulaire en ajoutant le support pour l’envoi de document (enctype) :

<form method="post" action="/users/add" enctype="multipart/form-data"> 

Le document sera envoyé sur le serveur dans un dossier à définir sur le serveur et son nom sera aléatoire. Cependant nous aurons accès à des informations sur le fichier tel que son nom original.

Envoi de document - formulaire

<form method="post" action="/users/add" enctype="multipart/form-data">
        <div>
            <label>Pseudo Utilisateur : </label>
            <input type="text" name="pseudo" />
        </div>
        <div>
            <label>Avatar Utilisateur :</label>
            <input type="file" name="avatar" />
        </div>
        <div>
            <input type="submit">
        </div>
</form>

Envoi de document - Multer(1)

Nous utiliserons le module Multer pour le traitement des documents.

Installation de Multer :

npm install multer 

Envoi de document - Multer(2)

Pour utiliser Multer, il faut lui indiquer où stocker les fichiers envoyés par l’utilisateur et quels noms doivent avoir ces fichiers. Ceci se fait via les 2 paramètres destination et filename.

  • destination : Répertoire où seront stockés les fichiers envoyés par l’utilisateur
  • filename :
    • indique comment sera construit le nom du fichier à stocker
    • il est important d’assigner un nouveau nom unique à ce fichier pour ne pas écraser d’autres fichiers préalablement sauvés !
    • l’utilisation de la date et heure pour rendre le fichier unique est une bonne solution
    • exemple de filename approprié :
      • 2022-08-6-16-22-22-masuperimage.png

Envoi de document - Multer(3)

Configuration de Multer :


// code to placed in routers before routes 
const multer = require('multer');
const storage = multer.diskStorage({
    destination: function (req, file, cb) {
        cb(null, 'public/images');
    },
    filename: function (req, file, cb) {
        const date = new Date();
        const uniquePrefix = date.getFullYear() + '-' + (date.getMonth() + 1) + 
        '-' + date.getDate() + '-' + date.getHours() + '-' + date.getMinutes() + 
        '-' + date.getSeconds();
        cb(null, uniquePrefix + '-' + file.originalname);
    }
})
const upload = multer({ storage: storage });

router.get('/', function (req, res, next) {
    res.render('index.hbs');
});

Envoi de document - Multer(4)

Récupération du fichier envoyé :

...
// avatar is the name of the input file  !!!
// call of the single function of multer  -> upload.single()
router.post('/add', upload.single('avatar'),
    (req, res, next) => {
    // req.file multer infos 
    console.log("req.file : " + JSON.stringify(req.file));
    // req.file.filename ex :  2023-03-04-10-05-02-masuperimage.png
    User.save({ pseudo: req.body.pseudo, image: req.file.filename });
    res.redirect('/users');
});

Semaine 6 - Validations

Introduction

Il est important de valider les inputs reçus par l’utilisateur afin notamment de :

  • valider la cohérence des données avant de les sauvegarder
  • éviter les problèmes de sécurité

Dans le cadre de ce cours, nous utiliserons le module validator pour cette tâche

validator - Installation

Installation du module validator :

npm install validator

validator - Etapes

Les validations se font en 2 étapes :

  • Tester les inputs
  • Afficher dans une vue handlebars les erreurs détectées

Le module validator dispose de toute une série de fonctions intéressantes pour la validation des inputs :

https://github.com/validatorjs/validator.js/#validators

Validator - Transmission des erreurs

Quand vous détectez une erreur, il faut ensuite l’afficher dans une vue. Plusieurs cas peuvent alors se présenter :

  • Votre route se termine par un render. Vous pouvez alors transmettre directement à la vue un message d’erreur ou un tableau de messages erreurs
  • Vous terminez votre route par un redirect. Vous pouvez alors utiliser :
    • des querys string pour transmettre un message d’erreur. Si vous n’avez pas beaucoup de messages d’erreurs, ceci est facile !
    • une variable de session. Après le render sur une autre page, vous devez alors supprimer la variable pour éviter que le message ne s’affiche en continu. Pour transmettre plusieurs erreurs, ceci est plus facile.

Voir exemples ci-après.

Validator - Exemple (1) - redirect

<form action="/adduser" method ="post">
  <input type="text" name="username">
  <input type="submit">
</form>
const validator = require('validator');

router.get('/users', function (req, res) {
    // retrieve errors message via req.query
    res.render('users/index.hbs', { errors : req.query.errors});
});

router.post('/adduser', function (req, res) {
    // validate name of user -> betweeen 5 and 10 character
    if (!validator.isEmail(req.body.username) {
    // errors sent via query string
      res.redirect('/users?errors=L\'email n\'est pas correct');
    }
    else {
      ...
    }
});

validator - Exemple (2) - redirect

<form action="/adduser" method ="post">
  <input type="text" name="username">
  <input type="submit">
</form>
const validator = require('validator');

router.get('/users', function (req, res) {
    // retrieve errors  via session variable
    res.render('users/index.hbs', { errors : req.session.errors});
    // destroy errors variable to avoid continous showing
    req.session.errors = null;
});

router.post('/adduser', function (req, res) {
    // validate name of user -> betweeen 5 and 10 character
    if (!validator.isEmail(req.body.username) {
      // errors placed in session variable
      req.sessions.errors = "L'email n'est pas correct";
      res.redirect('/users');
    }
    else {
      ...
    }
});

Examen

Rappel pondérations

  • UE Web 1
    • 10% HTML
    • 90% JS (Examen)

Attention le projet Web a son UE propre -> Note séparée projet et Web 1 !

Examen - Déroulement :

  • 2h
  • sur les machines de l’école (Windows)
  • sans internet
  • matière examen -> tous les exercices
  • syllabus Web1 disponible durant l’examen
  • 3 pages (recto-verso) de notes personnelles (manuscrites ou imprimées) sont autorisées
  • l’examen partira de la dernière solution de la semaine 6
  • on vous demandera l’ajout ou modification de fonctionnalités dans le site des exoplanètes

Examen - Critères d’évaluation :

  • Fonctionnel : la fonctionnalité demandée fonctionne ou pas
  • Respect des consignes
  • Qualité du code :
    • les variables, méthodes, … sont correctement nommées. Les instructions se terminent par un ; , …
    • le code est clair, lisible
    • le code est bien indenté
    • Respect du MVC
  • Performance du code :
    • Utilisation de la puissance du SQL quand c’est possible

Solutions des exercices

Chaque vendredi, les solutions des exercices de la semaine écoulée seront disponibles.

Consulter les solutions

Consultation en ligne : Dépôt Git

Récupérer les solutions via Git

Outil pour le développement

Préliminaires

Avant de pouvoir récupérer les solutions sur votre machine, il faut :

  • se connecter à Gitlab
  • définir un mot de passe

Voir en image la procédure avec les slides ci-après.

Se connecter à Gitlab.vinci.be

Définir un mot de passe sur Gitlab.vinci.be

Introduction - Git

Git est un système de contrôle de version distribué gratuit et open source conçu pour tout gérer, des petits aux très grands projets, avec rapidité et efficacité.

OK mais en clair ?

Git permet notamment de :

  • partager facilement du code entre plusieurs développeurs
  • intégrer les changements de plusieurs développeurs sur un même projet

Dans le cadre de ce cours, l’utilisation de Git sera limitée à la récupération des solutions des exercices.

Installation de Git

Si vous travaillez sur une machine de l’école, Git est déjà installé.

Par contre, si vous travaillez sur votre propre machine il faudra installer Git.

Utilisation Git

Pour récupérer les solutions :

  • Faites une copie du dépôt des solutions (à faire une seule fois)
git clone https://gitlab.vinci.be/olivier.choquet/web1_solutions.git
  • Récupérer les solutions (à faire chaque vendredi)
git pull origin main
  • Installer les dépendances des solutions
npm install 

Vidéo Git

Video Solutions via Git