AzDev

Opérations CRUD sur une base E-commerce

- Maîtriser les 4 opérations CRUD dans MongoDB - Comprendre la structure des documents BSON - Manipuler des documents imbriqués et des tableaux - Utiliser les opérateurs de requête MongoDB

Publié le
Opérations CRUD sur une base E-commerce

1. Configuration initiale

Installation et connexion

1# Connexion au serveur MongoDB local
2mongosh
3
4# Ou connexion à MongoDB Atlas
5mongosh "mongodb+srv://cluster.xxxxx.mongodb.net/shop_maroc" --username etudiant

Création et utilisation de la base de données

1// Créer/utiliser la base de données
2use shop_maroc
3
4// Vérifier la base courante
5db.getName()

2. Structure des données

Collection products (Produits)

1{
2  "_id": ObjectId(),
3  "sku": "CAF-001",
4  "name": "Caftan Marocain Premium",
5  "category": "Vêtements",
6  "subcategory": "Traditionnel",
7  "price": {
8    "amount": 2500,
9    "currency": "MAD"
10  },
11  "stock": {
12    "quantity": 15,
13    "warehouse": "Casablanca"
14  },
15  "attributes": {
16    "color": ["Bleu", "Vert", "Rouge"],
17    "size": ["S", "M", "L", "XL"],
18    "material": "Soie naturelle"
19  },
20  "ratings": {
21    "average": 4.8,
22    "count": 127
23  },
24  "tags": ["traditionnel", "mariage", "luxe"],
25  "createdAt": ISODate("2024-01-15"),
26  "lastModified": ISODate("2024-03-20")
27}

Collection customers (Clients)

1{
2  "_id": ObjectId(),
3  "customerId": "CLT-10234",
4  "firstName": "Fatima",
5  "lastName": "Benali",
6  "email": "fatima.benali@email.com",
7  "phone": "+212 6 12 34 56 78",
8  "address": {
9    "street": "45 Boulevard Mohammed V",
10    "city": "Rabat",
11    "postalCode": "10000",
12    "country": "Maroc"
13  },
14  "preferences": {
15    "newsletter": true,
16    "language": "fr",
17    "currency": "MAD"
18  },
19  "loyaltyPoints": 1250,
20  "registeredAt": ISODate("2023-05-10"),
21  "lastPurchase": ISODate("2024-03-15")
22}

Collection orders (Commandes)

1{
2  "_id": ObjectId(),
3  "orderNumber": "CMD-2024-0543",
4  "customerId": "CLT-10234",
5  "items": [
6    {
7      "productId": ObjectId("..."),
8      "sku": "CAF-001",
9      "name": "Caftan Marocain Premium",
10      "quantity": 1,
11      "unitPrice": 2500,
12      "total": 2500
13    },
14    {
15      "productId": ObjectId("..."),
16      "sku": "BAB-003",
17      "name": "Babouche artisanale",
18      "quantity": 2,
19      "unitPrice": 350,
20      "total": 700
21    }
22  ],
23  "totals": {
24    "subtotal": 3200,
25    "shipping": 50,
26    "tax": 640,
27    "total": 3890
28  },
29  "shipping": {
30    "method": "Amana",
31    "address": {
32      "street": "45 Boulevard Mohammed V",
33      "city": "Rabat",
34      "postalCode": "10000"
35    },
36    "estimatedDelivery": ISODate("2024-03-25")
37  },
38  "payment": {
39    "method": "CMI",
40    "status": "completed",
41    "transactionId": "TRX-789456"
42  },
43  "status": "processing",
44  "statusHistory": [
45    {
46      "status": "pending",
47      "date": ISODate("2024-03-20T10:00:00Z")
48    },
49    {
50      "status": "paid",
51      "date": ISODate("2024-03-20T10:05:00Z")
52    },
53    {
54      "status": "processing",
55      "date": ISODate("2024-03-20T10:30:00Z")
56    }
57  ],
58  "createdAt": ISODate("2024-03-20T10:00:00Z")
59}

