JavaScript

Complément Front-end / Back-end

https://slides.com/benjichaz/but2

Avant-propos

Qui suis-je ?

 

Benjamin Chazelle, 26 ans

 

Passionné d'informatique 💻

de musique 🎹 🎸et de cuisine 🍪

 

IUT Bourg, DUT Info 2016

INSA Lyon, Dép. Info 2019

 

 

 

 

 

 

 

www.chazelle.pro

Module

24 heures

De fin février à mi-avril

 

Évaluation par un projet + DS

 

iut@chazelle.pro

06 51 23 51 39

Contenu

Rappels JavaScript

Frontend, Vue.js

Backend, Node.js/Express

API REST, Auth, Database

 

Qui suis-je ?

 

Ingénieur en développement

spécialisé front-end

 

Chez Ubitransport

Éditeur de logiciel pour le monde du transport de voyageur

Comment ça va ?

 

D'où venez vous ?

 

Comment est-ce que vous sentez ce cours "JavaScript : Front-end / Back-end" ?

 

Votre aisance en développement, en général

 

Votre aisance en JavaScript

 

Vous avez déjà l'habitude de travailler avec ces technos ?

 

Dans le développement web, vous préférez

 

Any question ?

Rappels JavaScript

Introduction

Langage pour le web (et +), interprété, orienté objet,

dynamique, typage faible,

riche en API

 

Standard et écosystème en évolution

 

JavaScript != Java

Historique

1995 - Créer par Brenden Eich chez Netscape/Mozilla

 

1997 - Standardiser comme ECMAScript (ES1)

 

2015 - Version majeur ES6 (ES2015)

 

Depuis 2015, une nouvelle version chaque année

Get started

Légende

ES6

OLD

Apparu avec ECMAScript 6

Usage déprécié

Intégration de JavaScript

// index.html
 
<!doctype html>
<html lang="fr">
<head>
  <meta charset="utf-8">
  <title>Apprenons JS</title>
  <script src="script.js"></script>
</head>
<body>

</body>
</html>
// script.js
 
window.addEventListener("load", () => {
    let body = document.body;
    body.textContent = "Hello world";
});

Les commentaires

// Commentaire monoligne

/*
Commentaire
multi
ligne
*/

Les variables

let x = 42; // 42

x = 1337;
// 1337

let y; // undefined
const x = 42; // 42

x = 1337;
// Uncaught TypeError: invalid assignment to const 'x'

ES6

Portée d'une variable

if(true) {
  let x = 42;
  x; // 42
}

x; // undefined

ES6

OLD

function f() {
  if(true) {
    var x = 42;
    x; // 42
  }

  x; // 42
}
x; // undefined

Nommage

Un nom de variable correct peut contenir :
   - lettres minuscules (a-z)

   - lettres majuscules (A-Z)
   - chiffres (0-9)
   - caractères dollar ($)
   - caractères underscore (_)
 
Un nom de variable ne commence jamais par un chiffre.

good

GOOD

$Good

_good_

gôöd

 

b-a-d

-Bad-

84D

Types primitifs et objets

Types objets (Object)
Object
Array
Set
Map
RegExp
Function
...
Types primitifs
string
number
boolean
null
undefined
symbol
BigInt

ES6

ES11

"hello"
1337
true
null
undefined
Symbol(42)
BigInt('1337')
[42, "Hello", true]
{ foo: "bar" }
function f() {}

Typage faible

let x = 1337;

x; // 1337

typeof x; // "number"

x = new Date();

typeof x; // "object"

// Le type peut varier pendant l'exécution
x = new Date();

typeof x; // "object"

x instanceof Date; // true

x instanceof RegExp; // false

Les opérateurs

// Arithmétique
(2 + 13 - 7) * 2 / 4 % 5
// Affectation
let x = 2 // 2
x += 13   // 15
x -= 7    // 8
x *= 2    // 16
x /= 4    // 4
x %= 5    // 4
// In/Décrémentation
let x = 2 // 2
x++       // 2
++x       // 4
x--       // 4
--x       // 2
// Logique
true && false // false
true || false // true

true || false && false // <=> true || (false && false) 
// true

let z = true; !z // false
// (Non) Egalité
23 == 23     // true
23 == "23"   // true
23 === "23"  // false
0 == false   // true
0 === false  // false

23 != 23     // false
23 != "23"   // false
23 !== "23"  // true
// Inégalité
42 < 32;  // false
42 <= 42; // true
42 > 6;   // true
17 >= 42; // false

Contrôle de flux

let number = getNumber()

if (number > 0) {
  console.log("Positive")
} else if (number < 0) {
  console.log("Negative")
} else if (number === 42) {
  console.log("Life answer")
} else {
  console.log("Zero")
}
let color = getColor()

switch (color) {
  case "RED":
    code = "#f00";
    break;
  case "GREEN":
    code = "#0f0";
    break;
  default:
    code = "#000";
}
let i = 0

while(i < 5) {
  doSomething(i)
  i++
}
for(let i = 0; i < 5; i++) {
  doSomething(i)
}

Opérateur ternaire

// condition ? siConditionVraie : siConditionFausse;

let x = age >= 18 ? "Majeur" : "Mineur";

Prêt pour cette

nouvelle session interactive ?

 

https://interactly.glitch.me/

 

Quand une variable est déclarée mais non initialisée, elle vaut

 

Cette variable à un nom valide en JavaScript: $foo

 

Que vaut la variable bar ?

 

Parmi ces types, lesquels sont primitifs ?

 

Un typage faible veut dire

Que vaut: typeof "42" ?

Quelles valeurs retournent les deux évaluations ?

Quelles sont les différences entre les opérateurs == et === ?

Number

let a = 42;
let b = 3.;
let c = 3.14;
let d = -20;
let e = 10e5;
let f = 0x20; // 32
let g = Infinity;
let h = -Infinity;
let i = NaN; // Not a Number
let j = +0;
let h = -0;

42 / +0; // Infinity

42 / -0; // -Infinity
// Float precision IEEE 754

0.1 + 0.2; // 0.3000000000004

Transtypage d'un nombre

// De Number to String

const x = 42;

// Lisible
String(x); // "42"


// Performant
x + ""; // "42"
// De String à Number

const x = "42";

// Lisible
parseInt(x, 10); // 42
parseFloat(x); // 42

// Performant
+x; // 42

Math

Math.round(100.7); // 101

Math.ceil(12.3); // 13

Math.floor(12.7); // 12

Math.random(); // 0.6493849611386405 

Math.PI; // 3.141592653589793

Math.cos(0); // 1

Math.min(2, 6); // 2

Math.max(2, 6); // 6

String

const x = "I'm your \"father\"\n";
const y = 'I\'m your "father"\n';
const z = `I'm your "father"\n`; 

ES6

const x = "foo";

x[0]; // "f"
x[1]; // "o"
x[2]; // "o"
const x = "foo";

x.length; // 3

Concaténation d'un String

const x = "Veni";
const y = "vidi";
const z = "vici";

// Performant
x + " " + y + " " + z; // "Veni vidi vici"

// Lisible
`${x} ${y} ${z}`; // "Veni vidi vici"

ES6

Méthodes de String

const x = "  JavaScript\n";

x.substring(4, 9); // "vaScr";

x.toUpperCase(); // "  JAVASCRIPT\n"
x.toLowerCase(); // "  javascript\n"

x.replace("a", "@"); // "  J@vaScript\n";
x.replace("a", /@/g); // "  J@v@Script\n";

x.indexOf("Java"); // 2
x.indexOf("Vue"); // -1

x.trim(); // "JavaScript";

ES6

let str = "Hello, world!"

str.startsWith("Hello") // true
str.endsWith("world!")  // false

str.includes("world")   // true

str = "abc"
str.repeat(3)           // "abcabcabc"

str = "123"
str.padStart(5, "0")    // "00123"
str.padEnd(5, "0")      // "12300"

ES6

RegExp

const x = /^\d{4} \d{4} \d{4} \d{4}$/g; // Compact

const y = new RegExp("^\d{4} \d{4} \d{4} \d{4}$", "g"); // Performant
const x = "I love JavaScript, Vue.js, Vuex and Vue-router";

x.match(/[A-Z][^, ]+/g); // ["JavaScript", "Vue.js", "Vuex", "Vue-router"]

