انتقال للمقال
وقت القراءة: ≈ 20 دقيقة (بمعدل فنجان واحد من القهوة 😊)

دليل شامل لـ HTTP Status Codes

السلام عليكم ورحمة الله وبركاته

المقدمة

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

هذا بالضبط ما تفعله الـ HTTP Status Codes
هي أرقام يرسلها الـ Server للـ Client لتوضيح نتيجة الطلب الذي قام به

فعندما ترسل طلبًا للـ Server سواء لإحضار بيانات أو إنشاء مستخدم جديد أو حذف مقالة
الـ Server لن يرد عليك ببيانات فقط، بل سيرفق معها رقم يوضح حالة الطلب
هل كل شيء تمام ؟ أم هناك مشكلة ؟ وما نوع هذه المشكلة ؟

لماذا نحتاج الـ Status Codes ؟

تخيل معي السيناريو التالي:

أنت كـ Frontend تريد إحضار بيانات مستخدم معين، فترسل طلبًا للـ API
الـ Server يرد عليك بهذه الرسالة فقط:

{
  "message": "Error"
}

حسنًا، حدث خطأ... لكن ما هو الخطأ ؟

  • هل المستخدم غير موجود ؟
  • هل أنا غير مسجل في النظام ؟
  • هل ليس لدي صلاحية للوصول لهذا المستخدم ؟
  • هل الـ Server نفسه به مشكلة ؟
  • هل أرسلت البيانات بشكل خاطئ ؟

أنت هنا لن تعرف أبدًا والـ Frontend لن يستطيع التعامل مع الخطأ بشكل صحيح

الآن تخيل أن الـ Server أرسل لك رقم 404 مع الرسالة:

// Status Code: 404
{
  "status": "error",
  "message": "User not found"
}

الآن أنت تعرف بالضبط أن المستخدم غير موجود
وتستطيع أن تعرض للمستخدم رسالة مناسبة مثل "عذرًا، هذا المستخدم غير موجود"

أو لو أرسل لك 401:

// Status Code: 401
{
  "status": "error",
  "message": "Please login first"
}

هنا تعرف أنك غير مسجل الدخول، فتقوم بتوجيه المستخدم لصفحة تسجيل الدخول

هذا هو جمال الـ Status Codes، كل رقم له معنى محدد ومتفق عليه
وهذا بدوره يسهل التواصل بين الـ Frontend والـ Backend بشكل كبير

هذه الأرقام تم الاتفاق عليها في بروتوكول الـ HTTP، وكل رقم له معنى محدد
وبعض الأرقام لم يتم الاتفاق على معنى لها حتى الآن وبقيت لاستخدامات مستقبلية

نحن هنا لنغطي أهم وأشهر هذه الأرقام التي ستحتاجها كـ Frontend أو Backend لفهم الـ RESTful API

تصنيف الـ Status Codes

تنقسم رموز الحالة إلى خمس فئات رئيسية، كل فئة تبدأ برقم معين
وهذا التقسيم يسهل عليك فهم طبيعة الرد حتى لو لم تكن تعرف الرقم بالتحديد

الفئة النطاق المعنى
1xx 100-199 Informational تم استلام الطلب وجاري المعالجة
2xx 200-299 Success تم استلام الطلب وفهمه وقبوله بنجاح
3xx 300-399 Redirection يجب اتخاذ إجراء إضافي لإكمال الطلب
4xx 400-499 Client Error الطلب يحتوي على خطأ أو لا يمكن تنفيذه
5xx 500-599 Server Error حدثت مشكلة غير متوقع في الـ Server

فلو رأيت رقمًا يبدأ بـ 2 فاعلم أن الأمور على ما يرام
ولو رأيت رقمًا يبدأ بـ 4 فاعلم أن هناك خطأ من الـ Client
ولو رأيت رقمًا يبدأ بـ 5 فاعلم أن المشكلة من الـ Server نفسه

1xx رموز المعلومات - Informational

هذه الفئة نادرًا ما تتعامل معها بشكل مباشر في الـ RESTful API
هى أشبه برسائل داخلية بين الـ Client والـ Server أثناء عملية نقل البيانات ليخبر كل منهما الآخر بأنه على المسار الصحيح أو مستعد لاستقبال المزيد من البيانات

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

2xx رموز النجاح - Success

هذه أهم فئة ستتعامل معها وأكثرها استخدامًا
عندما ترى رقمًا يبدأ بـ 2، اعلم أن طلبك تم بنجاح

200 OK

الأكثر شيوعًا واستخدامًا وييدل على أنه تم تنفيذ العملية بنجاح وسيرسل الـ Server بيانات كنتيجة للعملية في الـ Body الخاص بالـ Response

لنرى مثالًا عمليًا:

// طلب إحضار بيانات مستخدم
async function getUser() {
  const response = await fetch("https://api.example.com/users/1");
  const data = await response.json();

  console.log(response.status); // 200
  console.log(data); // بيانات المستخدم
}

الرد من الـ Server:

// Status Code: 200 OK
{
  "status": "success",
  "code": 200,
  "data": {
    "id": 1,
    "name": "Ahmed",
    "email": "ahmed@domain.com",
    "age": 25
  }
}

متى نستخدمها ؟

  • GET: عند إحضار بيانات بنجاح والبيانات موجودة في الـ Body
  • POST: عندما يقوم الـ Server بتنفيذ عملية معينة دون إنشاء شيء ويريد إرجاع بيانات في الـ Body
  • PUT أو PATCH: عند تعديل شيء ما أو القيام بعملية معينة وتريد إرجاع بعض البيانات في الـ Body
  • DELETE: عند حذف شيء ما وتريد إرجاع بيانات العنصر المحذوف في الـ Body

