دليلك الشامل لـ Node.js: بناء REST API من الصفر مع Express و MongoDB

بتذكر أول مرة سمعت عن Node.js، كنت قاعد مع صديق إلي، مبرمج مخضرم، في مكتبي بنابلس. كنا بنشرب قهوة وبنتناقش بمشروع جديد. أنا كنت وقتها شغال بشكل أساسي بلغات زي Python و Java للخوادم، وكان كل شي ماشي “بالدور”. يعني الطلب بييجي، السيرفر بيشتغله، بيستنى قاعدة البيانات ترد، وبعدين بيرجع الجواب. لو إجا 100 طلب مع بعض، لازم يستنوا بعض.

صاحبي حكالي: “يا أبو عمر، جربت Node.js؟ الوضع هناك مختلف تمامًا”. وشبّهلي إياها بمطعم فلافل شاطر. في المطاعم العادية، الجرسون بياخد طلبك، بيروح عالمطبخ، بستنى الصحن يجهز، وبعدين بيجيبلك إياه. خلال كل هالوقت، هو “محجوز” إلك. أما في مطعم الفلافل السريع، المعلم بياخد طلبك، وبياخد طلب اللي بعدك واللي بعده، وبصرخ فيهم كلهم للمطبخ، وأول صحن بيجهز بيطلع لصاحبه. ما حدا بيستنى حدا.

هاي القصة البسيطة هي جوهر قوة Node.js، النموذج غير المتزامن (Non-blocking). ومن يومها، صارت Node.js جزء أساسي من صندوق أدواتي التقنية، خصوصًا لما بدنا نبني تطبيقات سريعة وفعالة بتقدر تخدم آلاف المستخدمين بنفس الوقت. اليوم، بدي آخذ بإيدكم ونبني مع بعض تطبيق REST API كامل من الصفر، عشان تشوفوا بنفسكم كيف “معلم الفلافل” هاد بيشتغل.

ما هي Node.js؟ وليش هالضجة كلها؟

ببساطة شديدة، Node.js مش لغة برمجة جديدة. هي بيئة تشغيل (Runtime Environment) بتسمحلك تشغل كود JavaScript خارج المتصفح، يعني على الخادم (Server). قبل Node.js، كانت JavaScript محصورة في عالم متصفحات الويب لتضيف تفاعلية للصفحات. لكن مع Node.js، صارت JavaScript لغة قادرة على بناء تطبيقات كاملة من الواجهة الأمامية للخلفية.

سر القوة: نموذج الإدخال/الإخراج غير المتزامن (Non-blocking I/O)

هون مربط الفرس، زي ما بحكوها. خلينا نرجع لمثال المطعم. معظم تقنيات الخوادم التقليدية (مثل Apache مع PHP) بتشتغل بنظام “الخيط لكل طلب” (Thread-per-request). يعني كل مستخدم بيطلب طلب، السيرفر بخصصله “عامل” (Thread) خاص فيه. هاد العامل بضل مع الطلب من أوله لآخره. لو الطلب احتاج يقرأ من ملف أو يستنى رد من قاعدة البيانات (عمليات I/O)، العامل بضل واقف بستنى وما بيعمل إشي ثاني.

أما Node.js، فتستخدم “خيط واحد” (Single Thread) مع ما يسمى بـ “حلقة الأحداث” (Event Loop). لما يوصل طلب فيه عملية I/O بطيئة:

  1. Node.js بتستقبل الطلب.
  2. بتشوف إنه بده عملية بطيئة (مثل الاستعلام من قاعدة البيانات).
  3. بتبعت العملية هاي لجهة مختصة تشتغلها في الخلفية.
  4. فورًا، بترجع تستقبل طلبات جديدة بدون ما تستنى نتيجة العملية الأولى.
  5. لما العملية البطيئة تخلص، بترجع النتيجة على شكل “حدث” (Event)، وحلقة الأحداث بتاخد النتيجة وبترجعها لصاحب الطلب الأصلي.