x.match(/\d/g); // null

Parmi ces propositions, quels sont les valeurs correctes pour initialiser une variable de type number ?

 

Pour convertir la variable string x = "99" en number, je peux utiliser ?

 

Je peux faire des calculs mathématiques grâce à l'objet global

 

Je peux concaténer des strings via

 

Je peux tester une expression régulière /regex/ sur une chaîne "foo" via

 

Exercice : Battre le géant

https://github.com/benjaminchazelle/The-Quest

Exercice : Transcripteur digital

 

Projet Glitch: Digital transcriptor

https://glitch.com/edit/#!/iut-digital-transcriptor

Array

Initialisation d'un Array

let x = [1, 2, 3];

let y = ["Hello", true, 42, {foo: "bar"}];

let z = [[1,2,3], [4,5,6]];

Lecture dans un Array

let x = [1, 2, 3];

x[0]; // 1

let y = ["Hello", true, 42, {foo: "bar"}];

x[3]; // {foo: "bar"}

let z = [[1,2,3], [4,5,6]];

x[1]; // [4,5,6]
x[1][2]; // 6

Écriture dans un Array

let x = [1, 2, 3];

x[0] = 42;

x; // [42, 2, 3]

Taille d'un Array

let x = [1, 2, 3];

x.length; // 3

Itération d'un Array

let digits = [1, 2, 3];

for(let i = 0; i < digits.length; i++) {
  // digits[i];
}

for(let digit of digits) {
  // digit;
}

// Performance
digits.forEach(digit => {
  // digit;
});

ES6

ES6

Manipulation d'un Array

let x = [12, 3, 1, 150];

x.push(17); // Ajoute 17 en fin de tableau
x; // [12, 3, 1, 150, 17]

x.pop(); // 17 - Supprime et retourne le dernier élement
x; // [12, 3, 1, 150]

x.unshift(42); // Ajoute 17 en début de tableau
x; // [42, 12, 3, 1, 150]

x.shift(); // 42 - Supprime et retourne le premier élement
x; // [12, 3, 1, 150]

x.splice(1, 2); // [12, 150] - Supprime 2 éléments à la position 1

Transformation d'un Array

let x = [12, 3, 1, 150];

x.map(element => element * 2); // [24, 6, 2, 300]

x.reduce((acc, element) => acc + element) // 166

x.sort();
x; // [1, 12, 150, 3]

x.sort((a, b) => a - b);
x; // [1, 3, 12, 150]

x.reverse()
x; // [150, 12, 3, 1]

Recherche dans un Array

let x = [12, 3, 1, 150];

x.indexOf(3); // 1 - Trouve l'index d'un élément

Recherche dans un Array

let x = [12, 3, 1, 150];

x.includes(3); // true - Indique la présence d'un élément

x.find(element => element === 3); // 3 - Trouve un élément
x.find(element => element === 0); // undefined

x.findIndex(element => element === 3); // 1 - Trouve l'index d'un élément
x.findIndex(element => element === 0); // undefined

x.filter(element => element < 10); // [3, 1] - Filtre les éléments

ES6

Object

Initialisation d'un objet

let x = {};

let y = new Object();

Initialisation d'un objet

let x = {
  firstname: "Benjamin",
  lastname: "Chazelle",
  age: 24,
  engineer: true,
  company: "Ubitransport"
};

Initialisation d'un objet

let x = {
  firstname: "Benjamin",
  "lastname": "Chazelle",
  age: 24,
  'engineer': true,
  company: "Ubitransport"
};

Propriétés d'un objet

let x = {
  firstname: "Benjamin",
  lastname: "Chazelle",
  age: 24,
  engineer: true,
  company: "Ubitransport"
};

x.age; // 24

x['company']; // "Ubitransport"

Propriétés d'un objet

let x = {
  firstname: "Benjamin",
  lastname: "Chazelle",
  age: 24,
  engineer: true,
  company: "Ubitransport"
};

x.age = 20;
x['company'] = "IUT Lyon 1";
x['passion'] = "Piano";


x;
// {
//   firstname: "Benjamin",
//   lastname: "Chazelle",
//   age: 20,
//   engineer: true,
//   company: "IUT Lyon 1",
//   passion: "Piano"
// }

Chaînage de propriétés

let x = {
  firstname: "Benjamin",
  company: {
    name: "Ubitransport",
    address: "200 Bd de la Résistance, 7100 Mâcon"
  }
};

x.company.name; // "Ubitransport"

Chaînage de propriétés

let x = {
  firstname: "Benjamin",
  company: {
    name: "Ubitransport",
    address: "200 Bd de la Résistance, 7100 Mâcon"
  }
};

x.home; // undefined

x.home.address; // Uncaught TypeError: x.home is undefined

Chaînage optionnel

let x = {
  firstname: "Benjamin",
  company: {
    name: "Ubitransport",
    address: "200 Bd de la Résistance, 7100 Mâcon"
  }
};

x.home; // undefined

x.home.address; // Uncaught TypeError: x.home is undefined


x?.home; // undefined

x?.home?.address; // undefined

ES6

Opérateur in

let x = {
  firstname: "Benjamin",
  company: "Ubitransport"
};

"firstname" in x; // true

"phone" in x; // false

Opérateur delete

let x = {
  firstname: "Benjamin",
  company: "Ubitransport"
};

delete x.firstname;

x; // { company: "Ubitransport" }

Lister les propriétés

let x = {
  firstname: "Benjamin",  
  company: {
    name: "Ubitransport",
    address: "200 Bd de la Résistance, 7100 Mâcon"
  }
};

Object.keys(x); // ["firstname", "company"]

Object.keys(x.company); // ["name", "address"]

Itérer sur les propriétés

let x = {
  firstname: "Benjamin",  
  company: {
    name: "Ubitransport",
    address: "200 Bd de la Résistance, 7100 Mâcon"
  }
};

// Historique
for(let property in x) {
    // x[property];
}

// Performance
Object.keys(x).forEach(property => {
    // x[property];
})

ES6

Affectation par composition

const firstname = "Benjamin";
const lastname = "Chazelle";

let x = { 
  firstname, 
  lastname 
};

x;
// {
//   firstname: "Benjamin",
//   lastname: "Chazelle"
// }

Vue

ES6

Affectation par composition

const firstname = "Benjamin";
const contact = {
  phone: "06 51 23 51 39",
  email: "benjamin@chazelle.pro"
}

let x = { 
  firstname, 
  contact
};

x;
// {
//   firstname: "Benjamin",
//   contact: {
//     phone: "06 51 23 51 39",
//     email: "benjamin@chazelle.pro"
//    }
// }

Opérateur de reste et Objet

const firstname = "Benjamin";
const contact = {
  phone: "06 51 23 51 39",
  email: "benjamin@chazelle.pro"
}

let x = { 
  firstname, 
  ...contact
};

x;
// {
//   firstname: "Benjamin",
//   phone: "06 51 23 51 39",
//   email: "benjamin@chazelle.pro"
// }

Vue

ES6

Affectation par composition

const firstname = "Benjamin";
const contact = {
  phone: "06 51 23 51 39",
  email: "benjamin@chazelle.pro"
}

let x = { 
  firstname, 
  contact
};

x;
// {
//   firstname: "Benjamin",
//   contact = {
//     phone: "06 51 23 51 39",
//     email: "benjamin@chazelle.pro"
//    }
// }

Affectation par composition

const firstname = "Benjamin";
const contact = {
  phone: "06 51 23 51 39",
  email: "benjamin@chazelle.pro"
}

let x = { 
  firstname, 
  contact
};

x;
// {
//   firstname: "Benjamin",
//   contact = {
//     phone: "06 51 23 51 39",
//     email: "benjamin@chazelle.pro"
//    }
// }

Prêt pour cette

nouvelle session interactive ?

 

https://interactly.glitch.me/

 

Dans un Array, je peux avoir des éléments de différents types

Dans un Array foo = ["x", "y", "z"], foo[2] retourne

Je peux connaître la taille d'un Array foo via

Utiliser des éléments de langage ES6 nativement implique

Je peux ajouter un élément à la fin de mon Array foo via

Je peux itérer sur un Array elements via

Pour transformer l'Array [1, 2, 3]

en [10, 20, 30], je peux utiliser

Dans la déclaration littérale d'un objet,