3. Opérations CREATE (Insertion)

3.1 Insertion simple (insertOne)

1// Insérer un nouveau produit
2db.products.insertOne({
3  sku: "THÉ-001",
4  name: "Thé à la menthe - Boîte luxe",
5  category: "Alimentation",
6  subcategory: "Boissons",
7  price: {
8    amount: 150,
9    currency: "MAD"
10  },
11  stock: {
12    quantity: 50,
13    warehouse: "Marrakech"
14  },
15  attributes: {
16    weight: "500g",
17    origin: "Maroc",
18    type: "Bio"
19  },
20  ratings: {
21    average: 0,
22    count: 0
23  },
24  tags: ["thé", "menthe", "bio", "cadeau"],
25  createdAt: new Date(),
26  lastModified: new Date()
27})
28
29// Vérifier l'insertion
30db.products.findOne({ sku: "THÉ-001" })

3.2 Insertion multiple (insertMany)

1// Insérer plusieurs clients
2db.customers.insertMany([
3  {
4    customerId: "CLT-10235",
5    firstName: "Mohammed",
6    lastName: "Alaoui",
7    email: "m.alaoui@email.com",
8    phone: "+212 6 22 33 44 55",
9    address: {
10      street: "12 Rue des Consuls",
11      city: "Casablanca",
12      postalCode: "20000",
13      country: "Maroc"
14    },
15    preferences: {
16      newsletter: true,
17      language: "ar",
18      currency: "MAD"
19    },
20    loyaltyPoints: 500,
21    registeredAt: new Date(),
22    lastPurchase: null
23  },
24  {
25    customerId: "CLT-10236",
26    firstName: "Amina",
27    lastName: "Rachidi",
28    email: "amina.r@email.com",
29    phone: "+212 6 33 44 55 66",
30    address: {
31      street: "78 Avenue Hassan II",
32      city: "Fès",
33      postalCode: "30000",
34      country: "Maroc"
35    },
36    preferences: {
37      newsletter: false,
38      language: "fr",
39      currency: "MAD"
40    },
41    loyaltyPoints: 0,
42    registeredAt: new Date(),
43    lastPurchase: null
44  }
45])
46
47// Vérifier le nombre de documents insérés
48db.customers.countDocuments()

3.3 Insertion avec génération d'ID personnalisé

1// Créer une nouvelle commande avec ID personnalisé
2const orderId = new ObjectId()
3const orderNumber = `CMD-2024-${Date.now()}`
4
5db.orders.insertOne({
6  _id: orderId,
7  orderNumber: orderNumber,
8  customerId: "CLT-10235",
9  items: [
10    {
11      productId: ObjectId(),
12      sku: "THÉ-001",
13      name: "Thé à la menthe - Boîte luxe",
14      quantity: 3,
15      unitPrice: 150,
16      total: 450
17    }
18  ],
19  totals: {
20    subtotal: 450,
21    shipping: 0, // Gratuit pour commandes > 400 MAD
22    tax: 90,
23    total: 540
24  },
25  shipping: {
26    method: "CTM Messagerie",
27    address: {
28      street: "12 Rue des Consuls",
29      city: "Casablanca",
30      postalCode: "20000"
31    },
32    estimatedDelivery: new Date(Date.now() + 3 * 24 * 60 * 60 * 1000) // +3 jours
33  },
34  payment: {
35    method: "COD", // Cash on Delivery
36    status: "pending"
37  },
38  status: "pending",
39  statusHistory: [
40    {
41      status: "pending",
42      date: new Date()
43    }
44  ],
45  createdAt: new Date()
46})
47
48print(`Commande créée avec succès : ${orderNumber}`)

4. Opérations READ (Lecture)

4.1 Requêtes simples