هذا الأسلوب بخلي السيرفر يستغل وقته بأفضل شكل ممكن، ويقدر يتعامل مع عدد هائل من الطلبات المتزامنة بموارد أقل بكثير. وهاد هو السبب الرئيسي لشعبيته في تطبيقات الدردشة، الألعاب الأونلاين، وأي تطبيق بيحتاج استجابة سريعة وفورية.

يلا نبدأ الشغل: تجهيز بيئة العمل

قبل ما نكتب أي كود، لازم نتأكد إنه كل شي جاهز عنا. راح نحتاج للآتي:

  • Node.js و npm: قم بتنزيلهم من الموقع الرسمي. npm (Node Package Manager) هو مدير الحزم اللي بيجي مع Node.js وبيسمحلنا نثبت مكتبات خارجية بسهولة.
  • محرر أكواد: أنا شخصيًا بفضل Visual Studio Code.
  • Postman: أداة ممتازة لاختبار الـ API Endpoints اللي راح نبنيها. تقدر تنزلها من موقعها الرسمي.
  • MongoDB: راح نستخدم قاعدة بيانات NoSQL. أسهل طريقة هي استخدام النسخة السحابية المجانية MongoDB Atlas. أنشئ حساب وجهز قاعدة بيانات فارغة واحصل على “رابط الاتصال” (Connection String).

بعد تجهيز كل شي، افتح الـ Terminal أو Command Prompt، وأنشئ مجلد جديد لمشروعنا، وادخل عليه:

mkdir todo-api
cd todo-api

الآن، لنبدأ مشروع Node.js جديد:

npm init -y

هذا الأمر راح ينشئ ملف اسمه package.json. هاد الملف هو هوية مشروعنا، بيحتوي على اسمه، إصداره، والأهم من هيك، قائمة بالمكتبات اللي بيعتمد عليها.

الآن، لنثبت المكتبات اللي بنحتاجها: Express, Mongoose, و dotenv.

npm install express mongoose dotenv
  • Express.js: هو إطار عمل (Framework) بسيط ومرن لـ Node.js بيسهل علينا بناء تطبيقات الويب والـ APIs. هو اللي راح يساعدنا ندير المسارات (Routes) والطلبات والاستجابات.
  • Mongoose: هي مكتبة بتسهل التعامل مع MongoDB من داخل Node.js. بتسمحلنا نعمل نماذج (Models) للبيانات ونتعامل معها ككائنات JavaScript بدل ما نكتب استعلامات قاعدة البيانات مباشرة.
  • dotenv: مكتبة صغيرة ومفيدة جدًا لتحميل متغيرات البيئة (Environment Variables) من ملف .env. هاد مهم عشان نخفي المعلومات الحساسة مثل رابط الاتصال بقاعدة البيانات.

بناء الهيكل الأساسي مع Express.js

صار وقت نكتب أول سيرفر إلنا. أنشئ ملف جديد في مجلد المشروع وسميه server.js.

أول سيرفر إلنا!

اكتب الكود التالي في ملف server.js:

// 1. استدعاء المكتبات المطلوبة
const express = require('express');

// 2. إنشاء تطبيق Express
const app = express();

// 3. تحديد البورت (المنفذ)
const PORT = process.env.PORT || 5000;

// 4. إنشاء مسار (Route) بسيط للتجربة
app.get('/', (req, res) => {
    res.send('مرحبًا بك في تطبيق مدير المهام!');
});

// 5. تشغيل السيرفر للاستماع على البورت المحدد
app.listen(PORT, () => {
    console.log(`السيرفر يعمل على البورت ${PORT}`);
});

شرح الكود:
بكل بساطة، استدعينا مكتبة express، وأنشأنا تطبيق جديد منها. عرفنا مسار أساسي (“/”) يستجيب لطلبات من نوع GET ويرجع رسالة ترحيبية. وأخيرًا، شغلنا السيرفر على بورت معين.

لتشغيل السيرفر، اكتب في الـ Terminal:

node server.js

إذا فتحت المتصفح على العنوان http://localhost:5000، راح تشوف رسالة الترحيب.