les propriétés

Jusqu'ici je kiff ce cours de JavaScript

Un objet peut en imbriquer d'autres

Je peux accéder à Ubitransport

Que vaut foo.bar ?

let foo = {};

foo.bar; // ?

Que vaut

foo.bar.qux   ?

let foo = {};

foo.bar.qux; // ?

Que vaut

foo?.bar.qux   ?

let foo = {};

foo?.bar.qux; // ?

Que vaut

foo?.bar?.qux   ?

let foo = {};

foo?.bar?.qux; // ?

Je peux tester si la propriété bar existe ou non dans l'objet foo

let width = 67
let height = 23
let measures = 
    { width, height }
let color = "red"
let texture = "vinyle"

let data = {
   color,
   texture: "iron",
   ...measures
}

data // ?
{  // Réponse B
   color: undefined,
   texture: "iron",
   width: 67,
   height: 23
}


{  // Réponse D
   color: "red",
   texture: "iron",
   measures: {
      width: 67,
      heigth: 23
   }
}
{  // Réponse A
   color: "red",
   texture: "vinyle",
   measures: {
      width: 67,
      height: 23
   }
}


{  // Réponse C
   color: "red",
   texture: "iron",
   width: 67,
   height: 23
}
let width = 67
let height = 23
let weight = 19
let measures = 
 {width, height, weight}

let data = {
   weight: 42,
   texture: "iron",
   ...measures
}

data // ?
{  // Réponse B
   texture: "iron",
   width: 67,
   height: 23,
   weight: 19,
}
   

{  // Réponse D
   texture: "iron",
   measures: {
      width: 67,
      heigth: 23,
      weight: 19,
   },
}
{  // Réponse A
   texture: "iron",
   weight: 42,
   measures: {
      width: 67,
      height: 23,
      weight: 19,
   }
}

{  // Réponse C
   weight: 19,
   texture: "iron",
   width: 67,
   height: 23,
}

Any question ?

Exercice: Battre le géant

https://github.com/benjaminchazelle/The-Quest

Exercice: Moteur de recherche

Projet Glitch : Search Engine

https://glitch.com/edit/#!/iut-search-engine

Set et Map

Set

let x = new Set();

x.add(42); // { 42 }
x.add("foo"); // { 42, "foo" }

x.has("foo"); // true
x.has(1337); // false

x.delete("foo");

x.size; // 1

for (let item of x) {
  // items; 
}

ES6

Map

let x = new Map();

x.set(42, "quarante-deux'");
x.set("foo", "f o o");

x.size; // 2

x.get(42); // "quarante-deux";
x.get("foo") // "f o o"

x.delete("foo");

x.forEach((value, key) => {
  // value;, key;
});

ES6

Date

Initialisation d'une Date

let a = new Date() // Maintenant

let b = new Date(1537621200000) // Milisecondes depuis l'epoch Unix

let c = new Date('2018-09-22T15:00:00+02:00') // Format ISO 8601

let d = new Date(2018, 8, 22) // Le mois est 0-indexé

ISO 8601

Formatter d'une Date

let x = new Date('2018-09-22T15:00:00+02:00')

x.getTime(); // 1537621200000

x.toLocaleDateString(); // "2018-9-22"

x.toLocaleTimeString(); // "15:00:00"

x.toISOString(); // "2018-09-22T13:00:00.000Z"

ISO 8601

Manipuler une Date

let x = new Date('2018-09-22T15:00:00+02:00');

x.getDate(); // 22

x.setDate(30); // Date { 2018-09-30T13:00:00.000Z }


// Je vous épargne tout les getters/setters

Maintenant

let now = new Date().getTime();

let now = Date.now();

ES6

JSON

JavaScript Object Notation

JSON est une syntaxe pour sérialiser des objets, tableaux, nombres, chaînes de caractères, booléens et valeurs null.

 

Elle est basée sur la syntaxe de JavaScript mais en est distincte : du code JavaScript n’est pas nécessairement du JSON, et du JSON n’est pas nécessairement du JavaScript.

 

Extrait de la Mozilla Developer Network

JSON invalide

{

    firstname: "Benjamin",

    'lastname': "Chazelle",

    "age": 024,

    "company": {

        `hiring`: true,

        "nextWebTalent": undefined

    },

    "confinedUntil": null,

    "courses": ['JavaScript', "Vue.js"],

}

JSON invalide

{

    firstname: "Benjamin",

    'lastname': 'Chazelle',

    "age": 024,

    "company": {

        `hiring`: true,

        "nextWebTalent": undefined

    },

    "confinedUntil": null,

    "courses": ['JavaScript', "Vue.js"],

}

JSON valide

{

    "firstname": "Benjamin",

    "lastname": "Chazelle",

    "age": 24,

    "company": {

        "hiring": true

    },

    "confinedUntil": null,

    "courses": ["JavaScript", "Vue.js"]

}

Encoder et décoder en JSON

let decoded = { firstname: 'Benjamin', age: 24, company: "Ubitransport" };

JSON.stringify(decoded);
// '{"firstname":"Benjamin","age":24,"company":"Ubitransport"}'


let encoded = '{"firstname":"Benjamin","age":24,"company":"Ubitransport"}';

JSON.parse(encoded);
// {
//    firstname: 'Benjamin',
//    age: 24, 
//    company: 'Ubitransport'
// }

Les fonctions

Déclaration d'une fonction

function maFonction () {
  
}

Déclaration d'une fonction

function maFonction () {
  
}

maFonction(); // undefined

Déclaration d'une fonction

function maFonction () {
  return 42;
}

maFonction(); // 42

Déclaration d'une fonction

function addition (a, b) {
  return a + b;
}

addition(10, 7); // 17

Déclaration d'une fonction

function addition (a, b, c = 0) {
  return a + b + c;
}

addition(10, 7); // 17

addition(10, 7, 100); // 117

Fonction anonyme

const addition = function (a, b, c = 0) {
  return a + b + c;
}

addition(10, 7); // 17

addition(10, 7, 100); // 117

Fonction fléchée

const addition = (a, b, c = 0) => {
  return a + b + c;
}

addition(10, 7); // 17

addition(10, 7, 100); // 117

ES6

Fonction fléchée

const addition = (a, b, c = 0) => a + b + c;

addition(10, 7); // 17

addition(10, 7, 100); // 117

ES6

Fonction fléchée

const majuscule = str => str.toUpperCase();

majuscule("hello world"); // "HELLO WORLD"

ES6

Opérateur de reste et Function

const casting = (a, b, ...plusEncore) => {
  let liste = `${a} et ${b}`;
  if(plusEncore.length > 0) {
    liste += ` (et ${plusEncore.join(", ")})`;
  }
  return liste;
};

casting("Alice", "Bob");
// "Alice et Bob"

casting("Alice", "Bob", "Charlie", "Dan");
// "Alice et Bob (et Charlie, Dan)"

ES6

Passage par copie / référence

function exclamer(str) {
  str += " !";
  return str;
}

// Type primitif
let x = "Hello world";


exclamer(x); // "Hello world !"
x; // "Hello world"

x = exclamer(x);
x; // "Hello world !"

!!!

function exclamer(obj) {
  obj.y += " !";
}


// Type objet
let x = {};
x.y = "Hello world";

exclamer(x);
x.y; // "Hello world !"

Type primitif passé par copie

Type objet passé par référence

Set et Map sont de types objets ?

Une Date JavaScript peut être initialisée à partir de

JSON

On peut sérialiser en JSON via

Une fonction

L'opérateur de reste (...) dans une fonction permet

Dans une fonction, un paramètre de type number serait passé par copie

Dans une fonction un paramètre de type Date serait passer par copie

Any question ?

Les erreurs

try catch

try {
  
  vivreTranquille();
  
} catch (e) {
  
  if(e.message === "covid") {
    
     seConfiner();    
    
  }
 
}
function vivreTranquile() {
  
  // ...
  
  throw new Error("covid");
 
  // ...
  
}

Programmation asynchrone

La boucle d'événement

-

-

-

File

d'événements

1 contexte

à la fois

Gére l'exécution asynchrone des événements

Fonction de rappel

document.body.addEventListener('click', () => {
  
  alert("Hello world");
  
})

Événement après un délai

console.log("Le petit oiseau va sortir dans 2 secondes !")


