Débuter avec NodeJS, Express et MongoDB

Un collègue m’a parlé de différents projets personnels orientés web et de son dilemne sur le choix des techologies. Curieux j’ai voulu essayer NodeJS, Express et MongoDB.

Généralement, on parle de MEAN qui est l’acronyme de plusieurs technologies : MongoDB, Express, AngularJS et NodeJs. Pour ma découverte, j’ai remplacé AngularJS par jQuery mais sous le capot c’est identique.

C’est parti pour débuter avec NodeJS, Express et MongoDB.

MongoDB

MongoDB est un moteur de base de données orientée documents et de type NoSQL, contrairement aux serveurs de base de données que j’ai l’habitude d’utiliser (SQL Server, mysql, postgresql).

Je vous propose un rapide tour sachant que ce n’est pas le but de l’article.

Avant de commencer un peu de vocabulaire

base de données : base de données

table : collection

ligne/enregistrement : document

colonne : champ

MongoDB communique en JSON ce qui le rend facilement utilisable dans l’écosystème web. Les données sont stockées en JSON et les requêtes sont aussi faites avec ce langage. Il n’est donc pas nécessaire de connaitre le SQL.

La même collection peut contenir des documents avec des propriétés différentes. Il n’y a pas de structure de table ce qui peut être déstabilisant.

Lancer le serveur MongoDB

REM lancer le moteur MongoDB avec la base associée à myapp
cd "C:\Program Files\MongoDB\Server\3.2\bin"
mongod.exe --dbpath c:\Users\Ghislain\Documents\nodejs\express\myapp\data

Serveur MongoDB

Comme vous serez amener à vous en servir souvent, le mieux est d’écrire un script pour faire ces actions (launchMongodb.bat).

Lancer le client MongoDB

REM lancer le client MongoDB  
cd "c:\Program Files\MongoDB\Server\3.2\bin"
mongo

REM afficher les bases
show dbs
REM utiliser une base
use <dbname>
REM inserer un document dans une collection
db.<collection>.insert()
REM lire un document dans une collection
db.<collection>.findOne()
REM mettre à jour un document dans une collection
db.<collection>.findOneAndUpdate() 
REM supprimer un document d'un collection
db.<collection>.findOneAndDelete()

Client MongoDB

Si vous souhaitez une GUI pour l’administration, vous trouverez une liste d’application à la page suivante : Documentation MongoDB – Admin UI

Documentation MongoDB – SQL to MongoDB Mapping Chart

NodeJS & Express

NodeJS open source et cross plateforme qui permet de faire des web développements coté serveur. Architecture orientée événement gérant les IO asynchrones.

Express est un framework pour nodejs permettant de construire des applications web et des API. Concrètement cela permet de développer plus rapidement une API REST:

  • CREATE : POST
  • GET : READ
  • UPDATE : PUT
  • DELETE : DELETE

Création du projet NodeJS.

mkdir myapp
cd myapp
npm init
npm install express --save
npm install body-parser --save
npm install mongodb --save
mkdir data
mkdir client
REM créer un app.js
node app.js

Pour interagir entre NodeJS et MongoDB, il est possible d’utiliser le “driver” par défaut ou des surcouches comme Mongoose. Pour cet article, je vais rester avec celui de base pour éviter de complexifier le code avec les schemas Mongoose par exemple.

Creation de l’application

Pour l’exemple je vous proposer une page permettant de gérer (CRUD) des utilisateurs ayant un nom et un prénom. Cette section présente dans un premier temps le code de l’application NodeJS/Express puis les requêtes jQuery du front.

Vous pourrez trouver le code complet sur le repo github en fin d’article.

NodeJS app.js

Il est bon de savoir que nodejs ne prend pas nativement en compte les modifications des sources de l’application.

Soit vous relancez le serveur node à chaque fois.

Soit vous installez un utilitaire qui relance automatiquement le serveur quand une modification a lieu dans le répertoire source. Exemple : Nodemon

Initialisation

// variables
var PORT = 3000;
var MONGODBURL = 'mongodb://127.0.0.1:27017/myapp';

