MongoDB: واجهة برمجة التطبيقات (API) القائمة على نمط REST…

آخر تحديث: 2026-08-26

تُعد واجهات برمجة التطبيقات (API) التي تعمل وفقًا لمعيار REST المعيار القياسي لخدمات الويب — وإتقانها يمكّنك من بناء بنية API محددة بدقة وقابلة للصيانة.

المفاهيم الأساسية والمفاهيم الخاطئة الشائعة حول REST: جوهر REST (نقل الحالة التمثيلية) هو «التوجه نحو الموارد» — حيث تمثل عناوين URL الموارد، وتمثل طرق HTTP العمليات. المفاهيم الخاطئة الشائعة — 1. REST ≠ CRUD: لا يقتصر REST على عمليات الإنشاء (Create) والقراءة (Read) والتحديث (Update) والحذف (Delete)؛ بل يمكنه أيضًا التعبير عن الإجراءات التجارية (على سبيل المثال، POST /orders/{id}/cancel). المفتاح هو أن عناوين URL تستند إلى الأسماء؛ 2. REST ≠ عديم الحالة: يعني عدم وجود الحالة أن كل طلب يحتوي على جميع المعلومات الضرورية (دون الاعتماد على جلسات العمل من جانب الخادم)، لكن البيانات التجارية هي، بالطبع، ذات حالة؛ 3. REST ≠ يجب استخدام JSON: لا يفرض REST قيودًا على التنسيق؛ JSON هو ببساطة الخيار الأكثر شيوعًا؛ 4. REST ≠ مثالي: لا يدعم REST الإشعارات الفورية في الوقت الحقيقي، أو الاستعلامات المعقدة، أو العمليات المجمعة بشكل جيد؛ ويُعد كل من gRPC و GraphQL أكثر ملاءمة لهذه السيناريوهات. إن فهم قيود REST أهم من حفظ قواعده.

نموذج نضج واجهات برمجة التطبيقات RESTful: يصنف نموذج ريتشاردسون للنضج واجهات برمجة التطبيقات REST إلى أربعة مستويات — المستوى 0 (نفق HTTP): يستخدم طلب POST فقط، حيث تُنفَّذ جميع العمليات عبر عنوان URL واحد (مثل SOAP)؛ المستوى 1 (الموارد): يُدخل مفهوم الموارد؛ حيث تستخدم الموارد المختلفة عناوين URL مختلفة، ولكن لا يُستخدم سوى طلبَي GET وPOST؛ المستوى 2 (أفعال HTTP): الاستخدام الصحيح لـ GET وPOST وPUT وDELETE جنبًا إلى جنب مع رموز الحالة — وهذا هو المستوى الذي تصل إليه معظم المشاريع؛ المستوى 3 (الوسائط الفائقة/HATEOAS): تتضمن الاستجابات روابط إلى الموارد ذات الصلة (على سبيل المثال، تحتوي استجابة الطلب على رابط للإلغاء)، مما يتيح الاكتشاف الذاتي. تتمثل القفزة الأكبر في الانتقال من المستوى 1 إلى المستوى 2 (الذي يوحد دلالات العمليات)، في حين نادرًا ما يُستخدم المستوى 3 في الممارسة العملية (فهو يزيد من التعقيد ولكنه يقدم فوائد محدودة للواجهة الأمامية). الهدف من هذه الدورة هو المستوى 2 — بناء واجهات برمجة تطبيقات (APIs) واضحة من خلال التطبيق الصحيح لدلالات HTTP.

1. ما ستتعلمه



2. مبادئ تصميم RESTful

نموذج نضج REST: حدد ليونارد ريتشاردسون أربعة مستويات نضج لواجهات برمجة التطبيقات (API) التي تعمل بنموذج REST — المستوى 0: عنوان URL واحد + POST (مثل SOAP)؛ المستوى 1: إدخال مفهوم الموارد (تمثل عناوين URL المختلفة موارد مختلفة)؛ المستوى 2: طرق HTTP الدلالية (تعبر طرق GET/POST/PUT/DELETE عن العمليات)؛ المستوى 3: HATEOAS (تتضمن الاستجابة رابطًا إلى الإجراء التالي). تظل معظم واجهات برمجة التطبيقات (APIs) المستخدمة في الإنتاج عند المستوى 2؛ ورغم أن المستوى 3 يتوافق بشكل أفضل مع مبادئ REST، إلا أن تنفيذه مكلف.

تصميم أمان واجهة برمجة التطبيقات (API): يتطلب أمان واجهة برمجة التطبيقات (API) التي تعمل بنموذج RESTful طبقات متعددة من الحماية — 1. طبقة النقل: فرض استخدام بروتوكول HTTPS لمنع هجمات «الرجل في الوسط»؛ 2. طبقة المصادقة: استخدام رموز JWT Bearer للتحقق من هوية المستخدم؛ 3. طبقة التفويض: التحكم في الوصول القائم على الأدوار (RBAC) (العميل/المسؤول/المشرف)؛ 4. طبقة الإدخال: التحقق من صحة joi + حد حجم نص الطلب (express.json({limit:'1mb'})); 5. طبقة تحديد المعدل: express-rate-limit لمنع هجمات القوة الغاشمة؛ 6. طبقة الأصول المتقاطعة: قائمة CORS البيضاء لتقييد نطاقات المصدر.

طبقة الأمان الهدف الدفاعي طريقة التنفيذ
HTTPS التنصت/التلاعب شهادات TLS
JWT تزوير الهوية رمز حامل
RBAC العمليات غير المصرح بها مصفوفة الأدوار + الصلاحيات
joi البيانات المُحقنة/التالفة التحقق من صحة المخطط
الحد الأقصى لمعدل الاستخدام DDoS تحديد الحد الأقصى لمعدل استخدام عناوين IP
CORS إساءة استخدام عبر الأصول النطاقات المدرجة في القائمة البيضاء

ما هو REST؟ REST (نقل الحالة التمثيلية) هو أسلوب معماري يتمثل مبدأه الأساسي في أن كل شيء يمثل مورداً، يتم تحديده بواسطة عنوان URL ويتم التعامل معه باستخدام طرق HTTP. REST ليس بروتوكولًا بل مجموعة من القيود — وتُسمى واجهة برمجة التطبيقات (API) التي تلتزم بهذه القيود بـ «واجهة برمجة تطبيقات RESTful». في أطروحته للدكتوراه عام 2000، حدد روي فيلدينج ستة قيود: نموذج العميل-الخادم، وعدم الارتباط بالحالة، والتخزين المؤقت، والواجهة الموحدة، والنظام الطبقي، والرمز حسب الطلب.

المبادئ الأساسية لتصميم RESTful:

المبدأ الوصف مثال
استخدم الأسماء للإشارة إلى الموارد عناوين URL تمثل الموارد، وليست الإجراءات /products/getProducts
الأسماء الجمع صيغة الجمع للأسماء الجماعية /products/product
دلالات طرق HTTP GET (قراءة) / POST (كتابة) / PUT (استبدال) / PATCH (تحديث) / DELETE (حذف) DELETE /products/:id
الموارد المتداخلة التعبير عن العلاقات الهرمية باستخدام المسارات /products/:id/reviews
التماثل تؤدي الاستدعاءات المتعددة لـ GET/PUT/DELETE إلى نفس النتيجة لا تؤدي الاستدعاءات المتكررة لـ DELETE إلى حدوث خطأ
بدون حالة يحتوي كل طلب على جميع المعلومات الضرورية يتم تضمين رمز JWT في كل طلب

REST مقابل RPC مقابل GraphQL:

البعد REST RPC GraphQL
المفاهيم الأساسية الموارد + دلالات HTTP أوامر الإجراء الاستعلامات عند الطلب
نمط عنوان URL الاسم الفعل نقطة نهاية واحدة
استرجاع البيانات بنية ثابتة بنية ثابتة محددة من قبل العميل
الاستخراج المفرط شائع شائع تجنب
منحنى التعلم منخفض منخفض متوسط
متوافق مع التخزين المؤقت التخزين المؤقت الأصلي لـ HTTP يجب تنفيذه يدويًّا معقد
حالات الاستخدام واجهة برمجة التطبيقات (CRUD) الخدمات الداخلية الواجهة الأمامية المعقدة