ملحوظة: ستجدني أكرر جملة وتريد إرجاع بيانات في الـ Body كثيرًا
بسبب أن الفرق بين 200 و 204 هو وجود بيانات في الـ Body من عدمه
فكلاهما يستخدمان لنفس الشيء لكن الفرق أن
200 يخبرك بأن العملية نجحت مع وجود بيانات في الـ Body الخاص بالـ Response
204 يخبرك بأن العملية نجحت ولكن لا توجد بيانات في الـ Body الخاصة بالـ Response

201 Created

تم إنشاء بيانات جديدة بنجاح
تُستخدم غالبًا مع POST عند إضافة عنصر جديد لقاعدة البيانات

الفرق الجوهري بين 200 و 201:

  • الـ 200 تقول: "تم تنفيذ طلبك بنجاح"
  • الـ 201 تقول: "تم إنشاء شيء جديد"

لنرى مثالًا عمليًا للـ 201 Created:

// طلب إنشاء مستخدم جديد
async function createUser() {
  const response = await fetch("https://api.example.com/users", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      name: "Ali",
      email: "ali@domain.com",
      age: 30,
    }),
  });

  console.log(response.status); // 201
}

الرد من الـ Server:

// Status Code: 201 Created
{
  "status": "success",
  "code": 201,
  "message": "User created successfully",
  "data": {
    "id": 25,
    "name": "Ali",
    "email": "ali@domain.com",
    "age": 30,
    "createdAt": "2024-12-07T10:30:00Z"
  }
}

لاحظ أن الـ Server أضاف معلومات إضافية مثل الـ id و createdAt
هذه معلومات لم يرسلها الـ Frontend لكن الـ Server أنشأها تلقائيًا
بمعنى أنه من المهم أن يرسل لك الـ Server البيانات الجديدة التي تم إنشاؤها في الـ Body
لأنها قد تحتوي على معلومات زائدة ومهمة لم تكن من ضمن البيانات التي أرسلتها للـ Server

نستخدم 201 Created في الحالات التالية:

  • POST: عند إنشاء بيانات جديدة بنجاح مثل مستخدم جديد، مقالة جديدة، إلخ ...
  • PUT: إذا كان العنصر المراد تعديله غير موجود وتم إنشاؤه بدلًا من ذلك

ملحوظة: عند إرسال 201، يُفضل إضافة header باسم Location يحتوي على رابط الـ Resource الجديد الذي تم إنشاؤه

HTTP/1.1 201 Created
Location: https://api.example.com/users/25

202 Accepted

هذا الرمز مميز ومختلف عن البقية
بحيث أنه يرمز إلى أن الطلب تم استلامه بنجاح، لكن لم يُنفذ بعد، وهى حاليًا تُنفذ في الخلفية
بمعنى أن الـ Server استلم الطلب ويؤكد استلامه، لكنه لم ينتهى من تنفيذ العملية بعد

لماذا قد نحتاج هذا ؟

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

بدلًا من ذلك، الـ Server يستلم الطلب ويرد بـ 202 ليؤكد الاستلام ويبدأ التنفيذ في الخلفية

// إرسال نشرة بريدية لكل المشتركين
async function sendNewsletter(subject, content) {
  const response = await fetch("https://api.example.com/newsletter/send", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      subject,
      content,
    }),
  });

  console.log(response.status); // 202
}

الرد من الـ Server:

// Status Code: 202 Accepted
{
  "status": "accepted",
  "code": 202,
  "message": "Newsletter is being sent to 10,000 subscribers"
}

ملحوظة: الـ 202 لا تعني أن العملية نجحت، بل تعني أنها بدأت
قد تفشل العملية لاحقًا أو قد تنجح، فالـ 202 تعني فقط أن الطلب تم استلامه وبدأ التنفيذ لا أكثر ولا أقل

أحيانًا قد يحتاج المستخدم إلى معرفة نتيجة العملية التي بدأت في الخلفية
في هذه الحالة يمكن للـ Server أن يرسل notification للمستخدم عند انتهاء العملية ويعلمه بالنتيجة سوواء بالنجاح أو الفشل
أو يمكن أن يتم استخدام نظام webhooks أو websockets لإعلام الـ Client بالنتيجة
أو أي وسيلة أخرى مناسبة حسب طبيعة المشروع

204 No Content

تم تنفيذ العملية بنجاح، لكن لا توجد بيانات لإرجاعها

ما الفرق بينها وبين 200 ؟

  • الـ 200 تقول: "نجحت العملية، وسأرسل لك البيانات الناتجة"
  • الـ 204 تقول: "نجحت العملية، ولا يوجد شيء لأرسله لك"

المثال الأشهر هو حذف عنصر:

// حذف مستخدم
async function deleteUser(userId) {
  const response = await fetch(`https://api.example.com/users/${userId}`, {
    method: "DELETE",
  });

  console.log(response.status); // 204
}

عندما تحذف شيئًا، ماذا سترجع ؟ الشيء المحذوف ؟
غالبًا الإجابة لا، لذلك الـ 204 هي الخيار الأمثل هنا في حالة عندما لا تحتاج لإرجاع أي بيانات في الـ Body الخاص بالـ Response

متى تستخدمها ؟

  • DELETE: عند حذف شيء ما بنجاح ولا تريد إرجاع بيانات العنصر المحذوف
  • PUT أو PATCH: عند تعديل شيء ما بنجاح ولا تريد إرجاع البيانات المعدلة
  • POST: عند تنفيذ عملية معينة بنجاح ولا توجد بيانات لإرجاعها مثل تسجيل الخروج