1// Trouver tous les produits
2db.products.find()
3
4// Limiter le nombre de résultats
5db.products.find().limit(5)
6
7// Trouver un produit spécifique
8db.products.findOne({ sku: "CAF-001" })
9
10// Trouver par catégorie
11db.products.find({ category: "Vêtements" })
12
13// Compter les documents
14db.products.countDocuments({ category: "Vêtements" })

4.2 Requêtes avec opérateurs de comparaison

1// Produits avec prix supérieur à 1000 MAD
2db.products.find({ 
3  "price.amount": { $gt: 1000 } 
4})
5
6// Produits entre 500 et 2000 MAD
7db.products.find({ 
8  "price.amount": { $gte: 500, $lte: 2000 } 
9})
10
11// Produits en rupture de stock (quantité < 5)
12db.products.find({ 
13  "stock.quantity": { $lt: 5 } 
14})
15
16// Clients avec plus de 1000 points de fidélité
17db.customers.find({ 
18  loyaltyPoints: { $gt: 1000 } 
19})

4.3 Requêtes avec opérateurs logiques

1// Produits traditionnels OU de luxe
2db.products.find({
3  $or: [
4    { tags: "traditionnel" },
5    { tags: "luxe" }
6  ]
7})
8
9// Produits en stock À Casablanca ET prix < 500 MAD
10db.products.find({
11  $and: [
12    { "stock.warehouse": "Casablanca" },
13    { "price.amount": { $lt: 500 } }
14  ]
15})
16
17// Produits qui NE SONT PAS dans la catégorie Vêtements
18db.products.find({
19  category: { $ne: "Vêtements" }
20})

4.4 Requêtes sur les tableaux

1// Produits disponibles en taille "L"
2db.products.find({
3  "attributes.size": "L"
4})
5
6// Produits avec TOUS ces tags
7db.products.find({
8  tags: { $all: ["traditionnel", "luxe"] }
9})
10
11// Produits avec AU MOINS UN de ces tags
12db.products.find({
13  tags: { $in: ["cadeau", "mariage"] }
14})
15
16// Commandes avec exactement 2 articles
17db.orders.find({
18  items: { $size: 2 }
19})

4.5 Requêtes sur les documents imbriqués

1// Clients de Casablanca
2db.customers.find({
3  "address.city": "Casablanca"
4})
5
6// Commandes payées par carte CMI
7db.orders.find({
8  "payment.method": "CMI",
9  "payment.status": "completed"
10})
11
12// Requête complexe : commandes en cours de livraison vers Rabat
13db.orders.find({
14  "shipping.address.city": "Rabat",
15  status: "shipping"
16})

4.6 Projections (sélection de champs)

1// Afficher seulement nom et prix des produits
2db.products.find(
3  { category: "Vêtements" },
4  { name: 1, price: 1, _id: 0 }
5)
6
7// Exclure certains champs
8db.customers.find(
9  {},
10  { loyaltyPoints: 0, preferences: 0 }
11).limit(3)
12
13// Projection sur documents imbriqués
14db.orders.find(
15  { status: "processing" },
16  { 
17    orderNumber: 1, 
18    "totals.total": 1,
19    "shipping.estimatedDelivery": 1,
20    _id: 0
21  }
22)

4.7 Tri et pagination

1// Trier par prix décroissant
2db.products.find().sort({ "price.amount": -1 })
3
4// Trier par plusieurs critères
5db.products.find().sort({ 
6  category: 1, 
7  "price.amount": -1 
8})
9
10// Pagination
11const pageSize = 10
12const pageNumber = 2
13
14db.products.find()
15  .skip((pageNumber - 1) * pageSize)
16  .limit(pageSize)
17  .sort({ createdAt: -1 })
18
19// Combinaison complexe
20db.products.find({ 
21  "stock.quantity": { $gt: 0 } 
22})
23  .sort({ "ratings.average": -1 })
24  .limit(5)
25  .project({ name: 1, price: 1, ratings: 1 })

4.8 Requêtes avec expressions régulières

