كيفية بناء CRUD API باستخدام Node.js وExpress للمبتدئين

كيفية بناء CRUD API باستخدام Node.js وExpress للمبتدئين

تعد واجهة CRUD نقطة بداية مناسبة لفهم تطوير الخادم، لأنها تجمع العمليات التي يحتاجها معظم التطبيقات: إنشاء مورد، وعرضه، وتعديله، وحذفه. بدلاً من حفظ المسارات كأمثلة منفصلة، سنبني خدمة صغيرة لإدارة المنتجات باستخدام Node.js وExpress. ستتعلم كيف يقرأ الخادم JSON، وكيف يميز بين قائمة المنتجات ومنتج واحد، وكيف يعيد رمزاً مناسباً عند الخطأ. سيستخدم المثال مصفوفة داخل الذاكرة حتى يكون التركيز على تصميم المسارات، ثم نوضح ما الذي يجب تغييره عند ربط التطبيق بقاعدة بيانات. هذه الطريقة تمنحك أساساً واضحاً قبل الانتقال إلى مشروع أكبر.



الفكرة الأساسية

في تطبيق Express يمثل كل مسار عقداً بين العميل والخادم. يحدد عنوان المسار ونوع الطلب البيانات التي يرسلها العميل والنتيجة التي ينتظرها. نستخدم GET للقراءة وPOST للإضافة وPATCH للتعديل وDELETE للحذف. يمر الطلب عادة عبر middleware يقرأ JSON أو يتحقق من تسجيل الدخول، ثم يصل إلى دالة المسار. من الأفضل أن تكون الاستجابة متناسقة، وأن يحتوي الخطأ على رسالة قصيرة ورمز HTTP يشرح الحالة. أما تخزين البيانات في الذاكرة فهو وسيلة تعليمية فقط، لأن إعادة تشغيل العملية تمسح القائمة.

مثال برمجي عملي

const express = require("express");

const app = express();
app.use(express.json());

let products = [
  { id: 1, name: "لوحة مفاتيح", price: 25 }
];

app.get("/api/products", (req, res) => {
  res.status(200).json(products);
});

app.post("/api/products", (req, res) => {
  const { name, price } = req.body;

  if (!name || typeof price !== "number" || price < 0) {
    return res.status(400).json({ error: "بيانات المنتج غير صحيحة" });
  }

  const product = {
    id: products.length + 1,
    name: name.trim(),
    price
  };

  products.push(product);
  res.status(201).json(product);
});

app.patch("/api/products/:id", (req, res) => {
  const product = products.find((item) => item.id === Number(req.params.id));

  if (!product) {
    return res.status(404).json({ error: "المنتج غير موجود" });
  }

  if (req.body.name) product.name = req.body.name.trim();
  if (typeof req.body.price === "number") product.price = req.body.price;
  res.json(product);
});

app.delete("/api/products/:id", (req, res) => {
  const index = products.findIndex((item) => item.id === Number(req.params.id));

  if (index === -1) {
    return res.status(404).json({ error: "المنتج غير موجود" });
  }

  products.splice(index, 1);
  res.status(204).send();
});

app.listen(3000, () => console.log("API running on port 3000"));

شرح المثال

يبدأ التطبيق باستيراد Express وتفعيل express.json حتى يستطيع قراءة جسم الطلب بصيغة JSON. يمثل المتغير products مخزناً مؤقتاً، بينما تستخدم دوال find وfindIndex للعثور على عنصر بالرقم. في الإضافة نفحص الاسم والسعر قبل إنشاء المنتج، ثم نعيد الرمز 201. في التعديل نستخدم PATCH لأن العميل قد يرسل حقلاً واحداً فقط، وفي الحذف نعيد 204 بعد نجاح العملية من دون جسم استجابة. تحويل قيمة المعرف من النص إلى رقم ضروري لأن معاملات المسار تصل عادة كنصوص.

أنشئ المشروع بأمر npm init -y ثم ثبت Express. ضع الكود في server.js وشغله بواسطة node server.js. اختبر GET من المتصفح، واستخدم curl أو Postman لطلبات POST وPATCH وDELETE. عند نجاح التجربة، انقل منطق الوصول إلى البيانات إلى ملف مستقل، ثم أضف ملف routes لتقليل حجم server.js. لاحقاً يمكن استخدام قاعدة بيانات، وطبقة تحقق، وmiddleware مركزي للأخطاء من دون تغيير شكل المسارات أمام العميل.

طريقة العمل قبل كتابة الكود

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

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

اختبار المثال في حالات مختلفة

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

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

ملاحظات تتعلق بجودة الكود

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

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

متى تعرف أن الحل يحتاج إلى تطوير؟

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

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

أخطاء ينبغي تجنبها

لا تعتمد على products.length + 1 لتوليد المعرف في مشروع حقيقي، لأن حذف عنصر أو تشغيل أكثر من عملية قد يسبب تكراراً. لا ترسل رمز 200 لكل الحالات، ولا تعرض تفاصيل أخطاء قاعدة البيانات للمستخدم. من الأخطاء كذلك تعديل السعر من دون التحقق من كونه رقماً غير سالب، أو السماح باسم فارغ بعد إزالة المسافات. حافظ على حدود واضحة بين استقبال الطلب والتحقق منه وحفظه، فذلك يجعل اختبار كل جزء أسهل.

خطوة تالية مناسبة

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

تنظيم الاستجابة قبل ربط قاعدة البيانات

من المفيد أن تحدد شكل الاستجابة منذ النسخة الأولى. قد تعيد الخدمة قائمة داخل خاصية data، وتضيف count لعدد العناصر، وتستخدم error عند الفشل. لا توجد صيغة واحدة تناسب كل مشروع، لكن الثبات مهم للواجهة التي تستهلك الخدمة. اكتب أمثلة قصيرة للطلبات والنتائج في ملف README، واذكر الحقول الإلزامية وأنواعها. إذا غيرت اسماً أو رمز حالة، حدّث التوثيق والاختبارات في الوقت نفسه. وعندما تنقل التخزين من المصفوفة إلى SQLite أو PostgreSQL، حافظ على مسؤولية المسارات كما هي قدر الإمكان؛ ففصل منطق البيانات عن HTTP يجعل التغيير أقل كلفة ويمنع انتشار تفاصيل قاعدة البيانات داخل كل دالة.

الخلاصة

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

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

تعليقات