MongoDB Academy · Leçon

Requêtes Mongoose, chaînage et documents allégés

Les apprenants chaîneront les assistants de requête Mongoose, utiliseront .lean() pour obtenir les performances brutes des POJO et compareront l’API de requête avec le pilote natif.

Leçon 3 sur 413 étapes

Requêtes Mongoose, chaînage et documents allégés est une leçon MongoDB Academy gratuite sur CoddyKit. Ceci est la leçon 3 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage MongoDB Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours MongoDB Academy comprend 4 leçons au total.

Objets de requête Mongoose

Lorsque vous appelez une méthode de requête Mongoose telle que User.find(), elle renvoie un objet Query plutôt qu’une promesse. Cet objet Query est paresseux : il ne s’exécute que lorsque vous l’appelez explicitement avec .then(), await ou .exec(). Avant son exécution, vous pouvez enchaîner des modificateurs de requête supplémentaires pour construire la requête complète. Cette API permettant l’enchaînement est l’une des fonctionnalités les plus ergonomiques de Mongoose.

const User = require('./models/user');

// This does NOT execute immediately — returns a Query object
const query = User.find({ active: true });

// Now execute it with await
const users = await query;

// Or chain modifiers before executing:
const result = await User.find({ active: true })
  .sort({ createdAt: -1 })
  .limit(10)
  .select('name email -_id');
  // select() projects fields: '+field' includes, '-field' excludes

Enchaîner les modificateurs de requête

Les modificateurs de requête Mongoose tels que .sort(), .limit(), .skip(), .select() et .populate() peuvent être enchaînés dans n’importe quel ordre avant l’exécution. L’objet Query sous-jacent accumule tous les modificateurs et envoie une seule requête optimisée à MongoDB. Cela équivaut fonctionnellement à transmettre des options au find(filter, options) du pilote natif, mais la lecture est plus naturelle, comme avec un générateur fluide.

const orders = await Order
  .find({ status: 'completed', userId: currentUserId })
  .sort({ createdAt: -1 })              // newest first
  .skip(page * pageSize)                // pagination offset
  .limit(pageSize)                      // page size
  .select('_id total status createdAt') // projection
  .lean();                              // return plain objects (discussed next)

console.log('Orders on this page:', orders.length);

La méthode .lean() : performances des POJO bruts

Par défaut, Mongoose enveloppe chaque document renvoyé par une requête dans une instance de document Mongoose — un objet lourd qui gère le suivi des modifications et inclut des méthodes, des virtuels et des hooks d’intergiciel. La méthode .lean() indique à Mongoose de renvoyer à la place de simples objets JavaScript (POJO). Les requêtes utilisant lean sont généralement 2 à 5 fois plus rapides et utilisent moins de mémoire, car Mongoose ignore l’enveloppement dans un document. Utilisez .lean() pour les opérations en lecture seule lorsque vous n’avez pas besoin des méthodes de document ni des hooks d’enregistrement ou de mise à jour.

// Without .lean() — heavy Mongoose Document objects
const docsWithMethods = await User.find({ active: true });
// docsWithMethods[0].save() works, but incurs overhead

// With .lean() — plain JavaScript objects, much faster
const pureObjects = await User.find({ active: true }).lean();
// pureObjects[0].save() does NOT work — it's a plain object
// But JSON.stringify, spread operators, and array methods are all faster

console.log(typeof docsWithMethods[0].save); // 'function'
console.log(typeof pureObjects[0].save);     // 'undefined'

Quand utiliser .lean() plutôt que des documents complets

Utilisez .lean() lorsque vous ne faites que lire des données (points d’accès GET), lorsque vous devez sérialiser rapidement en JSON ou lorsque vous traitez de nombreux documents en bloc. N’utilisez pas .lean() lorsque vous devez appeler .save() sur le résultat, utiliser des propriétés virtuelles, exécuter un intergiciel de document ou accéder à des méthodes d’instance. Une bonne règle générale : lectures d’API → lean, flux de modification → documents Mongoose complets.

// API read endpoint — use .lean() for speed
router.get('/products', async (req, res) => {
  const products = await Product.find({}).lean(); // fastest, no doc wrapper
  res.json(products);
});

// Update endpoint — use full Mongoose document to access instance methods
router.post('/users/:id/deactivate', async (req, res) => {
  const user = await User.findById(req.params.id); // full document, NO .lean()
  await user.sendDeactivationEmail(); // instance method won't work with .lean()
  user.active = false;
  await user.save(); // document method won't work with .lean()
  res.json({ success: true });
});

Méthodes pratiques findById et findOne

