3.3 · MongoDB & Mongoose : base NoSQL

Niveau 3 · Express & bases de données

3.3MongoDB & Mongoose : base NoSQL

Objectif : persister des données avec MongoDB et Mongoose : connexion, schémas validés, modèles, et CRUD complet asynchrone — l'étape qui transforme un serveur en application réelle.
Temps estimé : 12 min · lecture autonome · Si vous n'avez que 5 min : lisez « L'essentiel en 30s » + le schéma.

L'essentiel en 30 secondes

  • MongoDB stocke des documents : objets type JSON/BSON en collections, sans schéma rigide — proche des objets JavaScript.
  • Mongoose ajoute la rigueur : schéma (types + validations required/unique) puis modèle offrant les méthodes de manipulation.
  • Le CRUD est asynchrone : create, find/findById, findByIdAndUpdate, findByIdAndDelete avec await, plus requêtes, populate et hooks.

Mini-cas filé — Salim stocke son catalogue sans chaos

Salim, développeur junior à Alger, gardait son catalogue dans un tableau en mémoire : tout disparaît à chaque redémarrage. Il passe à MongoDB : connexion via mongoose.connect avec une URI factice de travail, schéma produit (nom requis, prix nombre positif, catégorie par défaut), modèle Produit, puis CRUD complet en await. Quand un champ email manque ou qu'un prix est négatif, la validation Mongoose rejette avant enregistrement. Fini les données fantaisistes.

Ce même cas revient dans le cours, le schéma, le script et le défi 48h.

1. MongoDB, base orientée documents

Une appli réelle doit persister ses données : MongoDB, base NoSQL orientée documents très populaire avec Node, stocke des documents (structures type JSON/BSON) regroupés en collections — pas de tables ni de lignes, pas de schéma rigide imposé par la base. Un document ressemble à { nom: 'Théière', prix: 2400 } : flexible, imbricable, naturel pour un développeur JavaScript.

Cette flexibilité est un atout (structures évolutives, prototypage rapide) mais aussi un risque : sans discipline, les documents divergent (champs renommés, types mélangés). D'où la couche Mongoose — et le choix NoSQL vs SQL (section 3-4) selon la nature des données : flexibles et peu relationnelles → MongoDB ; fortement liées et critiques → SQL.

2. Mongoose : schémas, modèles, CRUD async

On travaille via Mongoose, un ODM qui habille MongoDB. Le schéma définit la forme attendue avec types et validations : new mongoose.Schema({ nom: String, email: { type: String, required: true, unique: true } }). Le modèle créé depuis le schéma (mongoose.model('User', userSchema)) offre les méthodes : create / new + save, find / findById, findByIdAndUpdate, findByIdAndDelete — toutes asynchrones, donc avec await.

Mongoose gère aussi les requêtes (filtres, tri, pagination), les relations (références + populate pour joindre) et les hooks (exécuter du code avant/après, comme hacher un mot de passe avant enregistrement — section 4-2). Réflexe de Salim : toujours définir un schéma même si MongoDB ne l'exige pas, et jamais de mot de passe ni d'URI réelle dans le code partagé.