كما قلت الفرق الوحيد بين 200 و 204 هو وجود بيانات في الـ Body من عدمه
بالتالي لو أردنا إرجاع البيانات المحذوفة مع الـ DELETE ففي هذه الحالة سنرسل 200 وليس 204

3xx رموز إعادة التوجيه - Redirection

هذه الفئة تخبر الـ Client بأن الـ Resource المطلوب موجود في مكان آخر
أو أنه يحتاج اتخاذ إجراء إضافي للوصول إليه

301 Moved Permanently

الـ Resource انتقل بشكل دائم لعنوان جديد
هذا يعني أن الرابط القديم لم يعد صالحًا ويجب استخدام الرابط الجديد دائمًا

لنفترض أن موقعك كان على http://old-site.com وانتقل إلى https://new-site.com
بالتالي عندما يحاول المستخدم الوصول لـ http://old-site.com/articles/5
سيتم تحويله تلقائيًا إلى https://new-site.com/articles/5 وسيجد الـ Status Code يساوي 301 في الـ Response

302 Found

الـ Resource انتقل بشكل مؤقت لعنوان آخر
هذا يعني أن الرابط الأصلي لا يزال صالحًا ويجب استخدامه في المستقبل

لنفترض أن موقعك تحت الصيانة وتريد توجيه المستخدمين لموقع أخر مؤقتًا أو لصفحة موقوف للصيانة
هنا عندما يحاول المستخدم الوصول إلى موقعك سيجد نفسه يتم توجيهه إلى موقع أخر أو رابط أخر مؤقتًا
مع Status Code يساوي 302 في الـ Response

ليس شرطًا أن يكون الموقع الآخر هو موقعك، قد يكون صفحة صيانة داخل موقعك نفسه
أو أنك تدخل إلى صفحة منتج ما، لكن المنتج غير متوفر حاليًا فتوجهك لصفحة أخرى مؤقتًا

303 See Other

يُستخدم لتوجيه الـ Client لرابط آخر باستخدام GET بعد تنفيذ الطلب بنجاح
تخيل معي أن المستخدم يقوم بإرسال طلب شراء لمنتج معين
هنا لدينا HTML Form يقوم المستخدم بملئه ثم يضغط على زر إرسال الطلب
هنا سيقوم الـ Client بإرسال طلب POST إلى الـ Server

POST /api/orders
{ "product": "laptop", "quantity": 1 }

هنا سيقوم الـ Server بإنشاء الطلب بنجاح ويخزنه في جدول الـ Orders في قاعدة البيانات بـ id معين وليكن 12345
هنا نحتاج من الـ Client أن يوجه المستخدم لصفحة تعرض له تفاصيل الطلب الخاص به

لكن المشكلة هنا ليست فقط أن الـ Client لا يعرف الـ id الخاص بالطلب الجديد
وإنما المشكلة الأهم هي تجنب إعادة إرسال طلب POST مرة أخرى في حال قام المستخدم بتحديث الصفحة، أي قام بـ Refresh للصفحة

فلو قام الـ Server بإرجاع 201 Created مع بيانات الطلب الجديد في الـ Body ثم قام المستخدم بتحديث الصفحة
هنا سيقوم المتصفح بإعادة إرسال طلب POST مرة أخرى، بالتالي سيؤدي إلى إنشاء طلب شراء مكرر

لذلك يقوم الـ Server بإرجاع 303 See Other مع Location في الـ headers يشير إلى رابط صفحة تفاصيل الطلب الجديد:

HTTP/1.1 303 See Other
Location: /orders/12345

الميزة الأساسية في 303 See Other أنه يفرض على المتصفح أن يقوم بطلب جديد من نوع GET
حتى وإن كان الطلب الأصلي POST, بالتالي سيقوم المتصفح بطلب GET /orders/12345 لعرض صفحة تأكيد الطلب
وأي تحديث للصفحة بعد ذلك سيعيد تنفيذ طلب GET فقط دون إعادة إرسال طلب الشراء مرة أخرى


ما شرحناه للتو هو مشكلة شائعة في المشاريع التي تقوم على الـ Server Side Rendering وتستخدم الـ HTML Forms
والحل هو اسلوب يسمى Post/Redirect/Get (PRG)
وهو كما يوحي فهو يقوم بثلاث خطوات:

  • Post: المستخدم يرسل طلب POST لإنشاء شيء ما
  • Redirect: الـ Server يعيد توجيه المستخدم إلى صفحة أخرى باستخدام 303 See Other
  • Get: المتصفح يقوم بعمل طلب GET للصفحة الجديدة في حال حدوث Refresh لصفحة الـ HTML Form

ملحوظة: غالبًا في المشاريع التي تعتمد على الـ RESTful API بشكل كامل يفضل استخدام 201 Created بدلًا من 303 See Other
أما إذا كان المشروع على نفس الـ Domain/Origin ويستخدم الـ HTML Forms والـ Backend هو Server Side Rendering يقوم بعمل Rendering للصفحات و يقوم بعمل Redirecting للصفحات فهنا يكون استخدام 303 See Other هو الأنسب لتطبيق فكرة الـ Post/Redirect/Get Pattern

304 Not Modified

الـ Resource لم يتغير منذ آخر مرة طلبته
هذا الرمز يُستخدم مع الـ Caching غالبًا

كيف يعمل ؟

  1. الـ Client يفتح صفحة المنتج لأول مرة
  2. يحصل على بيانات المنتج مع Status Code يساوي 200
    ويخزن نسخة من هذه البيانات في الـ Cache من ناحية الـ Client أو من الـ Proxy Server مثل CDN
  3. في المرة التالية التي يفتح فيها نفس الصفحة، يرسل طلبًا مع header يسمى If-Modified-Since أو ETag
    ليخبر الـ Server بآخر نسخة مخزنة لديه
  4. إذا لم يتغير المنتج، يرد الـ Server بـ Status Code يساوي 304 Not Modified
    مما يخبر الـ Client بأنه يمكنه استخدام النسخة المخزنة في الـ Cache دون الحاجة لعمل Query جديد على قاعدة البيانات أو إعادة إرسال البيانات من الـ Server
  5. إذا تغير المنتج، يرد الـ Server بـ 200 OK مع البيانات الجديدة