setTimeout(() => {
  
  console.log("Coucou !");
  
}, 2000);

Événement récurrent

let compteur = 10;

setInterval(() => {
  
  if(compteur === 0) {
    console.log("BOOM !")
  } 
  else if(compteur > 0) {
    console.log(compteur);
  }
  
  compteur--;
  
}, 1000);

Événement récurrent

let compteur = 10;

const interval = setInterval(() => {
    
  if(compteur === 0) {
    console.log("BOOM !")
    clearInterval(interval);
  }
  
  console.log(compteur--);
  
}, 1000);

Les promesses

ES6

Les promesses

ES6

En attente

Résolue

Rejetée

Les promesses

ES6

En attente

Résolue

Rejetée

Kilian se prépare à tirer

La France croise les doigts

Kilian pleure

Kilian dance

Les promesses

ES6

En attente

Résolue

Rejetée

Kilian se prépare à tirer

La France croise les doigts

Kilian pleure

Macron perd de la popularité

Kilian dance

La France se réjouit

Les promesses

ES6

promise.then(() => makeKilianDancing())

promise.then(() => rejoiceFrance())
  
promise.catch(() => {
  
    makeKilianCrying()
  
    makeMacronUnpopular()
  
})
const promise = new Promise((resolve, reject) => {
  
  let kilianHasScored = undefined
  
  while(kilianHasScored === undefined) {
    kilianHasScored = doesKilianScored()
  }
 
  if(kilianHasScored) {
    	resolve()
  } else {
    	reject()
  }
  
})

Les promesses

ES6

promise.then((happiness) => {
  
    if(happiness > 100) {
       makeKilianDancing() 
    } else {
       makeKilianSmiling() 
    }
  
}).catch((sadness) => {
  
    if(sadness > 100) {
       makeKilianFalling()
    } else {
       makeKilianCrying()
    }
  
})
const promise = new Promise((resolve, reject) => {
  
  let kilianHasScored = undefined
  
  while(kilianHasScored === undefined) {
    kilianHasScored = doesKilianScored()
  }
 
  if(kilianHasScored) {
    	resolve(getKilianHappiness())
  } else {
    	reject(getKilianSadness())
  }
  
})

async / await

async function () {
  
  try {
    
    let hapiness = await promise;
    
    //...
   
    
  } catch(sadness) {
    
    sadness;
    
    //...
    
  }
}
const promise = new Promise((resolve, reject) => {
  
  let kilianHasScored = undefined;
  
  while(kilianHasScored === undefined) {
    kilianHasScored = doesKilianScored();
  }
 
  if(kilianHasScored) {
    	resolve(estimateKilianHapiness());
  } else {
    	reject(estimateKilianSadness());
  }
  
})

Gestion asynchrone sur les fonctions basée sur l'attente de la résolution des promesses

Déboguer malin

Logguer en console

let maVariable = 42;

console.log(maVariable);
// > 42

console.log({maVariable});
// > { maVariable: 42 }

Logguer en console

console.info("foo");

console.warn("bar");

console.error("qux");

Plus loin avec la console

// Effacer la console
console.clear();


// Afficher la pile d'appel 
console.trace();


// Mesurer le temps
console.time("mesurer durée traitement");

console.timeEnd("mesurer durée traitement");

Le débogueur

debugger;

Les modules

Import export

// module.js
 
export default { 
  name: "Benjamin"
};

ES6

Vue

// main.js

import Module from "./module.js";
Module; // { name: "Benjamin" }


import AutreNom from "./module.js";
AutreNom; // { name: "Benjamin" }

Import export

// module.js
 
export default { 
  name: "Benjamin"
};

export const email = "iut@chazelle.pro";

ES6

Vue

// main.js

import Module from "./module.js";
Module; // { name: "Benjamin" }


import { email } from "./module.js";
email; // "iut@chazelle.pro"

Import export

// module.js
 
export default { 
  name: "Benjamin"
};

export const email = "iut@chazelle.pro";

let website = "chazelle.pro";

export website;

ES6

Vue

// main.js

import Module from "./module.js";
Module; // { name: "Benjamin" }


import { email } from "./module.js";
email; // "iut@chazelle.pro"

import { website as url } from "./module.js";
url; // "chazelle.pro"

Import export

// module.js
 
export default { 
  name: "Benjamin"
};

const email = "iut@chazelle.pro";

let website = "chazelle.pro";

export { email, website };

ES6

Vue

// main.js

import Module from "./module.js";
Module; // { name: "Benjamin" }


import { email, website as url } from "./module.js";
email; // "iut@chazelle.pro"
url; // "chazelle.pro"

Import export

// module.js
 
export default { 
  name: "Benjamin"
};

const email = "iut@chazelle.pro";

let website = "chazelle.pro";

export { email, website };

ES6

Vue

// main.js

import Module, { email, website as url } from "./module.js";
Module; // { name: "Benjamin" }
email; // "iut@chazelle.pro"
url; // "chazelle.pro"

Import export

// module.js
 
export default { 
  name: "Benjamin"
};

const email = "iut@chazelle.pro";

function share() {};

export { email, share };

ES6

Vue

// main.js

import Module, { email, share } from "./module.js";
Module; // { name: "Benjamin" }
email; // "iut@chazelle.pro"
share; // function share() {}

Quizz

https://interactly.glitch.me/

JavaScript n’exécute jamais deux contextes d’exécution en parallèle

 

Une fonction de rappel est une fonction passée en paramètre d'une fonction pour être appelée a posteriori

 

Un try catch ne peut pas capturer d'erreur si elle est levée au sein d'une fonction appelée dans le bloc try

 

Les promesses

 

async / await permet de bloquer un contexte d’exécution en attendant la fin d'une promesse

 

La console permet de

 

x vaut 42 après un import x from "modules.js"; si dans ce fichier référencé j'ai fais

 

y vaut "foo" après un import { y } from "modules.js"; si dans ce fichier référencé j'ai fais

 

Séance 2

Comment ça va cet aprem' ?

https://interactly.glitch.me/

Vous avez passé une bonne semaine ?

https://interactly.glitch.me/

Quoi de neuf depuis la dernière fois ?

 

(réponse libre)

Comment vous avez vécu le premier cours ?

https://interactly.glitch.me/

Comment jugeriez-vous l'apport théorique ?

https://interactly.glitch.me/

Comment jugeriez-vous l'exercice ?

https://interactly.glitch.me/

DOM API

Document Object Model

  • Représente la page web sous forme d'arbre de nœuds
  • Chaque nœud représente un élément HTML, un attribut, du texte ou un commentaire
  • Manipulable (ajout, modification, suppression) par une API
  • Utilisé pour créer des applications web interactives et dynamiques.
  • Standard du World Wide Web Consortium (W3C) / JS

Accès à un élément

document.body;
// <body>

document.getElementById("titre");
// <input id="titre">

document.querySelectorAll("*");
// [<html>, <head>, ...]

window.title;
// "Apprenons JS"
<!doctype html>
<html lang="fr">
<head>
  <meta charset="utf-8">
  <title>Apprenons JS</title>
  <script src="script.js"></script>
</head>
<body>
  <h1>Hello world</h1>
  <input id="titre" type="text" />
  <button>Envoyer</button>
</body>
</html>

Manipuler les attributs

let input = document.querySelector("#titre");

// Valeur du champ
input.value;

input.getAttribute("type"); // "text"

input.setAttribute("type", "password");
<!doctype html>
<html lang="fr">
<head>
  <meta charset="utf-8">
  <title>Apprenons JS</title>
  <script src="script.js"></script>
</head>
<body>
  <h1>Hello world</h1>
  <input id="titre" type="text" />
  <button>Envoyer</button>
</body>
</html>

Manipuler les nœuds textes

let paragraph = document.querySelector("p");

paragraph.textContent;
// "Hello world"

paragraph.innerHTML;
// "Hello<br />world"

paragraph.outerHTML;
// "<p>Hello<br />world</p>"
<!doctype html>
<html lang="fr">
<head>
  <meta charset="utf-8">
  <title>Apprenons JS</title>
  <script src="script.js"></script>
</head>
<body>
  <p>Hello<br />world</p>
  <input id="titre" type="text" />
  <button>Envoyer</button>
</body>
</html>

Manipuler les classes