قيود REST وحرياتها: إن القيود الستة لـ REST — نموذج العميل-الخادم، وعدم الارتباط بالحالة، والتخزين المؤقت، والواجهة الموحدة، والنظام الطبقي، والرمز حسب الطلب — ليست متطلبات إلزامية. فـ REST من المستوى 2 (الموارد + دلالات طرق HTTP) تلبي بالفعل 90% من احتياجات واجهة برمجة التطبيقات (API). إن السعي المفرط وراء نقاء REST (مثل HATEOAS من المستوى 3) يؤدي في الواقع إلى زيادة تكاليف التطوير. في المشاريع الواقعية، لا يوجد سوى ثلاثة مبادئ أساسية: 1. عناوين URL هي أسماء (موارد)، وطرق HTTP هي أفعال (عمليات)؛ 2. التعبير عن النتائج باستخدام رموز الحالة؛ وعدم تكرارها في نص الاستجابة؛ 3. المصادقة غير المرتبطة بالحالة (يتم إرفاق JWT مع كل طلب ولا يعتمد على الجلسات).

متى لا ينبغي استخدام REST: لا تناسب REST جميع السيناريوهات — 1. الاتصال في الوقت الفعلي (تُعد WebSockets خيارًا أفضل؛ حيث إن نموذج الطلب-الاستجابة في REST لا يدعم الإرسال التلقائي من جانب الخادم)؛ 2. العمليات المجمعة (تعتبر عمليات REST لكل مورد على حدة غير فعالة؛ وتُعد نقاط النهاية من نوع /batch على غرار RPC أكثر عملية للاستيراد/التصدير المجمّع)؛ 3. الاستعلامات المعقدة (توفر GraphQL مرونة أكبر للتصفية باستخدام شروط متعددة مجمعة؛ أما معلمات استعلام URL في REST فهي محدودة التعبير)؛ 4. تحميل الملفات (لا يُعد multipart/form-data طريقة تفاعل قياسية في REST، ولكنه مقبول في الممارسة العملية). المفتاح لاختيار النهج الصحيح هو البراغماتية، وليس التعصب.

100%
graph LR
    Client[Client] -->|GET| R1[GET /products<br/>List]
    Client -->|GET| R2[GET /products/:id<br/>Details]
    Client -->|POST| R3[POST /products<br/>Create]
    Client -->|PUT| R4[PUT /products/:id<br/>Complete Update]
    Client -->|PATCH| R5[PATCH /products/:id<br/>Partial Update]
    Client -->|DELETE| R6[DELETE /products/:id<br/>Delete]

    style R1 fill:#d4edda
    style R3 fill:#cce5ff
    style R6 fill:#f8d7da

ممارسات أليس في تصميم REST: عندما صممت أليس واجهة برمجة تطبيقات (API) المنتجات لـ ShopHub، تعاملت مع عناوين URL على أنها مسارات للموارد: يمثل /api/products مجموعة من المنتجات، ويمثل /api/products/PHONE-001 منتجًا معينًا، ويمثل /api/products/PHONE-001/reviews التقييمات الخاصة بذلك المنتج — حيث تعمل عناوين URL نفسها كموارد ذاتية التوثيق.

الأهمية العملية لخاصية الإيدمبوتنت: تُعد خاصية الإيدمبوتنت أساس موثوقية واجهة برمجة التطبيقات (API) — حيث يمكن إعادة محاولة العمليات الإيدمبوتنت بأمان دون التسبب في آثار جانبية. سيناريوهات عملية: 1. إعادة المحاولة في حالة انتهاء مهلة الشبكة — بعد انتهاء مهلة طلب PUT/PATCH/DELETE، يمكن للواجهة الأمامية إعادة المحاولة تلقائيًا (مع الحصول على نفس نتيجة المحاولة الأولى)؛ لا يمكن إعادة محاولة طلبات POST تلقائيًا (لأن ذلك قد يؤدي إلى تكرار البيانات)؛ 2. ضعف اتصال الشبكة على الأجهزة المحمولة — إذا انقطع الاتصال بالشبكة بعد أن ينقر المستخدم على «حذف»، فإن إعادة المحاولة بعد استعادة الاتصال لن تؤدي إلى حذف سجل آخر؛ 3. إعادة المحاولة في الخدمات الصغيرة — عندما تفشل المكالمات بين الخدمات ويتم إعادة محاولتها، لا تنتج العمليات المتجانسة أي آثار جانبية. تتطلب العمليات غير المتكررة (مثل إنشاء POST) مفتاح تكرار لضمان التكرار — يقوم العميل بإنشاء معرّف فريد، ويتحقق الخادم مما إذا كان قد تمت معالجته بالفعل أم لا.