1// Produits contenant "Caftan" dans le nom
2db.products.find({
3  name: /Caftan/i  // i = insensible à la casse
4})
5
6// Clients dont l'email se termine par @gmail.com
7db.customers.find({
8  email: /@gmail\.com$/
9})
10
11// Produits commençant par "Thé"
12db.products.find({
13  name: /^Thé/
14})

5. Opérations UPDATE (Mise à jour)

5.1 Mise à jour simple (updateOne)

1// Mettre à jour le prix d'un produit
2db.products.updateOne(
3  { sku: "CAF-001" },
4  { 
5    $set: { 
6      "price.amount": 2800,
7      lastModified: new Date()
8    }
9  }
10)
11
12// Incrémenter la quantité en stock
13db.products.updateOne(
14  { sku: "THÉ-001" },
15  { 
16    $inc: { "stock.quantity": 20 }
17  }
18)
19
20// Ajouter un nouveau tag
21db.products.updateOne(
22  { sku: "CAF-001" },
23  { 
24    $push: { tags: "bestseller" }
25  }
26)

5.2 Mise à jour multiple (updateMany)

1// Appliquer une réduction de 10% sur tous les vêtements
2db.products.updateMany(
3  { category: "Vêtements" },
4  { 
5    $mul: { "price.amount": 0.9 },
6    $set: { 
7      promotion: true,
8      lastModified: new Date()
9    }
10  }
11)
12
13// Ajouter des points de fidélité à tous les clients actifs
14db.customers.updateMany(
15  { lastPurchase: { $ne: null } },
16  { 
17    $inc: { loyaltyPoints: 100 }
18  }
19)

5.3 Opérations sur les tableaux

1// Ajouter plusieurs couleurs
2db.products.updateOne(
3  { sku: "CAF-001" },
4  { 
5    $addToSet: { 
6      "attributes.color": { 
7        $each: ["Noir", "Blanc", "Or"] 
8      }
9    }
10  }
11)
12
13// Retirer un élément d'un tableau
14db.products.updateOne(
15  { sku: "CAF-001" },
16  { 
17    $pull: { tags: "bestseller" }
18  }
19)
20
21// Mettre à jour le premier élément correspondant dans un tableau
22db.orders.updateOne(
23  { 
24    orderNumber: "CMD-2024-0543",
25    "items.sku": "CAF-001"
26  },
27  { 
28    $set: { "items.$.quantity": 2 }
29  }
30)

5.4 Mise à jour ou insertion (upsert)

1// Créer ou mettre à jour un produit
2db.products.updateOne(
3  { sku: "ARG-001" },
4  { 
5    $set: {
6      name: "Huile d'Argan Bio 100ml",
7      category: "Cosmétique",
8      price: { amount: 450, currency: "MAD" },
9      stock: { quantity: 30, warehouse: "Agadir" }
10    },
11    $setOnInsert: {
12      createdAt: new Date(),
13      ratings: { average: 0, count: 0 }
14    }
15  },
16  { upsert: true }
17)

5.5 Mise à jour complexe avec pipeline

1// Calculer et mettre à jour le total d'une commande
2db.orders.updateOne(
3  { orderNumber: "CMD-2024-0543" },
4  [
5    {
6      $set: {
7        "totals.subtotal": { 
8          $sum: "$items.total" 
9        }
10      }
11    },
12    {
13      $set: {
14        "totals.tax": { 
15          $multiply: ["$totals.subtotal", 0.2] 
16        }
17      }
18    },
19    {
20      $set: {
21        "totals.total": { 
22          $add: ["$totals.subtotal", "$totals.tax", "$totals.shipping"] 
23        }
24      }
25    }
26  ]
27)

5.6 FindAndModify operations

1// Trouver et mettre à jour, retourner le document modifié
2const updatedProduct = db.products.findOneAndUpdate(
3  { sku: "THÉ-001" },
4  { 
5    $inc: { "stock.quantity": -1 },
6    $set: { lastSold: new Date() }
7  },
8  { returnDocument: "after" }
9)
10
11print("Stock mis à jour:", updatedProduct.stock.quantity)

