Grégory Bourguin
SysReIC - LISIC - ULCO
MongoDB & mongoose

MongoDB

MongoDB est un SGBD NoSQL orienté documents (source wikipédia)

  • Les données correspondent à des documents enregistrés dans des collections.
  • Une collection peut contenir un nombre quelconque de documents.
  • Les collections sont comparables aux tables et les documents aux enregistrements des bases de données relationnelles.
  • Les champs d'un enregistrement sont libres et peuvent être différents d'un enregistrement à un autre au sein d'une même collection.
  • Le seul champ commun obligatoire est le champ de clé principale _id.

La documentation officielle peut être consultée sur https://docs.mongodb.com/.

Pour mettre en oeuvre un SGBD MongoDB, il existe 2 principales solutions :

Dans les salles de TP, nous travaillerons avec une version locale.

MongoDB Community Edition

La version MongoDB Community permet d'installer un serveur MongoDB sur une machine.

Vous pourrez trouver la version qui correspond à votre OS ICI.

Dans les salles de TP, vous trouverez les dossiers d'installation de mongoDB (contenant mongod) et de mongosh dans le dossier /usr/local/

Commande mongod

L'installation de MongoDB rend disponible la commande mongod (plus un ensemble d'autres outils qui ne seront pas détaillés ici).

Démarrage du serveur

La commande mongod --dbpath chemin sert à démarrer un serveur MongoDB en indiquant dans le dossier où seront/sont stockées les données du serveur.

Pour tester :

  • créez un dossier mongodata quelque part sur votre disque.
  • exécutez la commande : mongod --dbpath votre_chemin_vers/mongodata