دليل اختيار رموز حالة HTTP: يجب أن تعكس رموز الحالة الخاصة بواجهات برمجة التطبيقات (API) التي تعمل وفقًا لمبادئ REST النتيجة بدقة — 2xx نجاح (200 OK، 201 Created، 204 No Content)، و4xx أخطاء العميل (400 Bad Request، 401 Unauthorized، 403 Forbidden، 404 Not Found، 409 Conflict)، و5xx أخطاء الخادم (500 Internal Server Error، 502 Gateway Error، 503 Service Unavailable). الأخطاء الشائعة: 1. إرجاع 200 + {error: "...}} لجميع الأخطاء (يخالف دلالات HTTP؛ حيث لا يمكن للواجهة الأمامية تحديد النتيجة بناءً على رمز الحالة)؛ 2. الخلط بين 401 و403 (يشير 401 إلى «لم تتم المصادقة» ويتطلب تسجيل الدخول، بينما يشير 403 إلى «تمت المصادقة ولكن لا يوجد إذن»)؛ 3. استخدام الرمز 400 كرمز شامل لجميع أخطاء 4xx (لا يمكن للواجهة الأمامية التمييز بين «فشل التحقق من الصحة» و«المورد غير موجود»). تجعل رموز الحالة الدقيقة كود الواجهة الأمامية أكثر إيجازًا — حيث تتعامل معترضات axios مع الطلبات بناءً على فروع رموز الحالة.

إرشادات التعامل مع أخطاء 5xx: يشير خطأ 5xx إلى وجود مشكلة من جانب الخادم— 1. 500 خطأ داخلي في الخادم: يجب تسجيل الاستثناء الذي لم يتم التقاطه (مثل TypeError أو انقطاع الاتصال بقاعدة البيانات) مع تتبع المكدس الكامل، وإرجاع رسالة خطأ عامة إلى العميل (دون الكشف عن تتبع المكدس)؛ 2. 502 بوابة غير صالحة: لا يمكن للوكيل العكسي (Nginx) الاتصال بتطبيق Node.js (تعطل التطبيق أو فشل في التشغيل)؛ قم بتشغيل تنبيه؛ 3. 503 الخدمة غير متاحة: التطبيق محمل بشكل زائد أو يخضع للصيانة؛ قم بإرجاع رأس Retry-After لإعلام العميل بموعد إعادة المحاولة؛ 4. المعالجة الموحدة لأخطاء 5xx: تقوم البرمجيات الوسيطة للخطأ في Express بالتقاط هذه الأخطاء بشكل موحد، وتسجيلها، وإرسال التنبيهات، وإرجاع رسالة عامة. يجب أن يكون معدل أخطاء 5xx في بيئة الإنتاج أقل من 0.1%؛ وتؤدي تجاوز هذه العتبة إلى تشغيل تنبيه PagerDuty.

طريقة HTTP الإجراء قابلية التكرار
الحصول على قراءة
منشور إنشاء
PUT تحديث كامل
تصحيح تحديث جزئي
حذف حذف
نمط عنوان URL المعنى
GET /api/products القائمة
GET /api/products/:id التفاصيل
POST /api/products إنشاء
PUT /api/products/:id التحديث الكامل
PATCH /api/products/:id تحديث جزئي
DELETE /api/products/:id حذف


3. مواصفات رموز حالة HTTP

لماذا تعتبر رموز الحالة مهمة؟ تعمل رموز حالة HTTP بمثابة «إشارات مرور» لواجهات برمجة التطبيقات (API) — حيث يستخدمها العملاء لتحديد نتيجة الطلب دون الحاجة إلى تحليل نص الاستجابة. تشير الرموز التي تبدأ بـ 2xx إلى النجاح، بينما تشير الرموز التي تبدأ بـ 4xx إلى خطأ من جانب العميل، وتشير الرموز التي تبدأ بـ 5xx إلى خطأ من جانب الخادم. إن إساءة استخدام رمز الحالة 200 (مثل إرجاع رمز الحالة 200 مصحوبًا برسالة خطأ حتى في حالة حدوث خطأ) يقوض دلالات HTTP ويمنع العملاء من معالجة الرد بشكل صحيح.

أكثر 6 رموز حالة شيوعًا: 90% من استجابات واجهة برمجة التطبيقات (API) في بيئات الإنتاج لا تتطلب سوى 6 رموز حالة — 200 OK (نجاح + إرجاع البيانات)، 201 Created (تم الإنشاء بنجاح)، 204 No Content (تم الحذف أو التحديث بنجاح دون إرجاع البيانات)، 400 Bad Request (فشل التحقق من الصحة)، 404 Not Found (المورد غير موجود)، و500 Internal Server Error (خطأ في الخادم). تُستخدم رموز الحالة الأخرى (401/403/409/422) في سيناريوهات محددة. المبدأ: استخدم الرموز الستة الأساسية كلما أمكن ذلك بدلاً من الرموز الأقل شيوعًا؛ فالاتساق أهم من الاكتمال.

التحديد الدقيق لرموز الحالة 4xx: تشير رموز الحالة 4xx إلى أخطاء من جانب العميل؛ ويساعد التحديد الدقيق مطوري الواجهة الأمامية على تحديد المشكلة بدقة — 400 (تنسيق الطلب غير صالح/فشل المصادقة؛ يجب على الواجهة الأمامية تعديل المدخلات وإعادة المحاولة)، 401 (لم تتم المصادقة؛ يجب على الواجهة الأمامية إعادة التوجيه إلى صفحة تسجيل الدخول)، 403 (تمت المصادقة ولكن لا يوجد إذن؛ يجب على الواجهة الأمامية عرض صفحة "لا يوجد إذن")، 404 (المورد غير موجود؛ يجب على الواجهة الأمامية عرض "غير موجود")، 409 (تعارض/تكرار؛ يجب على الواجهة الأمامية عرض "موجود بالفعل")، 422 (خطأ دلالي؛ اجتياز التحقق من الصحة ولكن لم يتم استيفاء قواعد العمل). الأخطاء الشائعة: استخدام الرمز 400 لتغطية جميع أخطاء 4xx (لا يمكن للواجهة الأمامية التمييز بين «لم يتم تسجيل الدخول» و«خطأ في المعلمة»)، أو استخدام الرمز 404 بدلاً من 403 (إخفاء وجود المورد مع إثارة جدل أمني — حيث يشير الرمز 404 إلى «غير موجود» بينما يعني في الواقع «رفض الوصول»).

إرشادات التعامل مع أخطاء 5xx: يشير خطأ 5xx إلى وجود خطأ من جانب الخادم — ولا يمكن للعميل حل هذا الخطأ، لذا يجب عليه إما إعادة المحاولة أو الانتظار. المبادئ الأساسية: 1. لا تكشف أبدًا عن تفاصيل الأخطاء الداخلية (أخطاء قاعدة البيانات ومسارات الملفات وتتبع المكدس لا فائدة منها للمستخدمين وتشكل مخاطر أمنية)؛ 2. قم بإرجاع معرّف الطلب (UUID) حتى يتمكن المستخدمون من تقديمه عند الإبلاغ عن المشكلات، ويمكن للمطورين استخدامه للبحث عن المشكلة وتحديد موقعها في السجلات؛ 3. استخدم الرمز 500 للأخطاء غير المعروفة، و502 لأخطاء البوابة العلوية (مثل انقطاع الاتصال بـ MongoDB)، و503 لحالة التحميل الزائد على الخدمة (استخدم رأس الاستجابة Retry-After لإعلام العميل بموعد إعادة المحاولة)؛ 4. قم بتشغيل التنبيهات (يؤدي معدل أخطاء 5xx الذي يتجاوز 1% إلى تشغيل تنبيه PagerDuty).

100%
graph TD
    Start[Request Result] --> Success{Success?}
    Success -->|Yes| Code2xx[2xx Status Code]
    Success -->|No| WhoFault{Whose Fault Is It?}
    
    Code2xx --> HasBody{Has a return value?}
    HasBody -->|Yes| OK[200 OK]
    HasBody -->|Create a New Resource| Created[201 Created]
    HasBody -->|No content| NoContent[204 No Content]
    
    WhoFault -->|Client| ClientErr{Client Error?}
    WhoFault -->|Server| ServerError[500 Internal Server Error]
    
    ClientErr -->|Yes| Client4xx[4xx Client Error]
    
    Client4xx --> What4xx{What's the problem?}
    What4xx -->|Parameter error| BR[400 Bad Request]
    What4xx -->|Not verified| UA[401 Unauthorized]
    What4xx -->|No permission| FB[403 Forbidden]
    What4xx -->|Does not exist| NF[404 Not Found]
    What4xx -->|Resource Conflicts| CF[409 Conflict]

    style OK fill:#d4edda
    style Created fill:#d4edda
    style BR fill:#f8d7da
    style UA fill:#fff3cd
رمز الحالة المعنى السيناريو
200 موافق نجاح (GET/PUT/PATCH)
201 تم الإنشاء تم الإنشاء بنجاح (POST)
204 لا يوجد محتوى نجاح: لا يوجد محتوى (DELETE)
400 طلب غير صحيح معلمات طلب غير صالحة
401 غير مصرح به لم تتم المصادقة
403 ممنوع لا يوجد إذن
404 غير موجود المورد غير موجود
409 تعارض تعارض في الموارد (مثل التكرار)
500 خطأ في الخادم خطأ في الخادم


4. نموذج الرد الموحد

لماذا يجب توحيد تنسيق الاستجابة؟ فبدون تنسيق موحد، قد تُرجع إحدى نقاط النهاية { product }، وأخرى { data: product }، وفي حالة حدوث خطأ، { message: "error" } — مما يتطلب من الواجهة الأمامية كتابة منطق تحليل مختلف لكل نقطة نهاية. أما مع وجود تنسيق موحد، فلا تحتاج الواجهة الأمامية سوى إلى مجموعة واحدة من المنطق: حيث يشير if (response.success) إلى النجاح، ويؤدي else إلى قراءة response.error.

تصميم هيكل الاستجابة القياسي:

الحقل النوع في حالة النجاح في حالة حدوث خطأ
النجاح منطقية صحيح خطأ
البيانات أي البيانات التجارية غير موجودة
ميتا كائن معلومات ترقيم الصفحات غير موجود
خطأ الكائن غير موجود تفاصيل الخطأ

مبادئ تصميم رموز الأخطاء: يجب أن تستخدم رموز الأخطاء (error.code) تنسيق الأحرف الكبيرة والشرطة السفلية (على سبيل المثال، VALIDATION_ERROR)، وألا تكشف عن تفاصيل تقنية (على سبيل المثال، لا تُرجع MongooseError)، وأن توفر معلومات قابلة للتنفيذ (على سبيل المثال، DUPLICATE_KEY + اسم الحقل المكرر).

HATEOAS واكتشاف واجهة برمجة التطبيقات (API) ذاتيًا: المستوى 3 من نموذج نضج REST هو HATEOAS (الوسائط الفائقة كمحرك لحالة التطبيق) — حيث لا تحتوي الاستجابات على البيانات فحسب، بل تتضمن أيضًا روابط إلى العمليات ذات الصلة. على سبيل المثال، قد تتضمن استجابة تفاصيل الطلب links: {pay: '/orders/123/pay', cancel: '/orders/123/cancel'}. يجعل HATEOAS واجهة برمجة التطبيقات (API) ذاتية الوصف — فلا يحتاج العملاء إلى ترميز عناوين URL بشكل ثابت، بل يمكنهم اكتشاف العمليات المتاحة ديناميكيًا من الاستجابة. في حين أن معظم المشاريع تحتاج فقط إلى الوصول إلى المستوى 2، فإن HATEOAS مناسب لمنصات واجهة برمجة التطبيقات المفتوحة (مثل Stripe وواجهة برمجة تطبيقات GitHub).

تصميم بيانات التعريف الخاصة بالترقيم: يجب أن تتضمن بيانات التعريف الخاصة بالترقيم (meta) لواجهة برمجة التطبيقات (API) الخاصة بالقائمة معلومات كافية لتتيح للواجهة الأمامية عرض عناصر التحكم في الترقيم — total (إجمالي عدد السجلات)، page (الصفحة الحالية)، limit (عدد السجلات في كل صفحة)، و totalPages (إجمالي عدد الصفحات = ceil(total/limit)). الحقول الاختيارية الإضافية: hasNext (ما إذا كانت هناك صفحة تالية) و hasPrev (ما إذا كانت هناك صفحة سابقة). تستخدم الواجهة الأمامية هذه الحقول لتحديد منطق عرض ترقيم الصفحات: لا يُعرض شريط ترقيم الصفحات إلا إذا كان totalPages > 1، ويُخفى زر «الصفحة التالية» إذا كان currentPage == totalPages.

المقاييس الكمية لأداء واجهة برمجة التطبيقات (API): يجب قياس أداء واجهات برمجة التطبيقات (API) التي تعمل بنمط RESTful كمياً — 1. زمن الاستجابة P50/P95/P99 (أزمنة الاستجابة لـ 50٪ و95٪ و99٪ من الطلبات؛ والهدف الشائع هو أن يكون P95 < 200 مللي ثانية)؛ 2. معدل الإنتاجية (QPS؛ عدد الطلبات المعالجة في الثانية؛ عادةً ما يتعامل إعداد Express + MongoDB ذو المثيل الواحد مع 500–2,000 QPS)؛ 3. معدل الأخطاء (النسبة المئوية لأخطاء 5xx من إجمالي الطلبات؛ يُعتبر < 0.1% معيارًا جيدًا). أدوات المراقبة: Prometheus + Grafana لجمع المقاييس وعرضها؛ Alertmanager للتنبيهات. أولوية تحسين الأداء: قم أولاً بتحسين واجهات برمجة التطبيقات (APIs) الأبطأ (تلك التي لديها أعلى قيمة لـ P95)، ثم قم بتحسين واجهات برمجة التطبيقات الأكثر استدعاءً (قائمة بالواجهات ذات أعلى معدل QPS).

استراتيجيات التخزين المؤقت لواجهات برمجة التطبيقات (API): تعد واجهات برمجة التطبيقات التي تتميز بأحجام قراءة كبيرة وأحجام كتابة منخفضة (مثل قوائم المنتجات وتفاصيل المقالات) مناسبة للتخزين المؤقت — 1. التخزين المؤقت عبر HTTP (Cache-Control: max-age=300؛ حيث تستخدم المتصفحات ذاكرة التخزين المؤقت مباشرةً لمدة 5 دقائق دون إرسال طلب)؛ 2. التخزين المؤقت عبر شبكة توزيع المحتوى (CDN) (التخزين المؤقت على حافة الشبكة من Cloudflare/CloudFront، مما يتيح للمستخدمين في جميع أنحاء العالم الوصول إلى المحتوى من أقرب موقع)؛ 3. التخزين المؤقت للتطبيق (يقوم Redis بتخزين البيانات التي يتم الوصول إليها بشكل متكرر مؤقتًا بفترة صلاحية (TTL) تتراوح بين 5 و10 دقائق)؛ 4. التخزين المؤقت لقاعدة البيانات (التخزين المؤقت WiredTiger في MongoDB، الذي تتم إدارته تلقائيًا). استراتيجيات انتهاء صلاحية ذاكرة التخزين المؤقت: 1. انتهاء الصلاحية التلقائي عند انتهاء مدة الصلاحية (TTL)؛ 2. انتهاء الصلاحية الاستباقي عند الكتابة (حذف مفتاح ذاكرة التخزين المؤقت المقابل بعد تحديث المقالة)؛ 3. التحكم في رقم الإصدار (تتضمن مفاتيح ذاكرة التخزين المؤقت رقم إصدار، يتم زيادته عند التحديثات).

الحماية من اختراق ذاكرة التخزين المؤقت و«انهيار ذاكرة التخزين المؤقت»: تنطوي أنظمة ذاكرة التخزين المؤقت على نمطين كلاسيكيين للفشل — 1. اختراق ذاكرة التخزين المؤقت: الاستعلام عن بيانات غير موجودة (على سبيل المثال، productId=999999)؛ لا تحتوي ذاكرة التخزين المؤقت على أي بيانات → يتم الاستعلام عن قاعدة البيانات → لم يتم العثور على البيانات هناك أيضًا → لا توجد بيانات مخزنة في ذاكرة التخزين المؤقت → يتم الاستعلام عن قاعدة البيانات مرة أخرى في المرة التالية. الوقاية: قم بتخزين النتائج التي تُرجع null (TTL 60 ثانية) في ذاكرة التخزين المؤقت، أو استخدم مرشح بلوم (Bloom filter) لتصفية المعرّفات غير الموجودة؛ 2. انهيار ذاكرة التخزين المؤقت: تنتهي صلاحية عدد كبير من عناصر ذاكرة التخزين المؤقت في وقت واحد (على سبيل المثال، انتهاء صلاحية دفعة واحدة في الساعة 3:00 صباحًا)، مما يتسبب في إغراق قاعدة البيانات بجميع الطلبات. الوقاية: إضافة إزاحة عشوائية إلى TTL (300 ± 30 ثانية) لتوزيع أوقات انتهاء الصلاحية؛ 3. اختراق ذاكرة التخزين المؤقت: يغمر عدد كبير من الطلبات قاعدة البيانات لحظة انتهاء صلاحية البيانات الساخنة. التخفيف: استخدام أقفال موزعة للبيانات الساخنة (السماح بطلب واحد فقط في كل مرة لإعادة بناء ذاكرة التخزين المؤقت)، أو تنفيذ ذاكرات تخزين مؤقت لا تنتهي صلاحيتها مع تحديثات غير متزامنة. تختلف استراتيجيات التخفيف الخاصة بهذه الأعطال الثلاثة ويجب تنفيذها بشكل منفصل.

JAVASCRIPT
// Successful Response
{
  "success": true,
  "data": { ... },
  "meta": { "page": 1, "limit": 20, "total": 100 }
}

// Error Response
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid input",
    "details": { "email": "Required" }
  }
}