Mongoose ajoute des méthodes de requête pratiques qui ne sont pas disponibles dans le pilote natif. Model.findById(id) équivaut à Model.findOne({ _id: id }) et convertit automatiquement les identifiants texte en ObjectId. Model.findByIdAndUpdate(id, update, options) et Model.findByIdAndDelete(id) combinent la recherche et la modification en une seule opération atomique. Ces méthodes réduisent considérablement le code répétitif dans les gestionnaires de routes CRUD.

// findById — automatic ObjectId conversion from string
const user = await User.findById('64a1b2c3d4e5f6789012345a').lean();

// findByIdAndUpdate — find, update, and return result atomically
const updatedProduct = await Product.findByIdAndUpdate(
  productId,
  { $set: { price: 199.99 }, $inc: { updateCount: 1 } },
  { new: true, runValidators: true }  // return new doc, run validators
);

// findByIdAndDelete — find and delete atomically
const deletedUser = await User.findByIdAndDelete(userId);
console.log('Deleted:', deletedUser ? deletedUser.email : 'not found');

Compter les documents

Mongoose fournit des méthodes efficaces pour compter les documents. Model.countDocuments(filter) applique un filtre et compte les documents correspondants — il parcourt les documents correspondants et utilise les index. Model.estimatedDocumentCount() utilise les métadonnées de la collection pour obtenir un compte approximatif mais instantané, sans filtre — cette méthode est utile pour les totaux de tableaux de bord sur de grandes collections lorsque le décompte exact n’est pas essentiel.

// Exact count with filter — uses an index if available
const activeUsers = await User.countDocuments({ active: true, role: 'user' });
console.log('Active users:', activeUsers);

// Fast approximate count — no filter, uses collection stats
const totalProducts = await Product.estimatedDocumentCount();
console.log('Approximate total products:', totalProducts);

// In Express pagination:
const [data, total] = await Promise.all([
  User.find({}).skip(offset).limit(pageSize).lean(),
  User.countDocuments({})
]);
res.json({ data, total, pages: Math.ceil(total / pageSize) });

Populate : résoudre les références

.populate() est l’une des fonctionnalités les plus puissantes de Mongoose : elle remplace un champ de référence ObjectId par le document réellement référencé, récupéré dans une autre collection. En interne, Mongoose exécute une deuxième requête sur la collection référencée et remplace les identifiants. Cela équivaut à $lookup dans la chaîne d’agrégation, mais avec une API plus simple.

const Order = mongoose.model('Order', new mongoose.Schema({
  userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
  productIds: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Product' }]
}));

// Populate the userId reference with the full User document
const order = await Order
  .findById(orderId)
  .populate('userId', 'name email')    // only select name and email from User
  .populate('productIds', 'name price') // populate array of references
  .lean();

console.log(order.userId.email);      // 'alice@example.com'
console.log(order.productIds[0].name); // 'Laptop'

Mongoose ou le pilote natif : lequel choisir

Mongoose ajoute la validation, populate, l’intergiciel et une API de requête pratique, au prix d’une certaine surcharge. Choisissez Mongoose lorsque votre application possède des schémas bien définis et stables, lorsque vous souhaitez une validation de schéma sans validateurs JSON Schema, lorsque vous avez besoin de populate pour résoudre des références ou lorsque vous construisez une API REST conventionnelle. Choisissez le pilote natif lorsque vous avez besoin de performances maximales, que vous travaillez avec des schémas dynamiques, que vous construisez des analyses reposant largement sur l’agrégation ou que vous écrivez un microservice avec peu de dépendances.

// Mongoose: ergonomic, validates, populate works
const user = await User.findOne({ email }).select('-password').populate('profile');

// Native driver: faster, raw, no middleware
const user = await db.collection('users')
  .findOne({ email }, { projection: { password: 0 } });

exec() et gestion des erreurs

L’appel explicite à .exec() convertit une Query Mongoose en promesse. C’est la méthode traditionnelle pour exécuter des requêtes avec un enchaînement de promesses utilisant .catch(). Avec async/await, vous pouvez omettre .exec() : un simple await User.find({}) fonctionne parfaitement. Toutefois, certains développeurs préfèrent .exec() pour plus de clarté ou lors de la construction programmatique d’objets de requête. Les deux approches produisent des résultats identiques.

// With .exec() — explicit Promise conversion
const user = await User.findOne({ email }).exec();

// Without .exec() — implicit execution via await
const user = await User.findOne({ email });

