JavaScript
Basé sur les slides de Victoria Kirst https://web.stanford.edu/class/cs193x/staff/
JavaScript
● Historiquement permet de programmer des interactions
au sein des navigateurs
○ Interagir : savoir qu’un bouton a été cliqué
○ Afficher : manipuler la page web pour rendre visible
des nouvelles parties
○ Communiquer : envoyer ou recevoir des requêtes
● C’est le seul langage disponible côté navigateur
● Mais est aussi disponible côté serveur via NodeJS
Exemple
<!doctype html>
<html lang="fr">
<head>
<title>Example</title>
<script>
var msg = "hello";
alert(msg);
</script>
</head>
<body>
</body>
</html>
Best practice
<!doctype html>
<html lang="fr">
<head>
<title>Example</title>
<script src="script.js"></script>
</head>
<body>
</body>
</html>
Messages de log
L’instruction qui permet d’afficher des messages de log en
JavaScript est :
script.js
console.log('Hello, world!');
Exécution de JavaScript
Il n’y a pas de "main method"
● Le script est exécuté de haut en bas
Il n’y a pas de compilation par le développeur
● JavaScript est compilé et exécuté à la volée par le
navigateur
Similarités avec Java, C, ...
for-loops:
for (let i = 0; i < 5; i++) { … }
while-loops:
while (notFinished) { … }
comments:
// comment or /* comment */
conditionals (if statements):
if (...) {
...
} else {
...
}
Fonctions
La syntaxe suivante est une des manières de définir une
fonction en JavaScript
function name() {
statement;
statement;
...
}
Exemple
hello();
hello();
function hello() {
console.log('Hello!');
console.log('Welcome to JavaScript');
}
Cela fonctionne car les déclarations de
fonctions sont hoisted : déplacées au
sommet du scope dans lesquelles elles
sont définies
il faut éviter de se reposer sur ce
mécanisme
Variables
Trois façons de déclarer des variables en JS
// Function scope variable
var x = 15;
// Block scope variable
let fruit = 'banana';
// Block scope constant; cannot be reassigned
const isHungry = true;
Le langage est dynamiquement typé
Paramètres de fonctions
function printMessage(message, times) {
for (var i = 0; i < times; i++) {
console.log(message);
}
}
Les paramètres de fonctions ne sont pas déclarés à l’aide de
let, const ou var
Comprendre var
function printMessage(message, times) {
for (var i = 0; i < times; i++) {
console.log(message);
}
console.log('Value of i is ' + i);
}
printMessage('hello', 3);
La valeur de i est accessible hors de
la boucle for car var déclare des
variables avec un scope fonction
Comprendre let
function printMessage(message, times) {
for (let i = 0; i < times; i++) {
console.log(message);
}
console.log('Value of i is ' + i);
}
printMessage('hello', 3);
La valeur de i n’est pas accessible
hors de la boucle for car let déclare
des variables avec un scope block
Comprendre const
const y = 10;
y = 0; // error!
y++; // error!
const list = [1, 2, 3];
list.push(4); // OK
Les variables déclarées avec const ne peuvent
pas être réaffectées
Cependant il reste possible de modifier l’objet
sous jacent
● Ressemble au final de Java
Bonnes pratiques
● Utiliser const partout où vous pouvez
● Si vous avez besoin d’une variable réaffectable, utilisez
let
● N’utilisez pas var.
○ const et let sont maintenant bien supportés par les
browsers
Types
Les variables JS n’ont pas de types, mais leurs valeurs si
Il y a plusieurs types primitifs:
● Boolean : true et false
● Number : tout est de type double (pas d’entiers)
● String: avec 'single' ou "double-quotes"
● Null: null une valeur qui signifie “ceci n’a pas de valeur”
● Undefined: la valeur d’une variable non affectée
Il y a aussi les types Object, comme Array, Date, et même
Function!
Transtypage booléen
Les valeurs non booléennes peuvent être utilisées dans les
instructions de contrôle, elles sont converties en "truthy" ou
"falsy"
○ null, undefined, 0, NaN, '' évaluent à false
○ Les autres valeurs évaluent à true
if (username) {
// username is defined
}
Egalité
== et != font une conversion implicite de type avant
comparaison
'' == '0' // false
'' == 0 // true
0 == '0' // true
NaN == NaN // false
[''] == '' // true
false == undefined // false
false == null // false
null == undefined // true
Opérateurs === et !==
=== et !== sont les véritables opérateurs de comparaison,
toujours les utiliser!
'' === '0' // false
'' === 0 // false
0 === '0' // false
NaN === NaN // still weirdly false
[''] === '' // false
false === undefined // false
false === null // false
null === undefined // false
null et undefined
Quelle différence?
● null est la valeur représentant l’absence de valeur
(comme null en Java)
● undefined est la valeur d’une variable n’ayant pas reçu
de valeur
Tableaux
Les tableaux sont les objets pour définir des listes
// Creates an empty list
const list = [];
const groceries = ['milk', 'cocoa puffs'];
groceries[1] = 'kix';
// For each loop
for (let item of groceries) {
console.log(item);
}
Objets
Collection de paires clé - valeur, peut être utilisé comme une
hashmap
const prices = {};
const scores = {
peach: 100,
mario: 88,
luigi: 91
};
console.log(scores['peach']); // 100
console.log(scores.peach); // 100
scores.peach = 20;
console.log(scores.peach); // 20
Itérer les propriétés d’un objet
Il est possible d’itérer sur les propriétés d’un objet
const scores = {
peach: 100,
mario: 88,
luigi: 91
};
for (let name in scores) {
console.log(name + ':' + scores[name]);
}
Fonctions de premier ordre
En JavaScript, les fonctions sont des objets comme les
autres
var add = function(a, b) {
return a + b;
}
add(2, 2); // 4
Ajout d’une fonction sur un objet
On peut ajouter une fonction dans un objet, this désigne
l’objet receveur
const scores = {
peach: 100,
mario: 88,
luigi: 91,
total: function() {
return this.peach + this.mario + this.luigi;
}
};
Classes ES6
class Person {
constructor(name, age) { ● Le constructeur est
this.name = name; optionnel
this.age = age; ● Tous les attributs et les
}
méthodes sont publics
displayAge() { ● Tous les appels de
console.log(this.age); méthodes et les accès
}
displayName() {
d’attributs se font via
console.log(this.age); this
}
displayAgeAndName() {
this.displayAge();
this.displayName();
}
}
const p = new Person("Joe",1);
Callbacks
Les fonctions peuvent être passées en paramètres d’autres
fonctions
const groceries = ['milk', 'cocoa puffs'];
// For each loop
for (let item of groceries)
console.log(item);
// Callback style
groceries.forEach(function(item) {
console.log(item);
});
// Nerdy callback style (use that!)
groceries.forEach(item => {
console.log(item);
});
Mais que fait forEach?
class Array {
forEach(callback) {
for (let i = 0; i < this.length; i++) {
callback(this[i], i, this);
}
}
}
forEach itère sur tous les éléments du tableau, pour chaque
élément, elle applique la fonction callback (passée en
paramètre de forEach) en lui fournissant en paramètre l’
élément courant forEach calls callback back!
DOM
On accède aux éléments composant la page web en
JavaScript au travers du Document Object Model
<html>
<head>
<title></title>
</head>
<body>
<h1></h1>
<div>
<p></p>
</div>
</body>
</html>
DOM et JavaScript
JavaScript peut :
● examiner les noeuds du DOM pour en inspecter l’état
(ex. voir le texte saisi par un utilisateur)
● editer les attributs des noeuds du DOM (ex. changer le
style d’un élément <h1>)
● ajouter ou supprimer des noeuds du DOM (ex. ajouter
un texte de statut quelque part)
Accéder aux noeuds du DOM
L’accès aux noeuds du DOM se fait via la fonction
querySelector:
document.querySelector('css selector');
● Retourne le premier noeud qui matche la règle CSS
document.querySelectorAll('css selector');
● Retourne tous les éléments qui matchent la règle CSS
Quelques propriétés des noeuds du DOM
Property Description
id la valeur de l’attribut id de l’élément
le HTML entre le noeud ouvrant et fermant de l’
innerHTML
élément vu comme une chaîne de caractères
Le contenu texte d’un noeud ainsi que celui de ses
textContent
descendants
Un objet contenant toute la liste des classes dans
classList
lesquelles l’élément se situe
Créer des éléments dans le DOM
Il est possible de créer des éléments dynamiquement via
createElement et appendChild:
document.createElement(tag string)
element.appendChild(element);
On peut supprimer des éléments via remove
element.remove();
Évènements
EVENT! function onClick()
Click Me!
{...}
Exemple : un élément de page avec lequel on peut
interagir. Quand on clique sur le bouton, un
événement est déclenché. L’utilisateur peut
enregistrer des callbacks sur les événements de son
choix
Envoyer des requêtes
// FAKE HYPOTHETICAL API.
// This is not real a JavaScript function!
const content = loadFromFile('images.txt');
Quelques problèmes avec cette API fictive:
● Ce serait mieux de charger les données de manière
asynchrone sinon l’appel va être bloquant
● Il n’est pas possible de vérifier le statut de la requête.
Quid si la ressource n’existe pas? Si l’on a pas les droits?
L’API Fetch
L’API Fetch comporte une seule fonction, concise et facile à
utiliser
fetch('images.txt');
● fetch prend en paramètre l’URL de la ressource que
vous voulez consulter
● Elle retourne une Promise
● What???
Les Promise
const promise = fetch('images.txt');
promise.then(onSuccess, onFail);
Une promesse peut être dans un des états suivants
● pending: état initial, promesse non-exécutée
● fulfilled: la promesse a été exécutée correctement
● rejected: la promesse a rencontrée une erreur
On attache un gestionnaire à une promesse via .then()
fetch('images.txt').then(response =>
console.log(response.status));
REST
Restful API
Une API web est dite “Restful” si elle a les caractéristiques
suivantes:
- Les requêtes sont envoyées sous forme de requêtes HTTP:
- Méthodes HTTP: GET, POST, PUT, PATCH, DELETE..
- Un Endpoint: http://example.com/api/ressources
- Les requêtes doivent être envoyées avec un
MIME*/Content-type spécifique tel: JSON, XML, HTML, etc.
* Multipurpose Internet Mail Extensions
Contraintes d’une API REST
Une API Restful doit respecter certaines contraintes d’architecture:
1. Archi client-serveur: chacun évolue indépendamment.
2. Serveur sans états (stateless): pas de sessions.
3. Utilisation du cache: le client sait combien de temps il peut
garder les données qu’il reçoit avant expiration.
4. Une interface uniforme: un seul id, contient l’URL des
ressources suivantes..
5. Archi en couches: Réduire la complexité de l’architecture
globale de l’API.
6. Code à la demande: étendre les fonctionnalités du client.
Ressources
En REST tout est resources, on interagit avec les ressources à
travers différents endpoints (URLs):
- http://example.com/products
- http://example.com/products/1
- http://example.com/products/1/description
Action sur les ressources
Pour interagir avec une ressource, il suffit d’utiliser la méthode
HTTP (verbe) adéquate :
- GET http://example.com/products/1 (Récupérer)
- POST http://example.com/products (Créer)
- PUT http://example.com/products/1 (Mettre à jour)
- DELETE http://example.com/products/1 (Effacer)
API Endpoints
API de Twitter
https://developer.twitter.com/en/docs/tweets/post-and-engage/overview
API Endpoints
POST statuses/update
Paramètres des Endpoints
Un endpoint peut accepter un ensemble de paramètres pour
effectuer sa tâche. Ces paramètres peuvent être passés de
trois manières différentes:
- Paramètres dans l’URL.
- Headers dans la requête.
- Corps (body) de la requête.
Paramètres des Endpoints - paramètres dans l’URL
Paramètres des Endpoints - headers de la requête
Paramètres des Endpoints - corps de la requête
Codes d’état HTTP (Status code)
Il existe plus de 40 codes HTTP, chacun ayant une siginification
bien précise. Voici les grandes catégories:
- 1xx: Information (e.g. 100 attente de la suite (continue))
- 2xx: Succès (e.g. 200 tout s’est bien passé (OK))
- 3xx: Redirection (e.g. 301 document déplacé (redirection))
- 4xx: Erreur du client web (e.g. 404 document introuvable)
- 5xx: Erreur du serveur (e.g. 500 Erreur interne)
API Restful et cache (E-tags)
Les E-tags permettent d’implémenter la contrainte de cache
pour une API Restful. Il existe également d’autres méthodes qui
sont l’utilisation des headers Expires et cache-control.
API Restful et cache (E-tags)
Une fois que je connais l’E-tag, je peux à l’avenir demander s’il
a changé ou non en envoyant une requête avec comme header:
If-None-Match: c0947-b1-4d0258df1f625
API Restful et cache (E-tags)
OpenAPI
C’est un ensemble de spécifications permettant de décrire
une API REST.
Cette description peut être comprise aisément à la fois par
les développeurs ainsi que par les ordinateurs.
L’API est décrite sous forme d’un fichier JSON ou YAML.
Un ensemble d’outils permet de facilement convertir ces
fichiers de description en documentation web ou encore en
bibliothèques pour directement utiliser l’API décrite sans
avoir à l’implémenter.
OpenAPI
OpenAPI
NodeJS
NodeJS
- C’est un runtime Javascript écrit en C++.
- Il permet d’interpréter et exécuter du code Javascript.
- Il fournit par défaut un ensemble de bibliothèques pour
interagir avec le système d’exploitation et concevoir des
serveurs Web.
NodeJS API V8 (chrome)
Un ensemble riche de C’est le “moteur” qui
bibliothèques Javascript permet à NodeJS
pour concevoir des d’interpréter et d’exécuter
serveurs Web. du code Javascript
Chrome
Chrome
Console.log(document.getElementById(‘information’));
NodeJS
NodeJS
Console.log(document.getElementById(‘information’));
document WHAT ?!
NodeJS
Console.log(“Hello”);
“Hello”
NodeJS en ligne de commande
Lancer node sans spécifier de fichier en argument le démarre
une boucle REPL (Read-Eval-Print-Loop).
$ node
> let i = 0
undefined
> i++
0
> i
1
NodeJS avec des scripts
NodeJS peut également être utilisé à travers des scripts.
Pour le lancer, il suffit simplement d'exécuter node en
spécifiant le fichier à exécuter en argument.
$ node monscript.js
function hello() {
console.log(“Hello World!”);
}
hello();
monscript.js
NPM: Node Package Manager
Lorsque vous installez node, vous installez également npm.
C’est un outil en ligne de commande qui vous permet de
facilement installer des packages écrits en Javascript et
compatibles avec NodeJS.
Pour rechercher des packages rendez-vous sur
https://npmjs.com
NPM: Pour les nuls...
$ npm init
$ npm install express [--save] [-g]
$ npm uninstall express
$ npm start
$ npm install
package.json
NPM: Pour les nuls...
$ npm start
$ npm test
NPM: Pour les nuls...
$ npm installDb
ExpressJS
ExpressJS
const express = require('express');
const app = express();
// répondre avec hello world quand on reçoit une
requête GET
app.get('/',(req, res) => {
res.send('hello world');
});
app.listen(3000, () => {
console.log("Serveur démarré");
});
app est une instance d’ExpressJS.
Routes
Une route est définie comme suit:
app.method(path, handler)
- method: permet de définir la méthode HTTP de la
requête.
- path: permet de définir le chemin de la ressource
demandée.
- handler: représente la fonction qui va gérer la requête
lors de sa réception.
Handler
Un handler reçoit toujours deux objets en paramètres. Ces
objets sont créés par express est sont spécifiques à chaque
requête reçue.
app.get('/', (req, res) => {
res.send('hello world');
});
res.send() envoie la réponse avec un MIME/Content-type par défaut à “text/html”
Chaîner les Handler
Il est également possible de chaîner les Handlers, pour ce
faire il suffit de spécifier le paramètre “next” et d’y faire
appel.
app.get('/example', (req, res, next) => {
console.log('La réponse sera envoyée par la
fonction suivante...');
next();
}, (req, res) => {
res.send('Hello from B!');
});
Ordre de déclaration des routes
L’ordre de déclaration des routes est important. Toujours
mettre le chemin racine en dernier.
app.get('/products', (req, res) => {
res.send('products list');
});
app.get('/', (req, res) => {
res.send('hello world');
});
Méthodes de l’objet réponse
res.send('hello world');
res.status(404).end();
res.status(404).send('product not found.');
res.json(json_object);
res.redirect(301, 'http://example.com');
Paramètres d’une requête HTTP
Il existe plusieurs méthodes pour récupérer les paramètres
d’une requête HTTP:
// http://localhost:3000/?prenom=john&nom=doe
app.get('/', (req, res) => {
res.send(req.query.prenom);
});
Paramètres d’une requête HTTP
Il existe plusieurs méthodes pour récupérer les paramètres
d’une requête HTTP:
// http://localhost:3000/john/doe
app.get('/:prenom/:nom', (req, res) => {
var prenom = req.params.prenom
res.send('Salut ' + prenom + ’ !’);
});
Headers d’une requête HTTP
Pour récupérer des headers depuis la requête entrante, il
vous suffit de faire appel à la méthode get().
req.get('user-agent');
console.log(req.headers);
Body d’une requête HTTP
Pour récupérer le body de la requête entrante, il vous suffit
d’utiliser l’attribut body de l’objet req.
<form action="login" method="post">
<input type="text" id="email" name="email">
<input type="password" name="password">
<input type="submit" value="Submit">
</form>
app.post('/login', function (req, res) {
res.json(req.body);
});
Données statiques
ExpressJS permet également de transmettre des fichiers
statiques tels des fichiers html, css, js, jpg…
const express = require('express');
const app = express();
app.use(express.static("public"));
app.get('/', (req, res) => {
res.send('hello world');
});
Outils: Supervision
Redémarrer automatiquement le serveur lorsqu’un
changement a été effectué sur un des fichiers du projet.
Différents outils :
● Forever
● nodemon
● pm2
● supervisor
Outils: cURL
C’est un outil qui va vous permettre de faire des requêtes
depuis votre terminal avec les méthodes HTTP que vous
voulez.
$ curl -X GET http://localhost:3000/john/doe
$ curl -X POST http://localhost:3000/john/doe
$ curl -X PUT http://localhost:3000/john/doe
$ curl -X DELETE http://localhost:3000/john/doe
Outils: Postman, insomnia
Si vous préférez utiliser plutôt une interface graphique riche
en fonctionnalités, vous pouvez également utiliser postman
ou encore insomnia.
getpostman.com
insomnia.rest
Outils: Insomnia
Outils: Ngrok
Si vous voulez partager votre localhost avec le reste du
monde, Ngrok vous permet d’avoir une URL publique.
https://ngrok.com
Outils: Ngrok
ExpressJS (suite)
Rappel
const express = require('express');
const app = express();
// répondre avec hello world quand on reçoit une
requête GET
app.get('/',(req, res) => {
res.send('hello world');
});
app.listen(3000, () => {
console.log("Serveur démarré");
});
Rappel
const express = require('express');
const app = express();
// répondre avec hello world quand on reçoit une
requête GET
app.get('/',(req, res) => {
res.send('hello world');
});
app.listen(3000, () => {
console.log("Serveur démarré");
});
Modules
NodeJS permet de charger des modules à travers la
commande require().
Pour créer votre propre module, il faut obligatoirement créer
un fichier javascript (un module = un fichier).
Par défaut, tout est privé dans un module (variable,
fonctions..). Pour qu’une variable ou fonction ne soit pas
privée, il faudra clairement le spécifier.
Modules
Chaque fichier JS possède un objet qui lui est propre nommé
module.
Lorsqu’on fait appel à la fonction require(), c’est l’attribut
exports de l’objet module du fichier JS qu’on import qui
sera retourné. Il est vide par défaut.
module.exports = “Hello World”
module.exports = (req, res) => {
res.send(“Hello World”)
}
Modules
function printHello() {
console.log("Hello")
}
function printWorld() {
console.log("World!")
}
module.exports.printHello = printHello
module.exports.printWorld = printWorld
Modules
Pour require() un module présent dans notre projet, il
faudra contrairement à ce qu’on a vu précédemment,
clairement spécifier le chemin relatif du fichier JS sans son
extension.
require(“./module1/fichier”)
Si on ne spécifie pas le chemin, la fonction require() ira
chercher le module dans le dossier node_modules.
Middleware
Avec ExpressJS, toutes les fonctions qui ont comme
argument la fonction next() ou non sont appelés
middleware.
Nous avions vu ça précedemment avec les handlers pour
gérer les routes, on avait dit qu’on pouvait chaîner les
handlers. Les handlers sont donc des middlewares.
function checkAuth(req, res, next) {
if (req.get("API-KEY")) next()
else res.send("Error: Auth missing")
}
app.get("/", checkAuth, ...)
Middleware: app.use()
Il est également possible de définir des Middleware qui seront exécutés
au début de chaque nouvelle requête entrante.
Ceci peut être utile pour par exemple définir des variables dans les objets
req et res qui pourront être accessibles à tout le reste de l’application.
Il suffit simplement d’utiliser la fonction use() de l’objet app.
app.use(checkAuth)
app.use(“/user/:id”, checkAuth)
Requêtes avec données dans le corps
Requêtes avec données dans le corps
BodyParser
C’est une bibliothèque vous permettant de directement
parser le corps d’une requête. Le résultat sera directement
disponible dans l’objet request.
BodyParser est middleware.
$ npm install body-parser
BodyParser
const bodyParser = require("body-parser")
// Content-type: application/json
app.use(bodyParser.json())
// Content-type: application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
BodyParser
app.post("/products", (req, res) => {
product = {
name: req.body.name,
price: req.body.price
}
res.json(product)
})
MongoDB
MongoDB
C’est une base de données orientée documents.
Contrairement à une base de données relationnelle, une
BDOD garde un ensemble de collections (tables)
composées d’un ensemble de documents (lignes).
Un document n’est rien d’autre qu’un objet JSON. En réalité
cet objet JSON sera stocké en tant qu’objet BSON (Binaire).
{
_id: 5abe492a8cbadb22dc80ab54
"nom": "iPhone X",
"prix": 1159
}
MongoDB: Schéma
Contrairement à une base de données relationnelle le
schéma n’est pas fixé à l’avance.
Une même collection peut contenir différents documents
(objets) de structure différentes.
{
_id: 5abe492a8cbadb22dc80ab54
"nom": "iPhone X",
"prix": 1159
}
{
_id: 4afe3fe83611502135847759
"nom": "iPhone X",
"description": "Dernier iPhone"
}
MongoDB vs DB relationnelle
Une des plus grandes différences est que dans MongoDB il
n’y a pas de clés étrangères ou de jointures.
Cependant, il est possible à un document JSON d’inclure un
autre document JSON mais il ne peut pas le référencer.
Évidemment, il est toujours possible de référencer un autre
document à travers son _id, mais il faudra le faire
manuellement.
MongoDB est ce qu’on appelle une base de données
NoSQL.
MongoDB
Une fois mongoDB installé, il faudra le lancer depuis
le terminal:
$ mongod
Le serveur sera donc lancé et écoutera sur le port
27017.
Afin d'interagir avec le serveur, il est possible
d’utiliser le client mongo depuis le terminal:
$ mongo
Commandes shell MongoDB
> show dbs
Affiche toutes les bases de données.
> use NomBD
Passer de la BD courante à la BD NomBD.
> show collections
Affiche les collections de la BD courante.
MongoDB et NodeJS
Afin de pouvoir interagir avec la base de données
MongoDB depuis NodeJS, il nous faudra récupérer
un nouveau module qu’on appel driver.
Il existe plusieurs drivers mongoDB pour NodeJS,
nous allons dans cours utiliser le module officiel
“MongoDB”:
$ npm install mongodb --save
Objets du module MongoDB
Le module propose plusieurs objets permettant de
manipuler les bases de données, collections et
documents:
- L’objet db qui nous permet de récupérer les
collections d’une base de donnée précise.
- L’objet Collection permet de récupérer, insérer,
modifier et supprimer des documents.
- Les documents sont simplement des objets
JavaScript.
MongoDB: récupérer l’objet Db
Pour récupérer la référence de l’objet db, il faudra
utiliser la fonction suivante:
MongoClient.connect(url, callback)
Où:
- url: est la chaîne de caractère utilisée pour se
connecter à mongodb.
- callback: Fonction appelée une fois connecté
avec comme argument la référence à l’objet db.
MongoDB: récupérer l’objet Db
const MongoClient = require('mongodb').MongoClient;
const MONGO_URL = 'mongodb://localhost:27017/maDb';
// Avec callback
MongoClient.connect(MONGO_URL, (err, database) => {
db = database;
})
MongoDB: récupérer l’objet Db
const MongoClient = require('mongodb').MongoClient;
const MONGO_URL = 'mongodb://localhost:27017/maDb';
let db = null;
function onConnected(err, database) {
db = database;
}
// Avec promesse
MongoClient.connect(MONGO_URL)
.then(onConnected)
MongoDB: récupérer l’objet Collection
Une fois l’objet db récupéré, il est possible de récupérer
l’objet collection à travers une fonction que possède l’objet
db:
const coll = db.collection(“maCollection”)
La fonction collection est synchrone.
Elle nous retourne un objet que l’on peut utiliser pour
ajouter/rechercher/modifier/supprimer des documents dans
notre collection.
Si la collection n’existe pas, elle sera automatiquement créé
au moment de l'écriture.
collection.insertOne()
collection.insertOne(doc, callback);
Permet d’insérer un document dans une collection.
- doc: n’est rien d’autre qu’un objet Javascript
contenant les données qui seront sauvegardées
dans notre collection en tant que document.
- callback: fonction qui sera appelée à la fin de la
sauvegarde, elle a deux arguments: err, result. où
result.insertedId représente le _id du document.
collection.insertOne()
function insertProduct(name, price) {
const product = {
"name": name,
"price": price
}
collection.insertOne(product, (err, result) => {
console.log(result.insertedId)
})
}
collection.findOne()
collection.findOne(query [, options], callback);
Permet de rechercher un document ayant les caractéristiques
spécifiées dans la query.
La query n’est autre qu’un objet Javascript ayant les associations
clés/valeur que l’on recherche.
collection.findOne()
function findProduct(name) {
collection.findOne(
{"name": name},
(err, product) => {
return product;
}
)
}
collection.findOne() avec ObjectID
Et si on souhaitait retrouver un document à partir de son _id?
function findProduct(id) {
collection.findOne(
{"_id": id},
(err, product) => {
return product;
}
)
}
collection.findOne() avec ObjectID
Et si on souhaitait retrouver un document à partir de son _id?
function findProduct(id) {
collection.findOne(
{"_id": id},
(err, product) => { Ne marche pas !
return product;
}
)
}
collection.findOne() avec ObjectID
Avant de rechercher un document avec son _id, il nous faut
convertir la chaîne de caractère que l’on a qui correspond à
son _id en un ObjectID.
const ObjectID = require('mongodb').ObjectID
function findProduct(id) {
collection.findOne( {"_id": ObjectID(id)},
(err, product) => {
return product;
})
}
collection.find()
Fonctionne de la même manière que findOne() à
l’exception qu’elle nous retourne non pas un document mais
un curseur qui pointe sur le premier document.
On peut utiliser hasNext et next pour avancer le curseur.
Requêtes et projections
Curseur et .toArray()
Chaque curseur possède une fonction permettant de le
convertir en un tableau possédant tous les documents que le
curseur pointe dans les limites de la mémoire disponible.
collection.findOne(...).toArray((err, items) => {
return items;
})
collection.update()
collection.update(query, newDocument);
C’est la version la plus basique pour mettre à jour des
documents. Elle permet de directement remplacer les documents
qui correspondent à la query avec le contenu de newDocument.
collection.update()
function updateProduct(name, price) {
const old_product = {
"name": name
}
const new_product = {
"name": name,
"price": price
}
collection.update(old_product, new_product)
}
collection.update() et upsert
collection.update(query, newDocument, params);
En plus des arguments vus précédemment, la fonction update
supporte aussi d’autres paramètres en argument, tel l’argument
upsert qui permet à la fonction en plus de mettre à jour le
document, d’automatiquement créer l’entrée si la query ne
retourne aucun résultat.
collection.update() et upsert
function updateProduct(name, price) {
const old_product = {
"name": name
}
const new_product = {
"name": name,
"price": price
}
const params = { upsert: true}
collection.update(old_product, new_product, params)
}
collection.deleteOne()
collection.deleteOne(query, callback);
Permet de supprimer le premier document qui correspond à
la query.
Le callback reçoit en paramètre: err et result, ou result
possède une variable result.deletedCount qui indique le
nombre de document supprimés, dans ce cas-ci un seul.
collection.deleteMany()
collection.deleteMany(query, callback);
Permet de supprimer tous les document qui correspondent à
la query.
Le callback reçoit en paramètre: err et result, ou result
possède une variable result.deletedCount qui indique le
nombre de document supprimés, dans ce cas-ci un seul.
collection.deleteMany()
Permet de vider toute la collection en une fois.
Opérateurs de Requêtes sur les documents
MongoDB possède une syntaxe particulière pour les
requêtes permettant de faire des recherches plus
sophistiquées.
Optimisation: indexes
// On crée notre index
db.records.createIndex( { userid: 1 } )
// On crée notre index multiple de produits
db.products.createIndex( { item: 1, category: 1,
price: 1 } )
// Query executée très rapidement
db.records.find( { userid: { $gt: 10 } } )
Optimisation: connexions multiples (pooling)
const MongoClient = require('mongodb').MongoClient;
const MONGO_URL = 'mongodb://localhost:27017/maDb';
let db = null;
function onConnected(err, database) {
db = database;
}
// Avec promesse
MongoClient.connect(MONGO_URL, {poolSize: 10})
.then(onConnected)
MongoDB: réplication
Utiliser MongoDB avec ExpressJS
MongoClient.connect(MONGO_URL, (err, database) => {
db = database;
app.get(“/”, (req, res) => {
db.collection(“products”).find({}, (err, items)
=> { res.json(items) })
})
app.listen(3000, () => {
console.log("En attente de requêtes...")
})
})
Utiliser MongoDB avec ExpressJS
let db = null;
function onConnected(err, database) {
db = database;
app.get(“/”, (req, res) => {
db.collection(“products”).find({}, (err, items)
=> { res.json(items) })
})
}
MongoClient.connect(MONGO_URL)
.then(onConnected)
MongoDB: Studio 3T
Authentification
Types d’authentification: Basic Auth
Types d’authentification: Basic Auth
L’authentification peut se faire deux manières différentes:
- Directement dans l’URL:
http://toto:[email protected]
- En tant que header:
Authorization: Basic AZIBAFJRFjpPcGVuU84JFN1l
Types d’authentification: API Key
La clé peut être transmise dans l’URL ou l’en-tête.
Types d’authentification: JSON Web Token
C’est un standard définissant une méthode légère et
sécurisée pour transmettre une information à travers un objet
JSON.
L’information étant transmise, on pourra aisément vérifier sa
validité.
header.payload.signature
JWT: Header
Le header contient généralement deux informations:
- Le type du token, dans notre cas JWT.
- L’agorithme de hashage utilisé, tels HMAC SHA256 ou
RSA.
{
"alg": "HS256",
"typ": "JWT"
}
Par la suite, il sera encodé en Base64Url.
JWT: Payload
Il est composé de 3 types de “réclamations” (claims):
1. réclamations inscrites: Non obligatoire, représente des
informations utiles: iss (issuer), exp (expiration time), sub
(subject), aud (audience), ...
2. réclamations publiques: Libre, définies par le
développeur.
3. réclamations privées: réclamations spécifiques qui ne
sont ni des réclamations inscrites ou publiques.
JWT: Payload
Voici un exemple:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
Par la suite, il sera encodé en Base64Url.
JWT: Signature
La signature est utilisée pour s’assurer que les informations
n’ont pas été modifiées en chemin.
Il est également possible si on utilise un algorithme
asymétrique de s’assurer à travers elle que la personne qui a
transmis l’information est bien celle qu'elle dit qu’elle est.
HMACSHA256(base64UrlEncode(header) + "." +
base64UrlEncode(payload), secret)
OAuth1a, OAuth2
OAuth (protocol d’autorisation) permet a une application
d’avoir une autorisation pour accéder à certaines
informations.