// Middleware:Standard Response
const sendSuccess = (res, data, meta = null) => {
  res.json({ success: true, data, meta });
};

const sendError = (res, code, message, status = 400, details = null) => {
  res.status(status).json({
    success: false,
    error: { code, message, details }
  });
};


5. طلب نسخة تجريبية (joi)

لماذا يعد التحقق من صحة الطلبات ضروريًا؟ لا تثق أبدًا في المدخلات الواردة من العميل — فالسلاسل الفارغة، والنصوص المفرطة الطول، والأحرف غير الصالحة، والحقول الإلزامية الناقصة، كلها عوامل قد تؤدي إلى: ① كتابة بيانات غير صحيحة في قاعدة البيانات؛ ② أخطاء في الاستعلامات؛ ③ ثغرات أمنية (هجمات الحقن). وتُعد طبقة التحقق من الصحة خط الدفاع الأول لواجهة برمجة التطبيقات (API)، حيث تعمل على اعتراض الطلبات غير الصحيحة قبل وصول البيانات إلى وحدة التحكم.

مكان طبقة التحقق من الصحة في مسار معالجة الطلب: يجب وضع البرمجيات الوسيطة الخاصة بالتحقق من الصحة مباشرةً قبل وحدة التحكم — بعد المصادقة/التفويض (للتحقق من الهوية قبل التحقق من صحة المدخلات) وقبل منطق الأعمال (لمنع وصول البيانات غير الصحيحة إلى وحدة التحكم). بمجرد اجتياز عملية التحقق من الصحة، تُخصص البيانات المنقحة إلى req.validated، ويستخدم وحدة التحكم بيانات validated فقط بدلاً من البيانات الأصلية req.body — وهذا يضمن أن وحدة التحكم تتلقى دائمًا بيانات صالحة.

مقارنة متعمقة بين joi و express-validator: joi هي مكتبة تحقق من الصحة قائمة بذاتها (مستقلة عن Express)، في حين أن express-validator هي برمجيات وسيطة مصممة خصيصًا لـ Express. مزايا joi: 1. إمكانية تصدير المخططات وإعادة استخدامها (تشترك الواجهة الأمامية والخلفية في نفس مجموعة قواعد التحقق من الصحة)؛ 2. قدرة أكبر على التعبير عن عمليات التحقق المعقدة (التحقق عبر الحقول، والتحقق الشرطي)؛ 3. لا تعتمد على إطار عمل معين (تعمل أيضًا مع Koa و Hapi). مزايا express-validator: 1. برمجيات وسيطة أصلية لـ Express مع تكامل لا يتطلب أي تكوين؛ 2. تستند إلى validator.js، مما يوفر مجموعة غنية من قواعد التحقق من الصحة؛ 3. بناء جملة متسلسل بديهي. يُوصى باستخدام joi للمشاريع الجديدة (نظرًا لإمكانية إعادة استخدامها العالية)، ولكن لا توجد حاجة لترحيل المشاريع التي تستخدم express-validator بالفعل.

