طَبِّقْهاتعلّم المبدأ، وطبّقه بيدكمقرر 132 · تقنيات تطوير الواجهات الخلفية
الدرس 23 · الوحدة 10: ربط الكود من جانب الخادم مع واجهات البرمجة (API)

ربط الواجهة الأمامية بالـ API

كيف تتحدث صفحات JavaScript وتطبيقات React إلى واجهتك الخلفية؟ ⏱ نحو 40 دقيقة.

بعد هذا الدرس ستستطيع أن:
  • تميّز بين صفحات يبنيها الخادم وواجهات تطلب API بـ fetch.
  • تربط صفحة HTML/JS بواجهة REST: قراءة وإضافة وحذف بـ fetch و JSON.
  • تعالج رموز الحالة والأخطاء في الواجهة الأمامية.
  • تشرح CORS والطلب التمهيدي، وتضبط ترويساته في PHP لأصول محددة.

١نوعان من الصفحات

حتى الآن كان الخادم يبني صفحة HTML كاملة مع كل طلب. والأسلوب الآخر المنتشر اليوم: الخادم يرسل صفحة ثابتة فيها JavaScript، ثم يطلب JavaScript البيانات من الـ API بـ fetch، ويحدّث الصفحة دون إعادة تحميلها. هكذا تعمل تطبيقات React (مقرر 242) وتطبيقات الجوال (251).

الخادم يبني الصفحة (الدروس السابقة)الواجهة تطلب API
ماذا يرسل الخادم؟HTML جاهزًاJSON فقط
من يبني HTML؟PHP على الخادمJavaScript في المتصفح
بعد إضافة عنصرإعادة تحميل الصفحة كاملةتحديث جزء من الصفحة فقط
مناسب لـالمواقع والمحتوى ومحركات البحثلوحات التحكم والتطبيقات التفاعلية

ولاحظ أن الـ API نفسها من الدرس السابق تخدم الصفحة، وتخدم تطبيق الجوال، وأي عميل آخر. فالواجهة الخلفية الواحدة تخدم واجهات أمامية كثيرة.

٢التطبيق كاملًا: صفحة + API

ملف index.html فيه HTML و JavaScript فقط (من مقرر 131)، وملف api/users.php هو واجهة الدرس 22 دون أي تغيير. شغّل المختبر وجرّب الإضافة والحذف، ثم افتح «📜 السجل»: سترى كل طلب أرسله fetch إلى الخادم:

🧪 مختبر الخادماضغط ▶ تشغيل. الصفحة تحمّل القائمة بـ GET، والنموذج يرسل POST بجسم JSON، وزر الحذف يرسل DELETE. جرّب بريدًا مكررًا أو اسمًا قصيرًا.

٣ثلاث قواعد لـ fetch مع API

  1. fetch لا ترمي خطأ عند 404 أو 422. ترمي فقط إن انقطع الاتصال. فافحص res.ok (صحيح للرموز 200–299) أو res.status بنفسك.
  2. أرسل JSON بترويسته: Content-Type: application/json و JSON.stringify(data)، وإلا لن يفهم الخادم الجسم.
  3. الرد 204 بلا جسم: لا تستدعِ res.json() عليه فيفشل.
async function api(method, url, data) {  const res = await fetch(url, {    method,    headers: data ? { 'Content-Type': 'application/json' } : {},    body: data ? JSON.stringify(data) : undefined,  });  const body = res.status === 204 ? null : await res.json();  if (!res.ok) throw new Error(body?.error || 'HTTP ' + res.status);  return body;} // الاستخدامtry {  const user = await api('POST', 'api/users.php', { name: 'ريم', email: 'r@tvtc.sa' });} catch (e) {  alert(e.message);       // رسالة الخادم نفسها: «البريد مسجّل من قبل»}

٤CORS: حين تكون الواجهة على عنوان آخر

في المثال السابق الصفحة والـ API على العنوان نفسه. لكن أثناء تطوير React تعمل الواجهة على http://localhost:5173 والـ API على http://localhost: «أصلان» Origins مختلفان. والمتصفح يمنع لحماية المستخدمين أن تقرأ صفحة من أصل ردود أصل آخر، إلا إذا سمح الخادم صراحة. هذه السياسة اسمها CORS (مشاركة الموارد بين الأصول).

  1. المتصفح يرسل مع الطلب الترويسة Origin: http://localhost:5173.
  2. إن كان الطلب «غير بسيط» (مثل POST بجسم JSON، أو DELETE) يرسل أولًا طلبًا تمهيديًّا OPTIONS يسأل: هل تسمح؟
  3. الخادم يجيب بترويسات Access-Control-Allow-….
  4. إن وافقت، يرسل المتصفح الطلب الحقيقي ويعطي الرد للصفحة؛ وإلا يحجبه ويظهر خطأ CORS في Console.