نصيحة من أبو عمر: كل ما تعدل على الكود، لازم توقف السيرفر (Ctrl+C) وترجع تشغله. هاي الشغلة مملة. عشان هيك، بنصحكم تثبتوا مكتبة اسمها nodemon. هاي المكتبة بتراقب ملفات المشروع، وأي تغيير بتعمله، بتعيد تشغيل السيرفر تلقائيًا.

لتثبيتها: npm install -g nodemon (الـ -g بتثبتها بشكل عام على جهازك).

وبعدها، شغل السيرفر باستخدام: nodemon server.js. شغل نظيف ومرتب.

تصميم الـ API: الطلبات والمسارات (Routes)

الـ REST API هو عبارة عن واجهة بتسمح للأنظمة المختلفة تتواصل مع بعضها عبر بروتوكول HTTP. التواصل هاد بيتم عن طريق إرسال طلبات (Requests) لنقاط نهاية (Endpoints) محددة، والسيرفر بيرد باستجابات (Responses) غالبًا ما تكون بصيغة JSON.

أفعال الـ HTTP: لغة التواصل مع السيرفر

في تطبيقنا “مدير المهام”، راح نحتاج العمليات التالية، وكل عملية بنربطها بفعل HTTP معين:

  • GET /tasks: جلب كل المهام.
  • POST /tasks: إنشاء مهمة جديدة.
  • GET /tasks/:id: جلب مهمة محددة عن طريق الـ ID تبعها.
  • PUT /tasks/:id: تحديث مهمة محددة (مثلاً، تغيير حالتها إلى “مكتملة”).
  • DELETE /tasks/:id: حذف مهمة محددة.

تنظيم الكود: فصل المسارات (Routes)

بدل ما نكتب كل المسارات في ملف server.js، الأفضل ننظم شغلنا. أنشئ مجلد جديد اسمه routes، وداخله ملف اسمه taskRoutes.js.

في ملف taskRoutes.js، اكتب الكود التالي:

const express = require('express');
const router = express.Router();

// هنا سنضع كل المسارات المتعلقة بالمهام

// مثال: مسار لجلب كل المهام
router.get('/', (req, res) => {
    res.json({ message: "سترجع هذه النقطة كل المهام" });
});

// مثال: مسار لإنشاء مهمة جديدة
router.post('/', (req, res) => {
    res.json({ message: "ستقوم هذه النقطة بإنشاء مهمة جديدة" });
});

module.exports = router;

الآن، نرجع لملف server.js ونطلب منه يستخدم ملف المسارات هذا:

// ... (الكود السابق)

// استدعاء ملف المسارات
const taskRoutes = require('./routes/taskRoutes');

// middleware لتحليل الـ JSON القادم في جسم الطلب
app.use(express.json()); 

// استخدام المسارات
// أي طلب يبدأ بـ /api/tasks سيتم توجيهه إلى taskRoutes
app.use('/api/tasks', taskRoutes);

// ... (كود تشغيل السيرفر)

لاحظ السطر app.use(express.json()). هذا مثال على ما يسمى بالـ Middleware. هو عبارة عن قطعة كود بتشتغل بين الطلب والاستجابة. في هاي الحالة، هو بيقرأ جسم الطلب (Request Body) وإذا كان بصيغة JSON، بحوله لكائن JavaScript عشان نقدر نستخدمه بسهولة في الكود تبعنا (مثلاً لما نبعت بيانات مهمة جديدة في طلب POST).

ربط قاعدة البيانات: MongoDB و Mongoose

الآن حان وقت ربط تطبيقنا بقاعدة بيانات حقيقية لتخزين المهام.

ليش MongoDB؟

MongoDB هي قاعدة بيانات من نوع NoSQL، بتخزن البيانات على شكل مستندات شبيهة بالـ JSON (اسمها BSON). هذا يجعلها مناسبة جدًا للعمل مع JavaScript و Node.js، لأننا بنتعامل مع نفس شكل البيانات (كائنات JavaScript) في كل مكان، من قاعدة البيانات للسيرفر للواجهة الأمامية.

تعريف النموذج (Schema) والـ Model

