on crée le dossier back
cd back && npm init && npm install mongodb express mongoose nodemon
comme ça c'est fait ^^
creation de .gitignore et de server.js qui sera le poitn d'entrée de notre application
const http = require('http');
const server = http.createServer((req, res) => {
res.end('Voilà la réponse du serveur !');
});
server.listen(process.env.PORT || 3000);
console.log("server start")
npm i express
dans le dossier backend
Création du fichier app.js qui contiendra notre app express
const express = require('express');
const app = express();
app.use((req, res) => {
res.json({ message: 'Votre requête a bien été reçue !' });
});
module.exports = app;
on importe notre appli express dans server.js
const http = require('http');
const app = require('./app');
app.set('port', process.env.PORT || 3000); La fonction app.set() permet d’attribuer le nom du paramètre à la valeur. Vous pouvez stocker n’importe quelle valeur de votre choix, mais certains noms peuvent être utilisés pour configurer le comportement du serveur.
const server = http.createServer(app); // on passe l'app express en paramètres de la méthode createServer
server.listen(process.env.PORT || 3000);
Une application Express est fondamentalement une série de fonctions appelées middleware. Chaque élément de middleware reçoit les objets request et response , peut les lire, les analyser et les manipuler, le cas échéant.
const http = require('http');
const app = require('./app');
const normalizePort = val => {
const port = parseInt(val, 10);
if (isNaN(port)) {
return val;
}
if (port >= 0) {
return port;
}
return false;
};
const port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
const errorHandler = error => {
if (error.syscall !== 'listen') {
throw error;
}
const address = server.address();
const bind = typeof address === 'string' ? 'pipe ' + address : 'port: ' + port;
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges.');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use.');
process.exit(1);
break;
default:
throw error;
}
};
const server = http.createServer(app);
server.on('error', errorHandler);
server.on('listening', () => {
const address = server.address();
const bind = typeof address === 'string' ? 'pipe ' + address : 'port ' + port;
console.log('Listening on ' + bind);
});
server.listen(port);
Aperçu rapide de ce qui se passe ici :
la fonction normalizePort renvoie un port valide, qu'il soit fourni sous la forme d'un numéro ou d'une chaîne ;
la fonction errorHandler recherche les différentes erreurs et les gère de manière appropriée. Elle est ensuite enregistrée dans le serveur ;
un écouteur d'évènements est également enregistré, consignant le port ou le canal nommé sur lequel le serveur s'exécute dans la console.
On remplace le middleware dans app.js par celui ci :
app.use('/api/stuff', (req, res, next) => {
const stuff = [
{
_id: 'oeihfzeoi',
title: 'Mon premier objet',
description: 'Les infos de mon premier objet',
imageUrl: 'https://cdn.pixabay.com/photo/2019/06/11/18/56/camera-4267692_1280.jpg',
price: 4900,
userId: 'qsomihvqios',
},
{
_id: 'oeihfzeomoihi',
title: 'Mon deuxième objet',
description: 'Les infos de mon deuxième objet',
imageUrl: 'https://cdn.pixabay.com/photo/2019/06/11/18/56/camera-4267692_1280.jpg',
price: 2900,
userId: 'qsomihvqios',
},
];
res.status(200).json(stuff);
});
le CORS est une sécurité qui empêche l'accès à l'api si l'adresse front et back n'est pas la mm
pour résoudre ce problème on crée un middleware d'autorisation qui va configurer les entêtes de réponses (voir app.js) Ces headers permettent : d'accéder à notre API depuis n'importe quelle origine ( '*' ) ; d'ajouter les headers mentionnés aux requêtes envoyées vers notre API (Origin , X-Requested-With , etc.) ; d'envoyer des requêtes avec les méthodes mentionnées ( GET ,POST , etc.). En résumé: La méthode app.use() vous permet d'attribuer un middleware à une route spécifique de votre application. Le CORS définit comment les serveurs et les navigateurs interagissent, en spécifiant quelles ressources peuvent être demandées de manière légitime – par défaut, les requêtes AJAX sont interdites. Pour permettre des requêtes cross-origin (et empêcher des erreurs CORS), des headers spécifiques de contrôle d'accès doivent être précisés pour tous vos objets de réponse.
il faut extraire le corps JSON le la requête pour cela on utilise le middleware app.use(express.json())
équivalent à body parser
ensuite on écrit notre middleware qui traitera la route post
app.post('/api/stuff', (req, res, next) => {
console.log(req.body);
res.status(201).json({
message: 'Objet créé !'
});
});
npm i mongoose
on l'importe dans app.js const mongoose = require('mongoose')
on crée la bdd on récupère les identifiants que l'on rentre en premier paramètre de la méthode connect de mongoose
on place cette méthode juste en dessous de la déclaration de app
on crée un dossier models on crée un fichier objet qui déterminera le schéma de notre objet on exporte le modèle grâce a mongoose.model('Nom du fichier', Nom du schéma)
On importe notre modèle dans app.js
const Thing = require('./models/thing');
On implémente la logique de la méthode post
app.post('/api/stuff', (req, res, next) => {
delete req.body._id;
const thing = new Thing({
...req.body
});
thing.save()
.then(() => res.status(201).json({ message: 'Objet enregistré !'}))
.catch(error => res.status(400).json({ error }));
});
app.get('/api/stuff/:id', (req, res, next) => {
Thing.findOne({ _id: req.params.id })
.then(thing => res.status(200).json(thing))
.catch(error => res.status(404).json({ error }));
});
Les méthodes de votre modèle Thing permettent d'interagir avec la base de données :
save() – enregistre un Thing ;
find() – retourne tous les Things ;
findOne() – retourne un seul Thing basé sur la fonction de comparaison qu'on lui passe (souvent pour récupérer un Thing par son identifiant unique).
La méthode app.get() permet de réagir uniquement aux requêtes de type GET.
app.put('/api/stuff/:id', (req, res, next) => {
Thing.updateOne({ _id: req.params.id }, { ...req.body, _id: req.params.id })
.then(() => res.status(200).json({ message: 'Objet modifié !'}))
.catch(error => res.status(400).json({ error }));
});
L'utilisation du mot-clé new avec un modèle Mongoose crée par défaut un champ_id . Utiliser ce mot-clé générerait une erreur, car nous tenterions de modifier un champ immuable dans un document de la base de données. Par conséquent, nous devons utiliser le paramètre id de la requête pour configurer notre Thing avec le même _id qu'avant.
On va déplacer la logique de routage dans le dossier routes et la logique métier dans le dossier controllers
On crée routes/stuff.js qui gérera les endpoints à partir de l'url 'api/stuff'
on exporte le router, on l'importe dans app.js, on l'instancie comme stuffRoutes et on l'utilise avec app.use('/api/stuff', stuffRoutes)
En résumé
La méthodeexpress.Router() vous permet de créer des routeurs séparés pour chaque route principale de votre application – vous y enregistrez ensuite les routes individuelles.
Un fichier de contrôleur exporte des méthodes qui sont ensuite attribuées aux routes pour améliorer la maintenabilité de votre application.
On installe ce package npm install mongoose-unique-validator
pour qu'on ne puisse pas s'inscrire 2 fois avec le même email
On crée le modèle User avec mongoose dans models/User
En résumé
bcrypt est un package de cryptage que vous pouvez installer avec npm .
mongoose-unique-validator améliore les messages d'erreur lors de l'enregistrement de données uniques.
Comme pour les Thing on crée un router qui servira les routes /auth... dans routes/user.js et un controller qui gérera les fonctions signup et login dans controllers/user.js
on installe le package bcrypt qui va nous permettre de hasher le map utilisateur npm i bcrypt
on importe le modèle User dans le controller
on crée la méthode signup en commençant par hasher le mdp puis on enregistre le nouvel utilisateur dans la bdd avec .save()
On implémente la logique de login en vérifiant si l'utilisateur existe, on renvoie un msg général si il n'existe pas car c'est déjà une fuite de donnée que de savoir si un utilisateur est inscrit ou pas à un site !! Si l'utilisateur existe on compare le hash du mdp avec celui enregistré en bdd grâce à bcrypt.compare , si c'est ok on renvoi l'_id de l'utilisateur et un token au front-end
On installe jwt npm install jsonwebtoken
, on l'importe dans controllers/user.js et on génère le token si les identifiants sont exacts
En résumé
Les JSON web tokens sont des tokens chiffrés qui peuvent être utilisés pour l'autorisation.
La méthode sign() du package jsonwebtoken utilise une clé secrète pour chiffrer un token qui peut contenir un payload personnalisé et avoir une validité limitée.
On crée un fichier middlewares/auth.js On crée une fonction qui va récupérer le token dans le header de la requête, le comparer grâce à .verify() et à la clé secrette 'RANDOM_TOKEN_SECRET', si le token est bon on extrait userId du token décodé et on l'ajoute à la requête avec req.auth. Ensuite on ajoute ce middleware à toutes les routes que l'on veut protéger dans notre routeur routes/stuff avant le controller correspondant à la requête En résumé
La méthode verify() du package jsonwebtoken permet de vérifier la validité d'un token (sur une requête entrante, par exemple).
Ajoutez bien votre middleware d'authentification dans le bon ordre sur les bonnes routes.
Attention aux failles de sécurité !
On va configurer un middleware qui nous permettra de stocker des fichiers entrants par exemple la photo d'un objet à vendre qu'un utilisateur chargera à partir de son dd
npm i multer
On crée un dossier images à la racine du backend pour enregistrer les images
On crée le middlaware multer-config.js afin de définir une configuration pour multer
Dans ce middleware :
Nous créons une constante storage , à passer à multer comme configuration, qui contient la logique nécessaire pour indiquer à multer où enregistrer les fichiers entrants :
la fonction destination indique à multer d'enregistrer les fichiers dans le dossier images ;
la fonction filename indique à multer d'utiliser le nom d'origine, de remplacer les espaces par des underscores et d'ajouter un timestamp Date.now() comme nom de fichier. Elle utilise ensuite la constante dictionnaire de type MIME pour résoudre l'extension de fichier appropriée.
Nous exportons ensuite l'élément multer entièrement configuré, lui passons notre constante storage et lui indiquons que nous gérerons uniquement les téléchargements de fichiers image.
En résumé
multer est un package de gestion de fichiers.
Sa méthode diskStorage() configure le chemin et le nom de fichier pour les fichiers entrants.
Sa méthode single() crée un middleware qui capture les fichiers d'un certain type (passé en argument), et les enregistre au système de fichiers du serveur à l'aide du storage configuré.
Pour que notre middleware de téléchargement de fichiers fonctionne sur nos routes, nous devrons modifier ces dernières, car le format d'une requête contenant un fichier du front-end est différent
Tout d'abord, ajoutons notre middleware multer à notre route POST dans notre routeur stuff router.post('/', auth, multer, stuffCtrl.createThing);
Pour gérer correctement la nouvelle requête entrante, nous devons mettre à jour notre contrôleur createThing
Que fait le code ?
Pour ajouter un fichier à la requête, le front-end doit envoyer les données de la requête sous la forme form-data et non sous forme de JSON. Le corps de la requête contient une chaîne thing, qui est simplement un objetThing converti en chaîne. Nous devons donc l'analyser à l'aide de JSON.parse() pour obtenir un objet utilisable.
Nous supprimons le champ_userId de la requête envoyée par le client car nous ne devons pas lui faire confiance (rien ne l’empêcherait de nous passer le userId d’une autre personne). Nous le remplaçons en base de données par le _userId extrait du token par le middleware d’authentification.
Nous devons également résoudre l'URL complète de notre image, car req.file.filename ne contient que le segment filename. Nous utilisons req.protocol pour obtenir le premier segment (dans notre cas 'http'). Nous ajoutons '://', puis utilisons req.get('host') pour résoudre l'hôte du serveur (ici, 'localhost:3000'). Nous ajoutons finalement '/images/' et le nom de fichier pour compléter notre URL.
Ensuite on rajoute une route dans app.js pour que notre requête à localhost://3000/images puisse avoir une réponse (jusqu'à présent nous n'avon de réponse que sur les routes /api/stuff et /api/auth)!!
Tout d'abord, ajoutons multer comme middleware à notre route PUT :
router.put('/:id', auth, multer, stuffCtrl.modifyThing);
À présent, nous devons modifier notre fonction modifyThing() pour voir si nous avons reçu ou non un nouveau fichier et répondre en conséquence :
Dans cette version modifiée de la fonction, on crée un objet thingObject qui regarde si req.file existe ou non. S'il existe, on traite la nouvelle image ; s'il n'existe pas, on traite simplement l'objet entrant. On crée ensuite une instance Thing à partir de thingObject, puis on effectue la modification. Nous avons auparavant, comme pour la route POST, supprimé le champ _userId envoyé par le client afin d’éviter de changer son propriétaire et nous avons vérifié que le requérant est bien le propriétaire de l’objet.
En résumé
JSON.parse() transforme un objet stringifié en Object JavaScript exploitable.
Vous aurez besoin dereq.protocol et de req.get('host'), connectés par '://' et suivis de req.file.filename, pour reconstruire l'URL complète du fichier enregistré.
Configurez votre serveur pour renvoyer des fichiers statiques pour une route donnée avec express.static() et path.join().
Modification de la route Delete pour supprimer du disque les images téléchargées en mm tp que la suppression de l'objet
Dans notre contrôleur stuff, il nous faut une nouvelle importation. Il s'agit du package fs de Node :
const fs = require('fs');
fs signifie « file system » (soit « système de fichiers », en français). Il nous donne accès aux fonctions qui nous permettent de modifier le système de fichiers, y compris aux fonctions permettant de supprimer les fichiers.
Dans cette fonction :
Nous utilisons l'ID que nous recevons comme paramètre pour accéder au Thing correspondant dans la base de données.
Nous vérifions si l’utilisateur qui a fait la requête de suppression est bien celui qui a créé le Thing.
Nous utilisons le fait de savoir que notre URL d'image contient un segment /images/ pour séparer le nom de fichier.
Nous utilisons ensuite la fonction unlink du package fs pour supprimer ce fichier, en lui passant le fichier à supprimer et le callback à exécuter une fois ce fichier supprimé.
Dans le callback, nous implémentons la logique d'origine en supprimant le Thing de la base de données.
En résumé
Le package fs expose des méthodes pour interagir avec le système de fichiers du serveur.
La méthode unlink() du package fs vous permet de supprimer un fichier du système de fichiers.