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();
}اختبر فهمك