مع Mongoose، أول خطوة هي تعريف “شكل” أو “هيكل” البيانات اللي بدنا نخزنها. هذا يسمى بالـ Schema. أنشئ مجلد جديد اسمه models، وبداخله ملف اسمه taskModel.js.

const mongoose = require('mongoose');

const taskSchema = new mongoose.Schema({
    title: {
        type: String,
        required: [true, 'عنوان المهمة مطلوب'],
        trim: true
    },
    completed: {
        type: Boolean,
        default: false
    }
}, {
    timestamps: true // هذا الخيار يضيف حقلي createdAt و updatedAt تلقائيًا
});

const Task = mongoose.model('Task', taskSchema);

module.exports = Task;

في هذا الكود، عرفنا إنه كل “مهمة” لازم يكون إلها title من نوع نصي وهو مطلوب، وحقل completed من نوع منطقي (Boolean) وقيمته الافتراضية false.

الاتصال بالقاعدة

الآن، لنقم بإعداد الاتصال. أنشئ ملفًا في جذر المشروع اسمه .env. هذا الملف لن يتم رفعه على Git (يجب إضافته لملف .gitignore) وسيحتوي على أسرارنا.

في ملف .env، أضف رابط الاتصال اللي حصلت عليه من MongoDB Atlas:

MONGO_URI=mongodb+srv://:@cluster0....mongodb.net/myTodoApp?retryWrites=true&w=majority

الآن، أنشئ مجلدًا جديدًا اسمه config وبداخله ملف اسمه db.js. هذا الملف سيكون مسؤولاً عن الاتصال.

const mongoose = require('mongoose');

const connectDB = async () => {
    try {
        const conn = await mongoose.connect(process.env.MONGO_URI);
        console.log(`تم الاتصال بقاعدة البيانات: ${conn.connection.host}`);
    } catch (error) {
        console.error(`خطأ: ${error.message}`);
        process.exit(1); // إغلاق التطبيق في حال فشل الاتصال
    }
};

module.exports = connectDB;

أخيرًا، نعدل ملف server.js الرئيسي لاستدعاء دالة الاتصال واستخدام متغيرات البيئة:

const express = require('express');
const dotenv = require('dotenv');
const connectDB = require('./config/db');

// تحميل متغيرات البيئة من ملف .env
dotenv.config();

// الاتصال بقاعدة البيانات
connectDB();

const app = express();

// ... باقي الكود

العمليات الأربعة المقدسة: CRUD

الآن وبعد أن تم ربط كل شيء، حان وقت البرمجة الحقيقية. سنقوم ببناء “المتحكمات” (Controllers) التي تحتوي على منطق كل عملية. أنشئ مجلدًا جديدًا اسمه controllers وبداخله ملف taskController.js.

في هذا الملف، سنكتب دوال لكل عملية من عمليات CRUD.

const Task = require('../models/taskModel');

// @desc    جلب كل المهام (مع إمكانية الفلترة)
// @route   GET /api/tasks
const getTasks = async (req, res) => {
    try {
        const filter = {};
        if (req.query.completed) {
            filter.completed = req.query.completed === 'true';
        }
        const tasks = await Task.find(filter);
        res.status(200).json(tasks);
    } catch (error) {
        res.status(500).json({ message: 'خطأ في السيرفر' });
    }
};

// @desc    إنشاء مهمة جديدة
// @route   POST /api/tasks
const createTask = async (req, res) => {
    const { title } = req.body;

    if (!title) {
        return res.status(400).json({ message: 'الرجاء إدخال عنوان للمهمة' });
    }

    try {
        const task = await Task.create({ title });
        res.status(201).json(task);
    } catch (error) {
        res.status(500).json({ message: 'خطأ في السيرفر' });
    }
};

// @desc    تحديث مهمة
// @route   PUT /api/tasks/:id
const updateTask = async (req, res) => {
    try {
        const task = await Task.findById(req.params.id);
        if (!task) {
            return res.status(404).json({ message: 'المهمة غير موجودة' });
        }

        const updatedTask = await Task.findByIdAndUpdate(req.params.id, req.body, { new: true });
        res.status(200).json(updatedTask);
    } catch (error) {
        res.status(500).json({ message: 'خطأ في السيرفر' });
    }
};