La gestion (et l'arrêt) du serveur peut alors être réalisée avec le shell mongosh.

Commande mongosh

Le shell mongosh propose un ensemble de commandes pour manipuler un SGBD MongoDB.

Installation

Pour installer mongosh : cliquez ICI.

Rappel : dans les salles de TP, mongosh est déjà installé dans /user/local/...

Connexion au serveur

La commande mongosh "mongodb://host:port" permet d'accéder à un serveur MongoDB.
(le port par défaut d'un serveur MongoDB est 27017)

Pour accéder au serveur local : mongosh "mongodb://127.0.0.1:27017".
(ce qui est équivalent à la commande mongosh seule du fait des valeurs par défaut)

NB : il existe d'autre manières d'accéder à un serveur MondoDB.

Gestion du serveur

mongosh offre de très nombreuses commandes de gestion du serveur, des bases, collections et documents.

Vous trouverez une large documentation sur le sujet : mongosh Usage, Getting Started, Perform CRUD Operations, etc.

Voici quelques exemples pour démarrer (à exécuter le shell mongosh) :

  • Nom de la base en cours d'utilisation : db.
    (NB : un serveur nouvellement créé sélectionne la base par défaut nommée 'test')
  • Changer de base : use dbname.
    Testez :
    use movies_db
    (NB : on peut sélectionner une base même si elle n'existe par encore vraiment : les bases sont effectivement créées lorsqu'on y ajoute des documents !).
  • Ajouter un document : db.nom_collection.insertOne(document).
    Testez :
    db.movies.insertOne({
        title : 'Terminator',
        synopsis: "A Los Angeles en 1984, un Terminator, cyborg surgi du futur, a pour mission d'exécuter une jeune femme dont l'enfant à naître doit sauver l'humanité."
    })
  • Lister les collections : show collections.
    Testez :
    show collections
    Vous devriez voir la collection movies créée précédemment.
  • Lister les DB : show databases.
    Testez :
    show databases
    Vous devriez voir toutes les DB du serveur y compris movies_db créée précédemment.
  • Arrêter le serveur : db.shutdownServer()

Commande mongoimport

La commande mongoimport permet d'importer les données d'un fichier JSON dans une DB.

Cette commande est installée en même temps que mongod.

Pour peupler votre DB de films, vous pouvez utiliser le fichier : movies.json.
(Les images correspondantes sont dans movies_images.zip)

L'importation de ces données dans un serveur local peut être faite grâce à :
mongoimport --db=movies_db --collection=movies --file=movies.json --jsonArray

Explications :

  • La base étant en local (127.0.0.1) sur le port par défaut (27017) et sans gestion des accès, on n'a ici pas besoin de préciser ces informations.
  • --db = le nom de la DB cible (dans laquelle on veut importer).
  • --collection = le nom de la collection cible (dans laquelle on veut importer).
  • --file = le nom du fichier source JSON contenant les données.
  • --jsonArray précise que le fichier source contient un tableau d'objets.

NB : il existe aussi la commande mongoexport.

MongoDB Atlas (optionnel)

MongoDB Atlas est une solution d'hébergement de bases de données MongoDB en cloud :

  • il n'y a pas de serveur local à installer.
  • la gestion des collections peut être faite simplement grâce à l'interface d'administration en ligne.

Création de Compte/Cluster

La procédure d'utilisation d'Atlas est détaillée (en anglais) sur le site Mozilla.

Vous trouverez ci-dessous les principales étapes.

  1. Créer un compte sur la solution cloud.
  2. Sélectionner l'offre de déploiement gratuite.
    - Cloud Provider : AWS.
    - Region : Paris.
    Puis cliquer sur Create Cluster.
  3. Créer un utilisateur (ex.root) et son mot de passe et NOTEZ CES INFORMATIONS !
    Ces informations sont celles que vous utiliserez pour permettre à votre serveur Node.js d'accéder à votre base de données MongoDB Atlas.
  4. Dans Network Access : -> Add IP Adress -> Anywhere.

Création de Base de Donnée + Collections

En cliquant sur la partie Database vous pouvez accéder à vos collections par le bouton Browse Collections.

Vous pourrez alors créer des Bases de Données qui contiendront des Collections (~tables) qui elle-mêmes contiendront des Documents (données).

Pour la 1ère création, il est possible que l'interface vous propose de peupler votre cluster avec des bases de données de test/démonstration. Pour ne pas polluer votre environnement, je vous conseille de plutôt sélectionner Add My Own Data.

Pour notre 1er exemple, créez une DB nommée movies_db contenant une collection nommée movies.

Création de Documents

En sélectionnant une collection dans l'interface d'administration, il est possible de cliquer sur le bouton INSERT DOCUMENT pour ajouter des documents (données).

Vous remarquerez qu'un champ _id est automatiquement généré.

On peut alors ajouter les paires clé/valeur qui nous intéressent. Nous allons créer le document suivant :

  • title : 'Terminator'
  • synopsis : 'A Los Angeles en 1984, un Terminator, cyborg surgi du futur, a pour mission d'exécuter une jeune femme dont l'enfant à naître doit sauver l'humanité.'

Un click sur Insert ajoute le document à la collection, et vous devez le voir apparaitre dans l'interface.

Mongoose

Mongoose est un module Node.js qui permet d'interagir avec les SGBD MongoDB.

Installation

Pour installer mongoose dans un projet Node.js : npm install mongoose.

Il peut alors être importé dans l'application :

// Importation du module mongoose
const mongoose = require('mongoose');

Connexion à MongoDB

La connexion à un serveur et une DB MongoDB est réalisé grâce à mongoose.connect.

La méthode accepte différents paramètres et options : cf. la documentation officielle.

Un schéma de connection "classique" utilise l'appel suivant :
mongoose.connect('mongodb://username:password@host:port/database') avec :

  • username : le nom de l'utilisateur MongoDB (ex. 'root').
    Il s'agit du nom d'utilisateur que vous avez noté lors de la création de cluster.
  • password : le mot de passe de l'utilisateur MongoDB (ex. 'unmdpcompliqué')
    Il s'agit du mot de passe que vous avez noté lors de la création de cluster.
  • host : le nom de la machine où tourne le serveur MongoDB (ex. '127.0.0.1').
  • port : le port de la machine où tourne le serveur MongoDB (ex. '27017').
  • database : le nom de la base de données où se trouvent les collections visées (ex. 'movies_db').

Connexion sur un serveur personnel

Par défaut, un serveur MongoDB ne gère pas les contrôles d'accès, ce qui signifie qu'il n'y a pas de user et password à donner pour s'y connecter.

Bien entendu, utiliser un serveur MongoDB sans contrôle d'accès n'est viable que dans le cadre du développement et de tests !

Si vous avez installé votre propre version de MongoDB en local, le code de connexion à utiliser est :

Code de connexion à un MongoDB Personnel

Connexion à Atlas

Si votre serveur est hébergé dans le cloud MongoDB Atlas, la construction de la chaine de connexion un peu plus complexe que pour un serveur local.

Heureusement, la partie Database d'Atlas propose un bouton Connect pour récupérer le pattern de connexion selon la méthode choisie : sélectionnez Connect your applicaption.

Vous pouvez alors copier le pattern de connexion au cluster...

... et l'utiliser dans app.js.

Code de connexion à MongoDB Atlas

Astuces :

Ce code étant assez volumineux, personnellement, je préfère le mettre dans un fichier (par exemple) nommé mongoose_init.js que je range dans le dossier controllers et que j'importe dans app.js grâce à un simple require("./controllers/mongoose_init").

De plus, les information de connexion user et pwd étant très sensibles, je préfère les externaliser dans un fichier bien rangé dans un dossier que je ne vais pas partager (par exemple en donnant une correction à mes étudiants :D).

J'ai défini ces valeurs dans des variables d'environnement (dans le fichier .env):
(cf. cours sur dotenv)

(dans le fichier .env)

MONGO_HOST='hote hebergeant le serveur MongoDB'
MONGO_USER='nom utilisateur'
MONGO_PWD='mot de passe compliqué'

Je récupère ensuite ces informations dans les variables d'environnement :

(dans le fichier qui effectue la connexion)

const host = process.env.MONGO_HOST ;
const user = process.env.MONGO_USER ;
const pwd = process.env.MONGO_PWD ;

const db_name = 'movies_db' ;

const mongoDB = `mongodb+srv://${user}:${pwd}@${host}/${db_name}?retryWrites=true&w=majority` ;
mongoose.connect(mongoDB, { useUnifiedTopology: true })
    .then(() => console.log('MongoDB OK !'))
    .catch(() => console.log('MongoDB ERREUR !'));

Schéma & Model mongoose

Mongoose utilise une approche Objet pour mapper les documents MongoDB.

Le mapping est effectué en créant des instances de la classe mongoose.Model grâce à la classe mongoose.Schema.

Les modèles créés sont par convention mis dans le dossier models du projet.

La collection movies que nous avons créée dans la DB movies_db contient comme son nom l'indique des documents qui représentent des films.

Nous allons donc créer le modèle Movie dans le fichier Movies.js.

Ce schéma/modèle indique en particulier que :

  • Le champ title doit être une chaîne (String).
    Il est requis (required: true), ce qui signifie qu'on ne peut pas créer de Movie sans lui donner un title.
  • Le champ synopsis doit être une chaîne (String).
    Il n'est pas requis et, s'il n'est pas fourni, il prendra la valeur indiquée pour default.

Requêtes mongoose

Les modèles (mongoose.Model) possèdent diverses méthodes pour lancer des requêtes (mongoose.Query) plus ou moins complexes.

La liste et les options sont exhaustives : nous n'allons pas toutes les étudier ici.
Utilisez la documentation de mongoose.Model et mongoose.Query en fonction de vos besoins.

Ces méthodes renvoient des Promise Javascript. On peut donc les utiliser en ansynchrone avec un .then(....).catch(....), ou en synchrone (mode bloquant) avec await.

Pour exemplifier, nous allons utiliser la méthode find en asynchrone pour récupérer tous les documents correspondant à un sélecteur (NB : si aucun sélecteur n'est fourni, find renvoie la totalité des documents).

Le fichier moviesController.js ci-dessous est utilisé dans l'application Express pour traiter les requêtes sur la route /movies en renvoyant la vue moviesList.

Vous remarquerez aussi qu'on peut enchainer les traitements des requêtes mongoose.
Par exemple, le code ci-dessous chaine le find avec un sort pour trier les données.

Exercice 11

Créez l'application et les fichiers cités ci-avant pour obtenir le site ci-dessous avec en particulier la route /movies qui affiche la liste des titres de films de la collection movies.

La liste des films doit être récupérée dynamiquement dans la DB (!).

NB : l'exemple ci-dessous se connecte à mon cluster MongoDB Atlas qui possède plusieurs documents/films dans sa collection /movies.

Site à reproduire :

C.R.U.D.

L'acronyme C.R.U.D. signifie Create / Read / Update / Delete.

C'est tout ce que nous allons apprendre à faire avec nos documents MongoDB.

Create

La création de document est réalisée grâce à méthode create de la classe mongoose.Model.

Ex. Voici comment créer un Movie et le sauvegarder dans sa collection :

Read

La lecture est réalisée grâce aux méthodes find, findOne, ou encore findById.

Ex. Voici comment récupérer le 1er Movie de la collection qui possède un title avec la valeur 'Terminator' avec findOne :

Update

La mise à jour est réalisée grâce aux méthodes update, updateOne, findByIdAndUpdate ou encore findOneAndUpdate. On peut aussi mettre à jour plusieurs documents en même temps grâce à updateMany.

Ex. Voici comment modifier un Movie avec un _id spécifique avec findByIdAndUpdate :

Delete

La suppression de document est réalisée grâce aux méthodes findByIdAndDelete, findOneAndDelete, ou encore deleteOne. On peut aussi supprimer plusieurs documents en même temps grâce à deleteMany.

Ex. Voici comment supprimer un Movie avec un titre spécifique avec findOneAndDelete :

Variantes des méthodes vues

Les méthodes que nous venons de voir pour le C.R.U.D. sont des méthodes de classe appelées directement sur un mongoose.Model : dans nos exemples, les méthodes sont appelées sur Movie.

La plupart des méthodes que nous venons de voir pour le C.R.U.D. possèdent un équivalent à appeler sur une instance de Model.

Par exemple, la méthode Model.create que nous avons vue est quelque peu équivalent à la méthode Model.prototype.save qui doit être appelée non plus sur Movie, mais sur une de ses instances.

Ces méthodes sont celles qui ont le préfixe Model.prototype l'API mongoose.Model.

Voici l'équivalent de notre exemple pour Create en utilisant Model.prototype.save :

Exercice 12

Pour tester les exemples de code vus ci-avant, créez un serveur/application avec un ensemble de routes GET dédiées à un exemple de chaque action CRUD.

Vous aurez ainsi les routes :

  • / : effectue une redirection vers la route /movies (ci-dessous).
  • /movies : affiche la liste des films (title + synopsis) sous forme de tableau dans une vue EJS.
  • /movies/create : crée un document avec le title 'House' et ré-affiche la liste (dans laquelle on doit donc voir le nouveau film).
  • /movies/read : affiche les informations (title + synopsis) du film 'House' dans une vue EJS.
  • /movies/update : modifie les informations du film 'House' et re-affiche la liste.
    (Pour simplifier, utilisez un findOneAndUpdate à la place du findByIdAndUpdate de l'exemple).
  • /movies/delete : supprime le film 'House' de la collection et et re-affiche la liste (dans laquelle le film doit donc avoir disparu).

Exemple après un /movies/create puis un /movies/update :

Pour les curieux : House :).