307 Temporary Redirect

هذا الرمز يشبه 302 إلى حد كبير، لكن مع فرق مهم جدًا جعله بديل أفضل عن 302 في كثير من الحالات
الـ 307 Temporary Redirect يعني أن الـ Resource انتقل بشكل مؤقت إلى عنوان آخر مع الحفاظ على نفس الـ HTTP Method دون تغييره

بمعنى أننا في 302 على سبيل المثال لو أرسلنا POST وتم إعادة التوجيه
فسيتحول الـ Client الطلب إلى GET في الرابط الجديد

أما مع 307، فلو أرسلنا POST سيتم إعادة إرسال POST في الرابط الجديد
على عكس 302 الذي يغير الـ Method إلى GET

308 Permanent Redirect

هذا الرمز هو النسخة الحديثة من 301 Moved Permanently
وهو يقوم بنفس الشيء لكن مع نفس الفرق الجوهري الذي بين 302 و 307

الـ 308 Permanent Redirect يعني أن الـ Resource انتقل بشكل دائم إلى عنوان جديد مع الحفاظ على نفس الـ HTTP Method دون تغييره

بمعنى أننا في 301 على سبيل المثال لو أرسلنا POST وتم إعادة التوجيه
فسيتحول الـ Client الطلب إلى GET في الرابط الجديد

أما مع 308، فلو أرسلنا POST سيتم إعادة إرسال POST في الرابط الجديد
على عكس 301 الذي يغير الـ Method إلى GET

4xx رموز أخطاء من الـ Client

هذه الفئة تخبر الـ Client بأن هناك خطأ من ناحيته
إما أن الطلب غير مفهوم أو لا يمكن تنفيذه

وهذه الفئة من أهم الفئات التي يجب أن تفهمها جيدًا
لأن استخدام الرمز الصحيح يسهل على الـ Frontend التعامل مع الأخطاء

400 Bad Request

الطلب نفسه غير صحيح أو غير مفهوم للـ Server
قد يكون هناك خطأ في صيغة البيانات أو في شكل الطلب بحيث لا يستطيع الـ Server تفسيره

على سبيل المثال:

  • إذا كان الـ JSON غير صحيح من ناحية الصياغة أي أنه Syntax Error
    مثل وجود فاصلة زائدة أو نقص في الأقواس
  • أو أن الـ Server يتوقع بيانات معينة مثل name، email، password
    لكن الـ Client أرسل جزءًا فقط منها بشكل غير كافٍ لفهم الطلب
  • أو أن نوع البيانات المرسلة غير قابل للفهم أصلًا مثل أن الـ Server يتوقع age كرقم وليكن 25
    لكن الـ Client أرسل نص "خمسة وعشرون"

مثال للرد من الـ Server:

// Status Code: 400 Bad Request
{
  "status": "error",
  "code": 400,
  "message": "Invalid request body",
  "errors": [
    {
      "field": "email",
      "message": "Email is required"
    },
    {
      "field": "age",
      "message": "Age must be a number"
    }
  ]
}

لاحظ أن الـ Server الجيد يوضح بالتفصيل ما هي المشكلة
ولا يكتفي بإرسال Bad Request فقط ويترك الـ Client يخمن بنفسه

غالبًا ما يستخدم 400 كرمز عام وافتراضي يحدث لأي خطأ من جهة الـ Client
لكن من الأفضل استخدام رموز أكثر تحديدًا مثل 401، 403، 404، 422 حسب نوع الخطأ

401 Unauthorized

المستخدم غير معروف للـ Server
بالتالي يجب اعادة تسجيل الدخول أو إرسال Token صالح
أو إرسال أي وسيلة تعرف الـ Server بهوية المستخدم

غالبًا ما يستخدم هذا الرمز مع الـ APIs التي تعتمد على الـ Token أو الـ JWT
ويحدث عندما لا يرسل الـ Client الـ Token أو يرسل Token غير صحيح أو منتهي الصلاحية
بالتالي الـ Server لا يستطيع التحقق من هوية المستخدم
فيقوم الـ Server بإرسال 401 مع رسالة توضح أنه يجب تسجيل الدخول أولًا

// Status Code: 401 Unauthorized
{
  "status": "error",
  "code": 401,
  "message": "Authentication required. Please login to access this resource."
}

بالتالي سيقوم الـ Client بتوجيه المستخدم لصفحة تسجيل الدخول لإعادة إنشاء Token جديد

403 Forbidden

المستخدم معروف للـ Server لكن ليس لديه صلاحية للوصول لهذا الـ Resource
بمعنى أن الهوية صحيحة لكن الصلاحيات غير كافية

// Status Code: 403 Forbidden
{
  "status": "error",
  "code": 403,
  "message": "You do not have permission to access this resource."
}

الفرق الجوهري بين 401 و 403:

  • 401: مشكلة هوية، بمعنى أن المستخدم غير معروف
  • 403: مشكلة صلاحيات، بمعنى أن المستخدم معروف لكن ممنوع عليه الوصول لهذا الـ Resource

ملحوظة: لمزيد من التوضيح يمكن قراءة الفرق بين الـ 401 و 403 في الـ RESTful API