6. Opérations DELETE (Suppression)

6.1 Suppression simple (deleteOne)

1// Supprimer un produit spécifique
2db.products.deleteOne({ sku: "TEST-001" })
3
4// Supprimer la première commande annulée trouvée
5db.orders.deleteOne({ status: "cancelled" })

6.2 Suppression multiple (deleteMany)

1// Supprimer tous les produits en rupture de stock
2db.products.deleteMany({ "stock.quantity": 0 })
3
4// Supprimer les clients inactifs (pas d'achat depuis 2 ans)
5const twoYearsAgo = new Date()
6twoYearsAgo.setFullYear(twoYearsAgo.getFullYear() - 2)
7
8db.customers.deleteMany({
9  $or: [
10    { lastPurchase: { $lt: twoYearsAgo } },
11    { lastPurchase: null }
12  ]
13})

6.3 FindAndDelete

1// Trouver et supprimer, retourner le document supprimé
2const deletedOrder = db.orders.findOneAndDelete(
3  { 
4    status: "pending",
5    createdAt: { $lt: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) }
6  }
7)
8
9if (deletedOrder) {
10  print(`Commande expirée supprimée: ${deletedOrder.orderNumber}`)
11}

7. Opérations avancées sur documents imbriqués

7.1 Manipulation de tableaux d'objets

1// Ajouter un article à une commande existante
2db.orders.updateOne(
3  { orderNumber: "CMD-2024-0543" },
4  {
5    $push: {
6      items: {
7        productId: ObjectId(),
8        sku: "ARG-001",
9        name: "Huile d'Argan Bio",
10        quantity: 1,
11        unitPrice: 450,
12        total: 450
13      }
14    },
15    $inc: {
16      "totals.subtotal": 450,
17      "totals.tax": 90,
18      "totals.total": 540
19    }
20  }
21)
22
23// Mettre à jour un article spécifique dans une commande
24db.orders.updateOne(
25  {
26    orderNumber: "CMD-2024-0543",
27    "items.sku": "CAF-001"
28  },
29  {
30    $inc: { 
31      "items.$.quantity": 1,
32      "items.$.total": 2500
33    }
34  }
35)
36
37// Supprimer un article d'une commande
38db.orders.updateOne(
39  { orderNumber: "CMD-2024-0543" },
40  {
41    $pull: { 
42      items: { sku: "BAB-003" } 
43    }
44  }
45)

7.2 Mise à jour de l'historique des statuts

1// Ajouter un nouveau statut à l'historique
2db.orders.updateOne(
3  { orderNumber: "CMD-2024-0543" },
4  {
5    $set: { 
6      status: "shipped" 
7    },
8    $push: {
9      statusHistory: {
10        status: "shipped",
11        date: new Date(),
12        note: "Expédié via Amana - Numéro de suivi: AM123456"
13      }
14    }
15  }
16)
17
18// Requête pour trouver l'historique d'une commande
19db.orders.findOne(
20  { orderNumber: "CMD-2024-0543" },
21  { statusHistory: 1, status: 1 }
22)

8. Opérations de validation et cohérence

8.1 Vérification de l'intégrité des données

1// Vérifier les commandes avec des clients inexistants
2const customerIds = db.customers.distinct("customerId")
3
4db.orders.find({
5  customerId: { $nin: customerIds }
6})
7
8// Vérifier les produits avec des prix invalides
9db.products.find({
10  $or: [
11    { "price.amount": { $lte: 0 } },
12    { "price.amount": { $exists: false } },
13    { "price.currency": { $ne: "MAD" } }
14  ]
15})

8.2 Nettoyage et maintenance

1// Nettoyer les tags en double
2db.products.find().forEach(product => {
3  if (product.tags) {
4    const uniqueTags = [...new Set(product.tags)]
5    if (uniqueTags.length !== product.tags.length) {
6      db.products.updateOne(
7        { _id: product._id },
8        { $set: { tags: uniqueTags } }
9      )
10    }
11  }
12})
13
14// Recalculer les moyennes de ratings
15db.products.updateMany(
16  { "ratings.count": 0 },
17  { $set: { "ratings.average": 0 } }
18)