// @desc    حذف مهمة
// @route   DELETE /api/tasks/:id
const deleteTask = async (req, res) => {
    try {
        const task = await Task.findById(req.params.id);
        if (!task) {
            return res.status(404).json({ message: 'المهمة غير موجودة' });
        }

        await task.remove();
        res.status(200).json({ id: req.params.id, message: 'تم حذف المهمة بنجاح' });
    } catch (error) {
        res.status(500).json({ message: 'خطأ في السيرفر' });
    }
};


module.exports = {
    getTasks,
    createTask,
    updateTask,
    deleteTask,
};

الآن، نربط هذه المتحكمات بالمسارات في ملف routes/taskRoutes.js:

const express = require('express');
const router = express.Router();
const { getTasks, createTask, updateTask, deleteTask } = require('../controllers/taskController');

// طريقة كتابة مرتبة للمسارات
router.route('/').get(getTasks).post(createTask);
router.route('/:id').put(updateTask).delete(deleteTask);

module.exports = router;

لاحظ كيف استخدمنا router.route() لجمع المسارات التي تشترك في نفس الرابط (/ أو /:id) ولكن تختلف في فعل الـ HTTP. هذا يجعل الكود أكثر تنظيمًا وقراءة.

اللمسات الاحترافية: الـ Middleware ومعالجة الأخطاء

الكود الحالي يعمل، ولكنه يكرر معالجة الأخطاء في كل دالة. يمكننا تحسين هذا باستخدام Middleware مخصص لمعالجة الأخطاء.

أنشئ مجلدًا جديدًا باسم middleware وداخله ملف errorMiddleware.js.

const notFound = (req, res, next) => {
    const error = new Error(`غير موجود - ${req.originalUrl}`);
    res.status(404);
    next(error);
};

const errorHandler = (err, req, res, next) => {
    // أحيانًا يأتي الخطأ بحالة نجاح (200)، نغيرها إلى 500
    const statusCode = res.statusCode === 200 ? 500 : res.statusCode;
    res.status(statusCode);
    res.json({
        message: err.message,
        // لا نظهر تفاصيل الخطأ في وضع الإنتاج
        stack: process.env.NODE_ENV === 'production' ? null : err.stack,
    });
};

module.exports = { notFound, errorHandler };

والآن نستخدمه في server.js. يجب أن تكون هذه الـ Middlewares آخر شيء يتم إضافته، بعد كل المسارات.

// ... (بعد app.use('/api/tasks', ...))

const { notFound, errorHandler } = require('./middleware/errorMiddleware');

app.use(notFound);
app.use(errorHandler);

// ... (كود تشغيل السيرفر)

يمكننا الآن تبسيط كود المتحكمات (Controllers) باستخدام try/catch مع مكتبة مثل express-async-handler لتمرير الأخطاء تلقائيًا إلى الـ Middleware، ولكن للتبسيط سنبقي على الـ try/catch اليدوي في هذا الشرح.

الاختبار مع Postman

الآن تطبيقنا جاهز للاختبار. افتح Postman واتبع الخطوات التالية:

  1. إنشاء مهمة (POST):
    • اختر نوع الطلب POST.
    • أدخل الرابط: http://localhost:5000/api/tasks
    • اذهب إلى تبويب Body، اختر raw ثم JSON.
    • اكتب في الجسم: { "title": "تعلم Node.js" }
    • اضغط Send. من المفترض أن ترى المهمة الجديدة التي تم إنشاؤها في الاستجابة مع ID فريد.
  2. جلب كل المهام (GET):
    • اختر نوع الطلب GET.
    • أدخل الرابط: http://localhost:5000/api/tasks
    • اضغط Send. سترى قائمة بكل المهام التي أنشأتها.
    • جرب الفلترة: http://localhost:5000/api/tasks?completed=false
  3. تحديث مهمة (PUT):
    • انسخ الـ ID الخاص بالمهمة التي أنشأتها.
    • اختر نوع الطلب PUT.
    • أدخل الرابط: http://localhost:5000/api/tasks/<ID_HERE>
    • اذهب إلى تبويب Body، اختر raw ثم JSON.
    • اكتب في الجسم: { "completed": true }
    • اضغط Send. سترى المهمة بعد تحديثها.
  4. حذف مهمة (DELETE):
    • اختر نوع الطلب DELETE.
    • أدخل الرابط: http://localhost:5000/api/tasks/<ID_HERE>
    • اضغط Send. ستحصل على رسالة تأكيد بالحذف. إذا حاولت جلب المهام مرة أخرى، لن تجد هذه المهمة.

