<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>تصميم واجهات البرمجة | أحمد كمال عمارة</title><link>https://akemara.com/ar/tags/%D8%AA%D8%B5%D9%85%D9%8A%D9%85-%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9/</link><atom:link href="https://akemara.com/ar/tags/%D8%AA%D8%B5%D9%85%D9%8A%D9%85-%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9/index.xml" rel="self" type="application/rss+xml"/><description>تصميم واجهات البرمجة</description><generator>Akemara Kit (https://akemara.com)</generator><language>ar</language><lastBuildDate>Fri, 17 Jul 2026 00:00:00 +0000</lastBuildDate><image><url>https://akemara.com/media/logo.svg</url><title>تصميم واجهات البرمجة</title><link>https://akemara.com/ar/tags/%D8%AA%D8%B5%D9%85%D9%8A%D9%85-%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9/</link></image><item><title>تصميم REST API: الدليل الكامل لبناء واجهات برمجية تدوم طويلًا</title><link>https://akemara.com/ar/blog/rest-api-design-complete-guide/</link><pubDate>Fri, 17 Jul 2026 00:00:00 +0000</pubDate><guid>https://akemara.com/ar/blog/rest-api-design-complete-guide/</guid><description>&lt;p&gt;تصميم REST API هو أمر سهل. الجزء الصعب حقًا هو تصميم واجهة لن تكرهها بصمت بعد مرور عام من الآن.&lt;/p&gt;
&lt;p&gt;في اللحظة التي يكتب فيها مطور آخر كودًا يتكامل مع الـ endpoints الخاصة بك، فإنك تكون قد قطعت له وعدًا: هذا الـ URL سيعمل دومًا، وهذا الحقل يُدعى &lt;code&gt;price&lt;/code&gt;، وهذا الـ call سيعيد الرمز &lt;code&gt;201&lt;/code&gt;. إذا غيرت رأيك لاحقًا، فلن يقتصر الأمر على عمل refactoring للكود الخاص بك فحسب، بل ستقوم بكسر الكود الخاص بهم، وفي بيئة الإنتاج (production)، وغالبًا يوم الجمعة!&lt;/p&gt;
&lt;p&gt;لهذا السبب يحتوي تصميم الـ APIs على الكثير من &amp;ldquo;القواعد&amp;rdquo;. إنها ليست مجرد بيروقراطية؛ بل هي الندوب المتراكمة لأشخاص حشروا أنفسهم في زوايا ضيقة كان الخروج منها مكلفًا للغاية. الخبر السار هو أن معظم هذه القرارات لها إجابة صحيحة واضحة، وقد قمت بجمعها هنا في مكان واحد. اقرأ الدليل من البداية إلى النهاية، أو انتقل مباشرة إلى القسم الذي تحتاجه وعد لاحقًا.&lt;/p&gt;
&lt;p&gt;الهدف الكامل هو بناء API قابل للتوقع (predictable). يجب أن يكون المطور الذي لم يرَ وثائقك (docs) من قبل قادرًا على تخمين كيفية عمل الـ API وتصيب توقعاته في معظم الأوقات.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="what-rest-actually-asks-of-you"&gt;ما تطلبه منك فلسفة REST فعليًا&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="القيود الأساسية التي تطلبها فلسفة REST من الـ API"
srcset="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/what-rest-actually-asks-of-you_hu_c0e95f8035168432.webp 320w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/what-rest-actually-asks-of-you_hu_d5cb3f70bb69ae0c.webp 480w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/what-rest-actually-asks-of-you_hu_afa0062e6a53ec8c.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/what-rest-actually-asks-of-you_hu_c0e95f8035168432.webp"
width="760"
height="428"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/what-rest-actually-asks-of-you_hu_fbe2bb2d6165293b.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;إن REST هو نمط معماري (architectural style) وليس مكتبة تقوم بتثبيتها. هناك بعض القيود (constraints) التي تهمك في عملك اليومي:&lt;/p&gt;
&lt;p&gt;يظل الـ client والـ server مستقلين تمامًا. لا يهتم الـ front end بكيفية تشكيل قاعدة البيانات (database)، ولا يهتم الـ server بما إذا كان المتصل متصفحًا، أو تطبيق هاتف، أو cron job.&lt;/p&gt;
&lt;p&gt;كل طلب (request) يقف بذاته (stateless). لا يحتفظ الـ server بأي ذاكرة عنك بين المكالمات، لذا يحمل كل request كل ما يحتاجه ليتم فهمه وتلبيته. قد يبدو هذا كأنه قيد يعيقك، حتى تحاول تشغيل عشر نسخ من خدمتك خلف load balancer، وعندها ستدرك أنه الشيء الوحيد الذي ينقذك.&lt;/p&gt;
&lt;p&gt;توضح الاستجابات (responses) ما إذا كان يمكن عمل caching لها، وتعيش الـ resources خلف مجموعة صغيرة ومعيارية من الـ HTTP methods. هذا القيد الأخير هو تحديدًا ما يكسره المطورون باستمرار، ومعظم هذا الدليل يدور حول احترامه.&lt;/p&gt;
&lt;p&gt;لا تحتاج إلى التعريفات الأكاديمية الجافة. أنت بحاجة إلى تطبيق هذه الأفكار عمليًا، ويبدأ ذلك من التسمية (naming).&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="resource-naming-and-url-design"&gt;تسمية الـ Resources وتصميم الـ URLs&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="تسمية الـ resources وتصميم الـ URLs في REST APIs"
srcset="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/resource-naming-and-url-design_hu_2b2ead5ae00f6e5a.webp 320w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/resource-naming-and-url-design_hu_7284645e20937355.webp 480w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/resource-naming-and-url-design_hu_f1c66ecada3571ca.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/resource-naming-and-url-design_hu_2b2ead5ae00f6e5a.webp"
width="760"
height="428"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/resource-naming-and-url-design_hu_6374897027802789.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;الـ resource هو &amp;ldquo;الشيء&amp;rdquo; الذي تعرضه الـ API الخاصة بك: مستخدم (user)، طلب (order)، أو منتج (product). الـ URLs تقوم بتسمية هذه الأشياء. لذا، قم ببنائها باستخدام الأسماء (nouns)، ودع الـ HTTP method توفر الفعل (verb).&lt;/p&gt;
&lt;h3 id="nouns-not-verbs"&gt;استخدم الأسماء (Nouns) وليس الأفعال (Verbs)&lt;/h3&gt;
&lt;p&gt;عندما تضع الإجراء (action) داخل المسار (path)، سينتهي بك الأمر بتكرار الفعل الذي يوفره لك بروتوكول HTTP بالفعل، ثم الغرق في مئات الـ endpoints المخصصة لحالات فردية.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-http" data-lang="http"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;❌ GET /getAllProducts
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;❌ POST /createProduct
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;❌ POST /products/5112/delete
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;✅ GET /products
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;✅ POST /products
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;✅ DELETE /products/5112
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="pluralize-your-collections"&gt;استخدم صيغة الجمع لمجموعات البيانات (Collections)&lt;/h3&gt;
&lt;p&gt;اختر صيغة الجمع والتزم بها دائمًا. صيغة الجمع تقرأ بشكل صحيح سواء كنت تجلب القائمة بأكملها أو عنصرًا واحدًا منها:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-http" data-lang="http"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;GET /users # everyone
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;GET /users/42 # one person
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;POST /users # add someone
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;❌ &lt;strong&gt;لا تفعل هذا:&lt;/strong&gt;
إذا قمت بتسمية القائمة بـ &lt;code&gt;/users&lt;/code&gt; والـ record الواحد بـ &lt;code&gt;/user/42&lt;/code&gt;، فقد أجبرت كل client على تذكر أي الـ endpoints تعتبر استثناءً.&lt;/p&gt;
&lt;h3 id="nest-relationships-but-not-very-deep"&gt;اجعل العلاقات متداخلة (Nested)، ولكن ليس بعمق&lt;/h3&gt;
&lt;p&gt;أظهر الملكية عبر التداخل. طلبات العميل تقع تحت هذا العميل:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-http" data-lang="http"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;GET /customers/42/orders # customer&amp;#39;s orders
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;GET /customers/42/orders/18 # one of them
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;GET /customers/42/orders/18/items/3 # one of them
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;مستويان أو ثلاثة مستويات من التداخل كافية تمامًا. في اللحظة التي تجد فيها نفسك تكتب URLs مثل هذه، توقف فورًا:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;❌ &lt;code&gt;/customers/42/orders/18/items/3/supplier/9&lt;/code&gt;
أو&lt;/li&gt;
&lt;li&gt;❌ &lt;code&gt;/companies/12/warehouses/4/aisles/2/shelves/45/products/908&lt;/code&gt;
مثل هذه الـ URLs تكون بائسة عند بنائها وتنكسر بمجرد أن يتحرك أي شيء. بدلًا من ذلك، ارفع الشيء ليكون resource رئيسيًا في المستوى الأعلى واستخدم الـ filtering.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="keep-paths-boring"&gt;حافظ على بساطة وهدوء المسارات (Paths)&lt;/h3&gt;
&lt;p&gt;اجعل كل شيء بأحرف صغيرة (lowercase)، واستخدم الشرطات (hyphens) للأسماء متعددة الكلمات: &lt;code&gt;/shipping-addresses&lt;/code&gt;، وليس &lt;code&gt;/shippingAddresses&lt;/code&gt; أو &lt;code&gt;/Shipping_Addresses&lt;/code&gt;. تجنب الشرطات المائلة في نهاية المسار (trailing slashes). وتجنب امتدادات الملفات مثل &lt;code&gt;.json&lt;/code&gt; (نوع المحتوى يحدد في الـ header وليس في الـ URL). واستقر على أسلوب تسمية واحد لحقول الـ JSON الخاصة بك، سواء كان &lt;code&gt;snake_case&lt;/code&gt; أو &lt;code&gt;camelCase&lt;/code&gt;، ولا تخلط بينهما أبدًا.&lt;/p&gt;
&lt;p&gt;أعلم أن نصيحة &amp;ldquo;كن متسقًا&amp;rdquo; تبدو بديهية أو مكررة، لكنها ليست كذلك. الاتساق (consistency) هو الميزة التي تتيح للمطور تخمين الـ endpoint التالي بشكل صحيح بدلاً من فتح مستنداتك (docs) مع كل call.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="http-methods-let-the-verb-carry-the-meaning"&gt;الـ HTTP Methods: دع الفعل يحمل المعنى&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="الـ HTTP methods والمعنى الذي يحمله كل فعل"
srcset="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/http-methods-let-the-verb-carry-the-meaning_hu_3ae51af675b2ca4e.webp 320w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/http-methods-let-the-verb-carry-the-meaning_hu_abab4286f07b0710.webp 480w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/http-methods-let-the-verb-carry-the-meaning_hu_4f7df8c3645e444c.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/http-methods-let-the-verb-carry-the-meaning_hu_3ae51af675b2ca4e.webp"
width="760"
height="428"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/http-methods-let-the-verb-carry-the-meaning_hu_8e3970bf9c810a31.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;الـ method تحدد ما تقوم بفعله. اضبط هذا الأمر بشكل صحيح لتظل الـ URLs الخاصة بك مستقرة بينما يظل السلوك واضحًا.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;الغرض منها&lt;/th&gt;
&lt;th style="text-align: center"&gt;Safe؟&lt;/th&gt;
&lt;th style="text-align: center"&gt;Idempotent؟&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;قراءة شيء ما&lt;/td&gt;
&lt;td style="text-align: center"&gt;Yes&lt;/td&gt;
&lt;td style="text-align: center"&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;POST&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;إنشاء شيء ما، أو بدء إجراء لا يمكنك تكراره بأمان&lt;/td&gt;
&lt;td style="text-align: center"&gt;No&lt;/td&gt;
&lt;td style="text-align: center"&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PUT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;استبدال الـ resource بالكامل&lt;/td&gt;
&lt;td style="text-align: center"&gt;No&lt;/td&gt;
&lt;td style="text-align: center"&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PATCH&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;تعديل جزء من الـ resource&lt;/td&gt;
&lt;td style="text-align: center"&gt;No&lt;/td&gt;
&lt;td style="text-align: center"&gt;Maybe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DELETE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;حذف الـ resource&lt;/td&gt;
&lt;td style="text-align: center"&gt;No&lt;/td&gt;
&lt;td style="text-align: center"&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;QUERY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;تنفيذ قراءة معقدة يوضع فيها الاستعلام داخل الـ body، اقرأ المزيد عنها في قسم
&lt;/td&gt;
&lt;td style="text-align: center"&gt;Yes&lt;/td&gt;
&lt;td style="text-align: center"&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;كلمة &amp;ldquo;Safe&amp;rdquo; تعني أن الـ call لا يغير أي حالة على الـ server. وكلمة &amp;ldquo;Idempotent&amp;rdquo; تعني أن استدعاء الـ call عشر مرات يترك النظام في نفس الحالة تمامًا كما لو استدعيته مرة واحدة.&lt;/p&gt;
&lt;p&gt;هناك أمران يترتبان على ذلك ولا يمكن التهاون فيهما. أولاً، يجب ألا يقوم الـ &lt;code&gt;GET&lt;/code&gt; بتغيير الحالة أبدًا. قد يبدو &amp;ldquo;الـ GET الذي يحذف&amp;rdquo; غير ضار حتى يقوم browser prefetcher أو crawler بمسح نصف قاعدة البيانات الخاصة بك! ثانيًا، الـ &lt;code&gt;PUT&lt;/code&gt; والـ &lt;code&gt;PATCH&lt;/code&gt; ليسا متطابقين: أرسل الكائن بالكامل مع &lt;code&gt;PUT&lt;/code&gt;، وأرسل فقط الحقول المعدلة مع &lt;code&gt;PATCH&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;تحتاج إلى تطبيق مبدأ &lt;em&gt;&lt;strong&gt;&amp;ldquo;Idempotency&amp;rdquo;&lt;/strong&gt;&lt;/em&gt; مع الـ &lt;code&gt;POST&lt;/code&gt; أيضًا، لكنه لا يأتي من مواصفات HTTP نفسها؛ بل هو نمط يمكنك إضافته إلى الـ API الخاصة بك حتى يتمكن الـ clients من إعادة محاولة request ينشئ شيئًا ما بأمان دون إنشائه مرتين عن طريق الخطأ. سنتوسع في ذلك لاحقًا.
&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="query-the-read-that-outgrew-the-url"&gt;الـ QUERY: عمليات قراءة تخطّت حدود الـ URL&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="طريقة الـ QUERY تحمل الاستعلام داخل الـ request body بأمان"
srcset="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/query-the-read-that-outgrew-the-url_hu_35191eab17ead3ab.webp 320w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/query-the-read-that-outgrew-the-url_hu_2bd3e91554592e94.webp 480w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/query-the-read-that-outgrew-the-url_hu_4b1d3d3df9ad37dd.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/query-the-read-that-outgrew-the-url_hu_35191eab17ead3ab.webp"
width="760"
height="428"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/query-the-read-that-outgrew-the-url_hu_8c834cabf1720730.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;عاجلًا أم آجلًا، ستنمو في كل API عملية قراءة لا يتسع لها الـ URL. شاشة بحث فيها عشرون filter اختياريًا. أداة لبناء التقارير. أي شيء يكون فيه الاستعلام مستندًا كاملًا، وليس ثلاثة parameters.&lt;/p&gt;
&lt;p&gt;حتى وقت قريب، كان أمامك حلان التفافيان فقط، وكلاهما حل وسط على مضض. الأول أن تستمر في حشو الـ query string حتى تصطدم بحد أقصى لم تختره أنت — فالمواصفة لا تُلزم الأنظمة إلا بقبول نحو 8,000 octets، وكثير منها يفرض أقل من ذلك، وكل filter ينتهي به المطاف في الـ server logs وسجل المتصفح والـ bookmarks. والثاني أن تمرر البحث من خلال &lt;code&gt;POST /products/search&lt;/code&gt;، وهو حل يعمل لكنه يكذب: فالـ POST ليس safe وليس idempotent، لذا تتجاهل الـ caches الاستجابة، ولا يستطيع الـ client إعادة محاولة request انقطع في منتصفه دون أن يتساءل عما فعله مرتين للتو. أنت تعلم أنها عملية قراءة، لكن HTTP لا يعلم.&lt;/p&gt;
&lt;p&gt;لم يضف بروتوكول HTTP أي method جديدة للأغراض العامة منذ &lt;code&gt;PATCH&lt;/code&gt; في عام 2010، وفي يونيو 2026 فعلها أخيرًا. جاء
— الذي استغرق إعداده سنوات، وكان يسمى &lt;code&gt;SEARCH&lt;/code&gt; في مسوداته الأولى — ليعرّف &lt;strong&gt;&lt;code&gt;QUERY&lt;/code&gt;&lt;/strong&gt;: طريقة safe و idempotent مثل &lt;code&gt;GET&lt;/code&gt;، وتحمل body مثل &lt;code&gt;POST&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-http" data-lang="http"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;QUERY /products HTTP/1.1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;Host: api.example.com
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;Content-Type: application/json
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;Accept: application/json
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt; &amp;#34;category&amp;#34;: &amp;#34;books&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt; &amp;#34;price&amp;#34;: { &amp;#34;min&amp;#34;: 10, &amp;#34;max&amp;#34;: 50 },
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt; &amp;#34;in_stock&amp;#34;: true,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt; &amp;#34;sort&amp;#34;: [&amp;#34;-created_at&amp;#34;, &amp;#34;name&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;تعود النتائج في &lt;code&gt;200&lt;/code&gt; عادي مثل أي عملية قراءة. لكن لأن الـ method نفسها أصبحت تعلن &amp;ldquo;هذه عملية قراءة&amp;rdquo;، صار بإمكان كل ما يقف في المنتصف أن يتعامل معها على هذا الأساس أخيرًا. يستطيع الـ clients والـ proxies إعادة محاولة QUERY بعد فشل الاتصال دون
التي يحتاجها الـ POST. والاستجابات قابلة للـ caching، مع دمج الـ request body داخل مفتاح الـ cache حتى لا يتصادم استعلامان مختلفان أبدًا. والطلبات الشرطية (conditional requests) تعمل تمامًا كما مع GET: أرسل &lt;code&gt;If-None-Match&lt;/code&gt; واحصل على &lt;code&gt;304&lt;/code&gt; رخيصة التكلفة عندما لا يتغير شيء.&lt;/p&gt;
&lt;p&gt;هناك بعض القواعد المرافقة. تحديد &lt;code&gt;Content-Type&lt;/code&gt; في الطلب إلزامي؛ ويجب على الـ server رفض أي QUERY بدونه بدلًا من تحسس محتوى الـ body والتخمين. والـ status codes تتصرف بالطريقة التي تتمناها: &lt;code&gt;415&lt;/code&gt; عندما لا تدعم صيغة الاستعلام تلك، و &lt;code&gt;400&lt;/code&gt; عندما لا يطابق الـ body النوع المعلن عنه، و &lt;code&gt;422&lt;/code&gt; عندما يُقرأ الاستعلام بنجاح لكنه لا يستطيع العمل فعليًا.&lt;/p&gt;
&lt;p&gt;تمنحك المواصفة أيضًا اثنين من الـ response headers يستحقان الانتباه. &lt;code&gt;Location&lt;/code&gt; يمكن أن يشير إلى URL يعيد تشغيل نفس الاستعلام كطلب &lt;code&gt;GET&lt;/code&gt; عادي، حتى يتمكن الـ clients من تكراره دون إعادة إرسال الـ body. و &lt;code&gt;Content-Location&lt;/code&gt; يمكن أن يشير إلى نسخة مخزنة من هذه النتيجة بعينها. كلاهما اختياري، وكلاهما ممتاز للاستعلامات المكلفة.&lt;/p&gt;
&lt;p&gt;أما اكتشاف الدعم (discovery) فبسيط بلا مفاجآت: يمكن للـ resource أن يعلن عن دعمه عبر response header باسم &lt;code&gt;Accept-Query&lt;/code&gt; يسرد الصيغ التي يقبلها ‏(&lt;code&gt;Accept-Query: application/json&lt;/code&gt;)، أو يمكنك البحث عن &lt;code&gt;QUERY&lt;/code&gt; داخل الـ &lt;code&gt;Allow&lt;/code&gt; header ضمن استجابة &lt;code&gt;OPTIONS&lt;/code&gt;، أو ترسل طلبًا ببساطة وتتعامل مع الـ &lt;code&gt;405&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;❌ &lt;strong&gt;لا تفعل هذا:&lt;/strong&gt;
لا تلجأ إلى &lt;code&gt;GET&lt;/code&gt; مع request body بدلًا من ذلك. المواصفة لا تمنح أي معنى لـ body مرسل مع GET، وأي proxy أو cache أو server على طول المسار حر في إسقاطه. قد يفلت Elasticsearch بهذا الأسلوب داخل cluster تتحكم أنت به؛ أما أي API عام فلن يفلت به.&lt;/p&gt;
&lt;p&gt;هل يجب أن تطلق QUERY اليوم؟ افحص الـ stack الخاص بك أولًا. المعيار جديد تمامًا، لذا فإن دعم الـ frameworks والـ gateways والـ CDNs ومكتبات الـ clients لا يزال في طور الاكتمال — كما أن استدعاءات المتصفح تدفع ثمن CORS preflight إضافي، لأن QUERY ليست ضمن القائمة الآمنة (safelist). وحيثما لا تكون أدواتك جاهزة، يبقى &lt;code&gt;POST /search&lt;/code&gt; المسمى بوضوح هو الحل البديل الصادق. وبالنسبة للـ filters البسيطة، استمر في فعل ما يقوله
: حتى المعيار نفسه يشير إلى أن الاستعلامات القصيرة مكانها الـ query string. أما الـ QUERY فهي للاستعلامات التي تخطّت تلك الحدود.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="status-codes-tell-the-truth-about-what-happened"&gt;الـ Status Codes: قل الحقيقة بشأن ما حدث&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="الـ status codes التي تصف ما حدث فعليًا"
srcset="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/status-codes-tell-the-truth-about-what-happened_hu_2332184dd3d80a9.webp 320w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/status-codes-tell-the-truth-about-what-happened_hu_9cec14b9917b997a.webp 480w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/status-codes-tell-the-truth-about-what-happened_hu_cf258351480f2f99.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/status-codes-tell-the-truth-about-what-happened_hu_2332184dd3d80a9.webp"
width="760"
height="428"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/status-codes-tell-the-truth-about-what-happened_hu_d275f8a07e71d3cf.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;أعد الـ code الذي يصف النتيجة الفعلية. تعلم مجموعة صغيرة وشائعة جيدًا بدلاً من البحث عن أكواد غريبة لا يتعرف عليها أحد.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;النجاح (2xx)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;200 OK&lt;/code&gt; — النجاح المعتاد كل يوم.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;201 Created&lt;/code&gt; — قمت بإنشاء شيء ما. أضف &lt;code&gt;Location&lt;/code&gt; header يشير إليه.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;202 Accepted&lt;/code&gt; — قبلت الطلب ولكنك ستقوم بمعالجته لاحقًا.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;204 No Content&lt;/code&gt; — نجحت العملية ولكن لا يوجد شيء لإرساله (ممتاز لـ &lt;code&gt;DELETE&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;إعادة التوجيه (3xx)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;301 Moved Permanently&lt;/code&gt; — يعيش هذا العنصر في URL جديد الآن.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;304 Not Modified&lt;/code&gt; — النسخة المخزنة مؤقتًا (cached) لدى الـ caller لا تزال صالحة (هذا صديقك؛ سنشرحه أكثر في قسم الـ caching).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;أخطاء جهة الـ Client ‏(4xx)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;400 Bad Request&lt;/code&gt; — الطلب نفسه غير صحيح التنسيق (malformed).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;401 Unauthorized&lt;/code&gt; — لا تعرف هويتهم بعد.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;403 Forbidden&lt;/code&gt; — تعرف هويتهم، لكن لا يُسمح لهم بالقيام بهذا الإجراء.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;404 Not Found&lt;/code&gt; — لا يوجد شيء بهذا الاسم.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;405 Method Not Allowed&lt;/code&gt; — الـ URL صحيح، لكن الـ verb (الأسلوب) خاطئ.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;409 Conflict&lt;/code&gt; — يتعارض مع الحالة الحالية، مثل محاولة إنشاء تكرار.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;422 Unprocessable Entity&lt;/code&gt; — الطلب سليم التنسيق ولكنه فشل في عملية التحقق (validation).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;429 Too Many Requests&lt;/code&gt; — يرسلون طلبات بمعدل سريع جدًا يتجاوز الحد المسموح.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;أخطاء جهة الـ Server ‏(5xx)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;500 Internal Server Error&lt;/code&gt; — حدث خطأ غير متوقع وانفجر شيء ما في جانبك.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;503 Service Unavailable&lt;/code&gt; — الخدمة متوقفة أو محملة بشكل زائد حاليًا.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;هناك خطيئة واحدة تستحق الذكر صراحة: إعادة الرمز &lt;code&gt;200 OK&lt;/code&gt; مع وجود خطأ مدفون داخل الـ response body. الـ clients يثقون في الـ status line؛ فإذا كذبت هناك، فإن كل معالج أخطاء (error handler) تالٍ سيكذب أيضًا.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="requests-and-responses"&gt;الطلبات والاستجابات (Requests and Responses)&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="أشكال موحدة وقابلة للتوقع للطلبات والاستجابات بتنسيق JSON"
srcset="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/requests-and-responses_hu_d0c0c919a14635f1.webp 320w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/requests-and-responses_hu_2a8193a2de034037.webp 480w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/requests-and-responses_hu_d14b242125be4ce6.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/requests-and-responses_hu_d0c0c919a14635f1.webp"
width="760"
height="428"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/requests-and-responses_hu_74b3796178e3d6d1.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;استخدم تنسيق JSON. لـ request bodies، وللـ responses، ولكل شيء تقريبًا ما عدا نقل الملفات عبر الشبكة. وقم دائمًا بتعيين الـ header التالي: &lt;code&gt;Content-Type: application/json&lt;/code&gt;. إرسال نص بتنسيق JSON بدون هذا الـ header يجبر الـ clients على التخمين والقيام بعملية الـ parse يدويًا.&lt;/p&gt;
&lt;p&gt;بعد إجراء &lt;code&gt;POST&lt;/code&gt; أو &lt;code&gt;PATCH&lt;/code&gt;، قم بإرجاع الـ resource المعدل في الـ response. لقد قام الـ client بتعديله للتو؛ فلا تجبره على إرسال request ثانٍ ليرى كيف أصبح شكله الآن.&lt;/p&gt;
&lt;p&gt;حافظ على شكل الاستجابات (responses) متوقعًا وموحدًا. بالنسبة للقوائم (lists)، غلف البيانات وضع معلومات الـ pagination في نفس الكائن حتى تظهر دائمًا في نفس المكان:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;data&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Wireless Mouse&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;price&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;24.99&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;pagination&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;page&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;per_page&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;total&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;137&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;next&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/products?page=2&amp;amp;per_page=20&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="filtering-sorting-and-pagination"&gt;الـ Filtering والـ Sorting والـ Pagination&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="الـ filtering والـ sorting والـ pagination عبر الـ query parameters"
srcset="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/filtering-sorting-and-pagination_hu_c08ef98f60d39b07.webp 320w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/filtering-sorting-and-pagination_hu_fe7bd93d3e6aee6e.webp 480w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/filtering-sorting-and-pagination_hu_7bccb9a1c1ea7098.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/filtering-sorting-and-pagination_hu_c08ef98f60d39b07.webp"
width="760"
height="428"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/filtering-sorting-and-pagination_hu_10aecbdab26d2065.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;المجموعات (collections) تكبر وتتوسع باستمرار. خطط لذلك من أول commit، وافعل ذلك باستخدام الـ query parameters. لا تقم أبدًا بدمج حد أقصى (limit) ثابت داخل المسار (path).&lt;/p&gt;
&lt;p&gt;استخدم الـ Filter لتضييق نطاق البيانات:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-http" data-lang="http"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;GET /products?category=books&amp;amp;status=active&amp;amp;min_price=10
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;قم بالترتيب (Sort) باستخدام أسلوب مقروء ومألوف. وضع علامة &lt;code&gt;-&lt;/code&gt; قبل الحقل للترتيب التنازلي (descending) هو نمط شائع يفهمه الجميع فورًا:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-http" data-lang="http"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;GET /products?sort=-created_at,name
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;قم بعمل Pagination لأي شيء يمكن أن يعيد قائمة كبيرة. لديك خياران رئيسيان:&lt;/p&gt;
&lt;p&gt;الـ Offset-based pagination (أو القائم على رقم الصفحة) هو الأبسط: &lt;code&gt;?page=3&amp;amp;per_page=20&lt;/code&gt;. وهو مثالي لقواعد البيانات الصغيرة والمتوسطة، ولأي واجهة مستخدم (UI) تعرض أرقام الصفحات.&lt;/p&gt;
&lt;p&gt;الـ Cursor-based pagination يعيد مؤشرًا مبهمًا (opaque pointer) للدفعة التالية بدلاً من ذلك: &lt;code&gt;?limit=20&amp;amp;cursor=eyJpZCI6MTQ0fQ&lt;/code&gt;. يكلف تطويره جهدًا أكبر، ولكنه يظل صحيحًا وسريعًا حتى عند إدخال صفوف جديدة أثناء تصفح المستخدم، وهذا هو السبب الرئيسي في اعتماد الـ APIs عالية الحركة مثل Stripe عليه. إذا كانت بياناتك تتغير باستمرار أو كانت جداولك ضخمة، فهذا هو خيارك الأنسب.&lt;/p&gt;
&lt;p&gt;أياً كان اختيارك، قم بإرجاع بعض البيانات الوصفية (metadata): مثل العدد الإجمالي (total)، أو روابط الـ next والـ previous، حتى لا يترك الـ client حائرًا يتساءل عما إذا كان هناك المزيد من البيانات.&lt;/p&gt;
&lt;p&gt;وعندما تكبر الـ filters نفسها على الـ query string، فهذه مهمة لقسم
.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="error-handling"&gt;معالجة الأخطاء (Error Handling)&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="شكل موحد لمعالجة الأخطاء في REST APIs"
srcset="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/error-handling_hu_7d9497aad873391e.webp 320w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/error-handling_hu_deb1ae4d10d8f08c.webp 480w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/error-handling_hu_eb511dc090030d1.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/error-handling_hu_7d9497aad873391e.webp"
width="760"
height="428"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/error-handling_hu_f182997bdea9722d.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;الأخطاء جزء من واجهتك البرمجية (interface). يجب على الـ clients قراءة الأخطاء برمجياً وليس بالعين المجردة فقط، لذا استخدم شكلاً موحدًا (envelope) في كل مكان. هناك معيار رسمي لذلك وهو
, ولكن استخدام نموذج موحد بسيط خاص بك يعتبر كافيًا أيضًا:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;error&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;code&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;validation_failed&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;message&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;The request contains invalid fields.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;details&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;field&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;email&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;issue&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;must be a valid email address&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;field&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;age&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;issue&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;must be greater than 0&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;الخطأ الجيد يمنح الـ caller رمزًا ثابتًا (stable machine code) ليتفرع الكود بناءً عليه (بحيث يتحقق من &lt;code&gt;validation_failed&lt;/code&gt; بدلاً من قراءة وفحص النصوص)، ورسالة واضحة للبشر لقراءة السجلات (logs)، وتفاصيل على مستوى الحقول عند فشل الـ validation. ما لا يجب تضمينه أبدًا في الخطأ هو الـ stack trace، أو جزء من استعلام SQL، أو مسار ملف داخلي؛ فهذه تساعد المخترقين وتربك الجميع.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="versioning"&gt;الـ Versioning&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="أساليب الـ versioning للواجهات البرمجية"
srcset="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/versioning_hu_c46e0d67940e9abd.webp 320w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/versioning_hu_9655f75a1ce5dd48.webp 480w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/versioning_hu_72f55b15ae51245b.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/versioning_hu_c46e0d67940e9abd.webp"
width="760"
height="428"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/versioning_hu_2719950a6d322884.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;الـ APIs العامة تتغير بمرور الوقت. قم بإصدار نسخ (Version) لـ API الخاصة بك من أول إطلاق حتى تتمكن من التطور والنمو دون كسر التطبيقات التي تعتمد عليك بالفعل.&lt;/p&gt;
&lt;p&gt;الأساليب الشائعة للـ versioning:&lt;/p&gt;
&lt;p&gt;الـ URI versioning يضع رقم النسخة في المسار مباشرة، مثل &lt;code&gt;/v1/products&lt;/code&gt;. وهو الخيار الافتراضي الأكثر شعبية لأنه واضح ومكشوف، ويسهل توجيهه (route) واختباره عبر المتصفح مباشرة دون تعقيد.&lt;/p&gt;
&lt;p&gt;الـ Header versioning يحافظ على نظافة الـ URLs عن طريق وضع رقم الإصدار داخل header مخصص مثل &lt;code&gt;Accept: application/vnd.myapi.v1+json&lt;/code&gt;. هذا الأسلوب يبدو أكثر ترتيبًا، لكنه أصعب في المعاينة البصرية والـ debugging.&lt;/p&gt;
&lt;p&gt;هناك أيضًا أسلوب الترقيم القائم على التاريخ (date-based flavor) الذي تستخدمه بعض الـ APIs الكبيرة، حيث تثبت الإصدار باستخدام شيء مثل &lt;code&gt;X-Api-Version: 2024-03-29&lt;/code&gt;. تعمل Stripe و GitHub بهذا الأسلوب، وهو رائع عندما تشحن تعديلات بشكل مستمر.&lt;/p&gt;
&lt;p&gt;اختر أسلوبًا واحدًا واستخدمه في كل مكان. القاعدة الذهبية التي تحكم كل هذه الأساليب: إضافة حقل اختياري (optional field) يعتبر تغييرًا آمنًا ولا يتطلب إصدارًا جديدًا، ولكن حذف حقل، أو إعادة تسميته، أو تغيير نوعه يعتبر تغييرًا كاسرًا (breaking change) ويجب أن يوضع في إصدار رئيسي (major version) جديد.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="security"&gt;الحماية (Security)&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="الحماية المطبقة على كل endpoint في الـ API"
srcset="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/security_hu_871626d3ce0f8d6e.webp 320w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/security_hu_99dfd0caca2f90ac.webp 480w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/security_hu_6c88377092e9fe22.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/security_hu_871626d3ce0f8d6e.webp"
width="760"
height="428"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/security_hu_519ded1459bc0b18.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;الأمان والحماية ليس مرحلة تأتي في نهاية المشروع؛ بل هو صفة يجب أن تتوفر في كل endpoint، لذا عامله على هذا الأساس.&lt;/p&gt;
&lt;p&gt;قم بتقديم كل شيء عبر بروتوكول HTTPS وارفض بروتوكول HTTP العادي تمامًا. إرسال token عبر HTTP يعني إرساله لأي شخص يتنصت على الشبكة.&lt;/p&gt;
&lt;p&gt;بالنسبة للـ authentication، حدد هوية المتصل. الـ API keys بسيطة وممتازة للتواصل بين الخوادم (server-to-server traffic) ولتحديد التطبيق الذي يتحدث إليك. بينما يعتبر OAuth 2.0 و OpenID Connect المعيار المعتمد عندما يمنح مستخدم ما صلاحية الوصول نيابة عنه. الـ JWTs هي tokens ذاتية الاحتواء (self-contained tokens) تُرسل في الـ header وتناسب طبيعة الـ REST عديمة الحالة (stateless) بشكل ممتاز.&lt;/p&gt;
&lt;p&gt;الـ authentication هي نصف الحكاية فقط؛ والـ authorization هو النصف الآخر: معرفة &lt;em&gt;من&lt;/em&gt; هو هذا الشخص لا تعني أنه مسموح له بالوصول إلى &lt;em&gt;هذا&lt;/em&gt; الـ record المحدد. تحقق من الصلاحيات في كل request، واجعل الرفض هو السلوك الافتراضي (default to denying).&lt;/p&gt;
&lt;p&gt;قم بفرض حدود على معدل الطلبات (Rate limit) للـ endpoints الخاصة بك حتى لا يتسبب عميل واحد جامح في إسقاط خادمك. عندما يتجاوز أحدهم الحد، أرجع الكود &lt;code&gt;429&lt;/code&gt; مع الـ header ‏&lt;code&gt;Retry-After&lt;/code&gt; حتى تعرف التطبيقات جيدة السلوك متى تتراجع وتهدئ من طلباتها بدلاً من الاستمرار في إرهاق الخادم.&lt;/p&gt;
&lt;p&gt;تحقق من صحة (Validate) كل البيانات الواردة. افترض أن كل request معادٍ حتى يثبت العكس. وحافظ على سرية البيانات الحساسة بعيدًا عن الـ URLs تمامًا، لأن الـ tokens الموضوعة في الـ query string ينتهي بها المطاف في الـ server logs وسجل المتصفح وأدوات التحليل (analytics) التي تراقب حركتك. الـ headers وجدت لسبب وجيه!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="performance-and-caching"&gt;الأداء والـ Caching&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="الأداء والـ caching في REST APIs"
srcset="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/performance-and-caching_hu_40e5fc06eed168cd.webp 320w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/performance-and-caching_hu_52fc2e93bf654a6e.webp 480w, https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/performance-and-caching_hu_811075f7b75c139e.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/performance-and-caching_hu_40e5fc06eed168cd.webp"
width="760"
height="428"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/rest-api-design-complete-guide/images/webp/performance-and-caching_hu_3bd8330a889e530d.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;بعض التحسينات السريعة وغير المكلفة هنا ستقطع بك شوطًا طويلاً:&lt;/p&gt;
&lt;p&gt;الـ &lt;code&gt;Cache-Control&lt;/code&gt; headers تخبر الـ clients والـ proxies بالمدة التي تظل فيها الاستجابة (response) حديثة وصالحة، مما يجنبك سيلًا من الطلبات عديمة الفائدة لبيانات نادرًا ما تتغير.&lt;/p&gt;
&lt;p&gt;تتكامل الـ ETags مع الـ &lt;code&gt;If-None-Match&lt;/code&gt; بحيث يمكن للـ client أن يسأل &amp;ldquo;هل تغير هذا؟&amp;rdquo; ويحصل على استجابة صغيرة بحجم &lt;code&gt;304 Not Modified&lt;/code&gt; عندما لا يطرأ أي تغيير. هذا يوفر حجم البيانات (bandwidth) المستهلك على كلا الطرفين بجهد شبه معدوم.&lt;/p&gt;
&lt;p&gt;قم بتفعيل ميزة الضغط (compression) مثل gzip أو Brotli. فنصوص الـ JSON تتقلص كثيرًا ولا تدفع مقابل ذلك سوى قدر ضئيل من المعالجة.&lt;/p&gt;
&lt;p&gt;واسمح للـ clients بطلب بيانات أقل عند الحاجة. استخدام الـ Sparse fieldsets مثل &lt;code&gt;?fields=id,name,price&lt;/code&gt; وميزة الـ opt-in expansion مثل &lt;code&gt;?expand=author&lt;/code&gt; يقلل من حجم البيانات المرسلة وعدد الرحلات للـ server (round trips)، كما يخلصك بصمت من مشكلات استعلامات الـ N+1 query.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="idempotency-so-retries-dont-hurt"&gt;الـ Idempotency، لكي لا تتسبب إعادة المحاولة في أي ضرر&lt;/h2&gt;
&lt;p&gt;أحيانًا تفقد الشبكات الطلبات في منتصف الطريق، وتقوم الـ clients بإعادة المحاولة (retry) عند حدوث ذلك. بالنسبة لأي عملية تنشئ سجلاً أو تنقل أموالاً، فإن تكرار الإرسال (double-submit) يمثل خطأً كارثيًا حقيقيًا وليس افتراضيًا.&lt;/p&gt;
&lt;p&gt;الحل يكمن في إرسال header باسم &lt;code&gt;Idempotency-Key&lt;/code&gt;. يرسل الـ client مفتاحًا فريدًا مع الـ request؛ وإذا رأى الـ server نفس المفتاح مرتين، فإنه يعيد النتيجة الأصلية المحفوظة بدلاً من تنفيذ العملية مرة أخرى. لقد شاع هذا النمط بفضل Stripe لسبب بديهي: لا أحد يريد سحب الأموال من العميل مرتين لمجرد أن هاتفه تعطل لثانية. اقتبس هذا الأسلوب واستخدمه.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="documentation"&gt;التوثيق (Documentation)&lt;/h2&gt;
&lt;p&gt;الـ API التي لا يستطيع أحد فهم كيفية عملها هي بمثابة واجهة معطلة. قم بوصف واجهتك باستخدام مواصفات
(والتي قد تسمع البعض يسميها Swagger). يتحول هذا الملف المقروء آليًا إلى مصدر الحقيقة الوحيد لديك، ومن خلاله يمكنك توليد توثيق تفاعلي (interactive docs)، ومكتبات برمجية للـ clients ‏(SDKs) بشتى لغات البرمجة، وخوادم وهمية (mock servers) لأغراض الاختبار.&lt;/p&gt;
&lt;p&gt;قم بتوثيق كل endpoint، ومعاملاته (parameters)، و — أرجوك — استجابات أخطائه أيضًا، كل ذلك مع أمثلة حقيقية. إن الـ APIs التي تشعر بسلاستها وسهولتها هي تلك التي تجيب مستنداتها (docs) عن سؤالك التالي قبل أن تنتهي حتى من صياغته في ذهنك.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="a-word-on-hateoas"&gt;كلمة عن HATEOAS&lt;/h2&gt;
&lt;p&gt;تتضمن معايير REST الصارمة مفهوم HATEOAS، حيث تحمل الاستجابات روابط تخبر الـ client بالخطوة التالية التي يمكنه اتخاذها، بحيث يتنقل داخل النظام دون الحاجة لكتابة URLs ثابتة (hardcoding) في الكود الخاص به:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;pending&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;_links&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;self&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;href&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/orders/42&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;cancel&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;href&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/orders/42/cancel&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;items&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;href&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/orders/42/items&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;في العالم الحقيقي، معظم الـ APIs في بيئات العمل الفعلي تطبق هذا المفهوم جزئيًا أو تتجاهله تمامًا، ومع ذلك تظل تعمل وتسمى RESTful طوال اليوم وتؤدي عملها بنجاح. تعامل مع هذا المفهوم كأداة مساعدة وليس كفرض ديني؛ أضف الروابط حيث تفيد المطور فعليًا وتساعده على اكتشاف ما يمكن فعله لاحقًا، ولا تقلق كثيرًا بشأن المثالية والنقاء الأكاديمي.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="the-checklist"&gt;قائمة التحقق (The Checklist)&lt;/h2&gt;
&lt;p&gt;قم بتعليق هذه القائمة في مكان تراه باستمرار وألقِ نظرة عليها قبل عملية الإطلاق (shipping):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; الـ URLs عبارة عن أسماء (nouns)؛ والـ method هي الفعل (verb).&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; المجموعات (collections) تأتي بصيغة الجمع ومسماة بشكل متسق.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; العلاقات لا تتداخل (nest) لأكثر من مستويين أو ثلاثة مستويات.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; المسارات مكتوبة بأحرف صغيرة (lowercase) ومفصولة بشرطات هيفن، بدون trailing slash وبدون امتداد &lt;code&gt;.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; استخدام أسلوب تسمية موحد لحقول الـ JSON في كل مكان.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; توافق الـ methods مع معناها (&lt;code&gt;GET&lt;/code&gt; لا يكتب أبدًا؛ &lt;code&gt;PUT&lt;/code&gt; يستبدل؛ &lt;code&gt;PATCH&lt;/code&gt; يعدل).&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; استخدام status codes صادقة وحقيقية، وتجنب إرسال رمز &lt;code&gt;200&lt;/code&gt; يلتف حول خطأ.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; استخدام JSON مع تحديد الـ &lt;code&gt;Content-Type&lt;/code&gt; الصحيح.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; تطبيق الـ filtering، والـ sorting، والـ pagination عبر الـ query params مع توفير الـ metadata اللازمة.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; القراءات المعقدة تستخدم
حيثما يدعمها الـ stack الخاص بك، ولا تستخدم أبدًا GET مع body.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; شكل موحد للأخطاء يحتوي على الرموز (codes)، والرسائل المفهومة، وتفاصيل الحقول الفاشلة.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; استخدام الإصدارات (versioned) من اليوم الأول، وتأجيل التغييرات الكاسرة (breaking changes) لإصدار رئيسي جديد فقط.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; العمل عبر HTTPS فقط، مع تطبيق auth حقيقي وصلاحيات (authorization) فعلية.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; وضع حدود لمعدل الطلبات (Rate limiting) مع إعادة رمز &lt;code&gt;429&lt;/code&gt; والـ header ‏&lt;code&gt;Retry-After&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; تفعيل الـ caching عبر الـ &lt;code&gt;Cache-Control&lt;/code&gt; والـ ETags.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; تفعيل مفاتيح التكرار (Idempotency keys) على أي عملية إنشاء أو دفع وسحب للأموال.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; توفير وثائق OpenAPI مصحوبة بأمثلة وحالات الأخطاء.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="faq"&gt;الأسئلة الشائعة (FAQ)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;ما الفرق بين PUT و PATCH؟&lt;/strong&gt;
الـ &lt;code&gt;PUT&lt;/code&gt; يستبدل الـ resource بالكامل، لذا فإن أي حقل لا ترسل قيمته سيتم مسحه أو إعادة تعيينه لقيمته الافتراضية. أما الـ &lt;code&gt;PATCH&lt;/code&gt; فيقوم بتعديل الحقول المرسلة فقط. استخدم &lt;code&gt;PUT&lt;/code&gt; عندما تريد استبدال الكائن بأكمله، واستخدم &lt;code&gt;PATCH&lt;/code&gt; عندما تريد تعديل بضع قيم فقط.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;هل أستخدم أسماء مفردة أم جمع للـ resources؟&lt;/strong&gt;
صيغة الجمع دائمًا وبشكل متسق. استخدام &lt;code&gt;/users&lt;/code&gt; للقائمة و &lt;code&gt;/users/42&lt;/code&gt; لعنصر واحد يقرأ بشكل طبيعي ويجنب الـ clients عناء حفظ الاستثناءات.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;كيف يجب أن أقوم بعمل version لـ API الخاصة بي؟&lt;/strong&gt;
ابدأ بعمل versioning من الإصدار الأول. يعتبر الـ URI versioning (مثل &lt;code&gt;/v1/...&lt;/code&gt;) هو الأسهل في الملاحظة والتوجيه (route)، على الرغم من أن الـ header-based أو الـ date-based versioning يعملان بشكل جيد أيضًا. أيًا كان اختيارك، استخدمه في كل مكان ولا تقم بزيادة الإصدار إلا عند حدوث تغييرات كاسرة (breaking changes).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;أيهما أفضل: Offset أم cursor pagination؟&lt;/strong&gt;
الـ Offset هو الأبسط ومناسب تمامًا لقواعد البيانات متواضعة الحجم والواجهات التي تستخدم ترقيم الصفحات. بينما يحافظ الـ Cursor pagination على سرعة وصحة البيانات عندما تتغير باستمرار في الوقت الفعلي أو عندما تكون الجداول ضخمة للغاية، وهو السبب في تفضيل الـ APIs الكبيرة له.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;هل تظل الـ API الخاصة بي تتبع REST إذا تجاهلت HATEOAS؟&lt;/strong&gt;
من الناحية النظرية الأكاديمية، يعتبر HATEOAS جزءًا لا يتجزأ من REST، ولكن معظم الـ APIs الحقيقية تتجاوزه أو تطبقه بشكل جزئي وتظل تصنف وتعمل كـ RESTful دون مشاكل. أضف الروابط التشعبية (hypermedia) فقط في الأماكن التي تساعد المطور فعليًا؛ وتجنب القلق بشأن المثالية التامة.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="last-thing"&gt;كلمة أخيرة&lt;/h2&gt;
&lt;p&gt;لا شيء من هذا يتطلب ذكاءً خارقًا أو حيلًا معقدة. فالتصميم الجيد للـ APIs يكمن في الانضباط لتكون مملًا وقابلاً للتوقع في كل الأماكن التي يتوقع منك المطور فيها أن تكون كذلك. اضبط الأساسيات بشكل صحيح، واعتمد الـ versioning من اليوم الأول، ولا تجبر أحدًا على قراءة كود المصدر (source code) الخاص بك لمجرد فهم ما يفعله الـ endpoint.&lt;/p&gt;
&lt;p&gt;افعل ذلك، وستستمر واجهتك البرمجية (API) في العمل بسلاسة بينما يعم الصخب والاضطراب كل ما حولها. وهو في الحقيقة أجمل ما يمكن لشخص أن يقوله عن أي برمجية بنيتها.&lt;/p&gt;</description></item></channel></rss>