Aller au contenu principal
MongoDBNoSQLBase de donnéesFormation

CRUD et requêtes MongoDB

30 min de lecture Apprendre MongoDB — Chapitre 2

Maîtrise les opérations CRUD avec mongosh, les filtres, projections, opérateurs de comparaison, les types BSON et construis une API de monitoring.

Après avoir compris les fondamentaux de MongoDB et installé ton instance Docker, il est temps de mettre les mains dans le moteur. Les opérations CRUD — Create, Read, Update, Delete — sont le cœur de toute interaction avec une base de données. En MongoDB, elles s’exécutent via mongosh avec une syntaxe JavaScript fluide et intuitive.

Ce chapitre couvre l’intégralité des opérations CRUD, les filtres avancés avec opérateurs, les projections pour contrôler les données retournées, et les types BSON qui font la richesse de MongoDB.

🎯 Objectifs de la leçon

  • Maîtriser insertOne, insertMany, find, updateOne, updateMany, deleteOne, deleteMany
  • Utiliser les opérateurs de comparaison et logiques pour des requêtes précises
  • Contrôler les résultats avec les projections, le tri et la pagination
  • Comprendre les types BSON et savoir quand les utiliser

Create : insérer des documents

MongoDB propose deux méthodes d’insertion. insertOne pour un seul document, insertMany pour plusieurs d’un coup. La méthode crée automatiquement la collection si elle n’existe pas encore — pas besoin de CREATE TABLE.

Voici comment insérer un document unique. MongoDB génère automatiquement un _id de type ObjectId si tu n’en fournis pas :

db.servers.insertOne({
  hostname: "web-01",
  ip: "10.0.1.10",
  role: "webserver",
  cpu_cores: 4,
  ram_gb: 16,
  status: "running",
  tags: ["production", "europe-west"],
  metrics: { cpu_usage: 45.2, ram_usage: 72.1 }
})
// { acknowledged: true, insertedId: ObjectId('65a1b2c3...') }

Pour insérer plusieurs documents en une seule opération, utilise insertMany avec un tableau. C’est nettement plus performant que des insertOne en boucle — une seule requête réseau au lieu de N :

db.servers.insertMany([
  { hostname: "web-02", ip: "10.0.1.11", role: "webserver",
    status: "running", tags: ["production"] },
  { hostname: "db-01", ip: "10.0.2.10", role: "database",
    cpu_cores: 8, ram_gb: 64, status: "running",
    tags: ["production"], services: ["postgresql"] },
  { hostname: "staging-01", ip: "10.0.3.10", role: "all-in-one",
    status: "stopped", tags: ["staging"] }
])

Remarque : db-01 a des champs services et ram_gb que staging-01 n’a pas. MongoDB accepte sans broncher — c’est la flexibilité du schéma document.

⚠️ Attention : Si tu fournis un _id manuellement et qu’il existe déjà, MongoDB renvoie une erreur E11000 duplicate key error. En production, préfère laisser MongoDB générer les ObjectId automatiquement sauf besoin spécifique (clé naturelle comme un code pays).

Read : requêtes et filtres

C’est la partie la plus riche de MongoDB. Les méthodes find et findOne acceptent un filtre (premier argument) et une projection optionnelle (second argument) pour contrôler quels champs retourner.

Filtres simples et opérateurs de comparaison

Un filtre est un objet JSON qui décrit les critères de sélection. Sans filtre, find() retourne tous les documents. Avec un filtre, seuls les documents correspondants sont retournés :

// Un seul critère
db.servers.find({ status: "running" })

// Plusieurs critères (AND implicite)
db.servers.find({ role: "webserver", status: "running" })

// Opérateurs de comparaison
db.servers.find({ cpu_cores: { $gt: 4 } })         // supérieur à 4
db.servers.find({ ram_gb: { $gte: 16, $lte: 64 } }) // entre 16 et 64
db.servers.find({ role: { $in: ["webserver", "database"] } }) // dans la liste

Les opérateurs essentiels : $eq (égal), $ne (différent), $gt/$gte (supérieur), $lt/$lte (inférieur), $in/$nin (dans/pas dans une liste), $exists (champ présent ou absent).

Opérateurs logiques et requêtes imbriquées

Pour des conditions plus complexes, MongoDB propose $and, $or et $not. Le $and est implicite quand tu mets plusieurs critères dans le même objet, mais tu en as besoin explicitement quand tu combines avec $or :

// OR : serveurs web OU database
db.servers.find({
  $or: [{ role: "webserver" }, { role: "database" }]
})

// Combiné : (web OU db) ET status running
db.servers.find({
  $and: [
    { $or: [{ role: "webserver" }, { role: "database" }] },
    { status: "running" }
  ]
})

// Dot notation pour les sous-documents
db.servers.find({ "metrics.cpu_usage": { $gt: 50 } })

💡 Tip DevOps : La dot notation ("metrics.cpu_usage") est l’une des fonctionnalités les plus puissantes de MongoDB. Elle permet de requêter n’importe quel niveau d’imbrication sans jointure. En monitoring, c’est indispensable pour filtrer sur des métriques imbriquées.

Projections : contrôler les champs retournés

Par défaut, MongoDB renvoie le document complet. Les projections permettent de sélectionner uniquement les champs utiles — essentiel pour les performances quand tes documents sont volumineux :