<?php$allowed = ['http://localhost:5173', 'https://app.tvtc-demo.sa'];$origin = $_SERVER['HTTP_ORIGIN'] ?? ''; if (in_array($origin, $allowed, true)) {    header("Access-Control-Allow-Origin: $origin");    header('Access-Control-Allow-Methods: GET, POST, DELETE');    header('Access-Control-Allow-Headers: Content-Type, Authorization');    header('Vary: Origin');}if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {    http_response_code(204);      // الطلب التمهيدي: الترويسات تكفي    exit;}header('Content-Type: application/json');echo '{"pong": true}';

الأصل المسموح أخذ ترويسات Access-Control، والأصل الغريب لم يأخذها، فسيحجب متصفح ضحيته الرد. لاحظ أن الخادم ردّ في الحالتين: CORS قيد في المتصفح، لا حماية للخادم من الطلبات المباشرة.

توقّع وتحقّق

fetch إلى API ردّت 404. ماذا يحدث؟

افحص res.ok.

الطلب التمهيدي في CORS يستخدم الطريقة:

Preflight.

من يطبّق سياسة CORS؟

الخادم يسمح، والمتصفح يطبّق.

الترويسة التي يسمح بها الخادم لأصل معيّن:

مع اسم الأصل.

الخلاصة

تحدٍّ برمجي: افتح الواجهة لتطبيق React

متقدم

فريق الواجهة الأمامية يبني تطبيق React على عنوانين، ويحتاج أن يقرأ api/list.php. أضف دعم CORS للأصلين فقط.

المطلوب:
  • الأصلان المسموحان: http://localhost:5173 و https://lab.tvtc-demo.sa.
  • للأصل المسموح: الترويسة Access-Control-Allow-Origin بقيمة الأصل نفسه (لا *)، ومعها Allow-Methods فيها PUT، و Allow-Headers فيها Content-Type.
  • الطلب OPTIONS ← 204 بلا جسم.
  • أصل غير مسموح ← لا ترويسة Access-Control-Allow-Origin إطلاقًا (لكن الرد نفسه يبقى 200).
👀 اعرض الاستجابة المطلوبة
✓ التحقق الآلي: يرسل المختبر هذه الطلبات إلى خادمك بالترتيب ثم يفحص الاستجابات
  1. OPTIONS /api/list.php
  2. GET /api/list.php
  3. GET /api/list.php
  4. GET /api/list.php
  • الكود يعمل بلا أخطاء أو تحذيرات
  • OPTIONS ← 204
  • OPTIONS ← Allow-Origin للأصل نفسه
  • OPTIONS ← Allow-Methods فيها PUT
  • OPTIONS ← Allow-Headers فيها Content-Type
  • GET من lab.tvtc-demo.sa ← مسموح
  • GET ← البيانات كما هي
  • أصل غريب ← الرد 200
  • أصل غريب ← بلا ترويسة Access-Control-Allow-Origin
  • لا تستخدم * (تحقق بقائمة)
  • لا Allow-Origin: *

💡 تلميح 1
الأصل يصل في $_SERVER['HTTP_ORIGIN'].
💡 تلميح 2
افحص OPTIONS بعد إرسال الترويسات، ثم http_response_code(204); exit;

حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.

<?phprequire __DIR__ . '/../db.php';header('Content-Type: application/json; charset=utf-8'); $allowed = ['http://localhost:5173', 'https://lab.tvtc-demo.sa'];$origin = $_SERVER['HTTP_ORIGIN'] ?? '';if (in_array($origin, $allowed, true)) {    header("Access-Control-Allow-Origin: $origin");    header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE');    header('Access-Control-Allow-Headers: Content-Type, Authorization');    header('Vary: Origin');}if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {    http_response_code(204);    exit;} $rows = $pdo->query('SELECT id, name FROM users ORDER BY id LIMIT 3')->fetchAll();echo json_encode($rows, JSON_UNESCAPED_UNICODE);
انتقل إلى تقييم الدرساختيار من متعدد، وصح وخطأ، وأكمل الفراغ، وتتبّع الناتج.ابدأ التقييم ←

📝 معلم؟ أرسل ملاحظة للمؤلف عن هذا الدرس