خلاصة أبو عمر ونصيحة من القلب 🚀

يا جماعة، اللي عملناه اليوم مش قليل. بنينا تطبيق خلفي (Backend) كامل من الألف إلى الياء. مررنا على مفاهيم أساسية وقوية جدًا في عالم تطوير الويب:

  • فهمنا قوة Node.js في التعامل مع الطلبات المتزامنة.
  • بنينا سيرفر باستخدام Express.js ونظمنا الكود بفصل المسارات والمتحكمات.
  • اتصلنا بقاعدة بيانات NoSQL حقيقية (MongoDB) باستخدام Mongoose.
  • طبقنا عمليات CRUD الأربعة الأساسية اللي هي عماد أي تطبيق.
  • أضفنا لمسات احترافية مثل معالجة الأخطاء واستخدام متغيرات البيئة.

هذا هو الأساس. من هنا تقدر تنطلق لعوالم أوسع: إضافة نظام مصادقة للمستخدمين (Authentication)، التعامل مع رفع الملفات، بناء تطبيقات real-time باستخدام WebSockets، وغيرها الكثير.

نصيحتي الأخيرة: لا تخاف من التجربة والخطأ. الكود اللي كتبناه اليوم هو مجرد بداية. افتحه، عدّل عليه، حاول تضيف ميزة جديدة (مثلاً، إضافة حقل “أولوية” للمهمة). أفضل طريقة للتعلم هي “باللعب” في الكود وتكسيره وإصلاحه. الكود مش مجرد أسطر بتكتبها، هو أداة بنبني فيها أفكارنا ومستقبلنا. الله يوفقكم، وإذا احتجتم أي شي، أنا موجود.

أبو عمر

سجل دخولك لعمل نقاش تفاعلي

كافة المحادثات خاصة ولا يتم عرضها على الموقع نهائياً

آراء من النقاشات

لا توجد آراء منشورة بعد. كن أول من يشارك رأيه!

آخر المدونات

تسويق رقمي

البريد الإلكتروني لم يمت، بل أصبح أذكى: دليلك الشامل للتسويق عبر البريد الإلكتروني بالذكاء الاصطناعي

البريد الإلكتروني لم يعد كما كان، فلقد تطور بفضل الذكاء الاصطناعي. في هذا الدليل، سأشاركك خبرتي كمطور فلسطيني في كيفية تحويل حملاتك من رسائل عامة...

17 يناير، 2026 قراءة المزيد
تسويق رقمي

من التجسس إلى الثقة: كيف تبني تسويقاً ناجحاً في عصر الخصوصية باستخدام البيانات الصفرية

نهاية ملفات تعريف الارتباط ليست نهاية العالم، بل فرصة ذهبية. كخبير برمجيات، سأشارككم كيف تحولنا من مطاردة العملاء إلى بناء الثقة معهم باستخدام "البيانات الصفرية"...

17 يناير، 2026 قراءة المزيد
تسويق رقمي

من الكنافة للذكاء الاصطناعي: كيف غيرت التجارة الاجتماعية 2.0 قواعد اللعبة؟

التجارة الاجتماعية لم تعد مجرد "زر شراء" على فيسبوك. إنها ثورة كاملة في طريقة اكتشافنا للمنتجات وشرائها، حيث أصبح المحتوى الإبداعي الأصيل أهم من الخوارزميات،...

16 يناير، 2026 قراءة المزيد
البودكاست