أحيانًا يتم إرسال 404 Not Found بدلًا من 403 Forbidden لأسباب أمنية لكي لا نعطي معلومات عن وجود الـ Resource من عدمه
مثلاً لو حاول مستخدم عادي الوصول إلى صفحة إدارة المستخدمين
لماذا نعطيه معلومة بأن الصفحة موجودة لكنه ممنوع من الوصول لها ؟
لأننا لو أرسلنا له 403 Forbidden فسيعرف أن الصفحة موجودة لكنه ممنوع من الوصول لها
لذلك نرسل له 404 Not Found ليعتقد أن الصفحة غير موجودة أصلًا

وفي حالة أنه مستخدم يحاول اختراق النظام فأننا نمنعه من معرفة وجود الصفحة من عدمه

404 Not Found

الـ Resource المطلوب غير موجود
لم يتم العثور على البيانات المطلوبة في الـ Server
وغالبًا ما يحدث هذا عندما يكون الـ endpoint غير صحيح أو الـ ID غير موجود في قاعدة البيانات
مثل أن يحاول المستخدم الوصول لصفحة أو منتج غير موجود

// Status Code: 404 Not Found
{
  "status": "error",
  "code": 404,
  "message": "User with id 99999 not found"
}

أيضًا الـ 404 Not Found قد تشير إلى أن الـ resource ليس متاحًا حاليًا
لكنه قد يكون موجودًا في المستقبل أو أنه موجود في مكان آخر
أو أنك أنه محذوف بشكل مؤقت أو أنك لا تملك صلاحية الوصول له

وذكرنا أننا قد نرسل 404 Not Found بدلًا من 403 Forbidden لأسباب أمنية

405 Method Not Allowed

الـ HTTP Method المستخدم غير مسموح لهذا الـ endpoint

لنفترض أن الـ endpoint الخاص بالمستخدمين يقبل GET و POST فقط
لكن المطور قام بإرسال طلب بـ DELETE إلى هذا الـ endpoint

هنا سيرد الـ Server بـ 405 Method Not Allowed

// Status Code: 405 Method Not Allowed
{
  "status": "error",
  "code": 405,
  "message": "Method DELETE not allowed on /api/users",
  "allowedMethods": ["GET", "POST"]
}

409 Conflict

الطلب يتعارض مع بيانات موجودة مسبقًا
بمعنى أن البيانات المرسلة صحيحة لكنها تتعارض مع شيء موجود في قاعدة البيانات

تخيل أنك تحاول تسجيل حساب جديد ب بريد إلكتروني موجود مسبقًا
البريد إلكتروني قد يكون صحيح دون أي خطأ، لكن التعارض يكمن في أنه مستخدم بالفعل

// طلب تسجيل مستخدم جديد
async function registerUser() {
  const response = await fetch("https://api.example.com/register", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      name: "Ahmed",
      email: "ahmed@domain.com",
      password: "123456",
    }),
  });

  console.log(response.status); // 409
}

الرد من الـ Server:

// Status Code: 409 Conflict
{
  "status": "error",
  "code": 409,
  "message": "Email already exists",
  "field": "email",
  "suggestion": "Try logging in or use a different email"
}

هذا الرمز يناسب أي تعارض، مثل محاولة إنشاء حساب مكرر أو تفعيل حساب مفعل بالفعل
أو حتى في حالة race condition مثل حجز آخر تذكرة متاحة من قبل مستخدمين في وقت واحد
فأول مستخدم يحصل على التذكرة، والآخر يحصل على 409 Conflict

410 Gone

الـ Resource كان موجودًا لكنه حُذف نهائيًا ولا يوجد أي إمكانية لاسترجاعه
وهو يشبه 404 Not Found لكن مع فرق جوهري:

  • 404 Not Found: يعني أن الـ Resource غير موجود حاليًا، لكنه قد يكون موجودًا في المستقبل
    أو أنه متواجد في مكان آخر أو يوجد وسيلة لاسترجاعه أو حذف بشكل مؤقت
    ولا يوضح إذا كان الـ Resource كان موجودًا في السابق أم لا
  • 410 Gone: يعني أن الـ Resource حُذف نهائيًا ولن يعود أبدًا مهما حصل
    وأيضًا يوضح أن هذا الـ Resource كان موجودًا في السابق لكنه الآن غير متاح

تخيل أن مستخدمًا يحاول الوصول إلى مقالة قديمة تم حذفها نهائيًا من الموقع
أو حساب مستخدم تم حذفه تمامًا من قاعدة البيانات

// طلب إحضار مقالة محذوفة
async function getArticle() {
  const response = await fetch(
    "https://api.example.com/articles/old-article-2020",
  );

  console.log(response.status); // 410
}

الرد من الـ Server:

// Status Code: 410 Gone
{
  "status": "error",
  "code": 410,
  "message": "This article has been permanently deleted",
  "deletedAt": "2024-01-15T10:30:00Z"
}

هذا يوضح لنا أن المقالة كانت موجودة في السابق لكنها حُذفت نهائيًا، ولا توجد أي طريقة لاسترجاعها


وأيضًا لدواعي أمنية فقد تجد من يستخدم 404 Not Found بدلًا من 410 Gone لعدم إعطاء معلومات أن هذا الـ Resource كان موجودًا في السابق أم لا
لأن الـ 410 Gone تؤكد أن هذا الـ Resource كان موجودًا في السابق
أما الـ 404 Not Found لا تؤكد أن هذا الـ Resource كان موجودًا أم لا

415 Unsupported Media Type

نوع البيانات المرسلة غير مدعوم
الـ Server لا يقبل هذه الصيغة من البيانات، مثل إرسال XML بينما يتوقع JSON
أو أن الموقع يستقبل صورًا بصيغ محددة فقط وأرسلت صيغة غير مدعومة