let header = document.querySelector("h1");

header.classList.contains('head');
// true

header.classList.add('strong');

header.classList.remove('head')
<!doctype html>
<html lang="fr">
<head>
  <meta charset="utf-8">
  <title>Apprenons JS</title>
  <script src="script.js"></script>
</head>
<body>
  <h1 class="head">
    Hello world
  </h1>
  <input id="titre" type="text" />
  <button>Envoyer</button>
</body>
</html>

Événements DOM

let bouton = document.querySelector("button");

bouton.addEventListener("click", () => {
  alert("Clicked !");
});


// Il y a de nombreux événements écoutables
// 
//  mousedown, mouseup, mousemove, click, dblclick 
//  keydown, keyup, keypress
//  scroll
//  ...
<!doctype html>
<html lang="fr">
<head>
  <meta charset="utf-8">
  <title>Apprenons JS</title>
  <script src="script.js"></script>
</head>
<body>
  <h1>Hello<br />world</h1>
  <input id="titre" type="text" />
  <button>Envoyer</button>
</body>
</html>

Créer un noeud

let img = document.createElement("IMG");

document.body.appendChild(img);
<!doctype html>
<html lang="fr">
<head>
  <meta charset="utf-8">
  <title>Apprenons JS</title>
  <script src="script.js"></script>
</head>
<body>
  <h1 class="head">
    Hello world
  </h1>
  <input id="titre" type="text" />
  <button>Envoyer</button>
</body>
</html>

Exercice : Todo list





Projet Glitch

https://glitch.com/edit/#!/iut-todo-list-js

Todo list en Vanilla JS

<body>
  <ul></ul>
  <input type="text" />
  <button>Ajouter</button>
</body>
let ul = document.querySelector("ul");
let input = document.querySelector("input");
let button = document.querySelector("button");

button.onclick = () => {
  let item_li = document.createElement("LI");

  let item_span = document.createElement("SPAN");
  item_span.textContent = input.value;
  item_li.appendChild(item_span);

  let item_button = document.createElement("BUTTON");
  item_button.textContent = "X";
  item_button.onclick = () => {
    item_span.style.textDecoration = "line-through";
  };
  item_li.appendChild(item_button);

  ul.appendChild(item_li);

  input.value = "";
};




Problèmes

  • Modèle de données
    • Couplé fortement à la vue
    • Non centralisé
    • Non écoutable
  • Vue
    • Mise à jour à la charge du développeur
  • Complexité de compréhension du code

Solution

Modèle

Vue

Logique

// View
let ul = document.querySelector("ul")
let input = document.querySelector("input")
let button = document.querySelector("button")

function render() { 
  ul.innerHTML = ""

  input.onkeyup = () => updateNewLabel(input.value)

  for (let todo of todos) {
    let item_li = document.createElement("LI")

    let item_span = document.createElement("SPAN")
    item_span.textContent = todo.label;
    item_span.style.textDecoration = todo.done 
      ? "line-through" : ""
    item_li.appendChild(item_span)

    let item_button = document.createElement("BUTTON")
    item_button.textContent = "X"
    item_button.onclick = () => removeTodo(todo);
    item_li.appendChild(item_button)
    
    ul.appendChild(item_li)
  }

  input.value = newLabel
  
  button.onclick = () => addTodo()
}
// Model
let todos = []
let newLabel = ""
// Actions
function addTodo() {
  todos.push({ 
    label: newLabel, 
    done: false 
  });
  newLabel = ""
  render()
}

function removeTodo(todo) {
  todo.done = true
  render()
}

function updateNewLabel(_newLabel) {
  newLabel = _newLabel
  render()
}
<body>
  <ul></ul>
  <input type="text" />
  <button>Ajouter</button>
</body>
// View

<template>
  <ul>
    <li v-for="todo in todos" :key="todo.key">
        <span :class="{strike: todo.done}">{{ todo.label }}</span>
        <button @click="toggleTodo(todo)">X</button>
    </li>
  </ul>
  <input v-model="newLabel" type="text" />
  <button @click="addTodo">Ajouter</button>
</template>

<style scoped>
.strike {
  text-decoration: line-through;
}
</style>
<script setup>
// Model
import { ref } from "vue"
  
let todos = ref([])
let newLabel = ref("")

// Logic
function addTodo() {
  todos.value.push({ 
    id: Date.now(),
    label: newLabel.value, 
    done: false 
  })
  newLabel.value = ""
}

function toggleTodo(todo) {
  todo.done = !todo.done
}
</script>

Vue.js

Front-end / Back-end

Frontend / Backend

Utilisateur

Frontend

Backend

Database

</>

JavaScript

Front-end

Front-end

  • Partie visible et interactive d'une application web
  • C'est ce avec quoi l'utilisateur interagit directement
  • UI/UX : boutons, formulaires, images, etc.
  • Techno HTML, CSS et JavaScript
  • Exigences : UI/UX efficace, rapidité/fluidité, compatibilité, accessibilité, sécurité, maintenabilité

Frameworks

React

201k

Angular

86k

Vue

202k

Svelte

65k

Vue.js

Framework JavaScript pour le développement d'application web

 

Simplifie le développement d'interface dynamique

 

Populaire : Adobe, GitLab, ...

Historique

Juil 2013 - Premier commit par Evan You (@Google Creative Lab)

 

Déc 2013 - Version 0.6

 

Oct 2015 - Version 1.0

 

Sep 2016 - Version 2.0

 

Fév 2022 - Version 3.0

Pourquoi Vue.js ?

Réelle plus value

 

Puissant

 

Léger

 

Open source

Populaire

 

Communauté active

 

Maintenu

 

Bien documenté

 

 

Grosso modo, que fait Vue ?

Permet de séparer la vue, le modèle de données et la logique applicative

  • Gère à notre place le rendu de la vue, et la mise à jour des données
  • Gère à notre place le binding événementiel

Installation

npm init vue@latest

Intégration

// Version de développement
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
// Version de production
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
// Plus usuellement
import { createApp } from "vue"

Architecture

 

Instance

<div id="app"></div>
import { createApp } from 'vue'

import App from './App.vue'

const app = createApp(App)

app.mount("#app")

Paradigme

Modèle

Vue

Logique

Get started

Modèle, logique et vue

<script setup>
import { ref } from 'vue'

const message = ref("Hello world")
</script>


<template>
  <h1>{{ message }}</h1>
</template>


<style scoped>
h1 {
  text-decoration: underline;
}
</style>
 

Réactivité

Mise à jour automatique de l'UI et des données en fonction des mutations dans les données

 

Basé sur le design pattern Observer

 

Développement plus simple

et efficace

Réactivité : illustration

 
<script setup>
import { ref } from 'vue'

const message = ref("Hello world")
</script>


<template>
  <h1>{{ message }}</h1>
</template>

Attribut dynamique

 
<script setup>
import { ref } from 'vue'

const url = ref("learn.png")

setTimeout(() => {
  url.value = "master.png"
}, 5000)
</script>


<template>
  <img :src="url" />
</template>

Rendu conditionnel

 
<script setup>
import { ref } from "vue"
  
const speed = ref(10)
</script>

<template>
  <p v-if="speed < 88">
    Plus vite Doc !
  </p>
  <p v-else-if="speed === 88">
    88 mph !
  </p>
  <p v-else>Retour vers le futur !</p>
  <pre>
    En console, entrer :
    window.speed.value = 100
  </pre>
</template>

Boucles

 
<script setup>
import { ref } from "vue"

const verbs = ref([
  { id: 0, text: "Veni" }, 
  { id: 1, text: "Vidi" }
  { id: 2, text: "Vici" }
])
</script>
<template>
  <ul>
    <li 
        v-for="verb in verbs" 
        :key="verb.id"
    >
      {{ verb.text }}
    </li>
  </ul>
</template>

Boucles avec index

 
<script setup>
import { ref } from "vue"

const verbs = ref([
  { id: 0, text: "Veni" }, 
  { id: 1, text: "Vidi" }
  { id: 2, text: "Vici" }
])
</script>
<template>
  <ul>
    <li 
        v-for="(verb, index) in verbs"
        :key="verb.id"
    >
      #{{ index }} {{ verb.text }}
    </li>
  </ul>
</template>

Événements utilisateurs

<script setup>
import { ref } from "vue"
  