100%
sequenceDiagram
    participant Client
    participant Route as Express Route
    participant Validate as joi Verification
    participant Controller
    participant DB as MongoDB

    Client->>Route: POST /api/products
    Route->>Validate: validate(req.body)
    alt Verification Failed
        Validate-->>Client: 400 VALIDATION_ERROR
    else Verification Passed
        Validate->>Controller: req.validated = value
        Controller->>DB: Product.create(validated)
        DB-->>Client: 201 Created
    end

استراتيجية الدفاع متعدد الطبقات للتحقق من صحة المدخلات: يجب تنفيذ عملية التحقق من صحة مدخلات واجهة برمجة التطبيقات (API) عبر طبقات متعددة — 1. طبقة التوجيه (Joi/express-validator): خط الدفاع الأول، تتحقق من صحة التنسيق والنوع والنطاق؛ وتُرجع رمز الحالة 400 بالإضافة إلى معلومات تفصيلية عن الخطأ؛ 2. طبقة وحدة التحكم: تتحقق من قواعد العمل (مثل «يجب أن تكون الفئة موجودة»، «لا يمكن تكرار SKU»)، الأمر الذي يتطلب استعلامًا عن قاعدة البيانات؛ 3. طبقة النموذج (Mongoose Schema): خط الدفاع الأخير، الذي يضمن أن البيانات المكتوبة في MongoDB تلتزم بدقة بالقيود الهيكلية. تخدم كل طبقة من طبقات التحقق الثلاث هذه غرضًا متميزًا — حيث تقوم طبقة التوجيه بتصفية 90% من المدخلات غير الصحيحة (الفشل السريع)، وتتعامل طبقة وحدة التحكم مع منطق الأعمال (الذي يتطلب استعلامات قاعدة البيانات)، وتعمل طبقة النموذج كشبكة أمان (تمنع تجاوز الطبقتين الأوليين).

مقارنة متعمقة بين Joi و express-validator: هاتان هما مكتبتا التحقق من صحة المدخلات الأكثر استخدامًا في Node.js — Joi: مكتبة تحقق من صحة المدخلات مستقلة وغير مرتبطة بأي إطار عمل، وتتميز بتعريفات أنيقة للمخططات (واجهة برمجة تطبيقات متسلسلة) ورسائل خطأ مفصلة، لكنها تتطلب تكاملاً يدويًّا مع Express (عبر تغليف البرامج الوسيطة)؛ express-validator: تستند إلى validator.js، ومتكاملة بعمق مع Express (يمكن استخدام req.check() مباشرةً في المسارات)، لكن تعريف مخططها أقل بديهية من Joi (تستخدم طرق check() المتسلسلة بدلاً من التعريفات القائمة على الكائنات). معايير الاختيار: 1. استخدام Joi للمشاريع الجديدة (المخططات قابلة لإعادة الاستخدام وقابلة للاختبار ويمكنها إنشاء وثائق Swagger)؛ 2. الاستمرار في استخدام express-validator للمشاريع الحالية (تكلفة الترحيل لا تستحق العناء)؛ 3. Joi أكثر ملاءمة للتحقق المعقد (التبعيات الشرطية، والتحقق عبر الحقول).

أنماط تصميم برامج الوسيطة الخاصة بالتحقق من الصحة: يُعد تغليف منطق التحقق من الصحة في شكل برامج وسيطة من أفضل الممارسات في Express— 1. تقبل برامج الوسيطة الخاصة بالتحقق من الصحة مخططًا (validate(createProductSchema))، وتقوم بالتحقق من صحة req.body، وتستدعي next() في حالة النجاح؛ وإلا، فإنها تُرجع رمز الحالة 400 مصحوبًا بتفاصيل الخطأ؛ 2. تقوم البرمجيات الوسيطة بإرفاق القيم التي تم التحقق من صحتها بـ req.validated (بدلاً من الاستمرار في استخدام req.body)، مما يمنع وحدات التحكم اللاحقة من معالجة البيانات غير التي تم التحقق من صحتها؛ 3. يتم فصل مخططات التحقق من الصحة حسب عمليات CRUD (على سبيل المثال، تحتوي createSchema على حقول إلزامية، بينما تحتوي updateSchema على حقول اختيارية بالكامل) ولا يتم خلطها. يضمن هذا النمط أن وحدات التحكم لا تحتاج إلى الاهتمام بمنطق التحقق من الصحة على الإطلاق — فهي تعالج البيانات التي تم التحقق من صحتها فقط.

JAVASCRIPT
// validators/productValidator.js
const Joi = require('joi');

const createProductSchema = Joi.object({
  sku: Joi.string().required().pattern(/^[A-Z0-9-]+$/),
  title: Joi.string().required().min(1).max(200),
  price: Joi.number().required().min(0),
  category: Joi.string().required().valid('Electronics', 'Books', 'Clothing'),
  stock: Joi.number().integer().min(0).default(0)
});

const updateProductSchema = Joi.object({
  title: Joi.string().min(1).max(200),
  price: Joi.number().min(0),
  category: Joi.string().valid('Electronics', 'Books', 'Clothing'),
  stock: Joi.number().integer().min(0)
}).min(1);

const validate = (schema) => (req, res, next) => {
  const { error } = schema.validate(req.body);
  if (error) {
    return res.status(400).json({
      success: false,
      error: { code: 'VALIDATION_ERROR', details: error.details }
    });
  }
  next();
};

module.exports = { createProductSchema, updateProductSchema, validate };
JAVASCRIPT
// routes/products.js
const { createProductSchema, updateProductSchema, validate } = require('../validators/productValidator');

router.post('/', validate(createProductSchema), ctrl.createProduct);
router.put('/:sku', validate(updateProductSchema), ctrl.updateProduct);


6. نقاط نهاية CRUD كاملة

نمط تصميم نقاط نهاية CRUD: يتبع كل مورد نمطًا موحدًا يتألف من خمس نقاط نهاية — القائمة (GET /)، والتفاصيل (GET /:id)، والإنشاء (POST /)، والتحديث (PUT /:id)، والحذف (DELETE /:id). نقاط القرار الرئيسية: ① هل يجب أن تستخدم القائمة ترقيم الصفحات أم المؤشر؟ ② هل يجب أن تستخدم عمليات التحديث PUT أم PATCH؟ ③ هل يجب أن تستخدم عملية الحذف الحذف النهائي أم الحذف المؤقت؟

مصفوفة أذونات نقاط النهاية CRUD: لكل نقطة نهاية متطلبات أذونات مختلفة — 1. GET / (القائمة): عامة أو تتطلب المصادقة (حسب منطق العمل)؛ قوائم منتجات التجارة الإلكترونية عامة، بينما تتطلب قوائم الطلبات المصادقة؛ 2. GET /:id (التفاصيل): نفس سياسة القائمة، لكن قد تتطلب تصفية الأذونات (يمكن للمستخدمين عرض تفاصيل طلباتهم الخاصة فقط)؛ 3. POST / (إنشاء): تتطلب المصادقة؛ وتتطلب بعض الموارد التفويض (على سبيل المثال، يمكن للمسؤولين فقط إنشاء المنتجات)؛ 4. PUT /:id (تحديث): تتطلب المصادقة + مالك المورد أو المسؤول (يمكن للمستخدمين تعديل تعليقاتهم الخاصة فقط)؛ 5. DELETE /:id (حذف): تتطلب المصادقة + مالك المورد أو المسؤول. يجب إجراء عمليات التحقق من الأذونات في البرمجيات الوسيطة (المصادقة + التفويض)؛ بينما يتولى «المتحكم» (Controller) معالجة منطق الأعمال فقط.

قرارات تصميم نقاط نهاية CRUD:

