Article écrit par Jordan
Ce tutoriel a pour objectif l’installation et l’utilisation de Node.js et de MongoDB par le biais d’un petit projet web. Node.js est une plateforme logicielle libre et événementielle en JavaScript orientée vers les applications réseau qui doivent pouvoir monter en charge. MongoDB est un SGBD (système de gestion de base de données) orienté documents, ne nécessitant pas de schéma prédéfini des données.
À la fin de ce tutoriel, vous serez capable de monter une API à l’aide de Node.js et de MongoDB !
Installation
Node.js
Pour installer Node.js, rendez-vous sur le site officiel et téléchargez Node.js et npm. Vérifiez l’installation avec les commandes suivantes dans le terminal :
$ node -v
$ npm -v
MongoDB
Pour installer MongoDB, rendez-vous sur le site officiel et téléchargez la version adaptée à votre système d’exploitation. Suivez les étapes pour modifier le fichier bash (.bashrc / .bash_profile) et ajouter le chemin du fichier téléchargé. Ensuite, créez un dossier pour la base de données et lancez le serveur MongoDB :
$ sudo mkdir -p /data/db
$ sudo chown -R id -un /data/db
Vous pouvez maintenant lancer votre serveur MongoDB avec la commande suivante.
$ mongod
Si vous souhaitez accéder à votre base de données en ligne de commande, il vous suffit d’ouvrir un autre terminal et de lancer la commande suivante.
$ mongo
Pour la suite de notre projet, il vous faut créer une base de données que vous nommerez amazon et dans laquelle vous stockerez vos livres.
> use amazon
> db.books.insert({title:"Harry Potter", author:"J.K.Rowling"})
Configuration
Création du projet
Utilisez Express Generator pour créer le squelette de l’application.
$ npm install express-generator -g
Une fois Express installé, vous allez générer le squelette de notre projet.
$ express bookServer
Mongoose
Vous allez installer Mongoose pour la suite du projet. Ce dernier est un framework permettant de faciliter la connexion entre MongoDB et Express.
$ npm install mongoose --save
Dans le fichier app.js
, ajoutez :
var mongoose = require('mongoose');
var config = require('mongodb://localhost:27017/amazon')
const connect = mongoose.connect(config, { useNewUrlParser: true });
connect.then(
db => {
console.log("Connected correctly to the server");
},
err => {
console.log(err);
}
);
Routes
Définissez les routes dans app.js
:
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users.routes');
var booksRouter = require('./routes/books.routes');
Il faut à présent définir ces différentes routes. Rendez-vous dans le dossier routes et créez un fichier nommé books.routes.js
dans lequel vous rentrerez les lignes suivantes.
const controller = require('../controllers/books.controller');
const express = require('express');
const router = express.Router();
router.use(express.json());
router
.route('/')
.get(controller.getAll)
.post(controller.addOne);
router
.route('/:bookId')
.get(controller.getOne)
.put(controller.updateOne)
.delete(controller.deleteOne);
module.exports = router;
Controller
À la racine de votre projet, vous allez créer un dossier controllers dans lequel vous allez créer un fichier books.controller.js. Ce fichier comportera toutes vos méthodes en lien avec les books.
const Books = require("../models/books.model");
const controller = {
getAll: (req, res, next) => {
Books.find({})
.then(
books => {
res.statutCode = 200;
res.setHeader("Content-Type", "application/json");
res.json(books);
},
err => next(err)
)
.catch(err => next(err));
},
addOne: (req, res, next) => {
Books.create(req.body)
.then(
book => {
res.statutCode = 200;
res.setHeader("Content-Type", "application/json");
res.json(book);
},
err => next(err)
)
.catch(err => next(err));
},
getOne: (req, res, next) => {
Books.findById(req.params.bookId)
.then(
book => {
res.statutCode = 200;
res.setHeader("Content-Type", "application/json");
res.json(book);
},
err => next(err)
)
.catch(err => next(err));
},
updateOne: (req, res, next) => {
Books.findByIdAndUpdate(req.params.bookId, { $set: req.body }, { new: true })
.then(
book => {
res.statutCode = 200;
res.setHeader("Content-Type", "application/json");
res.json(book);
},
err => next(err)
)
.catch(err => next(err));
},
deleteOne: (req, res, next) => {
Books.findByIdAndRemove(req.params.bookId)
.then((resp) => {
res.statutCode = 200;
res.setHeader("Content-Type", "application/json");
res.json(resp);
},
err => next(err)
)
.catch(err => next(err));
}
};
module.exports = controller;
Models
Vous allez créer les différents modèles de votre application. Commencez par créer un dossier modèles à la racine de votre projet. Dans ce dossier, créez à présent un fichier books.models.js
. Ces modèles feront le mapping entre votre base de données et votre API.
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
require('mongoose-currency').loadType(mongoose);
const Currency = mongoose.Types.Currency;
const commentSchema = new Schema({
rating: { type: Number, min: 1, max: 5, required: true },
comment: { type: String, required: true },
author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }
}, { timestamps: true });
const bookSchema = new Schema({
title: { type: String, required: true },
author: { type: String, required: true },
image: { type: String },
year: { type: Number },
price: { type: Currency },
description: { type: String, required: true },
bestseller: { type: Boolean, default: false },
comments: [commentSchema]
}, { timestamps: true });
const Books = mongoose.model('Book', bookSchema);
module.exports = Books;
Vous venez d’utiliser le type de monnaie de mongoose, il faut donc veiller à l’installer auparavant. Rendez-vous dans votre terminal et tapez y la commande suivante.
$ npm install mongoose-currency --save
Lancement
Vous avez maintenant la base du projet, vous pouvez le tester en tapant la commande suivante.
$ npm start
Rendez-vous sur votre navigateur ou sur Postman à l’adresse suivante pour pouvoir tester votre application : http://localhost:3000/books/
PassportJS
Vous allez maintenant rendre votre application un peu plus sûre, pour cela, vous allez créer des utilisateurs. À la création d’un utilisateur un token sera généré et c’est uniquement grâce à ce dernier que vous pourrez avoir accès aux modifications de la base de données. Avant de commencer, veuillez installer les dépendances nécessaires.
$ npm install mongoose bcrypt-nodejs jsonwebtoken morgan passport passport-jwt --save
Commencez par créer un user. Pour ce faire, créer un dossier config
à la racine de votre projet. Dans ce dossier, créez y un premier fichier nommé database.js
dans lequel vous allez ajouter les lignes suivantes.
module.exports = {
'secret': 'nodeauthsecret',
'database': 'mongodb://localhost:27017/amazon'
};
Dans le second fichier, que vous appellerez passport.js
, vous allez ajouter les lignes suivantes.
var JwtStrategy = require('passport-jwt').Strategy;
var ExtractJwt = require('passport-jwt').ExtractJwt;
var User = require('../models/users.model');
var config = require('../config/database');
module.exports = function (passport) {
var opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderWithScheme("jwt");
opts.secretOrKey = config.secret;
passport.use(new JwtStrategy(opts, function (jwt_payload, done) {
User.findOne({ id: jwt_payload.id }, function (err, user) {
if (err) {
return done(err, false);
}
if (user) {
done(null, user);
} else {
done(null, false);
}
});
}));
};
Comme vous avez pu le constater, vous avez appelé le model User, vous allez donc le créer. Dans le dossier models
, créez un fichier users.model.js
dans lequel vous allez définir le schéma de votre user.
var bcrypt = require('bcrypt-nodejs');
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
var userSchema = new Schema({
firstName: { type: String },
lastName: { type: String },
username: { type: String, unique: true, required: true },
password: { type: String, required: true }
}, { timestamps: true });
userSchema.pre('save', function (next) {
var user = this;
if (this.isModified('password') || this.isNew) {
bcrypt.genSalt(10, function (err, salt) {
if (err) {
return next(err);
}
bcrypt.hash(user.password, salt, null, function (err, hash) {
if (err) {
return next(err);
}
user.password = hash;
next();
});
});
} else {
return next();
}
});
userSchema.methods.comparePassword = function (passw, cb) {
bcrypt.compare(passw, this.password, function (err, isMatch) {
if (err) {
return cb(err);
}
cb(null, isMatch);
});
};
const User = mongoose.model('User', userSchema);
module.exports = User;
Une fois le modèle créé, il faut créer les différentes routes. Vous aurez deux routes : une première permettant la création d’un utilisateur et une seconde permettant l’authentification et la connexion. Dans le dossier routes
, créez un fichier users.routes.js
et ajoutez les différentes routes.
var express = require('express');
var router = express.Router();
var mongoose = require('mongoose');
var passport = require('passport');
var config = require('../config/database');
require('../config/passport')(passport);
var jwt = require('jsonwebtoken');
var User = require("../models/users.model");
router.get('/', function (req, res, next) {
res.send('respond with a resource');
});
router.post('/enregistrement', function (req, res) {
if (!req.body.username || !req.body.password) {
res.json({ success: false, msg: 'Veuillez renseigner le username et le mot de passe.' });
} else {
var newUser = new User({
username: req.body.username,
password: req.body.password
});
newUser.save(function (err) {
if (err) {
return res.json({ success: false, msg: 'Ce username existe deja.' });
}
res.json({ success: true, msg: 'Succes. Nouvel utilisateur créé.' });
});
}
});
router.post('/connexion', function (req, res) {
User.findOne({
username: req.body.username
}, function (err, user) {
if (err) throw err;
if (!user) {
res.status(401).send({ success: false, msg: 'Authentication erronée. Utilisateur non trouvé.' });
} else {
user.comparePassword(req.body.password, function (err, isMatch) {
if (isMatch && !err) {
var token = jwt.sign(user.toJSON(), config.secret);
res.json({ success: true, token: 'JWT ' + token });
} else {
res.status(401).send({ success: false, msg: 'Authentication erronée. Mauvais mot de passe.' });
}
});
}
});
});
module.exports = router;
Une fois ces fichiers créés, il suffit de renseigner dans le fichier app.js
.
// ...
var config = require('./config/database');
var passport = require('passport');
// ...
app.use(passport.initialize());
// ...
Il faut à présent mettre en place cette sécurité, vous allez l’appliquer pour la modification de vos livres en base de données, c’est-à-dire pour les fonctions addOne, updateOne, et deleteOne. Rendez-vous dans le fichier books.controller.js
et commencez par ajouter la fonction qui permettra de récupérer le token dans l’en-tête de l’URL. Tout en bas, ajoutez la fonction suivante.
// ...
getToken = function (headers) {
if (headers && headers.authorization) {
var parted = headers.authorization.split(' ');
if (parted.length === 2) {
return parted[1];
} else {
return null;
}
} else {
return null;
}
};
À présent dans les 3 fonctions citées précédemment, encadrer vos corps d’une vérification du token à l’aide d’un simple if/else.
deleteOne: (req, res, next) => {
var token = getToken(req.headers);
if (token) {
// corps de la fonction
} else {
return res.status(403).send({ success: false, msg: 'Acces refusé' });
}
}
Dans le fichier books.routes.js
vous allez renseigner les différentes fonctions pour lesquelles passport est utilisé.
const authenticate = passport.authenticate('jwt', { session: false });
// ...
var passport = require("passport");
// ...
router.use(express.json());
router
// ...
.post(authenticate, controller.addOne);
router
// ...
.put(authenticate, controller.updateOne)
.delete(authenticate, controller.deleteOne);
Pour utiliser Postman, il faut commencer par créer un user, pour se faire lancer un POST avec l’adresse suivante http://localhost:3000/users/enregistrement en prenant soin de renseigner le username et le password dans le body comme suit.
{
"username": "ouidou",
"password": "ouidou"
}
Vous devriez obtenir un résultat comme celui-ci.
{
"success": true,
"msg": "Succes. Nouvel utilisateur créé."
}
À présent, avec l’utilisateur créé, vous allez vous connecter à l’adresse suivante http://localhost:3000/users/connexion en renseignant dans le body le même username et password créé précédemment. Vous devriez obtenir un résultat comme suit.
{
"success": true,
"token": "JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI1YzdjMGU3ZTMxMjYzZjQyZjcyOTFjMTIiLCJ1c2VybmFtZSI6ImpvaiIsInBhc3N3b3JkIjoiJDJhJDEwJFJiZ3Z5NG9EU2liVmZxUHJBRE1LTS5CMjIuRHBWWmdSZzJEQ1ZwR0NpTHRYMTgzZkJhR3hlIiwiY3JlYXRlZEF0IjoiMjAxOS0wMy0wM1QxNzoyNzoyNi4zOTZaIiwidXBkYXRlZEF0IjoiMjAxOS0wMy0wM1QxNzoyNzoyNi4zOTZaIiwiX192IjowLCJpYXQiOjE1NTE2MzQxNzN9.fzFUDem8vV6TFT1xJniTOm7KHueNyBZr3MsW9huQpuE"
}
Vous venez de générer votre token et pour vous en servir rien de plus simple. Dans les fonctions dans lesquelles vous avez utilisé passport (addOne, updateOne, et deleteOne) vous aller dans les headers de votre URL et vous allez rajouter la Key Authorization et coller la token généré.
Cors
Cors est un package Node.js destiné à fournir un middleware Connect/Express pouvant être utilisé pour activer Cors avec diverses options. Pour installer Cors, il vous suffit de taper la commande suivante :
$ npm install cors --save
Il faut ensuite spécifier à votre application l’utilisation de ce nouveau module. Pour cela, vous devez vous rendre dans votre fichier app.js
et y insérer les deux lignes suivantes.
// ...
var cors = require('cors');
// ...
app.use(cors());
HTTPS
L’HyperText Transfer Protocol Secure (HTTPS, littéralement « protocole de transfert hypertexte sécurisé ») est la combinaison du HTTP avec une couche de chiffrement comme SSL ou TLS. Vous allez maintenant ajouter ce protocole dans votre application. Vous allez avoir besoin de OpenSSL, si vous ne le possédez pas, je vous invite à le télécharger à cette adresse. Ouvrez un terminal et placez vous dans votre projet.
$ openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365
Il faut ensuite répondre à toutes les questions qui vous seront posées. Vous devriez voir apparaître deux nouveaux fichiers : cert.pem
et key.perm
.
Vous allez maintenant “créer un serveur https”. Rendez-vous dans le fichier app.js
et ajoutez les lignes suivantes.
// ...
var https = require('https');
var fs = require('fs');
// ...
https.createServer({
key: fs.readFileSync('key.pem'),
cert: fs.readFileSync('cert.pem')
}, app).listen(443);
// ...
Une fois cela effectué, vous pouvez exécuter la commande de lancement.
$ npm start
Il est possible qu’une erreur apparaisse, dans ce cas la vous devez supprimer le mot de passe avec la commande suivante.
$ openssl rsa -in key.pem -out newkey.pem
Après avoir fait cela un fichier newkey.pem devrait apparaître. Supprimer l’ancien key.pem
et renommé newkey.pem
en key.pem
.
Vous pouvez vérifier que votre serveur https fonctionne en vous rendant a l’adresse https://localhost:443/books. Pour utiliser sur Postman, vous devez vous rendre dans les préférences de l’application, onglet Certificates et ajoutez le certificat créé au début.
Vous devez aussi vous rendre dans l’onglet Général mettez à “off” la ligne SSL certificate verification.
La dernière étape consiste à rediriger notre application vers https lorsque l’on entre dedans. Pour ce faire, vous allez utiliser un module spécifique. Commencez par télécharger express force SSL.
$ npm install express-force-ssl --save
Rendez-vous dans le fichier app.js
et ajouter les lignes suivantes.
// ...
var forceSsl = require('express-force-ssl');
// ...
app.use(forceSsl);
À présen, toutes les URLs que vous utiliserez seront sur l’adresse https://localhost:443/.
Book subdocument
Vous voulez à présent mettre en place des commentaires qui seront relatifs à chaque livre, pour se faire, vous allez mettre en place une structure de commentaire.
Comme vous avez pu le constater dans le début du tutoriel, vous aviez déjà créé la structure des commentaires dans le fichier books.model.js
.
Commencez par vous rendre dans le fichier books.routes.js
et rajoutez les routes avec lesquelles vous accéderez aux commentaires.
// ...
const controller_comments = require('../controllers/comments.controller');
// ...
router
.route('/:bookId/comments')
.get(controller_comments.getAll)
.post(authenticate, controller_comments.addOne);
router
.route('/:bookId/comments/:commentId')
.get(controller_comments.getOne)
.put(authenticate, controller_comments.updateOne)
.delete(authenticate, controller_comments.deleteOne);
Une fois les routes mises en place, vous allez créer votre controller. Dans le dossier controllers, créez un fichier comments.controller.js
.
const Books = require("../models/books.model");
const controller_comments = {
getAll: (req, res, next) => {
Books.findById(req.params.bookId)
.then(
book => {
res.statutCode = 200;
res.setHeader("Content-Type", "application/json");
res.json(book.comments);
},
err => next(err)
)
.catch(err => next(err));
},
addOne: (req, res, next) => {
var token = getToken(req.headers);
if (token) {
Books.findByIdAndUpdate(req.params.bookId, { $push: { comments: req.body } })
.then(
book => {
res.statutCode = 200;
res.setHeader("Content-Type", "application/json");
res.json(book.comments);
},
err => next(err)
)
.catch(err => next(err));
} else {
return res.status(403).send({ success: false, msg: 'Interdit' });
}
},
getOne: (req, res, next) => {
Books.findById(req.params.bookId)
.then(
book => {
res.statutCode = 200;
res.setHeader("Content-Type", "application/json");
res.json(book.comments.id(req.params.commentId));
},
err => next(err)
)
.catch(err => next(err));
},
updateOne: (req, res, next) => {
var token = getToken(req.headers);
if (token) {
Books.update(
{
"_id": req.params.bookId,
"comments._id": req.params.commentId
},
{
$set: {
"comments.$.rating": req.body.rating,
"comments.$.comment": req.body.comment,
"comments.$.author": req.body.author
}
},
{
new: true
}
)
.then(
book => {
res.statutCode = 200;
res.setHeader("Content-Type", "application/json");
res.json(book);
},
err => next(err)
)
.catch(err => next(err));
} else {
return res.status(403).send({ success: false, msg: 'Interdit' });
}
},
deleteOne: (req, res, next) => {
var token = getToken(req.headers);
if (token) {
Books.findByIdAndUpdate(
req.params.bookId,
{
$pull:
{
comments:
{
"_id": req.params.commentId
}
}
}
)
.then(
book => {
res.statutCode = 200;
res.setHeader("Content-Type", "application/json");
res.json(book);
},
err => next(err)
)
.catch(err => next(err));
} else {
return res.status(403).send({ success: false, msg: 'Interdit' });
}
}
};
Vous pouvez à présent tester à l’aide de Postman la récupération/modification des commentaires liés à chaque livre.
Et voilà ! Vous avez maintenant acquis les compétences nécessaires pour créer une API en utilisant Node.js et MongoDB. Les concepts que nous avons explorés sont souvent applicables à d’autres langages et technologies lors de la création d’API, vous donnant ainsi une petite base pour aborder d’autres projets.