const count = ref(0)

function increment() {
  count.value++
}
</script>


<template>
  <p>{{ count }}</p>
  <button @click="increment">
    Incrémenter !
  </button>
</template>
 

Gestionnaire paramétré

<script setup>
import { ref } from "vue"
  
const count = ref(0)

function increment(delta) {
  count.value += delta
}
</script>


<template>
  <p>{{ count }}</p>
  <button @click="increment(3)">
    Incrémenter !
  </button>
</template>
 

Binding bidirectionnel

 
<script setup>
import { ref } from "vue"

const input = ref("Hello !")
</script>


<template>
  <input v-model="input" />
  <p>{{ input }}</p>
  <button @click="input = 'Hello !'">
    Reset
  </button>
</template>

Style dynamique

 
<script setup>
import { ref } from "vue"
  
const size = ref(10)
</script>


<template>
  <button @click="size = 10">Small</button>
  <button @click="size = 20">Medium</button>
  <button @click="size = 30">Large</button>
  <h1 :style="{fontSize: size + 'px'}">
    Hello world
  </h1>
</template>

Classe dynamique

 
<script setup>
import { ref } from "vue"
const isRed = ref(true)
</script>

<template>
  <h1 class="text" 
      :class="{highlight: isRed}"
  >
    Hello world
  </h1>
  <button @click="isRed = !isRed">
    Toggle
  </button>
</template>

<style scoped>
  .highlight { color: red; }
  .text { font-style: italic; }
</style>

Les composants

// LightSaber.vue
<script setup>
</script>

<template>
  <span>Yoda</span>
  <span class="handle">|||||</span>
  <span class="light">======</span>  
</template>

<style scoped>
.handle {
  display: inline-block;
  background: grey;
}

.light {
  display: inline-block;
  background: lightgreen;
  color: transparent;
}
</style>
<script setup>
import LightSaber from "./LightSaber.vue"
</script>


<template>
  <LightSaber />
</template>
 

Les props

// LightSaber.vue
<script setup>
const props = defineProps({
  owner: String,
  color: String
})
</script>

<template>
  <div>
    <span>{{ owner }}</span>
    <span class="handle">|||||</span>
    <span class="light" 
          :style="{background: color}">
      ======
    </span>  
  </div>
</template>

<style scoped>...</style>
<script setup>
import LightSaber from "./LightSaber.vue"
</script>


<template>
  <LightSaber owner="Mace windu" color="magenta" />
</template>
 

Les props et les boucles

<script setup>
import LightSaber from "./LightSaber.vue"
  
const jedis = ref([
  { name: 'Obi-Wan', color: 'lightblue' },
  { name: 'Darth Vader', color: 'red' },
  { name: 'Rey', color: 'orange' },
])
</script>


<template>
  <LightSaber 
    v-for="jedi in jedis" 
    :owner="jedi.name" 
    :key="jedi.name" 
    :color="jedi.color" 
  />
</template>
 

Exercice : List

Créer une micro application permettant d'ajouter des éléments à une liste à l'aide d'un champ texte et d'un bouton.

Séance 3

Propriété calculée

Problématiques

<div>
  {{ message.split('').reverse().join('') }} <!-- #1 Sémantique difficile -->
</div>

...

<div>
  {{ message.split('').reverse().join('') }} <!-- #2 Performance dégradée -->
</div>

...

<div>
  {{ message.split('').reverse().join('') }} <!-- #3 Répétition évitable -->
</div>

Propriété calculée / Computed

<script setup>
import { ref, computed } from "vue"

const message = ref("Hello world")

const reversed = computed(() => 
  message.value
    .split('')
    .reverse()
    .join('')
) 
// Une computed est une référence calculée à partir d'une expression
// Elle se met à jour automatiquement de façon réactive
// C'est une sorte de référence en lecture seule
</script>

<template>
  <p>Message original : {{ message }}</p>
  <p>Message inversé : {{ reversed }}</p>
</template>
 

computed vs function

const now = computed(() => 
  Date.now()
)

// Une computed n'est recalculée que sur des références réactives
<script setup>
// ...  
const reverse = () => 
  message.value
    .split('')
    .reverse()
    .join('')
</script>

<template>
  Message inversé : {{ reverse() }}
</template>

Différence : computed met en cache son résultat alors qu'une fonction est executé à chaque rendu de la vue

Filtrage d'un tableau

<script setup>
import { ref, computed } from "vue"

const numbers = ref([1, 2, 3, 4, 5])

const even = computed(() => {
  return numbers.value.filter(
    n => n%2 === 0
  )
}) // [2, 4]
</script>

<template>
  <li v-for="n in even">{{ n }}</li>
</template>
 

Mutation d'un tableau

<script setup>
import { ref } from "vue"

let letters = ref(
  ["J", "X", "O", "B", "U", "I"]
)

let sort = () => letters.value.sort()
</script>

<template>
  <div @click="sort">
    {{ letters }}
  </div>
</template>
 

Méthodes de mutations réactives

push(), pop(), shift(), unshift(),

splice(), sort(), reverse()

Vue.js devtools

Explorer le VDOM

Exercice

Rajouter un select et un champ pour filtrer la liste des tâches par status et par label.

 

https://glitch.com/edit/#!/iut-todo-base

Connecter un backend

Fetch API

function getHelloWorld() {
  
  const promise = fetch('https://www.xul.fr/ajax/ajax-get.json')
  
  promise.then(res => res.json()) // { message: "Hello World !" }
  
}

// On préférera utiliser la syntaxe async/await

Fetch API + async/await

async function getHelloWorld() {
  
  const res = await fetch('https://www.xul.fr/ajax/ajax-get.json')
  
  const obj = await res.json() // { message : "Hello World !" }
  
}

Corps de réponse

async function getSomething() {
  
  const res = await fetch('https://dummyjson.com/products/3')

  // res.text()
  // res.json() 
  // res.blob()
  // res.arrayBuffer()
  // res.formData() 
  
}

Méthode HTTP

async function deleteProduct() {
  
  await fetch('https://dummyjson.com/products/1', { 
    method: 'DELETE' 
  })
  
}

En-têtes de requête

async function getProducts() {
  
  const res = await fetch('https://dummyjson.com/products/', { 
    headers: {
      Authorization: `Token MY_API_KEY`,
      Accept: 'application/json',
    },
  })
  
  return await res.json()
  
}

Corps de requête

async function putProduct() {
  
  const res = await fetch('https://dummyjson.com/products/', { 
    method: 'PUT',
    headers: {
      Content-Type: 'application/json',
    },
    body: JSON.stringify({ name: 'Foo', description: 'Lorem ipsum' })
  })
  
  return await res.json()
  
}

Status de réponse

async function getNotFound() {
  
  const res = await fetch('https://www.jack-ceparou.com/olydri')
  
  // res.status			// 404
  // res.statusText		// "Not Found"
  
}

En-têtes de réponse

async function getDocumentations() {
  
  const res = await fetch('https://developer.mozilla.org')
  
  res.headers.get("Content-type") // "text/html; charset=utf-8" 
  
  for(let [header, value] of res.headers.entries()) {
      console.log(header, value)
  }
  
}

Gestion d'erreur

async function getDocumentations() {
  
  try {
    const res = await fetch(`http://somewhere.com/somewhat`)
    
    if (!response.ok) {
      throw new Error('Fail during fetching somewhat');
    }
    
    return await response.json();
    
  } catch (error) {
    console.error(error);
  }
}

Exercice

Brancher un back-end sur le front-end

 

Frontend de base https://glitch.com/edit/#!/iut-todo-more

 

Spec : https://glitch.com/edit/#!/iut-todo-server?path=README.md

 

1) Récupérer toutes les tâches + label bouton "Chargement"

 

2) Permettre de filtrer par owner (computed)

 

3) Pouvoir enregistrer une nouvelle tâche dans le backend

 

4) Pouvoir passer à done une tâche

TODO

  • Projet site enchère Backend
    • auth
    • CRUD
    • pagination
    • compte à rebour
    • JWT

Séance 4

JavaScript

Back-end

Back-end

  • "Côté serveur" d'une application web
  • Traitement et stockage de données (logique applicative, base de données, service tiers, etc)
  • Techno PHP, ASP, Ruby on Rails, J2EE, Node.js, ...
  • Exigences : nombre de requêtes parallèle, temps de réponse, sécurité des données