نقطة اتخاذ القرار الخيار أ الخيار ب التوصية
طريقة ترقيم الصفحات page + limit (offset) cursor استخدم "page" للترقيم السطحي و"cursor" للترقيم العميق
طريقة التحديث PUT (استبدال كامل) PATCH (تحديث جزئي) PATCH (أكثر أمانًا؛ لا يمسح الحقول غير المحددة)
طريقة الحذف الحذف الفعلي (إزالة) الحذف المؤقت (علامة isDeleted) الحذف المؤقت (قابل للاستعادة، الامتثال لمتطلبات البيانات)
تنسيق المعرّف ObjectId SKU/Slug مخصص SKU للمستخدمين، وObjectId للاستخدام الداخلي
فرز القوائم حقل واحد حقول متعددة (تركيبة) حقول متعددة (فرز + ترتيب)

تحسين الأداء الذي قام به بوب: في ShopHub، لاحظ بوب أن استعلامات القوائم كانت تُنفَّذ في كل من find وcountDocuments في آن واحد. فاستخدم Promise.all لتنفيذها بشكل متوازٍ — مما أدى إلى تقليل وقت الاستعلام من 200 مللي ثانية + 200 مللي ثانية = 400 مللي ثانية إلى max(200 مللي ثانية، 180 مللي ثانية) = 200 مللي ثانية.

JAVASCRIPT
// controllers/productController.js
const Product = require('../models/Product');

// GET /api/products
exports.list = async (req, res) => {
  const { page = 1, limit = 20, sort = 'createdAt', order = 'desc', category, search } = req.query;

  const query = { isActive: true };
  if (category) query.category = category;
  if (search) query.title = new RegExp(search, 'i');

  const [products, total] = await Promise.all([
    Product.find(query)
      .select('sku title price thumbnail rating')
      .sort({ [sort]: order === 'desc' ? -1 : 1 })
      .limit(limit * 1)
      .skip((page - 1) * limit)
      .lean(),
    Product.countDocuments(query)
  ]);

  res.json({
    success: true,
    data: products,
    meta: { page: +page, limit: +limit, total, pages: Math.ceil(total / limit) }
  });
};

// GET /api/products/:sku
exports.get = async (req, res) => {
  const product = await Product.findOne({ sku: req.params.sku }).lean();
  if (!product) return res.status(404).json({ success: false, error: { code: 'NOT_FOUND' } });
  res.json({ success: true, data: product });
};

// POST /api/products
exports.create = async (req, res) => {
  const product = await Product.create(req.body);
  res.status(201).json({ success: true, data: product });
};

// PUT /api/products/:sku
exports.update = async (req, res) => {
  const product = await Product.findOneAndUpdate(
    { sku: req.params.sku },
    req.body,
    { new: true, runValidators: true }
  );
  if (!product) return res.status(404).json({ success: false, error: { code: 'NOT_FOUND' } });
  res.json({ success: true, data: product });
};

// DELETE /api/products/:sku
exports.remove = async (req, res) => {
  const product = await Product.findOneAndDelete({ sku: req.params.sku });
  if (!product) return res.status(404).json({ success: false, error: { code: 'NOT_FOUND' } });
  res.status(204).send();
};


7. معالجة الأخطاء

استراتيجية معالجة أخطاء واجهة برمجة التطبيقات (API) متعددة الطبقات: لا يقتصر التعامل مع الأخطاء على مجرد «إضافة كتلة try-catch»، بل هو نظام دفاع متعدد الطبقات — حيث تعترض طبقة التحقق من الصحة المدخلات غير الصالحة (4xx)، وتعترض طبقة منطق الأعمال الأخطاء المنطقية (مثل: المنتج غير موجود → 404)، وتعترض طبقة قاعدة البيانات أخطاء النظام (مثل: فقدان الاتصال → 5xx)، بينما تلتقط الطبقة الخارجية جميع الأخطاء غير المتوقعة.

الأخطاء في التصنيف والمعالجة:

مصدر الخطأ مثال رمز الحالة الحل
طبقة التحقق من الصحة فشل التحقق من صحة joi 400 إرجاع تفاصيل الخطأ على مستوى الحقل
طبقة الأعمال المنتج غير موجود 404 إرجاع "المورد غير موجود"
طبقة قاعدة البيانات تعارض المفتاح الفريد 409 إرجاع الحقل المتعارض
طبقة المصادقة رمز غير صالح 401 تم إرجاع "غير مصدق"
مستوى الإذن الدور غير كافٍ 403 لا يوجد إذن
مستوى النظام استثناء غير معروف 500 يُرجع خطأً عامًّا (لم يتم الكشف عن التفاصيل)

مبادئ تصميم البرامج الوسيطة لمعالجة الأخطاء: تُعد البرامج الوسيطة لمعالجة الأخطاء في Express دالة ذات 4 معلمات (err، req، res، next)، ويجب وضعها بعد جميع المسارات. نقاط التصميم الرئيسية: 1. أولوية تصنيف الأخطاء — التحقق بالترتيب التالي: ValidationError → MongoError → JsonWebTokenError → 500 الشامل، لأن أنواع الأخطاء المحددة يمكن أن توفر رموز حالة أكثر دقة؛ 2. عدم الكشف عن تتبع المكدس في بيئة الإنتاج — يتم إرجاع err.stack في بيئة التطوير فقط؛ أما في بيئة الإنتاج، فيتم إرجاع رسالة عامة؛ 3. مستويات التسجيل — يتم تسجيل أخطاء 4xx كـ warn (أخطاء العميل)، ويتم تسجيل أخطاء 5xx كـ error (أخطاء الخادم)؛ 4. سياق الطلب — يجب أن تتضمن سجلات الأخطاء requestId و userId و path لتسهيل استكشاف الأخطاء وإصلاحها.

التدابير الأمنية المتعلقة بردود الأخطاء: تُعد ردود أخطاء واجهة برمجة التطبيقات (API) مصدرًا رئيسيًّا لتسرب المعلومات — 1. لا تقم بإرجاع الرسالة الأولية لأخطاء قاعدة البيانات (التي قد تحتوي على أسماء المجموعات أو عبارات الاستعلام)؛ 2. لا تقم بإرجاع تتبعات المكدس إلا في بيئة التطوير؛ 3. يمكن إرجاع Mongoose ValidationError مباشرةً (أخطاء مستوى الحقول آمنة)، لكن يجب تصفية MongoError (يمكن إرجاع تعارضات المفاتيح الفريدة؛ ويجب إخفاء الباقي)؛ 4. قم بتوحيد تنسيق الأخطاء على النحو التالي: {success: false, error: {code, message, details?}} حتى لا تضطر الواجهة الأمامية إلى تحليل بنية الاستجابة. خلاصة هذا المبدأ: يجب ألا تكشف أي أخطاء من فئة 5xx تفاصيل التنفيذ الداخلية للعميل.

JAVASCRIPT
// middlewares/errorHandler.js
const errorHandler = (err, req, res, next) => {
  console.error(err);

  if (err.name === 'ValidationError') {
    return res.status(400).json({
      success: false,
      error: { code: 'VALIDATION_ERROR', message: err.message, details: err.errors }
    });
  }

  if (err.code === 11000) {
    return res.status(409).json({
      success: false,
      error: { code: 'DUPLICATE_KEY', message: 'Duplicate key', details: err.keyValue }
    });
  }

  res.status(500).json({
    success: false,
    error: { code: 'INTERNAL_ERROR', message: 'Internal server error' }
  });
};


8. تجربة عملية: واجهة برمجة تطبيقات التعليقات

تصميم توجيه الموارد المتداخلة: تعتبر التعليقات تابعة للمنتجات، لذا تم تصميم عنوان URL على النحو التالي: /products/:productId/reviews بدلاً من /reviews?productId=xxx — فالأول أكثر وضوحًا من الناحية الدلالية وأكثر توافقًا مع قواعد REST. ومع ذلك، تستخدم العمليات المباشرة على التعليقات (التحديث/الحذف) عنوان /reviews/:reviewId، نظرًا لأن سياق المنتج غير مطلوب في هذه الحالات.

تصميم واجهة برمجة تطبيقات نظام التعليقات:

نقطة النهاية الطريقة المصادقة الوصف
/products/:productId/reviews GET لا عرض قائمة التقييمات
/products/:productId/reviews POST نعم نشر تقييم
/reviews/:reviewId PUT نعم (المؤلف) تعديل التعليق
/reviews/:reviewId حذف نعم (المؤلف) حذف التعليق
/reviews/:reviewId/like POST نعم الإعجاب/إلغاء الإعجاب
JAVASCRIPT
// routes/reviews.js
router.get('/products/:productId/reviews', ctrl.listReviews);
router.post('/products/:productId/reviews', authenticate, ctrl.createReview);
router.put('/reviews/:reviewId', authenticate, ctrl.updateReview);
router.delete('/reviews/:reviewId', authenticate, ctrl.deleteReview);
router.post('/reviews/:reviewId/like', authenticate, ctrl.likeReview);