Erreur d'interprétation fréquente : « MongoDB sans schéma veut dire sans aucune règle, et les méthodes Mongoose sont synchrones ». Non. Sans schéma Mongoose, les données deviennent vite incohérentes (champs et types divergents) ; le schéma apporte validation et rejets propres. Et toutes les méthodes CRUD renvoient des Promises : sans await, on manipule une promesse en attente, pas le document.
MongoDB + Mongoose Souplesse du document, rigueur du schéma DocumentsJSON/BSON en collections+SchémaTypes + required/unique+Modèle + CRUDcreate, find, update, delete en await Le schéma valide avant d'enregistrer ; le CRUD est async.
Souplesse du document, rigueur du schéma
Version texte du schéma (SEO / lecteurs d'écran / traduction) : 1) Documents : Salim range ses produits en objets flexibles dans une collection. 2) Schéma : il impose nom requis et prix positif via Mongoose. 3) Modèle + CRUD : create, findById, update et delete en await, avec populate pour les avis liés. Leçon : la souplesse NoSQL vit sous contrôle du schéma.

🔎 Exemple concret — Schéma, modèle et CRUD validé

Salim persiste son premier produit avec validation.

const mongoose = require('mongoose');
// URI FACTICE pédagogique — jamais d'URI réelle dans le code partagé
await mongoose.connect('mongodb://127.0.0.1:27017/demo-catalogue');
const produitSchema = new mongoose.Schema({
  nom: { type: String, required: true },
  prix: { type: Number, min: 0 }
});
const Produit = mongoose.model('Produit', produitSchema);
const p = await Produit.create({ nom: 'Théière', prix: 2400 });
const lu = await Produit.findById(p._id);

Lecture : connexion, schéma validé, modèle, puis create/findById en await.

Leçon : une donnée invalide est rejetée par le schéma avant la base.

Vocabulaire de la section

Persistance
Sauvegarde durable des données dans une base (utilisateurs, produits…) au lieu de la mémoire volatile.

Exemple simple : Le catalogue de Salim conservé après redémarrage.

Ce que ce n'est pas : un tableau JS vidé à chaque arrêt du serveur.

Réflexe : persister dès que la donnée doit survivre.

MongoDB (NoSQL document)
Base stockant des documents type JSON/BSON en collections, sans schéma rigide ; flexible et proche du JS.

Exemple simple : La collection produits aux documents variés.

Ce que ce n'est pas : une table SQL à colonnes strictes et jointures.

Réflexe : l'envisager pour données flexibles et évolutives.

Mongoose (ODM)
Couche pratique sur MongoDB : schémas validés, modèles, requêtes, relations et hooks.

Exemple simple : Le schéma produit de Salim avec required et min.

Ce que ce n'est pas : le driver brut sans validation ni modèles.

Réflexe : passer par Mongoose pour la rigueur quotidienne.

Schéma & modèle
Le schéma définit structure et validations ; le modèle, créé depuis le schéma, offre les méthodes CRUD.

Exemple simple : produitSchema puis mongoose.model('Produit', produitSchema).

Ce que ce n'est pas : des documents jetés en base sans forme déclarée.

Réflexe : déclarer types, required et unique avant le premier create.

CRUD async
Create, Read, Update, Delete via méthodes Mongoose asynchrones (await) : create, find, findByIdAndUpdate, findByIdAndDelete.

Exemple simple : await Produit.create(...) puis findById.

Ce que ce n'est pas : des appels synchrones imaginaires sans await.

Réflexe : await systématique et gestion d'erreur try/catch.

En pratique — Persister des données avec MongoDB et Mongoose

  1. Connectez-vous à MongoDB avec mongoose.connect(uri) (base locale ou MongoDB Atlas en cloud, URI factice en exercice).
  2. Définissez un schéma (types + validations : required, unique) et créez un modèle avec mongoose.model('User', userSchema).
  3. Réalisez un CRUD complet (asynchrone, avec await) : create (créer), find/findById (lire), findByIdAndUpdate (modifier), findByIdAndDelete (supprimer).
  4. Explorez les requêtes (filtres, tri, pagination) et une relation entre documents (référence + populate).
Vous persistez des données avec MongoDB et Mongoose : connexion, schémas validés, modèles, et CRUD complet asynchrone — l'étape qui transforme un serveur en application réelle.

Script prêt à dire — présenter sa persistance Mongo (30 s)

« Le catalogue est persisté sous MongoDB : connexion mongoose, schéma produit avec nom requis et prix positif, modèle Produit, CRUD complet en await. La validation rejette les données invalides avant la base, et je teste sur une URI locale factice, jamais de secret réel dans le code. »

À dire en démo ; montrer un create rejeté par la validation.

Modèle copier-coller — Mongoose CRUD (URI factice)

const mongoose = require('mongoose');
// URI FACTICE — exemple pédagogique, remplacer en local, secret en variable d'environnement
const URI_FACTICE = 'mongodb://127.0.0.1:27017/demo-catalogue';
async function main() {
  await mongoose.connect(URI_FACTICE);
  const produitSchema = new mongoose.Schema({
    nom: { type: String, required: true },
    prix: { type: Number, min: 0, required: true }
  });
  const Produit = mongoose.model('Produit', produitSchema);
  const cree = await Produit.create({ nom: 'Théière', prix: 2400 });
  const lu = await Produit.findById(cree._id);
  await Produit.findByIdAndUpdate(cree._id, { prix: 2600 });
  await Produit.findByIdAndDelete(cree._id);
  await mongoose.disconnect();
}
main().catch(console.error);

Indépendant

Un seul modèle Mongoose validé suffit à fiabiliser les devis stockés.

PME à Alger

Salim modélise produits et avis liés, avec populate pour afficher les avis par produit.

Équipe produit

Imposer required/unique au schéma pour que chaque équipe écrive des données cohérentes.

Débutant

Cette semaine : un schéma à deux champs et un CRUD create + find en await.

Prudence : Connexion et secrets : utilisez une URI factice ou locale pour apprendre (ex. mongodb://127.0.0.1:27017/demo-catalogue) et ne collez jamais une URI réelle ni un mot de passe dans le code, les captures ou les messages. En production, l'URI vit dans une variable d'environnement hors dépôt, avec un utilisateur aux droits minimaux. En cas d'exposition accidentelle, régénérez le secret et changez le mot de passe avant toute reconnexion.

✓ Bonnes pratiques

  • Toujours définir un schéma Mongoose (rigueur + validation).
  • Utiliser le CRUD asynchrone avec await.
  • Hacher les mots de passe via un hook avant enregistrement (voir 4.2).

✗ Pièges fréquents

  • Renoncer au schéma sous prétexte que MongoDB est « flexible » (données chaotiques).
  • Accepter n'importe quel champ envoyé par un client (risque d'injection de champs).
  • Oublier que les méthodes Mongoose sont asynchrones (await).

Points clés à retenir

  • Une application réelle PERSISTE ses données. Deux familles : NoSQL (MongoDB) et SQL/relationnel (3-4).
  • MongoDB (NoSQL orienté DOCUMENTS) stocke des documents type JSON/BSON en COLLECTIONS — flexible, proche des objets JS.
  • MONGOOSE (ODM) ajoute schémas (structure + VALIDATION) et modèles (méthodes) : rigueur ET souplesse.
  • CRUD asynchrone (await) : create / find(ById) / findByIdAndUpdate / findByIdAndDelete ; aussi requêtes, relations (populate), hooks.

Défi 48h — appliquez demain

Définissez 1 schéma Mongoose avec 2 validations et réalisez un CRUD create + find en await.

Checklist sortie de section
  • Je distingue document/collection de table/ligne.
  • Mon schéma valide required et types.
  • Mon CRUD est async avec await.
Auto-évaluation 1-5 : mes données invalides sont-elles rejetées par le schéma ?mes secrets sont-ils hors du code (URI factice) ?

Questions fréquentes

MongoDB (NoSQL) ou SQL : comment choisir ?

Selon la nature des données. MongoDB convient aux données flexibles et évolutives, aux structures imbriquées proches du JSON, au prototypage rapide et aux données peu relationnelles. Le SQL convient aux données fortement relationnelles (jointures nombreuses), à l'intégrité critique (schéma strict, transactions ACID) et aux requêtes croisées complexes. Ni l'une ni l'autre n'est universellement meilleure : Salim commence en MongoDB pour son catalogue souple et garde le SQL (3.4) pour la facturation liée.

Pourquoi utiliser Mongoose plutôt que MongoDB directement, et faut-il des schémas ?

Mongoose apporte schémas + validation (rejet des données incohérentes avant enregistrement), des modèles et méthodes élégantes (CRUD, filtres, tri, pagination, populate), des hooks (hacher un mot de passe avant save) et une API cohérente. Oui, définissez des schémas même si MongoDB ne l'impose pas : sans eux, champs renommés et types mélangés garantissent le chaos. La souplesse reste (champs optionnels, structures imbriquées), mais sous contrôle.

Vérifiez votre compréhension

Que fait le schéma Mongoose pour des données MongoDB ?

Ressources supplémentaires (optionnel, pour aller plus loin)

Examen du niveau 3Questions inédites et durcies pour valider vos acquis