تخيل معين أن الـ Server يستطيع أن يقوم بعمل import لبيانات وهو يستقبل ملف بصيغة من صيغ الـ excel فقط مثل xlsx
وأنت قمت بإرسال ملف بصيغة csv، هنا سيرد عليك بـ 415 Unsupported Media Type

// طلب رفع ملف بصيغة غير مدعومة
async function importData(file) {
  const formData = new FormData();
  formData.append("file", file);

  const response = await fetch("https://api.example.com/data/import", {
    method: "POST",
    body: formData,
  });

  console.log(response.status); // 415
}

الرد من الـ Server:

// Status Code: 415 Unsupported Media Type
{
  "status": "error",
  "code": 415,
  "message": "Unsupported Media Type",
  "supportedTypes": ["xlsx"]
}

هذا يحدث أيضًا في رفع الملفات، مثل رفع فيديو في مكان يقبل صور فقط
الـ Server يفهم الطلب لكنه يرفض الصيغة، ويتم إخبار الـ Client بالصيغ المدعومة

422 Unprocessable Entity

البيانات وصلت بشكل صحيح لكنها غير صالحة بالنسبة للمشروع أو بالنسبة لشروط الـ Business Logic
بمعنى أن القيم والبيانات صحيحة ويمكن أن تقبل في ظروف أخرى، أو في مشاريع أخرى بدون مشاكل بحسب الـ Business Requirements

أو يمكن أن البيانات سليمة من ناحية الصياغة لكن لا تتوافق مع قواعد التحقق Validation Rules
لأن القيم غير منطقية ولا يمكن قبولها في النظام الخاص بنا
مثل أننا نقبل أسعار المنتجات كأرقام موجبة فقط، لكن تم إرسال سعر سالب

أوأننا استقبلنا اسم منتج يقل عن 3 حروف، أو بريد إلكتروني بصيغة غير صحيحة
وهكذا ...

// طلب إنشاء مستخدم مع بيانات غير منطقية
async function createUser() {
  const response = await fetch("https://api.example.com/users", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      name: "a",
      email: "not-an-email",
      age: -5,
      password: "123",
    }),
  });

  console.log(response.status); // 422
}

الرد من الـ Server:

// Status Code: 422 Unprocessable Entity
{
  "status": "error",
  "code": 422,
  "message": "Validation failed",
  "errors": [
    {
      "field": "name",
      "message": "Name must be at least 3 characters long"
    },
    {
      "field": "email",
      "message": "Invalid email format"
    },
    {
      "field": "age",
      "message": "Age must be a positive number"
    },
    {
      "field": "password",
      "message": "Password must be at least 8 characters"
    }
  ]
}

هذا الرمز مثالي لأخطاء التحقق وهى الـ Validation Errors
لاحظ أن الـ Server كان بإمكانه تخزين هذه البيانات في قاعدة البيانات كما هى دون مشاكل من ناحية الصياغة
لكن هنا ما يحكمنا هو الـ Business Logic والـ Validation Rules الخاصة بالمشروع والتي تمنع قبول هذه القيم
لذالك نستخدم 422 Unprocessable Entity لإبلاغ الـ Client بأن البيانات غير صالحة لهذا المشروع


بعض الناس يستخدمون 400 بدلًا منه، لكن 422 يعد الأدق لأن البيانات صحيحة من ناحية الصياغة ومفهومة، لكن لا يمكن قبولها بسبب قواعد الـ Validation أو الـ Business Logic
قد يختلط على البعض الفرق بين 400 و 422، لكن يمكننا التمييز بينهما كالتالي:

  • 400 Bad Request: البيانات غير صحيحة في الصيغة أو غير مفهومة من الأصل
    ويمكننا اختصار الأمر في جملة وهى أن الـ Server لم يستطع فهم الطلب حتى يعالجه من الأساس
    • الـ JSON الخاصة بالـ Payload غير صالح أو غير مكتمل بشكل يمنع الـ Server من فهمه
    • حقول مفقودة ضرورية مثل عدم إرسال email أو password
    • أنواع بيانات غير قابلة للقراءة مثل نص بدل رقم
  • 422 Unprocessable Entity: البيانات صحيحة من ناحية الصياغة ومفهومة، لكن لا يمكن قبولها بسبب قواعد الـ Validation أو الـ Business Logic
    أي أن الـ Server فهم الطلب لكنه ويمكنه معالجته لكنه يرفض ذلك بسبب الشروط المحكومة والمفروضة عليه
    • البيانات لا تتوافق مع قواعد وشروط المشروع
    • البيانات لا تتوافق مع متطلبات الـ Business
    • البيانات لا تتوافق مع شروط وقواعد الـ Validation

ولا ننسى أيضًا أن الـ 400 يستخدم كخطأ عام لأي مشكلة من جهة الـ Client دون تخصيص
لكن كما قلنا من الأفضل استخدام رموز أكثر تحديدًا مثل 401، 403، 404، 422 حسب نوع الخطأ
ويعد الـ 422 هو الأنسب لأخطاء التحقق الخاصة بالـ Validation على البيانات أو من متطلبات الـ Business

429 Too Many Requests

يستخدم عندما يتم إرسال عدد كبير جدًا من الطلبات في فترة زمنية قصيرة
بالتالي ستجد أن الـ Server يرفض المزيد من الطلبات مؤقتًا لحماية نفسه من الضغط الزائد

تخيل مستخدمًا يحاول تسجيل الدخول بكلمات مرور خاطئة عدة مرات في فترة قصيرة
بعد 5 محاولات، ستجد أن الـ Server يمنع المستخدم من المحاولة مرة أخرى لمدة زمنية معينة