API

Application Programming Interface

 

Fourni un accès structuré à une application ou un service

 

Basé sur un protocole (ex: HTTP, TCP, ...) et un standard (ex: XML, JSON, ...) de communication

 

Architectures populaires : REST, SOAP

API REST

Representational State Transfer

 

Architecture logicielle pour système distribué largement utilisé pour les API web

 

Basé sur HTTP pour effectuer des opérations CRUD (Create, Read, Update, Delete) via les méthodes/verbes HTTP POST, GET, PUT ou PATCH, DELETE sur des ressources

 

Données au format JSON ou plus rarement XML

Node.js

  • Environnement JavaScript open source
  • Basé sur le moteur JavaScript V8 de Google Chrome
  • Hautement événementielle : adapté aux I/O intensifs (streaming, temps réel, traitement de données en masse, ...)
  • NPM : Écosystème riche de modules et de bibliothèques
  • Multiplateforme: Windows, Linux et macOS.
  • Paradigme Node.js != Paradigme PHP

Express

Framework back-end open-source pour Node.js.

 

Fonctionnalités : routage, gestion des requêtes et des réponses HTTP, authentification et bien plus encore.

 

Adapté pour la création d'API

Installation

npm install express

Get started

import express from 'express'

const app = express()

app.listen(3000, () => {
  console.log('Server is listening on port 3000 !')
})

Hello world

import express from 'express'

const app = express()

app.get('/', (req, res) => {
  res.send('Hello world !');
})

app.listen(3000, () => {
  console.log('Server is listening on port 3000 !')
})

Méthodes HTTP

app.get('/', (req, res) => {
  res.send('GET at root');
})

app.post('/', (req, res) => {
  res.send('POST at root');
})

app.put('/', (req, res) => {
  res.send('PUT at root');
})

app.delete('/', (req, res) => {
  res.send('DELETE at root');
})

Routage

app.get('/', (req, res) => {
  res.send('GET at root');
})

app.get('/foo', (req, res) => {
  res.send('GET at /foo');
})

Corps de réponse

app.get('/asHTML', (req, res) => {
  res.send('GET at root'); // Content-Type : text/html
})

app.get('/asJSON', (req, res) => {
  res.send({ foo: 'bar' }); // Content-Type : application/json
})

app.get('/asBinary', (req, res) => {
  res.send(Buffer.from('baz')); // Content-Type : application/octet-stream
})

app.get('/fromFile', (req, res) => {
  res.sendFile('/path/to/image.png');
})

En-tête de réponse

app.get('/notFound', (req, res) => {
  res.status(404)
  res.end()
})

app.get('/headers', (req, res) => {
  res.set('Access-Control-Allow-Origin', '*')

  res.set({
    'Content-Type': 'text/plain',
    'Content-Length': '123',
    'ETag': '12345'
  })
  
  res.end()
})

Redirections

app.get('/redirection', (req, res) => {
  res.redirect('/foo/bar')
  // res.redirect('http://example.com')
  // res.redirect(301, 'http://example.com')
  // res.redirect('../login')
})

Fichiers statiques

app.use(express.static('public'))
// http://localhost:3000/js/app.js
// http://localhost:3000/images/bg.png
// http://localhost:3000/hello.html

app.use('/static', express.static('public'))
// http://localhost:3000/static/js/app.js
// http://localhost:3000/static/images/bg.png
// http://localhost:3000/static/hello.html

Middleware globaux

app.get('/', (req, res) => {
  res.send('Hello World!')
})

app.use((req, res, next) => {
  console.log(`Request received at ${new Date()}`)
  next() // Call the next middleware function
})

app.use((req, res, next) => {
  res.setHeader('X-Custom-Header', 'Hello World')
  next() // Call the next middleware function
})

Middleware locaux

const authMiddleware = (req, res, next) => {

  const token = req.headers.authorization.split(' ')[1]
  
  if(verifyToken(token)) {
      next() // Call the next middleware function
  } else {
      res.status(401).json({ error: "Not authorized" })
  }
  
}

app.get('/accounts', authMiddleware, (req, res) => {
  res.send('Safe Hello World!')
})

Corps de requête JSON

import express from 'express'

const app = express()

app.use(express.json())

app.post('/users', (req, res) => {
  const { name, email } = req.body
  
  res.send(`User created: ${name} (${email})`)
})

Corps de requête HTML Form

import express from 'express'
import bodyParser from 'body-parser' // npm install body-parser

const app = express()

app.use(bodyParser.urlencoded({ extended: true }))

app.post('/login', (req, res) => {
  const { username, password } = req.body
  res.send(`Logged in as ${username}`)
})
<form method="POST" action="/login">
  <input type="text" name="username" placeholder="Username">
  <input type="password" name="password" placeholder="Password">
  <button type="submit">Log In</button>
</form>

Paramètre de requête

// http://localhost/search?q=Foo
app.get('/search', (req, res) => {
  const { q } = req.query
  res.send(`Search query is: ${q}`) // Search query is: Foo
})

// http://localhost/search?tag=Foo&tag=Bar
app.get('/search', (req, res) => {
  const { tag } = req.query     // Array if several given
  res.send(`Tags are: ${tag}`)  // Tags are: ['Foo', 'Bar']
})

Paramètre de chemin

app.get('/users/:userId', (req, res) => {
  const { userId } = req.params
  res.send(`User ID is: ${userId}`)
})


app.get('/users/:userId/posts/:postId', (req, res) => {
  const { userId, postId } = req.params
  res.send(`User ID is: ${userId}, Post ID is: ${postId}`)
})

Base de données

Sequelize

  • ORM (Object-Relational Mapping) pour Node.js permettant de faciliter l'interaction avec une BDD relationnelle en manipulant les données relationnelles sur des modèles objets
  • Supporte MySQL, PostgreSQL, SQLite et autres
  • Offre une interface programmatique pour interagir avec la BDD via les objets JavaScript, pour ne pas écrire de requêtes SQL
  • Fonctionnalités avancées : pagination, associations, validation de données et migration de schéma de base de données
  • Open source et communauté active

Usage

npm i sequelize sqlite3
const sequelize = new Sequelize('sqlite::memory:')

Modèle de données

import { DataTypes } from 'sequelize'

const User = sequelize.define('User', {
  id: {
    type: DataTypes.UUID,
    defaultValue: DataTypes.UUIDV4,
    allowNull: false,
    primaryKey: true
  },
  username: {
    type: DataTypes.STRING,
    allowNull: false,
    unique: true
  },
  age: {
    type: DataTypes.DECIMAL,
    allowNull: false
  },
})

CRUD

import { Op } from "sequelize"

// Create
await User.create({ username: "john", age: 42 }) // instance

// Read
const userById = await User.findByPk("de262606-070c-42e1-a43d-75c9696e919a") // instance | null

