JavaScript
Complément Front-end / Back-end
https://slides.com/benjichaz/but2
Avant-propos
Qui suis-je ?
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
Et vous ? =)
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
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.
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
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
Vous êtes super !
Bonne continuation
à vous =)
BUT2 - JavaScript - Front-end + Back-end
By Benji Chaz
BUT2 - JavaScript - Front-end + Back-end
- 572