// محاولة تسجيل دخول متعددة
async function login() {
  const response = await fetch("https://api.example.com/login", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      email: "ahmed@domain.com",
      password: "wrong6",
    }),
  });

  console.log(response.status); // 429
}

الرد من الـ Server:

// Status Code: 429 Too Many Requests
{
  "status": "error",
  "code": 429,
  "message": "Too many login attempts. Please try again later."
}

هذا الرمز مهم جدًا في حماية الـ API من الهجمات مثل Brute Force أو DDoS
وهى أنواع من وسائل الهجوم التي تعتمد على إرسال عدد كبير من الطلبات في وقت قصير

فمثلًا تخيل شخص قام بعمل script يرسل آلاف الطلبات في الثانية لمحاولة اختراق النظام
أو لمحاولة سرقة حسابات المستخدمين عن طريق تجربة كلمات مرور كثيرة في وقت قصير

بالتالي يطبق الـ Server مفهوم الـ Rate Limiting ليحد من عدد الطلبات المسموح بها في فترة زمنية معينة
مثل السماح بـ 100 طلب في الدقيقة لكل مستخدم أو لكل عنوان IP
ويمكن تعديل هذه القيم حسب الحاجة
ويمكنه حظر مستخدم معين مؤقتًا إذا تجاوز الحد المسموح به
أو حذفه نهائيًا إذا استمر في المحاولة


لدواعي أمنية لا يعطي الـ Server تفاصيل أكثر عن الحد المسموح به أو مدة الحظر
ستجده يقول لك Please try again later فقط دون تحديد وقت معين
وهذه من باب عدم إعطاء معلومات إضافية قد يستغلها المهاجمون

5xx رموز أخطاء من الـ Server

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

عندما يظهر رقم يبدأ بـ 5، فهذا يعني أن الـ Server نفسه به مشكلة
وغالبًا لا يوجد شيء يمكن للـ Frontend فعله سوى إظهار رسالة خطأ للمستخدم ويجب أن تكون رسالة عامة وغير تقنية مثل "حدث خطأ غير متوقع، يرجى المحاولة لاحقًا"

لذلك إن كنت مطور Frontend، ووجدت رمز خطأ يبدأ بـ 5 فأرسل رسالة إلى صديقك مطور Backend
وإن كانت المشكلة ظهرت في الـ Production فأدعو له بالتوفيق في إيجاد حل سريع أو في إيجاد فرصة عمل جديدة، أيهما أقرب

500 Internal Server Error

الطلب نفسه صحيح لكن حدث خطأ داخلي غير متوقع في الـ Server
قد يكون هناك خطأ في الكود أو في الاتصال بقاعدة البيانات بحيث لا يستطيع الـ Server إكمال العملية

على سبيل المثال:

  • هناك خطأ في الكود البرمجي أدى إلى تعطل العملية مثل قسمة على صفر أو تجاوز الذاكرة المسموح بها
  • هناك Exception لم يتم التعامل معه مثل محاولة الوصول إلى متغير داخل object غير موجود أو قيمته null
  • الـ Server يحاول الاتصال بقاعدة البيانات لكن الاتصال فشل لسبب ما
  • ... وغيرها من الأخطاء الغير متوقعة

مثال للرد من الـ Server:

// Status Code: 500 Internal Server Error
{
  "status": "error",
  "code": 500,
  "message": "Something went wrong on our end"
}

لاحظ أن الـ Server الجيد يوضح رسالة عامة للمستخدم دون تفاصيل حساسة
غالبًا ما يستخدم 500 كرمز عام وافتراضي لأي خطأ داخلي غير متوقع في الـ Server
مثله مثل الـ 400 من جهة الـ Client

غالبًا ما يكون من الصعب تحديد السبب الدقيق للخطأ من جهة الـ Client أو حتى من جهة الـ Backend
لذلك يجب على مطور الـ Backend تسجيل أي خطأ غير متوقع داخل ملفات الـ logs مع تخزين كل البيانات اللازمة
لكي يتمكن من تتبع المشكلة وحلها في حال عدم فصله من الشركة بعد

501 Not Implemented

الميزة أو الخاصية أو الصفحة المطلوبة غير مدعومة أو لم تُنفذ بعد في الـ Server
وسيتم تنفيذها في المستقبل أو أنها غير مدعومة نهائيًا

غالبًا ما يستخدم هذا الرمز مع الـ API التي لا تزال قيد التطوير
ويحدث عندما يكون الـ endpoint موجود لكن الكود الخاص به لم يكتب بعد
بالتالي الـ Server يعرف الطلب لكنه لا يستطيع تنفيذه حاليًا
فيقوم الـ Server بإرسال 501 مع رسالة توضح أن الميزة قادمة قريبًا

// Status Code: 501 Not Implemented
{
  "status": "error",
  "code": 501,
  "message": "This feature is not implemented yet. Stay tuned!"
}

بالتالي سيقوم الـ Client بإظهار رسالة للمستخدم تفيد بأن الميزة غير متوفرة بعد

502 Bad Gateway

تخيل أنك لديك مدونة جميلة وقمت برفعها على Cloudflare أو استخدمت Nginx
وفي هذه الحالة فإن Nginx أو Cloudflare يعملان كـ Reverse Proxy أو Gateway بين المستخدم والـ Server الذي يستضيف المدونة

في حالة حدوث مشكلة في الاتصال بين الـ Reverse Proxy والـ Server الأساسي الخاص بالمدونة فإن الـ Reverse Proxy سيرد على المستخدم بـ 502 Bad Gateway
لأنه لا يستطيع الوصول إلى الـ Server الخاص بالمدونة

503 Service Unavailable