const userByName = await User.findOne({ where: { username: "alice" }) // instance | null

const users = await User.findAll({ where: { age: { [Op.gt]: 18 } } }) // array<instance>

// Update
await userByName.update({ age: 43 }) // instance

// Delete
await User.destroy({ where: { id: "de262606-070c-42e1-a43d-75c9696e919a" } }) // number

userById.destroy() // void

Validation

try {
  
  User.create({ 
    email: 'user@example.com',
    password: 'password' 
  })
  
} catch(e) {
  
  // e.errors
  
  // e.errors.map(error => error.message)
  
}

const User = sequelize.define('User', {
  id: {
    type: DataTypes.UUID,
    defaultValue: DataTypes.UUIDV4,
    allowNull: false,
    primaryKey: true
  },
  email: {
    type: DataTypes.STRING,
    allowNull: false,
    unique: true,
    validate: {
      isEmail: true 
    }
  },
  password: {
    type: DataTypes.STRING,
    allowNull: false,
    validate: {
      len: [8, 20]
    }
  }
})

Exercice

  • Écrire le backend de l'exercice des TODO list avec Express et Sequelize

 

Remixer ou cloner

https://glitch.com/edit/#!/iut-todo-server-base

Association de modèles

User.hasMany(Task, { 
  as: 'tasks', 
  foreignKey: 'userId', 
  onDelete: 'CASCADE' 
})

Task.belongsTo(User, { 
  as: 'owner', 
  foreignKey: 'userId' 
})
import { DataTypes } from 'sequelize'

const Task = sequelize.define('Task', {
  id: {
    type: DataTypes.UUID,
    defaultValue: DataTypes.UUIDV4,
    allowNull: false,
    primaryKey: true
  },
  text: {
    type: DataTypes.STRING,
    allowNull: false
  },
  done: {
    type: DataTypes.BOOLEAN,
    allowNull: false
  },
})

Projections et inclusions

User.hasMany(Task, { 
  as: 'tasks', 
  foreignKey: 'userId', 
  onDelete: 'CASCADE' 
})

Task.belongsTo(User, { 
  as: 'owner', 
  foreignKey: 'userId' 
})
const user = await User.findOne({
  attributes: ['id', 'username'],
  include: [{
      model: Task,
      as: 'tasks',
      attributes: ['label'],
      where: { done: false }
   }]
})


{
  id: '1f2fadb1-0d7e-47a7-9d5b-5d3e',
  username: 'casear',
  tasks: [
    { label: 'Vini' },
    { label: 'Vidi' },
    { label: 'Vici' }
  ]
}

Inclusions profondes

User.hasMany(Collection, { 
  as: 'collections', 
  foreignKey: 'userId', 
  onDelete: 'CASCADE' 
})

Collection.belongsTo(User, { 
  as: 'owner', 
  foreignKey: 'userId' 
})

Collection.hasMany(Task, { 
  as: 'tasks', 
  foreignKey: 'collectionId', 
  onDelete: 'CASCADE' 
})

Task.belongsTo(Collection, { 
  as: 'collection', 
  foreignKey: 'collectionId' 
})
const user = await User.findOne({
  attributes: ['username'],
  include: [{
      model: Collection,
      as: 'collections',
      include: [{
        model: Task, 
        as: 'tasks',  
        attributes: ['label']
      }]
   }]
})


{
  username: 'casear',
  collections: [{
      tasks: [
        { label: 'Vini' },
        { label: 'Vidi' },
        { label: 'Vici' }
      ]
  }]
}

JSON Web Token

JSON Web Token

  • Format de jeton d'authentification compact et signé
  • Basé sur une signature numérique (cryptographie asymétrique)
  • Moyen d'authentification et d'autorisation des utilisateurs
  • JWT contient des données signées, mais pas chiffrées
  • Standard industriel RFC 7519

Notions de sécurité

Identification

Processus de reconnaissance d'un utilisateur ou d'une entité par le biais d'un nom d'utilisateur ou d'un identifiant unique.

 

Authentification

Processus de vérification de l'identité d'un utilisateur ou d'une entité.

 

Autorisation

Processus de détermination des droits d'accès d'un utilisateur ou d'une entité à des ressources spécifiques après qu'ils aient été identifiés et authentifiés.

JWT ≈ Certificat COVID

Authentification et autorisation

Utilisateur

Authentification

Service d'authentification

Preuve (mot de passe, SMS, ...)

Token

Autorisation

Utilisateur

Service applicatif

Service d'authentification

Requête + Token

Token

Validation signature + droits

Réponse

Vérification

de non-révocation

Peut être

la même instance

Émission du jeton

Structure d'un JWT

 

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

 
 
 

{
  "alg": "HS256",
  "typ": "JWT"
}

{
  "sub": "123456789",
  "name": "John Doe",
  "iat": 1516239022
}

Hash(Secret,

Header + Payload)

Header

Payload

Signature

Structure d'un JWT

 
 
 

{
  "alg": "HS256",
  "typ": "JWT"
}

{
  "sub": "123456789",
  "name": "John Doe",
  "iat": 1516239022
}

Hash(Secret,

Header + Payload)

Header

Payload

Signature

  • Type de jeton et l'algorithme de hachage utilisé pour signer le jeton
  • Informations à transmettre (identité utilisateur, autorisations,  contexte, ...)
  • Signature  qui garantit l'intégrité des informations contenues dans le jeton.

Émission d'un JWT

import jwt from 'jsonwebtoken'

// Nous sommes libres sur le contenu du payload, 
// mais il des pratiques normalisées

const data = {
  jti: 'd6a0bf5d-bec1-4c85-852a-80cfd6ffbc74',    // Numéro de série
  exp: Math.floor(Date.now() / 1000) + (60 * 60), // Expire dans 1h
  username: 'john.doe',
  roles: ['ADMIN']
}

const token = jwt.sign(data, 'THE_SECRET')

Vérification d'un JWT

import jwt from 'jsonwebtoken'

try {
  const data = jwt.verify(token, 'THE_SECRET');
    
  // Vérifier que data.exp n'a pas expiré
  
  // Vérifier que data.roles est suffisant pour l'action en cours
  
  // Vérifier que data.jti est un numéro de série non revoquée
   
} catch(e) {
  // Erreur de vérification
}

npm

Node package manager

  • Gestionnaire de paquets pour Node.js
    • Permet d'installer, de gérer et de partager des bibliothèques de code JavaScript réutilisables, appelées "packages" ou "modules"
  • NPM est installé avec Node.js
  • package.json pour lister les dépendances théoriques
  • package-lock.json pour lister les dépendances effectives
  • node_modules/ contient les dépendances installées

package.json

{
  "name": "mon-projet",
  "version": "1.0.0",
  "description": "Un exemple de projet avec NPM",
  "main": "index.js",
  "scripts": {
    "start": "node index.js",
    "clean": "rm -rf node_modules"
  },
  "author": "John doe",
  "license": "MIT",
  "dependencies": {
    "express": "^4.17.1",
    "lodash": "^4.17.21"
  },
  "devDependencies": {
    "nodemon": "^2.0.12"
  }
}

package-lock.json

{
  "name": "mon-projet",
  "version": "1.0.0",
  "lockfileVersion": 1,
  "requires": true,
  "packages": {
    "": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/mon-projet/-/mon-projet-1.0.0.tgz",
      "integrity": "sha512-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
      "dependencies": {
        "express": "^4.17.1"
      }
    },
    "express": {
      "version": "4.17.1",
      "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
      "integrity": "sha512-mHJ9O79RqluphRrccoy1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
      "requires": {
        "body-parser": "1.19.0",
        ...
      }
    }
  }
}

Initialiser un projet npm

# Créer un package.json originel pour votre projet
npm init

Ajouter une dépendance

# Ajoute une dépendance au package.json et l'installe dans node_modules/
npm install lodash
# Idem pour une dépendance de développement
npm install typescript --save-dev

Installer un projet

# Installe les node_modules à partir des dépendances du package.json
npm install

Utiliser une dépendance

import lodash from "lodash"


// Old way
const lodash = require("lodash")

Jouer un script

# Lance un script définit dans package.json
npm run start

Publier un package

# Publie une archive du package dans npmjs.com
npm publish

Et maintenant,

on passe

aux choses sérieuses

BidBay

https://github.com/benjaminchazelle/bidbay-starter

À propos

  • Projet en binôme http://goo.su/groupes
  • Deadline : jeudi 20 avril 2023 à 23h 59
  • Notation
    • Automatique par test E2E : npm run test
    • Part personnelle : formulaire en fin de projet

Cypress

  • cd frontend && npm run test
  • Lancer sous Chrome : Vérifier bien qu'au clonage du projet, aucun test ne passe

DS - 5 avril - 1h

  • Document autorisé
  • QCM choix unique (10 questions)
    • Similaire aux questions interactly
  • Questions ouvertes (5 questions)
    • Grands concepts et problématiques liées au frontend et au backend
  • Code à trou
    • Vue.js
    • API Fetch
    • Express + Sequelize

Projet - Deadline

jeudi 20 avril 2023 23:59

Checklist

🚧 Remplir le Google Sheet, finir le projet

 

📝 Remplir le Google Form (à venir)

 

💼 Kiffer votre stage

Équipes bienveillantes et compétentes

 

Cœur de métier sympa

 

Boîte en hyper croissance, French Tech 120

 

Vue.js, Symfony, Google Cloud Platform

 

Lyon, Mâcon, home office

Si jamais

benjamin@chazelle.pro

 

06 51 23 51 39

 

linkedin.com/in/benjamin-chazelle

 

www.chazelle.pro

Vous êtes super !

Bonne continuation

à vous =)

Made with Slides.com