9. Cas pratiques complets

9.1 Processus de commande complet

1// 1. Vérifier la disponibilité du stock
2const product = db.products.findOne({ sku: "CAF-001" })
3if (product.stock.quantity < 1) {
4  print("Produit en rupture de stock!")
5} else {
6  // 2. Créer la commande
7  const order = {
8    orderNumber: `CMD-${new Date().getFullYear()}-${Date.now()}`,
9    customerId: "CLT-10234",
10    items: [{
11      productId: product._id,
12      sku: product.sku,
13      name: product.name,
14      quantity: 1,
15      unitPrice: product.price.amount,
16      total: product.price.amount
17    }],
18    totals: {
19      subtotal: product.price.amount,
20      shipping: 50,
21      tax: product.price.amount * 0.2,
22      total: product.price.amount * 1.2 + 50
23    },
24    status: "pending",
25    createdAt: new Date()
26  }
27  
28  // 3. Insérer la commande
29  const result = db.orders.insertOne(order)
30  
31  // 4. Mettre à jour le stock
32  db.products.updateOne(
33    { sku: "CAF-001" },
34    { 
35      $inc: { "stock.quantity": -1 },
36      $set: { lastModified: new Date() }
37    }
38  )
39  
40  // 5. Mettre à jour les points de fidélité du client
41  db.customers.updateOne(
42    { customerId: "CLT-10234" },
43    { 
44      $inc: { loyaltyPoints: Math.floor(order.totals.total / 10) },
45      $set: { lastPurchase: new Date() }
46    }
47  )
48  
49  print(`Commande créée avec succès: ${order.orderNumber}`)
50}

9.2 Rapport des meilleures ventes

1// Trouver les 5 produits les plus vendus ce mois
2const startOfMonth = new Date()
3startOfMonth.setDate(1)
4startOfMonth.setHours(0, 0, 0, 0)
5
6// Utilisation du framework d'agrégation (aperçu)
7db.orders.aggregate([
8  { 
9    $match: { 
10      createdAt: { $gte: startOfMonth } 
11    } 
12  },
13  { $unwind: "$items" },
14  { 
15    $group: {
16      _id: "$items.sku",
17      productName: { $first: "$items.name" },
18      totalQuantity: { $sum: "$items.quantity" },
19      totalRevenue: { $sum: "$items.total" }
20    }
21  },
22  { $sort: { totalQuantity: -1 } },
23  { $limit: 5 }
24])

10. Bonnes pratiques et conseils

Conventions de nommage

  • Collections au pluriel : products, customers, orders
  • Champs en camelCase : firstName, createdAt
  • Constantes en MAJUSCULES : MAD, PENDING

Performance

  • Toujours utiliser des projections pour limiter les données retournées
  • Créer des index sur les champs fréquemment interrogés
  • Éviter les requêtes qui scannent toute la collection

Sécurité

  • Valider les entrées avant l'insertion
  • Utiliser des transactions pour les opérations critiques
  • Ne jamais stocker de mots de passe en clair

Debugging

1// Activer le profiling pour analyser les performances
2db.setProfilingLevel(1, { slowms: 100 })
3
4// Voir les opérations lentes
5db.system.profile.find().limit(5).sort({ ts: -1 }).pretty()
6
7// Expliquer une requête
8db.products.find({ category: "Vêtements" }).explain("executionStats")

Exercices de synthèse

  1. Gestion de panier : Créer un système de panier d'achat temporaire
  2. Système de notation : Implémenter l'ajout et le calcul de moyennes de ratings
  3. Gestion de stock : Créer des alertes pour les produits en rupture
  4. Rapport client : Extraire l'historique d'achat d'un client spécifique
  5. Migration de données : Transformer des données relationnelles en documents MongoDB

Tags