SQL vs NoSQL
قواعد البيانات التقليدية (MySQL, Postgres) تحفظ البيانات كـ جداول بأعمدة محدّدة. كل صف يجب أن يطابق الهيكل.
MongoDB قاعدة NoSQL — تحفظ documents (مثل JSON) في collections (مثل الجداول لكن مرنة). كل document يمكن أن يكون مختلفاً.
مثال مقارن
SQL (Postgres):
CREATE TABLE users (
id SERIAL,
name VARCHAR(100),
email VARCHAR(200)
);MongoDB:
// لا schema — تُدخل ما تريد
{
_id: ObjectId("..."),
name: "أحمد",
email: "[email protected]",
preferences: { theme: "dark", lang: "ar" }, // ← كائن متداخل
tags: ["vip", "early-adopter"], // ← مصفوفة
}متى نختار MongoDB؟
✅ مناسبة لـ:
- بيانات مرنة غير محدّدة الهيكل (content management, IoT)
- مشاريع سريعة التطوّر حيث الـ schema يتغيّر كثيراً
- بيانات hierarchical (شجريّة)
- scaling أفقي على عدّة خوادم
❌ غير مناسبة لـ:
- معاملات مالية معقّدة (SQL أفضل لـ ACID)
- تقارير تحليلية بعلاقات كثيرة
- حين تحتاج ضمانات consistency قوية
التثبيت
الأسهل: MongoDB Atlas (cloud مجاني 512MB).
محلياً:
# Windows: MongoDB Community من mongodb.com/download
# Mac
brew install mongodb-community
brew services start mongodb-community
# Linux
sudo apt install mongodbالمفاهيم الأساسية
| SQL | MongoDB |
|-----|---------|
| Database | Database |
| Table | Collection |
| Row | Document |
| Column | Field |
| JOIN | $lookup (aggregation) |
| PRIMARY KEY | _id |
الاتصال من Node.js
الطريقة الشائعة: Mongoose (ODM).
npm install mongooseimport mongoose from "mongoose";
await mongoose.connect("mongodb://localhost:27017/myapp");
console.log("✓ متصل");تعريف Schema
رغم أن MongoDB مرن، Mongoose يُضيف schema للتحقّق من البيانات:
import mongoose from "mongoose";
const userSchema = new mongoose.Schema({
name: { type: String, required: true, trim: true },
email: { type: String, required: true, unique: true, lowercase: true },
age: { type: Number, min: 0, max: 150 },
tags: [String],
profile: {
bio: String,
avatar: String,
},
createdAt: { type: Date, default: Date.now },
});
export const User = mongoose.model("User", userSchema);CRUD
Create
// طريقة 1
const user = new User({ name: "سارة", email: "[email protected]" });
await user.save();
// طريقة 2
const created = await User.create({
name: "محمد",
email: "[email protected]",
});
// عدّة وثائق
await User.insertMany([
{ name: "علي", email: "[email protected]" },
{ name: "فاطمة", email: "[email protected]" },
]);Read
// كل الوثائق
const all = await User.find();
// بشرط
const adults = await User.find({ age: { $gte: 18 } });
// بمعرّف
const user = await User.findById("507f1f77bcf86cd799439011");
// واحد
const user = await User.findOne({ email: "[email protected]" });
// اختيار أعمدة
const lean = await User.find().select("name email").lean();
// ترتيب + تحديد
const top = await User.find()
.sort({ createdAt: -1 })
.limit(10);
// pagination
const page2 = await User.find()
.skip(20)
.limit(10);عمليات مقارنة
// أكبر من
User.find({ age: { $gt: 18 } });
// بين قيمتين
User.find({ age: { $gte: 18, $lte: 65 } });
// موجود في قائمة
User.find({ role: { $in: ["admin", "editor"] } });
// بحث نصّي
User.find({ name: /أحمد/i });
// متعدّد الشروط
User.find({
$or: [
{ role: "admin" },
{ age: { $lt: 25 } },
],
});Update
// واحد
await User.updateOne({ _id: id }, { $set: { age: 30 } });
// كل المطابقين
await User.updateMany(
{ role: "user" },
{ $set: { verified: true } }
);
// إضافة لمصفوفة
await User.updateOne(
{ _id: id },
{ $push: { tags: "premium" } }
);
// زيادة
await User.updateOne({ _id: id }, { $inc: { loginCount: 1 } });
// إرجاع المُحدَّث
const updated = await User.findByIdAndUpdate(
id,
{ $set: { name: "اسم جديد" } },
{ new: true }
);Delete
await User.deleteOne({ _id: id });
await User.deleteMany({ active: false });
await User.findByIdAndDelete(id);Aggregation — القوّة الحقيقية
للتحليل، استخدم pipeline:
const stats = await User.aggregate([
// 1. صفِّ المستخدمين الفعّالين
{ $match: { active: true } },
// 2. جمِّع حسب الدور
{
$group: {
_id: "$role",
count: { $sum: 1 },
avgAge: { $avg: "$age" },
},
},
// 3. رتِّب
{ $sort: { count: -1 } },
]);$lookup — أقرب شيء لـ JOIN
const withOrders = await User.aggregate([
{
$lookup: {
from: "orders", // collection ثانية
localField: "_id",
foreignField: "userId",
as: "orders",
},
},
]);الفهارس
// فهرس فريد
userSchema.index({ email: 1 }, { unique: true });
// فهرس مركّب
userSchema.index({ role: 1, createdAt: -1 });
// فهرس نصّي للبحث
userSchema.index({ name: "text", bio: "text" });
User.find({ $text: { $search: "أحمد مطوّر" } });العلاقات
Embedding (أفضل حين البيانات مرتبطة بقوّة)
const postSchema = new Schema({
title: String,
comments: [
{
author: String,
text: String,
createdAt: { type: Date, default: Date.now },
},
],
});Referencing (عند الحاجة للبيانات منفصلة)
const orderSchema = new Schema({
userId: { type: Schema.Types.ObjectId, ref: "User" },
items: [...],
});
// جلب المستخدم مع الطلب
const order = await Order.findById(id).populate("userId");
console.log(order.userId.name);أفضل الممارسات
lean()عند القراءة فقط — أسرع بـ3 مرّات- فهرس على كل حقل تبحث فيه
unique: trueللإيميلات والمفاتيح الطبيعية- timestamps:
{ timestamps: true }يُضيف createdAt/updatedAt تلقائياً - تحقّق من البيانات قبل
save()— Mongoose يفعل ذلك تلقائياً بالـ schema
الأسئلة الشائعة
هل أستخدم Mongoose أم driver الرسمي؟
- Mongoose: للمعظم — schema validation، middleware، راحة الـ API
- Driver رسمي (
mongodbpackage): حين تحتاج أداء أقصى أو تحكّماً دقيقاً
MongoDB vs Postgres — أيّهما؟
اختر حسب طبيعة البيانات:
- بيانات موحّدة الهيكل + تقارير + معاملات → Postgres
- بيانات متنوّعة + scaling ضخم + مرونة → Mongo
هل MongoDB جيّدة للتطبيقات الصغيرة؟
ممتازة — Atlas مجاني، Mongoose سهل. للمشاريع الجديدة، جرّبها.