// Error handling with try/catch (both forms work the same)
try {
  const user = await User.findById(id);
  if (!user) throw new Error('User not found');
} catch (err) {
  if (err.name === 'CastError') {
    res.status(400).json({ error: 'Invalid ID format' });
  } else {
    res.status(500).json({ error: err.message });
  }
}

Modèle du générateur de requêtes

Comme les requêtes Mongoose sont paresseuses, vous pouvez construire des requêtes de manière conditionnelle dans plusieurs instructions avant leur exécution. Cette approche est utile lorsque les paramètres de requête sont facultatifs : vous n’ajoutez le tri ou les filtres que lorsque le paramètre est présent. Ce modèle est beaucoup plus propre que la construction de chaînes de requête dynamiques et préserve la lisibilité du code.

async function searchProducts(filters) {
  let query = Product.find();

  if (filters.category) {
    query = query.where('category').equals(filters.category);
  }
  if (filters.maxPrice) {
    query = query.where('price').lte(filters.maxPrice);
  }
  if (filters.inStock) {
    query = query.where('stock').gt(0);
  }

  const sortField = filters.sortBy || 'createdAt';
  query = query.sort({ [sortField]: -1 }).limit(50).lean();

  return query; // executes here via await in the caller
}

Chaîne d’agrégation dans Mongoose

Les modèles Mongoose prennent également en charge la chaîne d’agrégation via Model.aggregate(pipeline). Contrairement aux requêtes Mongoose classiques, l’agrégation ignore la conversion des types par le schéma, l’intergiciel Mongoose et populate : elle se comporte de manière similaire à un appel direct à aggregate du pilote natif. Aggregate renvoie un simple tableau d’objets, jamais des documents Mongoose. Utilisez Model.aggregate() pour les analyses et les rapports complexes qui ne tirent pas parti des abstractions de Mongoose.

// Aggregation in Mongoose — bypasses Mongoose middleware and casting
const salesByRegion = await Order.aggregate([
  { $match: { status: 'completed' } },
  {
    $group: {
      _id: '$region',
      totalRevenue: { $sum: '$total' },
      orderCount: { $sum: 1 },
      avgOrder: { $avg: '$total' }
    }
  },
  { $sort: { totalRevenue: -1 } }
]);

// salesByRegion is a plain array — no Mongoose Document wrapper

Vérification rapide

Testez votre compréhension des concepts MongoDB et NoSQL abordés dans cette leçon.

Récapitulatif de la leçon

Dans cette leçon, vous avez appris que les méthodes de requête Mongoose renvoient des objets Query paresseux qui peuvent être enchaînés avec .sort(), .limit(), .skip(), .select() et .populate() avant l’exécution, que .lean() renvoie de simples objets JavaScript pour de meilleures performances lors des opérations en lecture seule, et que Model.aggregate() ignore les abstractions de Mongoose et se comporte comme le pilote natif pour les chaînes d’analyse. Nous allons maintenant découvrir l’intergiciel Mongoose — les hooks pre et post pour appliquer une logique personnalisée autour de save, find et d’autres opérations.

Gratuit pour commencer

Apprends JavaScript avec un tuteur IA — gratuit

Écris et exécute du vrai code dans ton navigateur, obtiens de l'aide instantanée d'un tuteur IA disponible 24h/24, et reprends là où tu t'es arrêté sur le web ou dans l'app.

Cours
30
Leçons
120

Questions Fréquemment Posées

La leçon « Requêtes Mongoose, chaînage et documents allégés » est-elle gratuite ?

Oui — le texte complet de « Requêtes Mongoose, chaînage et documents allégés » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours MongoDB Academy, passe à CoddyKit PRO. Le cours MongoDB Academy comprend 4 leçons au total.

Qu'est-ce que j'apprendrai dans « Requêtes Mongoose, chaînage et documents allégés » ?

Les apprenants chaîneront les assistants de requête Mongoose, utiliseront .lean() pour obtenir les performances brutes des POJO et compareront l’API de requête avec le pilote natif. Tu pratiques MongoDB Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.

Dois-je avoir de l'expérience pour commencer MongoDB Academy ?

Aucune expérience préalable n'est requise. MongoDB Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 3 sur 4.

Combien de temps prend la leçon « Requêtes Mongoose, chaînage et documents allégés » ?

La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.

Peux-tu écrire et exécuter du code dans cette leçon MongoDB Academy ?

Oui. Chaque leçon MongoDB Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.

Toutes les leçons de ce cours

  1. Se connecter avec le pilote Node.js officiel
  2. Schémas, modèles et propriétés virtuelles Mongoose
  3. Requêtes Mongoose, chaînage et documents allégés
  4. Intergiciels Mongoose : hooks avant et après
← Retour à MongoDB Academy