🌱Idée sous-jacente
Pour Mongoose : les virtuals sont utilisés pour simuler des relations entre entités, notamment avec populate et les mécanismes de lookup côté MongoDB.
Virtual = champ calculé, non stocké en base
Virtual populate = relation déclarative
🎯But
Le but est de séparer logique et persistance
- Le modèle MongoDB reste simple (sans duplication ou clé étrangère obligatoire).
- Les entités sont reliées au niveau applicatif, via Mongoose, ce qui rend le code plus expressif et maintenable.
🆘Explication
lecture : → virtual
Virtual est au coeur de mongoose !
Autres exemples.
🆘Le coût d'une pizza : champ calculé non stocké.
- import mongoose from "mongoose";
- const pizzaSchema = new mongoose.Schema({
- name: { type: String, required: true },
- // `description` stores a simple recipe/notes string (renamed from `recept`)
- description: { type: String, default: "" },
- });
- // Include virtuals when converting documents to objects/JSON
- pizzaSchema.set("toObject", { virtuals: true });
- pizzaSchema.set("toJSON", { virtuals: true });
- // Virtual populate: connect Pizza -> Topping via Topping.pizzas (inverse relation)
- pizzaSchema.virtual("toppings", {
- ref: "Topping",
- localField: "_id",
- foreignField: "pizzas",
- justOne: false,
- });
- // Virtual total price (cents) includes toppings when `toppings` is populated
- pizzaSchema.virtual("totalPriceCents").get(function () {
- // Compute total price from populated `toppings` only (no base pizza price)
- if (
- !this.toppings ||
- !Array.isArray(this.toppings) ||
- this.toppings.length === 0
- )
- return 0;
- return this.toppings.reduce((acc, t) => acc + (t.priceCents || 0), 0);
- });
- pizzaSchema.virtual("totalPriceEur").get(function () {
- return (this.totalPriceCents || 0) / 100;
- });
- export const Pizza = mongoose.model("Pizza", pizzaSchema);
🆘relation inverse !
Soit un schéma topping :
const toppingSchema = new mongoose.Schema({
title: { },
pizzas: [{ type: mongoose.Schema.Types.ObjectId, ref: "Pizza" }],
});
pizzas = ensemble des pizzas qui a cet ingrédients.
🛟Comment trouver les pizzas qui ont un ingredient ?
→ Dans le schéma pizza on ecrit un virtual !
🥷pizzaSchema.virtual("toppings", { ref: "Topping", localField: "_id", foreignField: "pizzas", justOne: false, });
Ici on crée une relation inverse :
ref: "Topping"→ référence le modèleTopping.localField: "_id"→ utilise l’ID de la pizza.foreignField: "pizzas"→ cherche dans le champpizzasdes documentsTopping(qui doit être un tableau d’IDs de pizzas).justOne: false→ une pizza peut avoir plusieurs toppings.
👉 En clair : ce virtual permet de récupérer tous les
Topping qui contiennent l’ID de cette pizza dans leur champ pizzas.