// Inclure seulement hostname et status (+ _id par défaut)
db.servers.find({ status: "running" }, { hostname: 1, status: 1 })

// Exclure _id et metrics
db.servers.find({}, { _id: 0, hostname: 1, ip: 1, role: 1 })

Règle importante : tu ne peux pas mélanger inclusion (1) et exclusion (0) dans la même projection. La seule exception est _id: 0 qui peut accompagner des inclusions.

Tri, pagination et comptage

Pour exploiter les résultats, MongoDB propose le chaînage de méthodes — sort, limit, skip et countDocuments :

// Top 3 serveurs par RAM décroissante
db.servers.find({}, { _id: 0, hostname: 1, ram_gb: 1 })
  .sort({ ram_gb: -1 }).limit(3)

// Pagination : page 2, 10 résultats par page
db.servers.find().skip(10).limit(10)

// Compter les serveurs en production
db.servers.countDocuments({ tags: "production" })

🧠 À retenir : sort() s’exécute toujours avant limit(), quel que soit l’ordre d’écriture dans la chaîne. MongoDB optimise automatiquement. Pour la pagination en production, préfère un curseur basé sur _id plutôt que skip() qui devient lent sur de gros volumes.

Update : modifier des documents

MongoDB sépare la mise à jour unitaire (updateOne) de la mise à jour en masse (updateMany). Les deux prennent un filtre et un objet de modification utilisant des opérateurs spéciaux.

L’opérateur $set modifie ou ajoute un champ. $inc incrémente une valeur numérique. $push ajoute un élément à un tableau. $pull en retire un. $unset supprime un champ :

// Mettre à jour les métriques d'un serveur
db.servers.updateOne(
  { hostname: "web-01" },
  { $set: { "metrics.cpu_usage": 52.1 }, $inc: { ram_gb: 16 } }
)

// Ajouter un tag à tous les serveurs de production
db.servers.updateMany(
  { tags: "production" },
  { $addToSet: { tags: "monitored" } }  // addToSet évite les doublons
)

// Upsert : créer si inexistant, mettre à jour sinon
db.servers.updateOne(
  { hostname: "cache-01" },
  { $set: { role: "cache", status: "running", ip: "10.0.5.10" } },
  { upsert: true }
)

🔥 Cas réel en entreprise : Dans un système de monitoring, un agent collecte les métriques toutes les 30 secondes et fait un updateOne avec $set sur les métriques courantes. L’upsert: true permet de gérer automatiquement les nouveaux serveurs qui apparaissent — pas besoin de les pré-enregistrer. C’est le pattern le plus courant en infra as code.

Delete : supprimer des documents

La suppression suit la même logique que les autres opérations : deleteOne pour un document, deleteMany pour plusieurs. Le filtre détermine ce qui est supprimé :

// Supprimer un serveur spécifique (toujours par _id si possible)
db.servers.deleteOne({ hostname: "staging-01" })

// Supprimer tous les serveurs arrêtés
db.servers.deleteMany({ status: "stopped" })

// Vider une collection (tous les documents, garde la collection)
db.servers.deleteMany({})

// Supprimer la collection elle-même
db.servers.drop()

⚠️ Attention : deleteMany({}) sans filtre supprime tous les documents de la collection. C’est irréversible. En production, protège-toi avec des backups réguliers (mongodump) et des rôles utilisateurs restreints qui n’ont pas le droit delete. Un junior avec accès admin sur la prod, c’est une bombe à retardement.

Les types BSON essentiels

MongoDB stocke les données en BSON (Binary JSON), un format qui étend JSON avec des types supplémentaires. Les plus importants à connaître :

ObjectId : identifiant unique de 12 octets, auto-généré pour _id. Contient un timestamp — tu peux extraire la date de création avec oid.getTimestamp().

Date : utilise new Date() ou ISODate("2026-03-20T10:00:00Z"). Ne stocke jamais une date en string — tu perds le tri et les comparaisons.

Decimal128 : NumberDecimal("19.99") pour les valeurs financières. Les Double classiques ont des erreurs d’arrondi (19.99 peut devenir 19.990000000000002).

Array : les tableaux sont des citoyens de première classe. Tu peux filtrer sur leur contenu ({ tags: "production" } matche si “production” est dans le tableau) et les manipuler avec $push, $pull, $addToSet.

🔥 Cas réel en entreprise : Une fintech stockait les montants de transactions en Double. Au bout de 6 mois, les rapprochements comptables ne tombaient plus juste — des centimes d’écart qui s’accumulaient. Migration vers Decimal128 et problème résolu. Règle d’or : si c’est de l’argent, c’est NumberDecimal.

Résumé

Les opérations CRUD de MongoDB sont expressives et puissantes. L’insertion est flexible (pas de schéma à déclarer), les requêtes combinent filtres, opérateurs et projections de manière intuitive, les mises à jour sont chirurgicales grâce aux opérateurs $set, $inc, $push, et la suppression reste simple mais dangereuse sans garde-fous.

🧠 À retenir : Utilise insertMany plutôt que des boucles d’insertOne. Maîtrise la dot notation pour les sous-documents. Préfère $addToSet à $push pour éviter les doublons. Et en production, ne donne jamais les droits delete à tout le monde — un deleteMany({}) mal placé et c’est la catastrophe.

➡️ La suite : Chapitre 3 — Agrégation et index MongoDB

Articles liés