عند استخدام Cloudflare أو Nginx كـ Reverse Proxy للمدونتك الجميلة
فإن الـ Reverse Proxy يقوم بتوجيه الطلبات إلى الـ Server الذي يستضيف مدونتك
وقلنا أنه في حالة حدوث مشكلة في الاتصال بين الـ Reverse Proxy والـ Server الخاص بالمدونة
سيقوم الـ Reverse Proxy بإرجاع 502 Bad Gateway

لكن في بعض الأحيان يكون الـ Server متاحًا لكن غير قادر على استقبال أو معالجة أي طلبات جديدة

هذا قد يحدث عندما يكون الـ Server الأساسي في وضع صيانة مؤقتة، أو يعاني من ضغط وحمل زائد، أو يمر بعملية Restart داخلية تمنعه من قبول الطلبات في تلك اللحظة
وعندما يحاول الـ Reverse Proxy إرسال الطلب ولا يحصل على استجابة تؤكد أن الـ Server الأساسي قادر على العمل، فإنه يعتبر أن الخدمة غير متاحة حاليًا

في هذه الحالة يقوم الـ Reverse Proxy بإرجاع كود 503 Service Unavailable
وهو يشير إلى أن الـ Server الأساسي موجود لكنه غير قادر على معالجة الطلب الآن، وغالبًا يمكن للمستخدم المحاولة مرة أخرى لاحقًا

504 Gateway Timeout

أظن أنك بمجرد قرأت كلمة Timeout فهمت المغزى من الرمز ومتى يستخدم

عند مرور الطلب عبر الـ Reverse Proxy ويقوم بتحويله إلى الـ Server الأساسي الخاص بالمدونة
ثم ينتظر الـ Reverse Proxy وقتًا طويلًا للحصول على استجابة من الـ Server الأساسي
ففي هذه الحالة يقوم الـ Reverse Proxy بإرسال 504 Gateway Timeout
سواء بسبب أن الـ Server الأساسي الخاص بالمدونة استغرق وقتًا أطول من الحد المسموح به للرد أو بسبب بطء شديد في الاستجابة

جدول لتلخيص رموز الـ HTTP Status

الرمز الفئة الاسم بالإنجليزية الوصف والملخص
200 Success OK تم تنفيذ الطلب بنجاح، ويحتوي الرد على بيانات في الـ Body
201 Success Created تم إنشاء Resource جديد بنجاح
202 Success Accepted تم استلام الطلب وبدء معالجته، لكن التنفيذ لم يكتمل بعد، وعادةً ما تُنفَّذ العملية في الخلفية
204 Success No Content تم تنفيذ الطلب بنجاح، ولا يحتوي الرد على بيانات في الـ Body
301 Redirection Moved Permanently الـ Resource انتقل إلى عنوان جديد بشكل دائم، وقد يؤدي ذلك إلى تغيير الـ Method إلى GET
302 Redirection Found الـ Resource انتقل إلى عنوان جديد بشكل مؤقت، وقد يؤدي ذلك إلى تغيير الـ Method إلى GET
303 Redirection See Other توجيه العميل لرابط آخر باستخدام GET بعد نجاح عملية POST
304 Redirection Not Modified الـ Resource لم يتغير منذ آخر طلب، ويجب على العميل استخدام النسخة المخزنة في الـ Cache
307 Redirection Temporary Redirect الـ Resource انتقل بشكل مؤقت لعنوان جديد مع الحفاظ على الـ Method
308 Redirection Permanent Redirect الـ Resource انتقل بشكل دائم لعنوان جديد مع الحفاظ على الـ Method
400 Client Error Bad Request الطلب غير صحيح، غير مفهوم، أو به خطأ في الصياغة مثل Syntax Error
أو يعبر على أي خطأ في الطلب بشكل عام وهو الخيار الافتراضي
401 Client Error Unauthorized المستخدم غير معروف، ويجب عليه تسجيل الدخول أو إرسال Token صحيح، أي أن المشكلة متعلقة بالهوية
403 Client Error Forbidden المستخدم معروف لكن ليس لديه صلاحية للوصول للـ Resource، وهي مشكلة صلاحيات
404 Client Error Not Found الـ Resource المطلوب غير موجود على الـ Server
405 Client Error Method Not Allowed الـ HTTP Method المستخدم مثل DELETE غير مسموح به لهذا المسار
409 Client Error Conflict الطلب صحيح لكنه يتعارض مع حالة الـ Resource الحالية في قاعدة البيانات
410 Client Error Gone الـ Resource كان موجوداً ولكنه حُذف نهائياً ولن يعود
415 Client Error Unsupported Media Type نوع البيانات المرسلة غير مدعوم من قِبل الـ Server، مثل إرسال XML بدلاً من JSON
422 Client Error Unprocessable Entity البيانات سليمة في الصياغة لكنها غير صالحة أو لا تتوافق مع قواعد التحقق المعروفة بـ Validation أو شروط العمل
429 Client Error Too Many Requests تم تجاوز الحد الأقصى المسموح به من الطلبات في فترة زمنية قصيرة بسبب القيود المعروفة بـ Rate Limiting
500 Server Error Internal Server Error حدث خطأ داخلي غير متوقع في الـ Server
501 Server Error Not Implemented الميزة أو الخاصية المطلوبة غير مدعومة أو لم تُنفذ بعد
502 Server Error Bad Gateway الـ Server الوسيط مثل Gateway أو Reverse Proxy تلقى رداً غير صالح من الـ Server الأساسي
503 Server Error Service Unavailable الخدمة غير متاحة مؤقتاً مثل الصيانة، الضغط الزائد، أو إعادة التشغيل
504 Server Error Gateway Timeout الـ Server الوسيط مثل Gateway لم يتلق استجابة من الـ Server الأساسي في الوقت المحدد