مبادئ تصميم نقاط نهاية واجهة برمجة التطبيقات (API): يوضح الجدول أعلاه تصميم نقاط نهاية واجهة برمجة التطبيقات (API) لنظام تقييمات التجارة الإلكترونية — 1. استخدم الأسماء بصيغة الجمع لتسمية الموارد (/products، وليس /product)؛ 2. استخدم الموارد المتداخلة للتعبير عن العلاقات الهرمية (/products/:productId/reviews تشير إلى «تقييمات منتج معين»)؛ 3. تحديد متطلبات المصادقة بوضوح — عمليات القراءة عامة (GET)، بينما تتطلب عمليات الكتابة المصادقة (POST/PUT/DELETE)؛ 4. استخدام مسارات فرعية للأفعال لنقاط النهاية القائمة على الإجراءات (/reviews/:reviewId/like بدلاً من الصيغة الجماعية /likes)؛ 5. استخدام بادئة إصدار متسقة (/api/v1/...، تم حذفها في الجدول).

ثلاث استراتيجيات لإدارة إصدارات واجهة برمجة التطبيقات (API): 1. بادئة عنوان URL (/api/v1/products): النهج الأكثر بديهية والأكثر استخدامًا؛ حيث تكون تغييرات الإصدار واضحة، لكن عناوين URL تصبح أطول؛ 2. رأس الطلب (Header: Api-Version: 1): يظل عنوان URL دون تغيير، لكن يجب على العملاء تعيين رأس الطلب، مما يجعل عملية تصحيح الأخطاء غير مريحة؛ 3. التفاوض على المحتوى (Accept: application/vnd.api+json; version=1): الطريقة الأكثر توافقًا مع REST، لكنها أيضًا الأكثر تعقيدًا؛ ونادرًا ما تُستخدم في المشاريع الفعلية. الاستراتيجية الموصى بها: 1 — سهلة الاستخدام للمطورين، وسهلة الاختبار، ومدعومة أصلاً بواسطة توجيه Nginx. عند ترقية الإصدارات: احتفظ بـ v1 دون تغيير، وأنشئ ملف توجيه جديد لـ v2، وقم بترحيل العملاء تدريجيًا، وأوقف v1 عن العمل بعد تحديد تاريخ انتهاء الصلاحية.

▶ المثال 1: عمليات CRUD للمنتج + ترقيم الصفحات + التحقق من الصحة

JAVASCRIPT
// === Complete Product CRUD API(Includes verification+Pagination+Error Handling)===
const express = require('express');
const Joi = require('joi');
const mongoose = require('mongoose');

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

// Schema
const ProductSchema = new mongoose.Schema({
  sku: { type: String, required: true, unique: true },
  title: { type: String, required: true },
  price: { type: Number, required: true, min: 0 },
  category: { type: String, required: true, enum: ['Electronics', 'Books', 'Clothing'] },
  stock: { type: Number, default: 0, min: 0 }
}, { timestamps: true });
const Product = mongoose.model('Product', ProductSchema);

// Joi Verification
const createSchema = Joi.object({
  sku: Joi.string().required().pattern(/^[A-Z0-9-]+$/),
  title: Joi.string().required().min(1).max(200),
  price: Joi.number().required().min(0),
  category: Joi.string().required().valid('Electronics', 'Books', 'Clothing'),
  stock: Joi.number().integer().min(0).default(0)
});

const validate = (schema) => (req, res, next) => {
  const { error, value } = schema.validate(req.body, { abortEarly: false });
  if (error) return res.status(400).json({ success: false, error: { code: 'VALIDATION_ERROR', details: error.details } });
  req.validated = value;
  next();
};

// CRUD
app.get('/api/products', async (req, res) => {
  const { page = 1, limit = 20, category } = req.query;
  const query = {};
  if (category) query.category = category;
  const [products, total] = await Promise.all([
    Product.find(query).select('sku title price').skip((page-1)*limit).limit(+limit).lean(),
    Product.countDocuments(query)
  ]);
  res.json({ success: true, data: products, meta: { page: +page, limit: +limit, total, pages: Math.ceil(total/limit) } });
});

app.post('/api/products', validate(createSchema), async (req, res) => {
  const product = await Product.create(req.validated);
  res.status(201).json({ success: true, data: product });
});

app.get('/api/products/:sku', async (req, res) => {
  const product = await Product.findOne({ sku: req.params.sku }).lean();
  if (!product) return res.status(404).json({ success: false, error: { code: 'NOT_FOUND' } });
  res.json({ success: true, data: product });
});

app.use((err, req, res, next) => {
  if (err.code === 11000) return res.status(409).json({ success: false, error: { code: 'DUPLICATE_KEY' } });
  res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR' } });
});

mongoose.connect('mongodb://localhost:27017/shopdb').then(() => app.listen(3000));

الناتج: واجهة برمجة تطبيقات (API) كاملة لإدارة عمليات إنشاء (CRUD) المنتج، بما في ذلك التحقق من صحة البيانات باستخدام Joi، والاستعلامات المقسمة إلى صفحات، ومعالجة الأخطاء، وتنسيق استجابة موحد.

▶ المثال 2: واجهة برمجة تطبيقات (API) كاملة للتعليقات وفقًا لمعايير REST + التحقق من الصحة باستخدام joi

JAVASCRIPT
// === 1. validators/reviewValidator.js - joi Verification ===
const Joi = require('joi');

const createReviewSchema = Joi.object({
  productId: Joi.string().required().pattern(/^[0-9a-fA-F]{24}$/),  // ObjectId
  content: Joi.string().required().min(10).max(1000),
  rating: Joi.number().integer().required().min(1).max(5),
  parentId: Joi.string().pattern(/^[0-9a-fA-F]{24}$/).allow(null)
});

const updateReviewSchema = Joi.object({
  content: Joi.string().min(10).max(1000),
  rating: Joi.number().integer().min(1).max(5)
}).min(1);  // At least one field

const validate = (schema) => (req, res, next) => {
  const { error, value } = schema.validate(req.body, { abortEarly: false });
  if (error) {
    return res.status(400).json({
      success: false,
      error: {
        code: 'VALIDATION_ERROR',
        details: error.details.map(d => ({ field: d.path.join('.'), message: d.message }))
      }
    });
  }
  req.validated = value;  // Validated data
  next();
};

module.exports = { createReviewSchema, updateReviewSchema, validate };

// === 2. controllers/reviewController.js ===
const Review = require('../models/Review');
const Product = require('../models/Product');

exports.list = async (req, res, next) => {
  try {
    const { productId } = req.params;
    const { page = 1, limit = 20, sort = '-createdAt' } = req.query;

    const reviews = await Review.find({ productId, parentId: null })
      .populate('userId', 'username avatar')
      .populate({
        path: 'replies',
        populate: { path: 'userId', select: 'username avatar' }
      })
      .sort(sort)
      .skip((page - 1) * limit)
      .limit(+limit)
      .lean();

    const total = await Review.countDocuments({ productId, parentId: null });

    res.json({
      success: true,
      data: reviews,
      meta: { page: +page, limit: +limit, total, pages: Math.ceil(total / limit) }
    });
  } catch (err) { next(err); }
};

exports.create = async (req, res, next) => {
  try {
    // 1. Verify Product Availability
    const product = await Product.findById(req.validated.productId).lean();
    if (!product) {
      return res.status(404).json({
        success: false,
        error: { code: 'PRODUCT_NOT_FOUND' }
      });
    }

    // 2. Verify that the parent comment exists(If this is a reply)
    if (req.validated.parentId) {
      const parent = await Review.findById(req.validated.parentId);
      if (!parent) {
        return res.status(404).json({
          success: false,
          error: { code: 'PARENT_REVIEW_NOT_FOUND' }
        });
      }
    }

    // 3. Create a Review
    const review = await Review.create({
      ...req.validated,
      userId: req.user._id
    });

    // 4. Update Product Ratings
    await updateProductRating(req.validated.productId);

    await review.populate('userId', 'username avatar');

    res.status(201).json({ success: true, data: review });
  } catch (err) { next(err); }
};