// load 
var express = require('express');
var bodyParser = require('body-parser');

// DB connexion
var db
const MongoClient = require('mongodb').MongoClient
var ObjectID = require('mongodb').ObjectID
MongoClient.connect(MONGODBURL, (err, database) => {
  if (err) return console.log(err)
  db = database
  app.listen(PORT, () => {
    console.log('listening on ' + PORT)
  })
})

var app = express();
// necessary for working with JSON
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
// path where files are not processed
app.use(express.static('client'));

// default root
app.get('/', function (req, res) {
  res.send('Hello World!');
});

CREATE

// POST : create
app.post('/users/', function(req, res){
    console.log('user created');
    console.log(req.body);
    
    db.collection('users').save(req.body, (err, result) => {
        if (err) return console.log(err)

        console.log('saved to database')
        res.redirect('/')
    })
})

READ

// GET : read one
app.get('/users/:userId', function (req,res){
    console.log("user details : " + req.params.userId);
    db.collection('users').findOne({_id : new ObjectID(req.params.userId)},(err, result) => {
        if (err == null)
        {
            console.log(result);
            res.send(result);
        } 

        return console.log(err);
    }) 
})

Les identifiants dans MongoDB – équivalent du ID autoincrément – sont des objets à part entière. Si vous passez une chaine de caractère, la requête échoue.

Vous avez probablement remarqué la ligne var ObjectID = require(‘mongodb’).ObjectID qui permet de manipuler les ObjectID.

Vous pouvez donc créer l’objet attendu pour avoir une requête opérationnel : new ObjectID(req.params.userId)

UPDATE

// PUT : update one
app.put('/users/:userId', function(req, res){
    console.log('user updated : ' + req.params.userId);
    console.log(req.body);

    db.collection('users').findOneAndUpdate({_id : new ObjectID(req.params.userId)}, {
        $set: {
            lastname: req.body.lastname,
            firstname: req.body.firstname
        }
    }, {
        sort: {_id: -1},
        upsert: true
    }, (err, result) => {
        if (err) return res.send(err)
        res.send(result)
    })
})

DELETE

// DELETE : delete
app.delete('/users/:userId', function(req, res){
    console.log('user deleted : ' + req.params.userId)
    db.collection('users').findOneAndDelete({_id : new ObjectID(req.params.userId)},
    (err, result) => {
        if (err) return res.send(500, err)
        
        res.send('user deleted')    
    })
})

Front jQuery

Rendu de la page CRUD

CREATE

// CREATE
var requestCreateUser = function(user){
    $.ajax({
        method : "POST",
        url: endpointUsers,
        data: user,
        success : function(data, textStatus, xhr){
            console.log("done : " + JSON.stringify(data));
            window.location.reload(true);
        },
        error: function(xhr, textStatus, errorThrown){
            console.log("fail : " + errorThrown);
        }
    });
};

READ

// READ  
function requestGetUser(userId){
    url = endpointUsers + userId
    return $.ajax({
        method : "GET",
        url: url,
        dataType: "text",
        success : function(data){
            console.log("requestGetUser done : " + data);
            return data;
        },
        error: function(){
            console.log("fail");
        }
    });
};

UPDATE

// UPDATE
function requestUpdateUser(userid, user){
    url = endpointUsers + userid;
    return $.ajax({
        method : "PUT",
        url: url,
        data: user,
        success : function(data, textStatus, xhr){
            console.log("done : " + data);
        },
        error: function(xhr, textStatus, errorThrown){
            console.log("fail : " + errorThrown);
        }
    });
}

DELETE

// DELETE
var requestDeleteUser = function(userid){
    console.log("requestDeleteUser :" + userid);
    url = endpointUsers + userid;
    $.ajax({
        method : "DELETE",
        url: url,
        success : function(data, textStatus, xhr){
            console.log("done : " + data);
            window.location.reload(true);
        },
        error: function(xhr, textStatus, errorThrown){
            console.log("fail : " + errorThrown);
        }
    });
};

Vous pourrez trouver l’intégralité du code de cet exemple sur mon repo Github : Repo GitHub.