Kodax

CRUD مع Mongoose — MongoDB

CRUD مع Mongoose

الإنشاء

User.create({...}) (يتحقق ويحفظ) أو new User({...}).save()، و insertMany([...]) للدفعات.

القراءة

// const users = await User.find({ age: { $gte: 18 }, role: "user" })
//   .select("name email")          // الإسقاط: الحقول المطلوبة فقط
//   .sort({ createdAt: -1 })       // تنازلي
//   .limit(20)
//   .lean();                       // كائنات JS عادية — أسرع للقراءة فقط
const filterExample = { age: { $gte: 18 }, role: { $in: ["user", "editor"] }, tags: "مبتدئ" };
console.log(JSON.stringify(filterExample));

| العامل | المعنى |

|---|---|

| $eq $ne | يساوي / لا يساوي |

| $gt $gte $lt $lte | مقارنات |

| $in $nin | ضمن قائمة |

| $exists | الحقل موجود |

| $regex | نمط (احذر من مدخلات المستخدم: أنماط مكلفة) |

| $or $and | منطق |

- findById(id) و findOne(filter) يعيدان مستنداً أو null — افحص دائماً.

- countDocuments(filter) للعد.

التحديث الذري

// await Product.updateOne({ _id: id, stock: { $gte: 1 } }, { $inc: { stock: -1 } });
// await User.findByIdAndUpdate(id, { $set: { name } }, { new: true, runValidators: true });
// await Post.updateOne({ _id: postId }, { $push: { comments: { text, author: userId } } });
const update = { $inc: { stock: -1 }, $set: { updatedBy: "u1" } };
console.log(Object.keys(update));

| العامل | الأثر |

|---|---|

| $set / $unset | تعيين / حذف حقل |

| $inc | زيادة/إنقاص ذري |

| $push / $pull / $addToSet | مصفوفات |

- { new: true }: أعد المستند بعد التعديل (الافتراضي قبله).

- { runValidators: true }: طبّق تحقق المخطط على التحديث.

- لا تقرأ ثم تعدّل ثم تحفظ لعدادات المخزون أو الإعجابات: طلبان متزامنان يضيعان تحديثاً. استخدم $inc مع شرط في الفلتر (stock: { $gte: 1 }) — عملية واحدة ذرية.

الحذف

deleteOne(filter)، deleteMany(filter)، findByIdAndDelete(id). ⚠️ deleteMany({}) يحذف المجموعة كلها. (الدالة القديمة remove() حُذفت من Mongoose.)

الأمان: المدخلات كعوامل

إذا مررت req.body.email مباشرة وكان كائناً { "$ne": null } فقد طابقت أول مستخدم! الحماية:

- mongoose.set("sanitizeFilter", true) يحيّد العوامل في الفلاتر.

- حوّل المدخلات لأنواعها: String(req.body.email).

- لا تمرر req.body كاملاً إلى create أو $set؛ اختر الحقول المسموحة.

تمرين

اكتب دالة purchase(productId, qty) تنقص المخزون ذرياً فقط إذا كان كافياً، وتعيد خطأ "نفد المخزون" إن لم يُعدَّل أي مستند (modifiedCount === 0).

// npm install mongoose
import mongoose from "mongoose";

await mongoose.connect(process.env.MONGODB_URI ?? "mongodb://127.0.0.1:27017/kodax_demo");
mongoose.set("sanitizeFilter", true);   // يحيّد عوامل $ القادمة من مدخلات المستخدم

const productSchema = new mongoose.Schema(
  {
    name: { type: String, required: true, trim: true },
    price: { type: Number, required: true, min: 0 },
    category: { type: String, enum: ["كتب", "إلكترونيات", "قرطاسية"], required: true },
    stock: { type: Number, default: 0, min: 0 },
    tags: [String],
  },
  { timestamps: true }
);
const Product = mongoose.models.Product ?? mongoose.model("Product", productSchema);

async function seed() {
  await Product.deleteMany({ name: /^تجربة/ });   // تنظيف بيانات التجربة فقط
  return Product.insertMany([
    { name: "تجربة: قلم", price: 1.5, category: "قرطاسية", stock: 100, tags: ["مدرسة"] },
    { name: "تجربة: سماعة", price: 25, category: "إلكترونيات", stock: 3 },
    { name: "تجربة: رواية", price: 9, category: "كتب", stock: 0 },
  ]);
}

async function listAvailable(maxPrice) {
  return Product.find({ stock: { $gt: 0 }, price: { $lte: Number(maxPrice) } })
    .select("name price stock")
    .sort({ price: 1 })
    .lean();
}

async function purchase(productId, qty) {
  const n = Number(qty);
  if (!Number.isInteger(n) || n < 1) throw new Error("كمية غير صالحة");
  // ذري: الشرط والتعديل في عملية واحدة
  const result = await Product.updateOne({ _id: productId, stock: { $gte: n } }, { $inc: { stock: -n } });
  if (result.modifiedCount === 0) throw new Error("نفد المخزون");
}

try {
  const [pen, headset] = await seed();
  console.log("المتاح بأقل من 30:", await listAvailable(30));

  await purchase(headset._id, 2);
  await purchase(headset._id, 2).catch((e) => console.log("الشراء الثاني:", e.message));

  const updated = await Product.findByIdAndUpdate(pen._id, { $set: { price: 2 }, $addToSet: { tags: "عرض" } }, { new: true, runValidators: true });
  console.log("بعد التحديث:", updated.price, updated.tags);
} finally {
  await mongoose.disconnect();
}
اختبر فهمك