exports.update = async (req, res, next) => {
  try {
    const review = await Review.findOneAndUpdate(
      { _id: req.params.reviewId, userId: req.user._id },  // Only the author can edit this.
      { $set: { ...req.validated, isEdited: true } },
      { new: true, runValidators: true }
    ).lean();

    if (!review) {
      return res.status(404).json({
        success: false,
        error: { code: 'NOT_FOUND_OR_NO_PERMISSION' }
      });
    }

    res.json({ success: true, data: review });
  } catch (err) { next(err); }
};

exports.remove = async (req, res, next) => {
  try {
    const review = await Review.findOneAndDelete({
      _id: req.params.reviewId,
      userId: req.user._id
    });
    if (!review) {
      return res.status(404).json({
        success: false,
        error: { code: 'NOT_FOUND_OR_NO_PERMISSION' }
      });
    }
    await updateProductRating(review.productId);
    res.status(204).send();
  } catch (err) { next(err); }
};

// === 3. routes/reviews.js ===
const router = require('express').Router();
const ctrl = require('../controllers/reviewController');
const { authenticate } = require('../middlewares/auth');
const { createReviewSchema, updateReviewSchema, validate } = require('../validators/reviewValidator');

router.get('/products/:productId/reviews', ctrl.list);
router.post('/products/:productId/reviews',
  authenticate, validate(createReviewSchema), ctrl.create);
router.put('/reviews/:reviewId',
  authenticate, validate(updateReviewSchema), ctrl.update);
router.delete('/reviews/:reviewId',
  authenticate, ctrl.remove);

module.exports = router;

// === 4. Test API ===
// curl http://localhost:3000/api/products/507f1f77bcf86cd799439021/reviews
// curl -X POST http://localhost:3000/api/products/507f1f77bcf86cd799439021/reviews \
//   -H "Authorization: Bearer <token>" \
//   -H "Content-Type: application/json" \
//   -d '{"content":"Great product!","rating":5}'

الناتج: واجهة برمجة تطبيقات (API) كاملة للتعليقات تعتمد على نمط RESTful وتدعم عرض قائمة التعليقات وإنشاءها وتحديثها وحذفها؛ ويضمن التحقق من صحة البيانات (joi validation) صحة البيانات؛ كما تحمي المصادقة أذونات المؤلف.

أنماط التصميم لطبقة وحدة التحكم: يوضح الكود أعلاه أنماط التصميم القياسية لطبقة وحدة التحكم — 1. تتوافق كل دالة مُصدَّرة مع نقطة نهاية مسار (exports.list → GET، exports.create → POST)؛ 2. يتم تغليف جميع العمليات غير المتزامنة في كتلة try-catch، ويتم التعامل مع أي استثناء (catch) بشكل موحد عن طريق تمرير الخطأ إلى البرمجيات الوسيطة لمعالجة الأخطاء عبر next(err)؛ 3. العودة المبكرة عند فشل التحقق من الصحة (404/403) لمنع الدخول إلى منطق الأعمال؛ 4. إضافة تصفية الأذونات إلى شروط الاستعلام (userId: req.user._id يضمن أن المستخدمين يمكنهم تعديل تعليقاتهم الخاصة فقط)؛ 5. استخدام lean() لتقليل العبء الإضافي لوثائق Mongoose (لا تتطلب سيناريوهات القراءة فقط تتبع التغييرات في Mongoose). تحافظ هذه الأنماط على بساطة وحدة التحكم — التحقق من الصحة → الاستعلام → الاستجابة — حيث يتراوح حجم كل دالة بين 10 و20 سطراً.

أنماط دمج المسارات والبرمجيات الوسيطة: توضح تعريفات المسارات استراتيجيات دمج البرمجيات الوسيطة — 1. البرمجيات الوسيطة المشتركة (router.use(authenticate)): تتطلب جميع المسارات المصادقة؛ 2. البرمجيات الوسيطة على مستوى المسار (validate(createReviewSchema)): تتطلب مسارات محددة فقط التحقق من الصحة؛ 3. ترتيب تنفيذ البرامج الوسيطة: authenticate → validate → controller، مرتبة حسب التبعية (يعتمد التحقق من الصحة على نتيجة المصادقة req.user)؛ 4. البرامج الوسيطة الشرطية: بعض المسارات لا تتطلب مصادقة (على سبيل المثال، GET list)، لذا يتم وضعها قبل authenticate أو استخدام برامج وسيطة اختيارية للمصادقة. يجعل نمط التركيب هذا مكدس البرامج الوسيطة لكل مسار واضحًا وسهل القراءة.

▶ مثال 3: واجهة برمجة تطبيقات RESTful كاملة مع الترقيم والتصفية(الصعوبة ⭐⭐)

JAVASCRIPT
// واجهة برمجة تطبيقات المنتجات لـ ShopHub
const express = require('express');
const router = express.Router();
const Product = require('../models/Product');

// قائمة المنتجات مع الترقيم والتصفية والفرز
router.get('/', async (req, res) => {
  const { page = 1, limit = 20, category, minPrice, maxPrice, sort = 'createdAt', order = 'desc' } = req.query;

  // بناء الاستعلام
  const query = { isActive: true };
  if (category) query.category = category;
  if (minPrice || maxPrice) {
    query.price = {};
    if (minPrice) query.price.$gte = parseFloat(minPrice);
    if (maxPrice) query.price.$lte = parseFloat(maxPrice);
  }

  // تنفيذ الاستعلام بالتوازي
  const [products, total] = await Promise.all([
    Product.find(query)
      .select('sku title price thumbnail rating')
      .sort({ [sort]: order === 'desc' ? -1 : 1 })
      .skip((page - 1) * limit)
      .limit(parseInt(limit))
      .lean(),
    Product.countDocuments(query)
  ]);

  res.json({
    success: true,
    data: products,
    meta: {
      page: parseInt(page),
      limit: parseInt(limit),
      total,
      pages: Math.ceil(total / limit)
    }
  });
});

// الحصول على منتج واحد
router.get('/:sku', async (req, res) => {
  const product = await Product.findOne({ sku: req.params.sku }).lean();
  if (!product) {
    return res.status(404).json({
      success: false,
      error: { code: 'PRODUCT_NOT_FOUND' }
    });
  }
  res.json({ success: true, data: product });
});

module.exports = router;

// اختبار API:
// GET /api/products?category=Electronics&minPrice=100&maxPrice=1000&sort=price&order=asc

الإخراج:

JSON
{
  "success": true,
  "data": [
    { "sku": "PHONE-001", "title": "هاتف ذكي X", "price": 599, "rating": 4.5 },
    { "sku": "LAPTOP-001", "title": "حاسوب محمول Pro", "price": 1299, "rating": 4.8 }
  ],
  "meta": { "page": 1, "limit": 20, "total": 2, "pages": 1 }
}

❓ أسئلة شائعة

س كيف أختار بين PUT و PATCH؟
ج تعمل PUT على استبدال المورد بالكامل (تُفقد الحقول غير المحددة)، بينما تقوم PATCH بإجراء تحديث جزئي. يُنصح باستخدام PATCH.
س هل يجب عليّ استخدام page أم cursor لترقيم الصفحات؟
ج استخدم page لترقيم الصفحات السطحي وcursor لترقيم الصفحات العميق (لضمان أداء متسق).
س أيهما أفضل، joi أم express-validator؟
ج joi أكثر قوة (على غرار Schema)، في حين أن express-validator أسهل في الدمج.

📖 ملخص


📝 تمارين

  1. تمرين أساسي (⭐): قم بتنفيذ مسارات لطرق HTTP الست (عمليات CRUD الخاصة بالمنتجات).
  2. المشكلة الأساسية (⭐): تنفيذ التحقق من صحة الطلبات باستخدام Joi (المطلوب، الطول، التعداد).
  3. تمرين متقدم (⭐⭐): قم بتطبيق تنسيق استجابة موحد (نجاح/بيانات/معلومات تعريفية/خطأ).
  4. تمرين متقدم (⭐⭐): قم بتنفيذ واجهة برمجة تطبيقات (API) كاملة للتعليقات (CRUD + الإعجابات + معالجة الأخطاء).
  5. التحدي (⭐⭐⭐): واجهة برمجة تطبيقات (API) متكاملة للتجارة الإلكترونية (المنتجات + التقييمات + الطلبات + المستخدمون + المصادقة باستخدام JWT).
Web-Tutorial.com

فريق Web-Tutorial التقني

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

100%