<?xml version="1.0"?>
<rss version="2.0"><channel><title>&#x627;&#x644;&#x628;&#x631;&#x645;&#x62C;&#x629;: nodejs</title><link>https://academy.hsoub.com/programming/javascript/nodejs/page/2/?d=2</link><description>&#x627;&#x644;&#x628;&#x631;&#x645;&#x62C;&#x629;: nodejs</description><language>ar</language><item><title>&#x627;&#x644;&#x628;&#x631;&#x645;&#x62C;&#x629; &#x63A;&#x64A;&#x631; &#x627;&#x644;&#x645;&#x62A;&#x632;&#x627;&#x645;&#x646;&#x629; &#x641;&#x64A; Node.js</title><link>https://academy.hsoub.com/programming/javascript/nodejs/%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%BA%D9%8A%D8%B1-%D8%A7%D9%84%D9%85%D8%AA%D8%B2%D8%A7%D9%85%D9%86%D8%A9-%D9%81%D9%8A-nodejs-r1467/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_02/62037ae409812_---.png.3b82043b96774c399aa8498ce5c4c762.png" /></p>

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

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="91610" href="https://academy.hsoub.com/uploads/monthly_2022_02/01_Callbacks.png.f4b9782d3b7235cf25452c35f2f6f249.png" rel=""><img alt="01_Callbacks.png" class="ipsImage ipsImage_thumbnailed" data-fileid="91610" data-unique="71f8fwgdx" src="https://academy.hsoub.com/uploads/monthly_2022_02/01_Callbacks.png.f4b9782d3b7235cf25452c35f2f6f249.png" style="width: 500px; height: auto;"></a>
</p>

<p>
	تستخدم البرامج المقاطعات interrupts داخليًا، فالمقاطعة هي إشارة تنبعث من المعالج لجذب انتباه النظام، ولن نخوض في التفاصيل الداخلية، ولكن ضع في بالك أن عدم تزامن البرامج أمرٌ طبيعي، إذ توقف تنفيذها إلى أن تتنبّه مرةً أخرى، بحيث يمكن للحاسوب تنفيذ أشياء أخرى في هذه الأثناء، فإذا انتظر برنامج استجابةً من الشبكة، فلا يمكن إيقاف المعالج إلى أن ينتهي الطلب.
</p>

<p>
	تكون لغات البرمجة متزامنةً عادةً، وتوفِّر بعضها طريقةً لإدارة عدم التزامن في اللغة نفسها أو من خلال المكتبات، فاللغات <a href="https://academy.hsoub.com/programming/c/" rel="">C</a> و <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D8%B3%D8%B1%D9%8A%D8%B9-%D9%84%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-java-r599/" rel="">Java</a> و <a href="https://academy.hsoub.com/programming/c-sharp/%D8%A7%D9%84%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D8%B3%D8%B1%D9%8A%D8%B9-%D9%84%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-c-r597/" rel="">C#‎</a> و <a href="https://wiki.hsoub.com/PHP" rel="external">PHP</a> و <a href="https://academy.hsoub.com/programming/go/%d8%aa%d8%b9%d8%b1%d9%81-%d8%b9%d9%84%d9%89-%d9%84%d8%ba%d8%a9-%d8%a7%d9%84%d8%a8%d8%b1%d9%85%d8%ac%d8%a9-go-r222/" rel="">Go</a> و <a href="https://wiki.hsoub.com/Ruby" rel="external">Ruby</a> و Swift و <a href="https://wiki.hsoub.com/Python" rel="external">Python</a> متزامنة افتراضيًا، كما تعالجِ بعضها عدم التزامن باستخدام الخيوط threads، مما ينتج عنه عملية جديدة، <a href="https://wiki.hsoub.com/JavaScript" rel="external">فلغة جافاسكربت</a> متزامنة افتراضيًا وتعمل على خيط وحيد، وهذا يعني أنّ الشيفرة لا يمكنها إنشاء خيوط جديدة وتشغيلها على التوازي، إذ تُنفَّذ سطور الشيفرة تسلسليًا سطرًا تلو الآخر كما في المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2129_20" style="">
<span class="kwd">const</span><span class="pln"> a </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> b </span><span class="pun">=</span><span class="pln"> </span><span class="lit">2</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> c </span><span class="pun">=</span><span class="pln"> a </span><span class="pun">*</span><span class="pln"> b
console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">c</span><span class="pun">)</span><span class="pln">
doSomething</span><span class="pun">()</span></pre>

<p>
	نشأت جافاسكربت داخل المتصفح، وكانت وظيفتها الرئيسية في البداية الاستجابة لإجراءات المستخدِم مثل <code>onClick</code> و<code>onMouseOver</code> و<code>onChange</code> و<code>onSubmit</code> وما إلى ذلك، ولكن بيئتها ساعدتها في التعامل مع نمط البرمجة المتزامن من خلال المتصفح الذي يوفّر مجموعةً من واجهات برمجة التطبيقات APIs التي يمكنها التعامل مع هذا النوع من العمليات، كما قدّم Node.js في الآونة الأخيرة بيئة إدخال/إخراج دون توقف لتوسيع هذا المفهوم ليشمل الوصول إلى الملفات واستدعاءات الشبكة وغير ذلك.
</p>

<h2>
	دوال رد النداء Callbacks
</h2>

<p>
	لا يمكنك معرفة الوقت الذي سينقر فيه المستخدِم على زر، لذلك تعرِّف معالج أحداث لحدث النقر الذي يقبل دالةً تُستدعَى عند بدء الحدث كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2129_22" style="">
<span class="pln">document</span><span class="pun">.</span><span class="pln">getElementById</span><span class="pun">(</span><span class="str">'button'</span><span class="pun">).</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">'click'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="com">//نُقِر العنصر</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	وهذا ما يسمى دالة رد النداء، وهي دالة بسيطة تُمرَّر على أساس قيمة إلى دالة أخرى وستُنفَّذ عند وقوع الحدث فقط، إذ يمكن ذلك لأن للغة جافاسكربت دوالًا من الصنف الأول، والتي يمكن إسنادها للمتغيرات وتمريرها إلى دوال أخرى تسّمى دوال الترتيب الأعلى higher-order functions، كما تُغلَّف شيفرة العميل في مستمع حدث <code>load</code> على الكائن <code>window</code> الذي يشغِّل دالة رد النداء عندما تكون الصفحة جاهزة فقط مثل المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2129_24" style="">
<span class="pln">window</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">'load'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="com">//حُمِّلت الصفحة</span><span class="pln">
  </span><span class="com">//افعل ما تريده</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	تُستخدَم دوال رد النداء في كل مكان، ولا تقتصر على <a href="https://academy.hsoub.com/programming/javascript/%D9%81%D9%87%D9%85-%D8%A7%D9%84%D8%A3%D8%AD%D8%AF%D8%A7%D8%AB-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r690/" rel="">أحداث DOM</a> فقط، فأحد الأمثلة الشائعة هو استخدام المؤقتات:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2129_29" style="">
<span class="pln">setTimeout</span><span class="pun">(()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="com">// تشغيل بعد 2 ثانية</span><span class="pln">
</span><span class="pun">},</span><span class="pln"> </span><span class="lit">2000</span><span class="pun">)</span></pre>

<p>
	تقبَل طلبات XHR دالة رد نداء عن طريق إسناد دالة لخاصية في المثال التالي، إذ ستُستدعَى هذه الدالة عند وقوع حدث معيّن -أي حدث تغيّرات حالة الطلب في مثالنا-:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2129_31" style="">
<span class="kwd">const</span><span class="pln"> xhr </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">XMLHttpRequest</span><span class="pun">()</span><span class="pln">
xhr</span><span class="pun">.</span><span class="pln">onreadystatechange </span><span class="pun">=</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">xhr</span><span class="pun">.</span><span class="pln">readyState </span><span class="pun">===</span><span class="pln"> </span><span class="lit">4</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    xhr</span><span class="pun">.</span><span class="pln">status </span><span class="pun">===</span><span class="pln"> </span><span class="lit">200</span><span class="pln"> </span><span class="pun">?</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">xhr</span><span class="pun">.</span><span class="pln">responseText</span><span class="pun">)</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">error</span><span class="pun">(</span><span class="str">'error'</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
xhr</span><span class="pun">.</span><span class="pln">open</span><span class="pun">(</span><span class="str">'GET'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'https://yoursite.com'</span><span class="pun">)</span><span class="pln">
xhr</span><span class="pun">.</span><span class="pln">send</span><span class="pun">()</span></pre>

<h3>
	معالجة الأخطاء في دوال رد النداء
</h3>

<p>
	تتمثَّل إحدى الإستراتيجيات الشائعة جدًا في استخدام ما يعتمده <a href="https://academy.hsoub.com/programming/javascript/nodejs/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-nodejs-r1463/" rel="">Node.js</a> وهو المعامل الأول في أيّ دالة رد نداء هي كائن الخطأ، وبالتالي تُسمّى دوال رد النداء مع معامل الأخطاء الأول error-first callbacks، فإذا لم يكن هناك خطأً، فستكون قيمة الكائن <code>null</code>، وإذا كان هناك خطأ، فسيحتوي هذا الكائن وصفًا للخطأ ومعلومات أخرى.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2129_37" style="">
<span class="pln">fs</span><span class="pun">.</span><span class="pln">readFile</span><span class="pun">(</span><span class="str">'/file.json'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">err</span><span class="pun">,</span><span class="pln"> data</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">err </span><span class="pun">!==</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">//عالِج الخطأ</span><span class="pln">
    console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  </span><span class="com">//لا يوجد خطأ، إذَا عالِج البيانات</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">data</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span></pre>

<h3>
	مشكلة دوال رد النداء
</h3>

<p>
	تُعَدّ دوال رد النداء رائعةً في الحالات البسيطة، ولكن تضيف كل دالة رد نداء مستوىً من التداخل nesting، وبالتالي تتعقَّد الشيفرة بسرعة كبيرة عند وجود كثير من دوال رد النداء كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2129_39" style="">
<span class="pln">window</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">'load'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  document</span><span class="pun">.</span><span class="pln">getElementById</span><span class="pun">(</span><span class="str">'button'</span><span class="pun">).</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">'click'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    setTimeout</span><span class="pun">(()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      items</span><span class="pun">.</span><span class="pln">forEach</span><span class="pun">(</span><span class="pln">item </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="com">//أضِف شيفرتك هنا</span><span class="pln">
      </span><span class="pun">})</span><span class="pln">
    </span><span class="pun">},</span><span class="pln"> </span><span class="lit">2000</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	تُعَدّ الشيفرة السابقة بسيطةً، إذ تتألف من 4 مستويات فقط، لكنك قد تصادف مستويات أكثر بكثير من التداخل وبالتالي سيزداد تعقيد الشيفرة.
</p>

<h3>
	بدائل دوال رد النداء
</h3>

<p>
	قدّمت جافاسكربت بدءًا من الإصدار ES6 ميزات متعددةً تساعدنا في التعامل مع الشيفرة غير المتزامنة التي لا تتضمن استخدام دوال رد النداء مثل:
</p>

<ul>
<li>
		<a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D9%88%D8%B9%D9%88%D8%AF-promise-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r915/" rel="">الوعود Promises</a> في الإصدار ES6.
	</li>
	<li>
		صيغة <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D9%84%D8%A7%D8%AA%D8%B2%D8%A7%D9%85%D9%86-%D9%88%D8%A7%D9%84%D8%A7%D9%86%D8%AA%D8%B8%D8%A7%D8%B1-asyncawait-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r921/" rel="">عدم التزامن/الانتظار Async/Await</a> في الإصدار ES8.
	</li>
</ul>
<h2>
	الوعود Promises
</h2>

<p>
	الوعود هي إحدى طرق التعامل مع الشيفرات غير المتزامنة في جافاسكربت دون كتابة كثير من دوال رد النداء في الشيفرة.
</p>

<h3>
	مدخل إلى الوعود
</h3>

<p>
	يُعرَّف <a href="https://wiki.hsoub.com/JavaScript/Promise" rel="external">الوعد Promise</a> عمومًا على أنه وكيل لقيمة ستتوفر في وقت لاحق، فالوعود موجودة منذ سنوات، لكن وُحِّدت وقُدِّمت في الإصدار ES2015، واُستبدِلت دوال عدم التزامن Async functions في الإصدار ES2017 بها والتي تستخدِم واجهة برمجة تطبيقات الوعود أساسًا لها، لذلك يُعَدّ فهم الوعود أمرًا أساسيًا حتى في حالة استخدام دوال عدم التزامن في الشيفرة الأحدث عوضًا عن الوعود، وإليك شرح مختصر عن كيفية عمل الوعود.
</p>

<p>
	يبدأ الوعد عند استدعائه في حالة انتظار pending state، أي أن الدالة المستدعِية تواصل التنفيذ في الوقت الذي تنتظر به الوعد لينفّذ معالجته الخاصة ويستجيب لها، حيث تنتظر الدالة المستدعِية إما إعادة الوعد في حالة التأكيد أو الحل resolved state أو في حالة الرفض rejected state، ولكن لغة جافاسكربت غير متزامنة، لذلك تتابع الدالة تنفيذها ريثما ينتهي الوعد من عمله، كما تستخدِم واجهات برمجة تطبيقات الويب المعيارية الحديثة الوعود بالإضافة إلى شيفرتك ومكتباتها، ومن هذه الواجهات البرمجية:
</p>

<ul>
<li>
		<a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-battery-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-%D8%A7%D9%84%D9%88%D8%B9%D9%88%D8%AF-r738/" rel="">Battery <abbr title="Application Programming Interface | واجهة برمجية">API</abbr></a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-fetch-%D9%81%D9%8A-javascript-r739/" rel="">Fetch <abbr title="Application Programming Interface | واجهة برمجية">API</abbr></a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/general/%D9%85%D9%81%D9%87%D9%88%D9%85-service-worker-%D9%88%D8%AA%D8%A3%D8%AB%D9%8A%D8%B1%D9%87-%D9%81%D9%8A-%D8%A3%D8%AF%D8%A7%D8%A1-%D9%88%D8%A8%D9%86%D9%8A%D8%A9-%D9%85%D9%88%D8%A7%D9%82%D8%B9-%D9%88%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r833/" rel="">Service Workers</a>.
	</li>
</ul>
<p>
	ستستخدَم الوعود بالتأكيد في جافاسكربت الحديثة، لذلك يجب فهمها جيدًا.
</p>

<h3>
	إنشاء وعد
</h3>

<p>
	تُظهِر واجهة برمجة الوعد <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-promise-%D9%81%D9%8A-javascript-r740/" rel="">Promise <abbr title="Application Programming Interface | واجهة برمجية">API</abbr></a> باني وعد Promise constructor يمكن تهيئته باستخدام الدالة <code>new Promise()‎</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2129_46" style="">
<span class="pln">let done </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">true</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> isItDoneYet </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Promise</span><span class="pun">(</span><span class="pln">
  </span><span class="pun">(</span><span class="pln">resolve</span><span class="pun">,</span><span class="pln"> reject</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">done</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">const</span><span class="pln"> workDone </span><span class="pun">=</span><span class="pln"> </span><span class="str">'Here is the thing I built'</span><span class="pln">
      resolve</span><span class="pun">(</span><span class="pln">workDone</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">const</span><span class="pln"> why </span><span class="pun">=</span><span class="pln"> </span><span class="str">'Still working on something else'</span><span class="pln">
      reject</span><span class="pun">(</span><span class="pln">why</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">)</span></pre>

<p>
	يتحقّق الوعد من الثابت العام <code>done</code>، فإذا كانت قيمته صحيحة true، فإننا نعيد قيمة وعد مؤكَّد، وإلا فسنعيد وعدًا مرفوضًا، كما يمكننا إعادة قيمة باستخدام القيم <code>resolve</code> و<code>reject</code>، حيث أعدنا سلسلةً نصيةً فقط في المثال السابق، لكنها يمكن أن تكون كائنًا أيضًا.
</p>

<h3>
	استهلاك وعد
</h3>

<p>
	لنرى الآن كيفية استهلاك أو استخدام وعد.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2129_48" style="">
<span class="kwd">const</span><span class="pln"> isItDoneYet </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Promise</span><span class="pun">(</span><span class="pln">
  </span><span class="com">//...</span><span class="pln">
</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> checkIfItsDone </span><span class="pun">=</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  isItDoneYet
    </span><span class="pun">.</span><span class="pln">then</span><span class="pun">((</span><span class="pln">ok</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">ok</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">})</span><span class="pln">
    </span><span class="pun">.</span><span class="kwd">catch</span><span class="pun">((</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      console</span><span class="pun">.</span><span class="pln">error</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">})</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	سيؤدي تشغيل الدالة <code>checkIfItsDone()‎</code> إلى تنفيذ الوعد <code>isItDoneYet()‎</code> وستنتظر إلى أن يُؤكَّد الوعد باستخدام دالة رد النداء <code>then</code>، وإذا كان هناك خطأ، فستعالجه في دالة رد النداء <code>catch</code>.
</p>

<p>
	إذا أردت مزامنة وعود مختلفة، فسيساعدك التابع <code>Promise.all()‎</code> على تحديد قائمة وعود، وتنفيذ شيء ما عند تأكيد هذه الوعود جميعها، وإليك المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2129_51" style="">
<span class="kwd">const</span><span class="pln"> f1 </span><span class="pun">=</span><span class="pln"> fetch</span><span class="pun">(</span><span class="str">'/something.json'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> f2 </span><span class="pun">=</span><span class="pln"> fetch</span><span class="pun">(</span><span class="str">'/something2.json'</span><span class="pun">)</span><span class="pln">
</span><span class="typ">Promise</span><span class="pun">.</span><span class="pln">all</span><span class="pun">([</span><span class="pln">f1</span><span class="pun">,</span><span class="pln"> f2</span><span class="pun">]).</span><span class="pln">then</span><span class="pun">((</span><span class="pln">res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'Array of results'</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span><span class="pln">
</span><span class="pun">.</span><span class="kwd">catch</span><span class="pun">((</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">error</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	تتيح لك صيغة إسناد الهدم destructuring assignment syntax الخاصة بالإصدار ES2015 تنفيذ ما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2129_53" style="">
<span class="typ">Promise</span><span class="pun">.</span><span class="pln">all</span><span class="pun">([</span><span class="pln">f1</span><span class="pun">,</span><span class="pln"> f2</span><span class="pun">]).</span><span class="pln">then</span><span class="pun">(([</span><span class="pln">res1</span><span class="pun">,</span><span class="pln"> res2</span><span class="pun">])</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'Results'</span><span class="pun">,</span><span class="pln"> res1</span><span class="pun">,</span><span class="pln"> res2</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	ليس الأمر مقتصرًا على <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D8%B3%D8%AA%D8%B9%D9%85%D8%A7%D9%84%D8%A7%D8%AA-%D9%85%D8%AA%D9%82%D8%AF%D9%85%D8%A9-%D9%84%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-fetch-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1297/" rel="">استخدام fetch</a> بالطبع، إذ يمكنك استخدام أيّ وعد، كما يُشغَّل التابع <code>Promise.race()‎</code> عند تأكيد أول وعد من الوعود التي تمرّرها إليه، ويشغِّل دالةَ رد النداء المصاحبة للوعد مرةً واحدةً فقط مع نتيجة الوعد الأول المُؤكَّد resolved، وإليك المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2129_56" style="">
<span class="kwd">const</span><span class="pln"> first </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Promise</span><span class="pun">((</span><span class="pln">resolve</span><span class="pun">,</span><span class="pln"> reject</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  setTimeout</span><span class="pun">(</span><span class="pln">resolve</span><span class="pun">,</span><span class="pln"> </span><span class="lit">500</span><span class="pun">,</span><span class="pln"> </span><span class="str">'first'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> second </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Promise</span><span class="pun">((</span><span class="pln">resolve</span><span class="pun">,</span><span class="pln"> reject</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  setTimeout</span><span class="pun">(</span><span class="pln">resolve</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">,</span><span class="pln"> </span><span class="str">'second'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span><span class="pln">
</span><span class="typ">Promise</span><span class="pun">.</span><span class="pln">race</span><span class="pun">([</span><span class="pln">first</span><span class="pun">,</span><span class="pln"> second</span><span class="pun">]).</span><span class="pln">then</span><span class="pun">((</span><span class="pln">result</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">result</span><span class="pun">)</span><span class="pln"> </span><span class="com">// second</span><span class="pln">
</span><span class="pun">})</span></pre>

<h3>
	سلسلة الوعود Chaining promises
</h3>

<p>
	يمكن أن يُعاد وعدٌ إلى وعد آخر، وبالتالي ستنشأ <a href="https://academy.hsoub.com/programming/javascript/%D8%B3%D9%84%D8%B3%D9%84%D8%A9-%D8%A7%D9%84%D9%88%D8%B9%D9%88%D8%AF-promises-chaining-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r916/" rel="">سلسلة من الوعود</a>، إذ تقدِّم واجهة Fetch <abbr title="Application Programming Interface | واجهة برمجية">API</abbr> -وهي طبقة فوق واجهة برمجة تطبيقات <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86-xmlhttprequest-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1299/" rel="">XMLHttpRequest</a>- مثالًا جيدًا عن سلسلة وعود، إذ يمكننا استخدام هذه الواجهة للحصول على مورد ووضع سلسلة من الوعود في طابور لتنفيذها عند جلب المورد، كما تُعَدّ واجهة Fetch <abbr title="Application Programming Interface | واجهة برمجية">API</abbr> آليةً قائمةً على الوعود، حيث يكافئ استدعاءُ الدالة <code>fetch()‎</code> تعريف وعد باستخدام <code>new Promise()‎</code>، وإليك المثال التالي عن كيفية سلسلة الوعود:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2129_59" style="">
<span class="kwd">const</span><span class="pln"> status </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">status </span><span class="pun">&gt;=</span><span class="pln"> </span><span class="lit">200</span><span class="pln"> </span><span class="pun">&amp;&amp;</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">status </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">300</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="typ">Promise</span><span class="pun">.</span><span class="pln">resolve</span><span class="pun">(</span><span class="pln">response</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="typ">Promise</span><span class="pun">.</span><span class="pln">reject</span><span class="pun">(</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Error</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">statusText</span><span class="pun">))</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> json </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">()</span><span class="pln">

fetch</span><span class="pun">(</span><span class="str">'/todos.json'</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">.</span><span class="pln">then</span><span class="pun">(</span><span class="pln">status</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">.</span><span class="pln">then</span><span class="pun">(</span><span class="pln">json</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">.</span><span class="pln">then</span><span class="pun">((</span><span class="pln">data</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'Request succeeded with JSON response'</span><span class="pun">,</span><span class="pln"> data</span><span class="pun">)</span><span class="pln"> </span><span class="pun">})</span><span class="pln">
  </span><span class="pun">.</span><span class="kwd">catch</span><span class="pun">((</span><span class="pln">error</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'Request failed'</span><span class="pun">,</span><span class="pln"> error</span><span class="pun">)</span><span class="pln"> </span><span class="pun">})</span></pre>

<p>
	نستدعي في المثال السابق التابع <code>fetch()‎</code> للحصول على قائمة من عناصر TODO من ملف <code>todos.json</code> الموجود في نطاق الجذر، وننشئ سلسلة من الوعود، كما يعيد تشغيل التابع <code>fetch()‎</code> <a href="https://fetch.spec.whatwg.org/#concept-response" rel="external nofollow">استجابةً</a> لها خاصيات منها:
</p>

<ul>
<li>
		<code>status</code> وهي قيمة عددية تمثِّل رمز حالة <a href="https://academy.hsoub.com/programming/general/%d9%85%d8%af%d8%ae%d9%84-%d8%a5%d9%84%d9%89-http-r73/" rel="">HTTP</a>.
	</li>
	<li>
		<code>statusText</code> وهي رسالة حالة تكون قيمتها <code>OK</code> إذا نجح الطلب.
	</li>
</ul>
<p>
	تحتوي الاستجابة <code>response</code> أيضًا على تابع <code>json()‎</code> الذي يعيد وعدًا سيُؤكد ويُربَط مع محتوى الجسم المُعالَج والمُحوَّل إلى <a href="https://academy.hsoub.com/programming/javascript/%D8%AA%D8%B9%D9%84%D9%85-json-r604/" rel="">JSON</a>.
</p>

<p>
	الوعد الأول في السلسلة هو الدالة التي حدّدناها وهي <code>status()‎</code> التي تتحقق من حالة الاستجابة، فإذا لم تكن استجابةً ناجحةً -أي قيمتها بين 200 و299، فسترفِض الوعد، إذ ستؤدي هذه العملية إلى تخطي جميع الوعود المتسلسلة المدرجَة في سلسلة الوعود وستنتقل مباشرةً إلى تعليمة <code>catch()‎</code> في الأسفل، مما يؤدي إلى تسجيل نص فشل الطلب <code>Request failed</code> مع رسالة الخطأ؛ أما إذا نجحت الاستجابة، فستُستدعَى دالة <code>json()‎</code> التي حدّدناها، وبما أنّ الوعد السابق يعيد كائن الاستجابة <code>response</code> عند النجاح، فسنحصل عليه على أساس دخل للوعد الثاني، وبالتالي نُعيد بيانات JSON المُعالَجة في هذه الحالة، لذا فإن الوعد الثالث يتلقى <a href="https://academy.hsoub.com/programming/javascript/%D9%83%D9%8A%D9%81-%D8%AA%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-json-%D9%81%D9%8A-javascript-r548/" rel="">JSON</a> مباشرةً مع تسجيله ببساطة في الطرفية كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2129_64" style="">
<span class="pun">.</span><span class="pln">then</span><span class="pun">((</span><span class="pln">data</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'Request succeeded with JSON response'</span><span class="pun">,</span><span class="pln"> data</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span></pre>

<h3>
	معالجة الأخطاء
</h3>

<p>
	ألحقنا في المثال السابق تعليمة <code>catch</code> بسلسلة وعود، فإذا فشل أيّ شيء في سلسلة الوعود مسببًا خطأً أو رفض وعد، فسينتقل التحكم إلى أقرب تعلمية <code>catch()‎</code> أسفل السلسلة.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2129_66" style="">
<span class="kwd">new</span><span class="pln"> </span><span class="typ">Promise</span><span class="pun">((</span><span class="pln">resolve</span><span class="pun">,</span><span class="pln"> reject</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">throw</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Error</span><span class="pun">(</span><span class="str">'Error'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span><span class="pln">
  </span><span class="pun">.</span><span class="kwd">catch</span><span class="pun">((</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">error</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> </span><span class="pun">})</span><span class="pln">

</span><span class="com">// أو</span><span class="pln">

</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Promise</span><span class="pun">((</span><span class="pln">resolve</span><span class="pun">,</span><span class="pln"> reject</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  reject</span><span class="pun">(</span><span class="str">'Error'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span><span class="pln">
  </span><span class="pun">.</span><span class="kwd">catch</span><span class="pun">((</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">error</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> </span><span class="pun">})</span></pre>

<p>
	إذا ظهر خطأ ضمن تعليمة <code>catch()‎</code>، فيمكنك إلحاق تعليمة <code>catch()‎</code> ثانية لمعالجة الخطأ وهلم جرًا وهذا ما يسمى بعملية معالجة توريث الأخطاء Cascading errors.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2129_68" style="">
<span class="kwd">new</span><span class="pln"> </span><span class="typ">Promise</span><span class="pun">((</span><span class="pln">resolve</span><span class="pun">,</span><span class="pln"> reject</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">throw</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Error</span><span class="pun">(</span><span class="str">'Error'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span><span class="pln">
  </span><span class="pun">.</span><span class="kwd">catch</span><span class="pun">((</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="kwd">throw</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Error</span><span class="pun">(</span><span class="str">'Error'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">})</span><span class="pln">
  </span><span class="pun">.</span><span class="kwd">catch</span><span class="pun">((</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">error</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> </span><span class="pun">})</span></pre>

<p>
	إذا ظهر الخطأ <code>Uncaught TypeError: undefined is not a promise</code> في الطرفية، فتأكد من استخدام <code>new Promise()‎</code> بدلًا من استخدام <code>Promise()‎</code>.
</p>

<h2>
	صيغة عدم التزامن/الانتظار async/await
</h2>

<p>
	تطوّرت <a href="https://academy.hsoub.com/programming/javascript/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D9%84%D8%BA%D8%A9-javascript-r664/" rel="">لغة جافاسكربت</a> في وقت قصير جدًا من دوال رد النداء callbacks إلى الوعود promises في الإصدار ES2015، وأصبحت لغة جافاسكربت غير المتزامنة منذ الإصدار ES2017 أبسط مع <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D9%84%D8%A7%D8%AA%D8%B2%D8%A7%D9%85%D9%86-%D9%88%D8%A7%D9%84%D8%A7%D9%86%D8%AA%D8%B8%D8%A7%D8%B1-asyncawait-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r921/" rel="">صيغة عدم التزامن/الانتظار async/await</a>، فالدوال غير المتزامنة هي مزيج من الوعود والمولِّدات generators، وهي في الأساس ذات مستوىً أعلى من الوعود من ناحية التجريد، فصيغة async/await مبنية على الوعود.
</p>

<p>
	سبب ظهور صيغة async/await هو أنها تقلل من الشيفرة التكرارية أو المتداولة boilerplate الموجودة في الوعود، وتقلّل من محدودية قيود عدم كسر السلسلة في تسلسل الوعود، فقد كان الهدف من تقديم الوعود في الإصدار ES2015 حلَّ مشكلة التعامل مع الشيفرة غير المتزامنة، وقد حلّت هذه المشكلة حقًا، ولكن كان واضحًا على مدار العامين اللذين فصلا بين الإصدارين ES2015 وES2017 أن الوعود ليست الحل النهائي.
</p>

<p>
	اُستخدِمت الوعود لحل مشكلة جحيم دوال رد النداء callback hell الشهيرة، لكنها أدخلت التعقيد فيها بالإضافة إلى تعقيد الصيغ، وقد كانت عناصرًا أوليةً جيدةً يمكن من خلالها إظهار صيغة أفضل للمطورين، لذلك حصلنا على دوال غير متزامنة في الوقت المناسب، إذ تظهر الشيفرة على أنها متزامنة، لكنها غير متزامنة وغير قابلة للتوقّف non-blocking في الحقيقة.
</p>

<h3>
	كيفية عمل صيغة async/await
</h3>

<p>
	تعيد الدالة غير المتزامنة وعدًا كما في المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2129_72" style="">
<span class="kwd">const</span><span class="pln"> doSomethingAsync </span><span class="pun">=</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Promise</span><span class="pun">((</span><span class="pln">resolve</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    setTimeout</span><span class="pun">(()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> resolve</span><span class="pun">(</span><span class="str">'I did something'</span><span class="pun">),</span><span class="pln"> </span><span class="lit">3000</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	إذا أردت استدعاء هذه الدالة، فستضيف الكلمة <code>await</code> في البداية، وستتوقف شيفرة الاستدعاء حتى تأكيد أو رفض الوعد.
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		تحذير: يجب تعريف دالة العميل على أنها غير متزامنة <code>async</code>.
	</p>
</blockquote>

<p>
	إليك المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2129_74" style="">
<span class="kwd">const</span><span class="pln"> doSomething </span><span class="pun">=</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">await doSomethingAsync</span><span class="pun">())</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	إليك المثال التالي أيضًا والذي يوضِّح استخدام صيغة async/await لتشغيل دالة تشغيلًا غير متزامن:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2129_76" style="">
<span class="kwd">const</span><span class="pln"> doSomethingAsync </span><span class="pun">=</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Promise</span><span class="pun">((</span><span class="pln">resolve</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    setTimeout</span><span class="pun">(()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> resolve</span><span class="pun">(</span><span class="str">'I did something'</span><span class="pun">),</span><span class="pln"> </span><span class="lit">3000</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> doSomething </span><span class="pun">=</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">await doSomethingAsync</span><span class="pun">())</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'Before'</span><span class="pun">)</span><span class="pln">
doSomething</span><span class="pun">()</span><span class="pln">
console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'After'</span><span class="pun">)</span></pre>

<p>
	ستطبع الشيفرة السابقة ما يلي في طرفية المتصفح:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2129_78" style="">
<span class="typ">Before</span><span class="pln">
</span><span class="typ">After</span><span class="pln">
I did something </span><span class="com">//after 3s</span></pre>

<h3>
	تطبيق الوعود على كل شيء
</h3>

<p>
	تعني إضافة الكلمة المفتاحية <code>async</code> في بداية أيّ دالة أنّ هذه الدالة ستعيد وعدًا، وإذا لم تفعل ذلك صراحةً، فستعيد وعدًا داخليًا، وهذا سبب كون الشيفرة التالية صالحةً valid:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2129_80" style="">
<span class="kwd">const</span><span class="pln"> aFunction </span><span class="pun">=</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="str">'test'</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

aFunction</span><span class="pun">().</span><span class="pln">then</span><span class="pun">(</span><span class="pln">alert</span><span class="pun">)</span><span class="pln"> </span><span class="com">// سيؤدي هذا إلى تنبيه‫ 'test'</span></pre>

<p>
	وكذلك الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2129_82" style="">
<span class="kwd">const</span><span class="pln"> aFunction </span><span class="pun">=</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="typ">Promise</span><span class="pun">.</span><span class="pln">resolve</span><span class="pun">(</span><span class="str">'test'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

aFunction</span><span class="pun">().</span><span class="pln">then</span><span class="pun">(</span><span class="pln">alert</span><span class="pun">)</span><span class="pln"> </span><span class="com">// سيؤدي هذا إلى تنبيه‫ 'test'</span></pre>

<p>
	تبدو الشيفرة السابقة بسيطةً للغاية إذا وازنتها مع الشيفرة التي تستخدِم وعودًا صريحةً مع الدوال المتسلسلة ودوال رد النداء، كما يُعَدّ المثال السابق بسيطًا للغاية، لذلك ستظهر الفوائد جليةً عندما تكون الشيفرة أكثر تعقيدًا، وإليك المثال التالي الذي يوضّح كيفية الحصول على مورد JSON وتحليله parse باستخدام الوعود:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2129_84" style="">
<span class="kwd">const</span><span class="pln"> getFirstUserData </span><span class="pun">=</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> fetch</span><span class="pun">(</span><span class="str">'/users.json'</span><span class="pun">)</span><span class="pln"> </span><span class="com">// الحصول على قائمة المستخدِمين</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">then</span><span class="pun">(</span><span class="pln">response </span><span class="pun">=&gt;</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">())</span><span class="pln"> </span><span class="com">// تحليل‫ JSON</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">then</span><span class="pun">(</span><span class="pln">users </span><span class="pun">=&gt;</span><span class="pln"> users</span><span class="pun">[</span><span class="lit">0</span><span class="pun">])</span><span class="pln"> </span><span class="com">// التقاط المستخدِم الأول</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">then</span><span class="pun">(</span><span class="pln">user </span><span class="pun">=&gt;</span><span class="pln"> fetch</span><span class="pun">(`/</span><span class="pln">users</span><span class="pun">/</span><span class="pln">$</span><span class="pun">{</span><span class="pln">user</span><span class="pun">.</span><span class="pln">name</span><span class="pun">}`))</span><span class="pln"> </span><span class="com">// الحصول على بيانات المستخدِم</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">then</span><span class="pun">(</span><span class="pln">userResponse </span><span class="pun">=&gt;</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">())</span><span class="pln"> </span><span class="com">// تحليل‫ JSON</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
getFirstUserData</span><span class="pun">()</span></pre>

<p>
	وإليك المثال التالي الذي ينفّذ ما يفعله المثال السابق ولكن باستخدام صيغة await/async:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2129_86" style="">
<span class="kwd">const</span><span class="pln"> getFirstUserData </span><span class="pun">=</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> response </span><span class="pun">=</span><span class="pln"> await fetch</span><span class="pun">(</span><span class="str">'/users.json'</span><span class="pun">)</span><span class="pln"> </span><span class="com">// الحصول على قائمة المستخدِمين</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> users </span><span class="pun">=</span><span class="pln"> await response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">()</span><span class="pln"> </span><span class="com">// تحليل‫ JSON</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> user </span><span class="pun">=</span><span class="pln"> users</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln"> </span><span class="com">// التقاط المستخدِم الأول</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> userResponse </span><span class="pun">=</span><span class="pln"> await fetch</span><span class="pun">(`/</span><span class="pln">users</span><span class="pun">/</span><span class="pln">$</span><span class="pun">{</span><span class="pln">user</span><span class="pun">.</span><span class="pln">name</span><span class="pun">}`)</span><span class="pln"> </span><span class="com">// الحصول على بيانات المستخدِم</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> userData </span><span class="pun">=</span><span class="pln"> await user</span><span class="pun">.</span><span class="pln">json</span><span class="pun">()</span><span class="pln"> </span><span class="com">// تحليل‫ JSON</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> userData
</span><span class="pun">}</span><span class="pln">

getFirstUserData</span><span class="pun">()</span></pre>

<h3>
	استخدام دوال متعددة غير متزامنة ضمن سلسلة
</h3>

<p>
	يمكن وضع الدوال غير المتزامنة ضمن سلسلة بسهولة باستخدام صيغة أكثر قابلية للقراءة من الوعود الصرفة كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2129_88" style="">
<span class="kwd">const</span><span class="pln"> promiseToDoSomething </span><span class="pun">=</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Promise</span><span class="pun">(</span><span class="pln">resolve </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    setTimeout</span><span class="pun">(()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> resolve</span><span class="pun">(</span><span class="str">'I did something'</span><span class="pun">),</span><span class="pln"> </span><span class="lit">10000</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> watchOverSomeoneDoingSomething </span><span class="pun">=</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> something </span><span class="pun">=</span><span class="pln"> await promiseToDoSomething</span><span class="pun">()</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> something </span><span class="pun">+</span><span class="pln"> </span><span class="str">' and I watched'</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> watchOverSomeoneWatchingSomeoneDoingSomething </span><span class="pun">=</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> something </span><span class="pun">=</span><span class="pln"> await watchOverSomeoneDoingSomething</span><span class="pun">()</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> something </span><span class="pun">+</span><span class="pln"> </span><span class="str">' and I watched as well'</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

watchOverSomeoneWatchingSomeoneDoingSomething</span><span class="pun">().</span><span class="pln">then</span><span class="pun">((</span><span class="pln">res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">res</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	ستطبع الشيفرة السابقة ما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2129_90" style="">
<span class="pln">I did something and I watched and I watched as well</span></pre>

<h3>
	سهولة تنقيح الأخطاء
</h3>

<p>
	يُعَدّ تنقيح أخطاء Debugging الوعود أمرًا صعبًا لأن منقِّح الأخطاء لن يتخطى الشيفرة غير المتزامنة، بينما تجعل صيغة Async/await هذا الأمر سهلًا لأنها تُعَدّ مجرد شيفرة متزامنة بالنسبة للمصرِّف compiler.
</p>

<p>
	ترجمة -وبتصرّف- للفصل Asynchronous programming من كتاب The Node.js handbook لصاحبه Flavio Copes.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال التالي: <a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%B7%D9%84%D8%A8%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D9%8A%D8%A9-%D9%81%D9%8A-nodejs-r1468/" rel="">التعامل مع الطلبيات الشبكية في Node.js</a>
	</li>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/javascript/nodejs/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D9%86%D9%81%D9%8A%D8%B0-%D8%A7%D9%84%D8%AF%D9%88%D8%A7%D9%84-%D8%AF%D8%A7%D8%AE%D9%84%D9%8A%D8%A7-%D8%B6%D9%85%D9%86-nodejs-r1466/" rel="">كيفية تنفيذ الدوال داخليا ضمن Node.js</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%BA%D9%8A%D8%B1-%D8%A7%D9%84%D9%85%D8%AA%D8%B2%D8%A7%D9%85%D9%86%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1304/" rel="">البرمجة غير المتزامنة في جافاسكريبت</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/c-sharp/xamarin/%D9%85%D9%82%D8%AF%D9%91%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%BA%D9%8A%D8%B1-%D8%A7%D9%84%D9%85%D8%AA%D8%B2%D8%A7%D9%85%D9%86%D8%A9-%D9%81%D9%8A-xamarin-r448/" rel="">مقدّمة إلى البرمجة غير المتزامنة في Xamarin</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/general/%D8%B9%D9%84%D9%88%D9%85-%D8%A7%D9%84%D8%AD%D8%A7%D8%B3%D9%88%D8%A8/" rel="">المدخل الشامل لتعلم علوم الحاسوب</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1467</guid><pubDate>Wed, 09 Feb 2022 11:38:48 +0000</pubDate></item><item><title>&#x643;&#x64A;&#x641;&#x64A;&#x629; &#x62A;&#x646;&#x641;&#x64A;&#x630; &#x627;&#x644;&#x62F;&#x648;&#x627;&#x644; &#x62F;&#x627;&#x62E;&#x644;&#x64A;&#x627; &#x636;&#x645;&#x646; Node.js</title><link>https://academy.hsoub.com/programming/javascript/nodejs/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D9%86%D9%81%D9%8A%D8%B0-%D8%A7%D9%84%D8%AF%D9%88%D8%A7%D9%84-%D8%AF%D8%A7%D8%AE%D9%84%D9%8A%D8%A7-%D8%B6%D9%85%D9%86-nodejs-r1466/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_02/62036ff329d16_------Node.png.ce4d4985f58ec9da03b2545ed2a80ef4.png" /></p>

<p>
	سنتعرّف في هذا المقال على مفهوم <a href="https://academy.hsoub.com/programming/javascript/%D8%AD%D9%84%D9%82%D8%A9-%D8%A7%EF%BB%B7%D8%AD%D8%AF%D8%A7%D8%AB-event-loop-%D8%B7%D8%B1%D9%8A%D9%82%D9%83-%D9%84%D9%81%D9%87%D9%85-%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D9%86%D9%81%D9%8A%D8%B0-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-%D9%81%D9%8A-%D8%A7%D9%84%D9%85%D8%AA%D8%B5%D9%81%D8%AD-r1241/" rel="">حلقة الأحداث</a> وكيفية سير عملية تنفيذ الدوال تنفيذًا غير متزامن ضمن نود، كما سنوضِّح كيفية التعامل مع الأحداث المخصَّصة من خلال الصنف EventEmitter الذي يُستخدَم لمعالجة الأحداث.
</p>

<h2>
	حلقة الأحداث event loop
</h2>

<p>
	تُعَدّ حلقة الأحداث Event Loop أحد أهم جوانب <a href="https://academy.hsoub.com/programming/javascript/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D9%84%D8%BA%D8%A9-javascript-r664/" rel="">جافاسكربت</a> التي يجب فهمها.
</p>

<h3>
	مدخل إلى حلقة الأحداث
</h3>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		توضيح: قد تكون مبرمج جافاسكربت منذ سنوات، ولكنك لم تفهم تمامًا كيفية سير الأمور الداخلية، إذ لا يُعَدّ عدم معرفتك بهذا المفهوم بالتفصيل عيبًا، ولكن معرفتك بذلك أمر جيد.
	</p>
</blockquote>

<p>
	سنشرح التفاصيل الداخلية لكيفية عمل جافاسكربت باستخدام خيط thread واحد، وسنوضّح كيفية <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%AF%D9%88%D8%A7%D9%84-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r781/" rel="">معالجة الدوال</a> غير المتزامنة.
</p>

<p>
	تُشغَّل <a href="https://wiki.hsoub.com/JavaScript" rel="external">شيفرة جافاسكربت</a> الخاصة بك ضمن خيط واحد، أي أن هناك شيئًا واحدًا فقط يحدث في الوقت نفسه، هذا القيد مفيد جدًا لأنه يبسّط كثيرًا من عملية البرمجة دون القلق بشأن مشاكل التزامن، فما عليك إلا التركيز على كيفية كتابة شيفرتك الخاصة وتجنب أي شيء يمكن أنه إيقاف الخيط مثل استدعاءات الشبكة المتزامنة أو الحلقات اللانهائية.
</p>

<p>
	توجد حلقة أحداث لكل تبويب في معظم المتصفحات لعزل العمليات عن بعضها البعض وتجنب صفحة الويب ذات الحلقات اللانهائية أو ذات المعالجة الكبيرة التي تؤدي إلى توقّف المتصفح بأكمله، كما تدير البيئة حلقات أحداث متزامنة متعددة لمعالجة استدعاءات <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-api-r1314/" rel="">واجهة <abbr title="Application Programming Interface | واجهة برمجية">API</abbr></a> مثلًا، كما تُشغَّل عمَّال الويب Web Workers في حلقة الأحداث الخاصة بها أيضًا، إذ يجب عليك الاهتمام فقط بتشغيل شيفرتك ضمن حلقة أحداث واحدة، وكتابة شيفرتك مع وضع ذلك في الحسبان لتجنب توقفها.
</p>

<h3>
	إيقاف حلقة الأحداث
</h3>

<p>
	ستوقِف شيفرة جافاسكربت التي تستغرق وقتًا طويلًا لإعادة التحكم إلى حلقة الأحداث مرةً أخرى تنفيذَ أيّ شيفرة جافاسكربت في الصفحة، إذ يمكن أن توقِف خيط <a href="https://academy.hsoub.com/design/user-interface/%D8%A7%D9%84%D8%AF%D9%84%D9%8A%D9%84-%D8%A5%D9%84%D9%89-%D8%AA%D9%87%D9%8A%D8%A6%D8%A9-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-ui-r652/" rel="">واجهة المستخدِم</a>، وبالتالي لا يمكن للمستخدِم تمرير الصفحة أو النقر عليها وغير ذلك، كما تُعَدّ جميع عناصر الدخل/الخرج الأولية في جافاسكربت غير قابلة للإيقاف non-blocking تقريبًا مثل طلبات الشبكة وعمليات نظام ملفات <a href="https://wiki.hsoub.com/Node.js" rel="external">Node.js</a> وما إلى ذلك، ولكن الاستثناء هو توقّفها، وهذا هو سبب اعتماد جافاسكربت الكبير على <a href="https://academy.hsoub.com/programming/javascript/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%B1%D8%AF%D9%88%D8%AF-%D8%A7%D9%84%D9%86%D8%AF%D8%A7%D8%A1-callbacks-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r914/" rel="">دوال رد النداء callbacks </a>واعتمادها مؤخرًا على الوعود promises وصيغة عدم التزامن/الانتظار async/await.
</p>

<h3>
	مكدس الاستدعاءات call stack
</h3>

<p>
	مكدس الاستدعاءات هو طابور LIFO أي القادم أخيرًا يخرج أولًا Last In First Out، حيث تتحقّق حلقة الأحداث باستمرار من مكدس الاستدعاءات للتأكد من وجود دالة يجب تشغيلها، حيث تضيف حلقة الأحداث عندها أي استدعاء دالة تجده إلى مكدس الاستدعاءات وتنفّذ كل استدعاء بالترتيب.
</p>

<p>
	قد تكون على دراية بتعقّب مكدس الأخطاء في منقِّح الأخطاء debugger أو في وحدة تحكم المتصفح، حيث يبحث المتصفح عن أسماء الدوال في مكدس الاستدعاءات لإعلامك بالدالة التي تنشئ الاستدعاء الحالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="91599" href="https://academy.hsoub.com/uploads/monthly_2022_02/01_ErrorStackTrace.png.457a64870fa1839fb9df19a96ceee88e.png" rel=""><img alt="01_ErrorStackTrace.png" class="ipsImage ipsImage_thumbnailed" data-fileid="91599" data-unique="wubptrzrt" src="https://academy.hsoub.com/uploads/monthly_2022_02/01_ErrorStackTrace.png.457a64870fa1839fb9df19a96ceee88e.png"></a>
</p>

<h3>
	شرح بسيط لحلقة الأحداث
</h3>

<p>
	افترض المثال التالي:
</p>

<pre class="ipsCode">
const bar = () =&gt; console.log('bar')

const baz = () =&gt; console.log('baz')

const foo = () =&gt; {
 console.log('foo')
 bar()
 baz()
}
foo()
</pre>

<p>
	الذي يطبع ما يلي:
</p>

<pre class="ipsCode">
foo
bar
baz
</pre>

<p>
	تُستدعَى الدالة <code>foo()‎</code> أولًا عند تشغيل الشيفرة السابقة، ثم نستدعي الدالة <code>bar()‎</code> أولًا ضمن الدالة <code>foo()‎</code>، ثم نستدعي الدالة <code>baz()‎</code>، ويبدو مكدس الاستدعاءات في هذه المرحلة كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="91600" href="https://academy.hsoub.com/uploads/monthly_2022_02/02_CallStackExample.png.9a767f218799431416cdadc257832714.png" rel=""><img alt="02_CallStackExample.png" class="ipsImage ipsImage_thumbnailed" data-fileid="91600" data-unique="t2oobe3xl" src="https://academy.hsoub.com/uploads/monthly_2022_02/02_CallStackExample.png.9a767f218799431416cdadc257832714.png"></a>
</p>

<p>
	تتأكد حلقة الأحداث في كل تكرار من وجود شيء ما في مكدس الاستدعاءات، وتنفِّذه كما يلي إلى أن يصبح مكدس الاستدعاءات فارغًا:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="91601" href="https://academy.hsoub.com/uploads/monthly_2022_02/03_EventLoopIteration.png.833047e7ef181ce44bd7e9d84916a048.png" rel=""><img alt="03_EventLoopIteration.png" class="ipsImage ipsImage_thumbnailed" data-fileid="91601" data-unique="rwld7vmvj" src="https://academy.hsoub.com/uploads/monthly_2022_02/03_EventLoopIteration.thumb.png.4c37976b4dfc0b38a8c2e2d2950406a9.png"></a>
</p>

<h3>
	تنفيذ طابور الدوال
</h3>

<p>
	لا يوجد شيء مميز في المثال السابق، حيث تعثر شيفرة جافاسكربت على الدوال لتنفيذها وتشغيلها بالترتيب، ولنشاهد كيفية تأجيل تنفيذ دالة إلى أن يصبح المكدس فارغًا، حيث تُستخدَم حالة الاستخدام <code>setTimeout(() =&gt; {}), 0)‎</code> لاستدعاء دالة، ولكنها تُنفَّذ عند كل تنفيذ لدالة أخرى في الشيفرة، وإليك المثال التالي:
</p>

<pre class="ipsCode">
const bar = () =&gt; console.log('bar')

const baz = () =&gt; console.log('baz')

const foo = () =&gt; {
 console.log('foo')
 setTimeout(bar, 0)
 baz()
}

foo()
</pre>

<p>
	تطبع الشيفرة السابقة ما يلي:
</p>

<pre class="ipsCode">
foo
baz
bar
</pre>

<p>
	تُستدعَى الدالة <code>foo()‎</code> أولًا عند تشغيل الشيفرة، ثم نستدعي setTimeout أولًا ضمن الدالة <code>foo()‎</code>، ونمرّر <code>bar</code> على أساس وسيط، ونطلب منه العمل على الفور بأسرع ما يمكنه، ونمرر القيمة <code>0</code> على أساس مؤقت timer، ثم نستدعي الدالة <code>baz()‎</code>، حيث يبدو مكدس الاستدعاءات في هذه المرحلة كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="91602" href="https://academy.hsoub.com/uploads/monthly_2022_02/04_setTimeoutCallStack.png.af39a4d1c26cafd9f58fd77e18331ac9.png" rel=""><img alt="04_setTimeoutCallStack.png" class="ipsImage ipsImage_thumbnailed" data-fileid="91602" data-unique="dv7ds9lo1" src="https://academy.hsoub.com/uploads/monthly_2022_02/04_setTimeoutCallStack.png.af39a4d1c26cafd9f58fd77e18331ac9.png"></a>
</p>

<p>
	يوضِّح الشكل التالي ترتيب تنفيذ جميع الدوال في البرنامج:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="91603" href="https://academy.hsoub.com/uploads/monthly_2022_02/05_ExecutionOrderForAllFunctions.png.2a1c1adcb901344ee06df9aad5822ed9.png" rel=""><img alt="05_ExecutionOrderForAllFunctions.png" class="ipsImage ipsImage_thumbnailed" data-fileid="91603" data-unique="3x6ipu44n" src="https://academy.hsoub.com/uploads/monthly_2022_02/05_ExecutionOrderForAllFunctions.thumb.png.40585b0593f8c77c1977d590802bc677.png"></a>
</p>

<h3>
	طابور الرسائل Message Queue
</h3>

<p>
	يبدأ المتصفح أو <a href="https://academy.hsoub.com/programming/javascript/nodejs/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-nodejs-r1463/" rel="">Node.js</a> المؤقت timer عند استدعاء الدالة setTimeout()‎، ثم توضَع دالة رد النداء callback function في طابور الرسائل Message Queue بمجرد انتهاء صلاحية المؤقت عالفور مثل حالة وضع القيمة 0 على أساس مهلة زمنية timeout.
</p>

<p>
	يُعَدّ طابور الرسائل المكان الذي توضَع الأحداث التي بدأها المستخدِم مثل أحداث النقر أو أحداث لوحة المفاتيح أو جلب الاستجابات الموجودة في طابور قبل أن تتاح لشيفرتك فرصة الرد عليها أو <a href="https://academy.hsoub.com/programming/javascript/%D9%81%D9%87%D9%85-%D8%A7%D9%84%D8%A3%D8%AD%D8%AF%D8%A7%D8%AB-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r690/" rel="">أحداث DOM</a> مثل <code>onLoad</code>.
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		ملاحظة: تعطِي الحلقة الأولوية لمكدّس الاستدعاءات، وتعالج كل شيء تجده فيه أولًا، ثم تنتقل لالتقاط الأشياء الموجودة في طابور الأحداث عند عدم وجود أي شيء في مكدّس الاستدعاءات.
	</p>
</blockquote>

<p>
	لا يتعيّن علينا انتظار دوال مثل الدالة <code>setTimeout</code> أو انتظار جلب أو تنفيذ أشياء أخرى لهذه الدوال لأن المتصفح يوفّرها وتتقيّد بخيوطها الخاصة، فإذا ضبطتَ مهلة <code>setTimeout</code> الزمنية على 2 ثانية مثلًا، فلن تضطر إلى الانتظار لمدة 2 ثانية، بل يحدث الانتظار في مكان آخر.
</p>

<h3>
	طابور العمل Job Queue الخاص بالإصدار ES6
</h3>

<p>
	قدّم المعيار ECMAScript 2015 مفهوم طابور العمل Job Queue الذي تستخدمه الوعود Promises التي قُدِّمت أيضًا ضمن الإصدار ES6/ES2015، ويُعَدّ هذا المفهوم طريقةً لتنفيذ نتيجة دالة غير متزامنة بأسرع ما يمكن بدلًا من وضعها في نهاية مكدس الاستدعاءات.
</p>

<p>
	ستُنفَّذ الوعود المؤكَّدة قبل انتهاء الدالة الحالية بعدها مباشرةً، حيث يشبه ذلك ركوب الأفعوانية في مدينة ملاهي، إذ يضعك طابور الرسائل بعد جميع الأشخاص الآخرين الموجودين في هذا الطابور، بينما طابور العمل هو مثل تذكرة Fastpass تتيح لك الركوب في رحلة أخرى في الأفعوانية بعد الانتهاء من الرحلة السابقة مباشرةً، وإليك المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9229_18" style="">
<span class="kwd">const</span><span class="pln"> bar </span><span class="pun">=</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'bar'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> baz </span><span class="pun">=</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'baz'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> foo </span><span class="pun">=</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
 console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'foo'</span><span class="pun">)</span><span class="pln">
 setTimeout</span><span class="pun">(</span><span class="pln">bar</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln">
 </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Promise</span><span class="pun">((</span><span class="pln">resolve</span><span class="pun">,</span><span class="pln"> reject</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln">
   resolve</span><span class="pun">(</span><span class="str">'should be right after baz, before bar'</span><span class="pun">)</span><span class="pln">
 </span><span class="pun">).</span><span class="pln">then</span><span class="pun">(</span><span class="pln">resolve </span><span class="pun">=&gt;</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">resolve</span><span class="pun">))</span><span class="pln">
 baz</span><span class="pun">()</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
foo</span><span class="pun">()</span></pre>

<p>
	تطبع الشيفرة السابقة ما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9229_20" style="">
<span class="pln">foo
baz
should be right after baz</span><span class="pun">,</span><span class="pln"> before bar
bar</span></pre>

<p>
	يشكّل ذلك فرقًا كبيرًا بين <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D9%88%D8%B9%D9%88%D8%AF-promise-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r915/" rel="">الوعود Promises</a> وصيغة Async/await المبنيّة على الوعود والدوال القديمة غير المتزامنة من خلال الدالة <code>setTimeout()‎</code> أو واجهات <abbr title="Application Programming Interface | واجهة برمجية">API</abbr> للمنصات الأخرى.
</p>

<h2>
	المؤقتات Timers: التنفيذ غير المتزامن في أقرب وقت ممكن
</h2>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="91604" href="https://academy.hsoub.com/uploads/monthly_2022_02/06_Timers.png.fe26d123e7d8c537ae27fd6ce416ef55.png" rel=""><img alt="06_Timers.png" class="ipsImage ipsImage_thumbnailed" data-fileid="91604" data-unique="cvrqj7pi3" src="https://academy.hsoub.com/uploads/monthly_2022_02/06_Timers.png.fe26d123e7d8c537ae27fd6ce416ef55.png" style="width: 550px; height: auto;"></a>
</p>

<p>
	تُعَدّ الدالة <code>process.nextTick()‎</code> جزءًا مهمًا من حلقة أحداث Node.js، حيث نسمّي كل دورة كاملة تدورها حلقة الأحداث بالاسم نبضة tick، كما يؤدي تمرير دالة إلى <code>process.nextTick()‎</code> إلى استدعاء هذه الدالة في نهاية العملية الحالية وقبل بدء نبضة حلقة الأحداث التالية.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9229_24" style="">
<span class="pln">process</span><span class="pun">.</span><span class="pln">nextTick</span><span class="pun">(()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
 </span><span class="com">//افعل شيئًا ما</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	حلقة الأحداث مشغولة بمعالجة شيفرة الدالة الحالية، كما يشغّل محرك JS عند انتهاء هذه العملية جميع الدوال المُمرَّرة إلى استدعاءات <code>nextTick</code> خلال تلك العملية، وهي الطريقة التي يمكننا من خلالها إخبار محرك JS بمعالجة دالة <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%BA%D9%8A%D8%B1-%D8%A7%D9%84%D9%85%D8%AA%D8%B2%D8%A7%D9%85%D9%86%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1304/" rel="">بطريقة غير متزامنة</a> بعد الدالة الحالية في أقرب وقت ممكن دون وضعها في طابور، كما سيؤدي استدعاء <code>setTimeout(() =&gt; {}, 0)‎</code> إلى تنفيذ الدالة في النبضة التالية بعد وقت أطول من استخدام الدالة <code>nextTick()‎</code>، واستخدم الدالة <code>nextTick()‎</code> عندما تريد التأكد من تنفيذ الشيفرة في تكرار حلقة الأحداث التالي.
</p>

<h3>
	الدالة setTimeout()‎
</h3>

<p>
	قد ترغب في تأخير تنفيذ دالة عند كتابة شيفرة جافاسكربت، وهذه هي مهمة الدالة <code>setTimeout</code>، حيث تحدِّد دالة رد نداء لتنفيذها لاحقًا مع قيمة تعبِّر عن مقدار التأخير لتشغيلها لاحقًا مقدَّرةً بالميلي ثانية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9229_28" style="">
<span class="pln">setTimeout</span><span class="pun">(()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
 </span><span class="com">// تشغيل بعد 2 ثانية</span><span class="pln">
</span><span class="pun">},</span><span class="pln"> </span><span class="lit">2000</span><span class="pun">)</span><span class="pln">
setTimeout</span><span class="pun">(()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
 </span><span class="com">// تشغيل بعد 50 ميلي ثانية</span><span class="pln">
</span><span class="pun">},</span><span class="pln"> </span><span class="lit">50</span><span class="pun">)</span></pre>

<p>
	تحدِّد هذه الصيغة دالةً جديدةً، حيث يمكنك استدعاء أيّ دالة أخرى تريدها هناك، أو يمكنك تمرير اسم دالة موجودة مسبقًا مع مجموعة من المعاملات كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9229_30" style="">
<span class="kwd">const</span><span class="pln"> myFunction </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">firstParam</span><span class="pun">,</span><span class="pln"> secondParam</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
 </span><span class="com">// افعل شيئًا ما</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="com">// تشغيل بعد 2 ثانية</span><span class="pln">
setTimeout</span><span class="pun">(</span><span class="pln">myFunction</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2000</span><span class="pun">,</span><span class="pln"> firstParam</span><span class="pun">,</span><span class="pln"> secondParam</span><span class="pun">)</span></pre>

<p>
	تعيد الدالة <code>setTimeout</code> معرِّف المؤقت timer id، وهذا المعرِّف غير مُستخدَم، ولكن يمكنك تخزينه ومسحه إذا أردت حذف تنفيذ الدوال المجدولة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9229_32" style="">
<span class="kwd">const</span><span class="pln"> id </span><span class="pun">=</span><span class="pln"> setTimeout</span><span class="pun">(()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
 </span><span class="com">// يجب تشغيله بعد 2 ثانية</span><span class="pln">
</span><span class="pun">},</span><span class="pln"> </span><span class="lit">2000</span><span class="pun">)</span><span class="pln">
</span><span class="com">//غيّرنا رأينا</span><span class="pln">
clearTimeout</span><span class="pun">(</span><span class="pln">id</span><span class="pun">)</span></pre>

<h3>
	الدالة setImmediate
</h3>

<p>
	إذا أردت تنفيذ جزء من الشيفرة بطريقة غير متزامنة ولكن في أقرب وقت ممكن، فإنّ أحد الخيارات هو استخدام الدالة <code>setImmediate()‎</code> التي يوفّرها Node.js:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9229_34" style="">
<span class="pln">setImmediate</span><span class="pun">(()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
 </span><span class="com">//شغّل شيئًا ما</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	تمثِّل الدالة المُرَّرة على أساس وسيط للدالة <code>setImmediate()‎</code> دالةَ رد نداء تُنفَّذ في تكرار حلقة الأحداث التالي، كما تختلف <code>setImmediate()‎</code> عن <code>setTimeout(() =&gt; {}, 0)‎</code> مع تمرير مهلة زمنية مقدارها 0 ميلي ثانية وعن <code>process.nextTick()‎</code>، إذ تُنفَّذ الدالة المُمرَّرة إلى <code>process.nextTick()‎</code> في تكرار حلقة الأحداث الحالي بعد انتهاء العملية الحالية، وهذا يعني أنها ستُنفَّذ دائمًا قبل <code>setTimeout</code> و<code>setImmediate</code>، كما تشبه دالةُ رد النداء <code>setTimeout()‎</code> مع تأخير 0 ميلي ثانية الدالةَ <code>setImmediate()‎</code>، في حين يعتمد ترتيب التنفيذ على عوامل مختلفة، ولكنهما ستُشغَّلان في تكرار حلقة الأحداث التالي.
</p>

<h3>
	التأخير الصفري Zero delay
</h3>

<p>
	إذا حدّدت تأخير المهلة الزمنية بالقيمة <code>0</code>، فستُنفَّذ دالة رد النداء في أقرب وقت ممكن ولكن بعد تنفيذ الدالة الحالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9229_36" style="">
<span class="pln">setTimeout</span><span class="pun">(()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
 console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'after '</span><span class="pun">)</span><span class="pln">
</span><span class="pun">},</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln">
console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">' before '</span><span class="pun">)</span></pre>

<p>
	ستطبع الدالة السابقة <code>before after</code>.
</p>

<p>
	يُعَدّ هذا مفيدًا لتجنب إيقاف <a href="https://academy.hsoub.com/certificates/comptia/%D9%88%D8%AD%D8%AF%D8%A9-%D8%A7%D9%84%D9%85%D8%B9%D8%A7%D9%84%D8%AC%D8%A9-%D8%A7%D9%84%D9%85%D8%B1%D9%83%D8%B2%D9%8A%D8%A9-r58/" rel="">وحدة المعالجة المركزية CPU</a> في المهام المكثفة والسماح بتنفيذ الدوال الأخرى أثناء إجراء عملية حسابية ثقيلة عن طريق وضع الدوال ضمن طابور في المجدوِل scheduler.
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		توضيح: تطبّق بعض المتصفحات مثل متصفح IE ومتصفح Edge دالة <code>setImmediate()‎</code> التي تؤدي العملية نفسها، ولكنها ليست معيارًا و<a href="https://caniuse.com/setimmediate" rel="external nofollow">غير متوفرة في المتصفحات الأخرى</a>، وإنما هي دالة معيارية في Node.js.
	</p>
</blockquote>

<h3>
	الدالة setInterval()‎
</h3>

<p>
	تُعَدّ <code>setInterval</code> دالةً مشابهةً للدالة <code>setTimeout</code>، مع اختلاف أنّ الدالة <code>setInterval</code> ستشغِّل دالةَ رد النداء إلى الأبد ضمن الفاصل الزمني الذي تحدِّده مقدَّرًا بالميلي ثانية بدلًا من تشغيلها مرةً واحدةً:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9229_39" style="">
<span class="pln">setInterval</span><span class="pun">(()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
 </span><span class="com">// تشغيل كل 2 ثانية</span><span class="pln">
</span><span class="pun">},</span><span class="pln"> </span><span class="lit">2000</span><span class="pun">)</span></pre>

<p>
	تُشغَّل الدالة السابقة كل 2 ثانية ما لم تخبرها بالتوقف باستخدام <code>clearInterval</code> من خلال تمرير معرِّف id الفاصل الزمني الذي تعيده الدالة <code>setInterval</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9229_41" style="">
<span class="kwd">const</span><span class="pln"> id </span><span class="pun">=</span><span class="pln"> setInterval</span><span class="pun">(()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
 </span><span class="com">// تشغيل كل 2 ثانية</span><span class="pln">
</span><span class="pun">},</span><span class="pln"> </span><span class="lit">2000</span><span class="pun">)</span><span class="pln">
clearInterval</span><span class="pun">(</span><span class="pln">id</span><span class="pun">)</span></pre>

<p>
	يشيع استدعاء <code>clearInterval</code> ضمن دالة رد نداء الدالة <code>setInterval</code>، للسماح لها بالتحديد التلقائي إذا وجب تشغيلها مرةً أخرى أو إيقافها، حيث تشغّل الشيفرة التالية شيئًا على سبيل المثال إذا لم تكن قيمة App.somethingIWait هي <code>arrived</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9229_43" style="">
<span class="kwd">const</span><span class="pln"> interval </span><span class="pun">=</span><span class="pln"> setInterval</span><span class="pun">(()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
 </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="typ">App</span><span class="pun">.</span><span class="pln">somethingIWait </span><span class="pun">===</span><span class="pln"> </span><span class="str">'arrived'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   clearInterval</span><span class="pun">(</span><span class="pln">interval</span><span class="pun">)</span><span class="pln">
   </span><span class="kwd">return</span><span class="pln">
 </span><span class="pun">}</span><span class="pln">
 </span><span class="com">// وإلّا افعل شيئًا ما</span><span class="pln">
</span><span class="pun">},</span><span class="pln"> </span><span class="lit">100</span><span class="pun">)</span></pre>

<h3>
	دالة setTimeout العودية
</h3>

<p>
	تبدأ <code>setInterval</code> دالةً كل n ميلي ثانية، دون الأخذ في الحسبان موعد انتهاء تنفيذ هذه الدالة، فإذا استغرقت الدالة القدر نفسه من الوقت دائمًا، فلا بأس بذلك:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="91605" href="https://academy.hsoub.com/uploads/monthly_2022_02/07_FunctionTakesAlwaysTheSameAmountOfTime.png.b7a360fee2bd7a8963bc8ea6608aadb4.png" rel=""><img alt="07_FunctionTakesAlwaysTheSameAmountOfTime.png" class="ipsImage ipsImage_thumbnailed" data-fileid="91605" data-unique="6dccblnic" src="https://academy.hsoub.com/uploads/monthly_2022_02/07_FunctionTakesAlwaysTheSameAmountOfTime.thumb.png.99a13880909f19ab3307afc7f4b84e9a.png"></a>
</p>

<p>
	قد تستغرق الدالة أوقات تنفيذ مختلفة اعتمادًا على ظروف <a href="https://academy.hsoub.com/certificates/cisco/ccna/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%B4%D8%A8%D9%83%D8%A7%D8%AA-%D8%A7%D9%84%D8%AD%D9%88%D8%A7%D8%B3%D9%8A%D8%A8-r2/" rel="">الشبكة</a> مثلًا:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="91606" href="https://academy.hsoub.com/uploads/monthly_2022_02/08_FunctionTakesDifferentExecutionTimes.png.dee2c599802c17c36a57bfc00de3e701.png" rel=""><img alt="08_FunctionTakesDifferentExecutionTimes.png" class="ipsImage ipsImage_thumbnailed" data-fileid="91606" data-unique="4fkb4dqmv" src="https://academy.hsoub.com/uploads/monthly_2022_02/08_FunctionTakesDifferentExecutionTimes.thumb.png.a418bb6570eb04094ecb96b044872fa2.png"></a>
</p>

<p>
	وقد يتداخل وقت تنفيذ دالة طويل مع وقت تنفيذ الدالة التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="91607" href="https://academy.hsoub.com/uploads/monthly_2022_02/09_LongExecutionOverlapsTheNextOne.png.cd4b03c5898399b84cd44babbaf5a867.png" rel=""><img alt="09_LongExecutionOverlapsTheNextOne.png" class="ipsImage ipsImage_thumbnailed" data-fileid="91607" data-unique="ndp3g5ebt" src="https://academy.hsoub.com/uploads/monthly_2022_02/09_LongExecutionOverlapsTheNextOne.thumb.png.747af4f7ab843d885abd0c4df8297d79.png"></a>
</p>

<p>
	يمكن تجنب ذلك من خلال جدولة دالة setTimeout العودية لتُستدعَى عند انتهاء دالة رد النداء:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9229_48" style="">
<span class="kwd">const</span><span class="pln"> myFunction </span><span class="pun">=</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
 </span><span class="com">// افعل شيئًا ما</span><span class="pln">
 setTimeout</span><span class="pun">(</span><span class="pln">myFunction</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1000</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
setTimeout</span><span class="pun">(</span><span class="pln">
 myFunction</span><span class="pun">()</span><span class="pln">
</span><span class="pun">},</span><span class="pln"> </span><span class="lit">1000</span><span class="pun">)</span></pre>

<p>
	بهدف تحقيق السيناريو التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="91608" href="https://academy.hsoub.com/uploads/monthly_2022_02/10_RecursivesetTimeout.png.a4b44ffe1f5d834fd4be7c61e2a032a5.png" rel=""><img alt="10_RecursivesetTimeout.png" class="ipsImage ipsImage_thumbnailed" data-fileid="91608" data-unique="0q27fwz0m" src="https://academy.hsoub.com/uploads/monthly_2022_02/10_RecursivesetTimeout.thumb.png.1e188edfdd3647979841c3633093ad04.png"></a>
</p>

<p>
	يتوفَّر كل من <code>setTimeout</code> و<code>setInterval</code> في Node.js من خلال <a href="https://nodejs.org/api/timers.html" rel="external nofollow">وحدة المؤقتات Timers module</a>، كما يوفِّر Node.js أيضًا الدالة <code>setImmediate()‎</code> التي تعادل استخدام <code>setTimeout(() =&gt; {}, 0)‎</code> المستخدَمة للعمل مع حلقة أحداث Node.js في أغلب الأحيان.
</p>

<h2>
	مطلق الأحداث Event Emitter الخاص بنود Node
</h2>

<p>
	إذا استخدمت جافاسكربت في المتصفح سابقًا، فلا بد أنك تعرف مقدار تفاعلات المستخدِم المُعالَجة من خلال الأحداث مثل نقرات الفأرة وضغطات أزرار لوحة المفاتيح والتفاعل مع حركة الفأرة وغير ذلك، وهنالك الكثير من الأحداث الأساسية في المتصفح ولكن قد تحتاج في وقت ما إلى أحداث مخصَّصة غير تلك الأساسية لتطلقها وفقًا لوقوع حدث ما ثم تعالجها بما يناسبك.
</p>

<p>
	يوفِّر نود على جانب الواجهة الخلفية خيارًا لإنشاء نظام مماثل باستخدام وحدة الأحداث <a href="https://wiki.hsoub.com/Node.js/events" rel="external"><code>events module</code></a>، إذ تقدّم هذه الوحدة الصنف <code>EventEmitter</code> الذي يُستخدَم لمعالجة الأحداث، كما يمكنك تهيئته كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9229_60" style="">
<span class="kwd">const</span><span class="pln"> eventEmitter </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'events'</span><span class="pun">).</span><span class="typ">EventEmitter</span><span class="pun">()</span></pre>

<p>
	يُظهِر هذا الكائن التابعين <code>on</code> و<code>emit</code> من بين أشياء متعددة.
</p>

<ul>
<li>
		<code>emit</code> الذي يُستخدَم لبدء حدث.
	</li>
	<li>
		<code>on</code> الذي يُستخدَم لإضافة دالة رد نداء والتي ستُنفَّذ عند بدء الحدث.
	</li>
</ul>
<p>
	لننشئ حدث <code>start</code> مثلًا ثم نتفاعل معه من خلال تسجيل الدخول إلى الطرفية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9229_56" style="">
<span class="pln">eventEmitter</span><span class="pun">.</span><span class="pln">on</span><span class="pun">(</span><span class="str">'start'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
 console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'started'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	فإذا شغّلنا ما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9229_63" style="">
<span class="pln">eventEmitter</span><span class="pun">.</span><span class="pln">emit</span><span class="pun">(</span><span class="str">'start'</span><span class="pun">)</span></pre>

<p>
	فستُشغَّل دالة معالج الأحداث، وسنحصل على سجل طرفية.
</p>

<p>
	يمكنك تمرير الوسائط إلى معالج الأحداث من خلال تمريرها على أساس وسائط إضافية إلى التابع <code>emit()‎</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9229_65" style="">
<span class="pln">eventEmitter</span><span class="pun">.</span><span class="pln">on</span><span class="pun">(</span><span class="str">'start'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">number</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
 console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(`</span><span class="pln">started $</span><span class="pun">{</span><span class="pln">number</span><span class="pun">}`)</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

eventEmitter</span><span class="pun">.</span><span class="pln">emit</span><span class="pun">(</span><span class="str">'start'</span><span class="pun">,</span><span class="pln"> </span><span class="lit">23</span><span class="pun">)</span></pre>

<p>
	أو من خلال تمرير وسائط متعددة كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9229_67" style="">
<span class="pln">eventEmitter</span><span class="pun">.</span><span class="pln">on</span><span class="pun">(</span><span class="str">'start'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">start</span><span class="pun">,</span><span class="pln"> end</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
 console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(`</span><span class="pln">started from $</span><span class="pun">{</span><span class="pln">start</span><span class="pun">}</span><span class="pln"> to $</span><span class="pun">{</span><span class="pln">end</span><span class="pun">}`)</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

eventEmitter</span><span class="pun">.</span><span class="pln">emit</span><span class="pun">(</span><span class="str">'start'</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">)</span></pre>

<p>
	يظهِر كائن EventEmitter توابعًا متعددةً أخرى للتفاعل مع الأحداث مثل:
</p>

<ul>
<li>
		<code>once()‎</code>: يضيف مستمعًا لمرة واحدة.
	</li>
	<li>
		<code>removeListener()‎</code> أو <code>off()‎</code>: يزيل مستمع حدث من الحدث.
	</li>
	<li>
		<code>removeAllListeners()‎</code>: يزيل جميع المستمعين لحدث ما.
	</li>
</ul>
<p>
	يمكنك قراءة جميع التفاصيل الخاصة بهذه التوابع في صفحة وحدة الأحداث events module على <a href="https://wiki.hsoub.com/Node.js/events" rel="external">Node.js</a>.
</p>

<p>
	ترجمة -وبتصرّف- للفصل Working with the event loop من كتاب The Node.js handbook لصاحبه Flavio Copes.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال التالي: <a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%BA%D9%8A%D8%B1-%D8%A7%D9%84%D9%85%D8%AA%D8%B2%D8%A7%D9%85%D9%86%D8%A9-%D9%81%D9%8A-nodejs-r1467/" rel="">البرمجة غير المتزامنة في Node.js</a>
	</li>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%AF%D9%84%D9%8A%D9%84%D9%83-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D9%85%D8%AF%D9%8A%D8%B1-%D8%A7%D9%84%D8%AD%D8%B2%D9%85-npm-%D9%81%D9%8A-nodejs-r1465/" rel="">دليلك الشامل إلى مدير الحزم npm في Node.js</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-nodejs-%D9%84%D8%B3%D9%8A%D8%B1-%D8%B9%D9%85%D9%84-%D9%8A%D8%B9%D8%AA%D9%85%D8%AF-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-docker-compose-r811/" rel="">إعداد تطبيق node.js لسير عمل يعتمد على الحاويات باستخدام Docker Compose</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%AA%D8%A3%D9%85%D9%8A%D9%86-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-nodejs-%D9%8A%D8%B9%D9%85%D9%84-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-nginx-%D9%88-let%E2%80%99s-encrypt-%D9%88-compose-docker-r814/" rel="">تأمين تطبيق Node.js يعمل على الحاويات باستخدام Nginx و Let’s Encrypt و Compose Docker</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%88%D8%B6%D8%B9-%D8%A7%D9%84%D8%AA%D9%81%D8%A7%D8%B9%D9%84%D9%8A-%D9%88%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%B3%D8%B7%D8%B1-%D8%A7%D9%84%D8%A3%D9%88%D8%A7%D9%85%D8%B1-%D9%81%D9%8A-nodejs-r1464/" rel="">استخدام الوضع التفاعلي والتعامل مع سطر الأوامر في Node.js</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1466</guid><pubDate>Wed, 09 Feb 2022 11:37:34 +0000</pubDate></item><item><title>&#x62F;&#x644;&#x64A;&#x644;&#x643; &#x627;&#x644;&#x634;&#x627;&#x645;&#x644; &#x625;&#x644;&#x649; &#x645;&#x62F;&#x64A;&#x631; &#x627;&#x644;&#x62D;&#x632;&#x645; npm &#x641;&#x64A; Node.js</title><link>https://academy.hsoub.com/programming/javascript/nodejs/%D8%AF%D9%84%D9%8A%D9%84%D9%83-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D9%85%D8%AF%D9%8A%D8%B1-%D8%A7%D9%84%D8%AD%D8%B2%D9%85-npm-%D9%81%D9%8A-nodejs-r1465/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_02/6203636bb8eb5_--npm-.png.fa6774ab78a65847c404daa209f4d993.png" /></p>

<p>
	يمثِّل <a href="https://academy.hsoub.com/programming/javascript/%D9%81%D9%8A%D8%AF%D9%8A%D9%88-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D8%AF%D9%8A%D8%B1-%D8%A7%D9%84%D8%AD%D8%B2%D9%85-npm-r1225/" rel="">مدير حزم نود npm</a> -اختصارًا إلى Node Package Manager- أساس نجاح <a href="https://wiki.hsoub.com/Node.js" rel="external">Node.js</a>، فقد صدر تقرير في شهر 1 من عام 2017 بوجود أكثر من 350000 حزمة مُدرجَة في سجل npm، مما يجعله أكبر مستودع لشيفرات لغة على الأرض، فكُن على ثقة أنك ستجد فيه حزمةً لكل شيء تقريبًا.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="91594" href="https://academy.hsoub.com/uploads/monthly_2022_02/01_npm.png.ebc4b8ad5a34ac7869b3e611dda16b9f.png" rel=""><img alt="01_npm.png" class="ipsImage ipsImage_thumbnailed" data-fileid="91594" data-unique="d6cf9ifvj" src="https://academy.hsoub.com/uploads/monthly_2022_02/01_npm.thumb.png.d6791b6a76342ab1d1b5b568d8e3674a.png" style="width: 400px; height: auto;"></a>
</p>

<p>
	يُعَدّ npm مدير حزم Node.js المعياري، فقد اُستخدِم في البداية على أساس طريقة لتنزيل وإدارة اعتماديات حزم Node.js، لكنه أصبح بعدها أداةً تُستخدَم في واجهة جافاسكربت الأمامية أيضًا.
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		توضيح: يُعَدّ Yarn بديلًا عن npm.
	</p>
</blockquote>

<h2>
	إدارة تنزيل الحزم والمكتبات والاعتماديات
</h2>

<p>
	يدير <code>npm</code> تنزيلات جميع اعتماديات مشروعك.
</p>

<h3>
	تثبيت جميع الاعتماديات Dependencies
</h3>

<p>
	يمكنك استخدام الأمر التالي إذا احتوى المشروع على ملف <code>packages.json</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_11" style="">
<span class="pln">npm install</span></pre>

<p>
	سيثبِّت كل ما يحتاجه المشروع في المجلد <code>node_modules</code> مع إنشاء هذا المجلد إذا لم يكن موجودًا مسبقًا.
</p>

<h3>
	تثبيت حزمة واحدة
</h3>

<p>
	يمكنك أيضًا تثبيت حزمة معينة عن طريق تشغيل الأمر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_13" style="">
<span class="pln">npm install </span><span class="pun">&lt;</span><span class="pln">package</span><span class="pun">-</span><span class="pln">name</span><span class="pun">&gt;</span></pre>

<p>
	سترى في أغلب الأحيان مزيدًا من الرايات flags المضافة إلى هذا الأمر مثل:
</p>

<ul>
<li>
		<code>‎--save</code> التي تثبّت وتضيف مدخلة إلى اعتماديات ملف <code>package.json</code> وهي الافتراضية فلا داعي لإضافتها في كل مرة تثبت فيها حزمة في مشروعك.
	</li>
	<li>
		<code>‎--save-dev</code> التي تثبّت وتضيف مدخلة إلى اعتماديات تطوير devDependencies ملف <code>package.json</code>.
	</li>
</ul>
<p>
	يتمثل الاختلاف الأساسي بينهما في أن اعتماديات التطوير devDependencis هي أدوات تطوير مثل مكتبة الاختبار، بينما تُجمَّع الاعتماديات <code>dependencies</code> مع التطبيق الذي يكون قيد الإنتاج.
</p>

<p>
	يمكن تثبيت إصدار أقدم من حزمة npm أو تثبيت إصدار محدد بعينه، وهو شيء قد يكون مفيدًا في حل مشكلة التوافق، كما يمكنك تثبيت إصدار قديم من حزمة npm باستخدام صيغة <code>@</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_15" style="">
<span class="pln">npm install </span><span class="pun">&lt;</span><span class="pln">package</span><span class="pun">&gt;@&lt;</span><span class="pln">version</span><span class="pun">&gt;</span></pre>

<p>
	يثبّت الأمر التالي الإصدار الإصدار الأخير الأحدث من حزمة cowsay:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_17" style="">
<span class="pln">npm install cowsay</span></pre>

<p>
	يمكنك تثبيت الإصدار 1.2.0 من خلال الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_20" style="">
<span class="pln">npm install cowsay@1</span><span class="pun">.</span><span class="lit">2.0</span></pre>

<p>
	يمكن تطبيق الشيء نفسه مع الحزم العامة كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_22" style="">
<span class="pln">npm install </span><span class="pun">-</span><span class="pln">g webpack@4</span><span class="pun">.</span><span class="lit">16.4</span></pre>

<p>
	وقد تكون مهتمًا بسرد جميع إصدارات الحزمة السابقة من خلال استخدام الأمر <code>npm view &lt;package&gt; versions</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_24" style="">
<span class="pln">npm view cowsay versions

</span><span class="pun">[</span><span class="pln">  </span><span class="str">'1.0.0'</span><span class="pun">,</span><span class="pln">
  </span><span class="str">'1.0.1'</span><span class="pun">,</span><span class="pln">
  </span><span class="str">'1.0.2'</span><span class="pun">,</span><span class="pln">
  </span><span class="str">'1.0.3'</span><span class="pun">,</span><span class="pln">
  </span><span class="str">'1.1.0'</span><span class="pun">,</span><span class="pln">
  </span><span class="str">'1.1.1'</span><span class="pun">,</span><span class="pln">
  </span><span class="str">'1.1.2'</span><span class="pun">,</span><span class="pln">
  </span><span class="str">'1.1.3'</span><span class="pun">,</span><span class="pln">
  </span><span class="str">'1.1.4'</span><span class="pun">,</span><span class="pln">
  </span><span class="str">'1.1.5'</span><span class="pun">,</span><span class="pln">
  </span><span class="str">'1.1.6'</span><span class="pun">,</span><span class="pln">
  </span><span class="str">'1.1.7'</span><span class="pun">,</span><span class="pln">
  </span><span class="str">'1.1.8'</span><span class="pun">,</span><span class="pln">
  </span><span class="str">'1.1.9'</span><span class="pun">,</span><span class="pln">
  </span><span class="str">'1.2.0'</span><span class="pun">,</span><span class="pln">
  </span><span class="str">'1.2.1'</span><span class="pun">,</span><span class="pln">
  </span><span class="str">'1.3.0'</span><span class="pun">,</span><span class="pln">
  </span><span class="str">'1.3.1'</span><span class="pln"> </span><span class="pun">]</span></pre>

<h3>
	مكان تثبيت npm للحزم
</h3>

<p>
	يمكنك إجراء نوعين من التثبيت، عند تثبيت حزمة باستخدام <code>npm</code> أو <code>yarn</code>:
</p>

<ul>
<li>
		تثبيت محلي local install.
	</li>
	<li>
		تثبيت عام global install.
	</li>
</ul>
<p>
	إذا كتبتَ أمر تثبيت <code>npm install</code> مثل الأمر التالي، فستُثبَّت الحزمة في شجرة الملفات الحالية ضمن المجلد الفرعي <code>node_modules</code> افتراضيًا، ويضيف عندها <code>npm</code> أيضًا المدخلة <code>lodash</code> في خاصية الاعتماديات <code>dependencies</code> الخاصة بملف <code>package.json</code> الموجود في المجلد الحالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_26" style="">
<span class="pln">npm install lodash</span></pre>

<p>
	يُطبَّق التثبيت العام باستخدام الراية <code>‎-g</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_28" style="">
<span class="pln">npm install </span><span class="pun">-</span><span class="pln">g lodash</span></pre>

<p>
	لن يثبِّت npm الحزمة ضمن المجلد المحلي وإنما سيستخدم موقعًا عامًا، إذ سيخبرك الأمر <code>npm root -g</code> بمكان هذا الموقع الدقيق على جهازك، حيث يمكن أن يكون هذا الموقع <code>‎/usr/local/lib/node_modules</code> في <a href="https://academy.hsoub.com/apps/operating-systems/macos/" rel="">نظام macOS</a> أو <a href="https://academy.hsoub.com/devops/linux/%d8%a3%d9%84%d9%81-%d8%a8%d8%a7%d8%a1-%d8%a3%d8%b3%d8%a7%d8%b3%d9%8a%d8%a7%d8%aa-%d8%a7%d9%84%d8%aa%d8%b9%d8%a7%d9%85%d9%84-%d9%85%d8%b9-%d9%84%d9%8a%d9%86%d9%83%d8%b3-r61/" rel="">لينكس</a>، ويمكن أن يكون <code>C:\Users\YOU\AppData\Roaming\npm\node_modules</code> على نظام ويندوز، لكن إذا استخدمت <code>nvm</code> لإدارة إصدارات Node.js، فقد يختلف هذا الموقع، حيث استخدمنا <code>nvm</code> على سبيل المثال وكان موقع الحزم هو <code>‎/Users/flavio/.nvm/versions/node/v8.9.0/lib/node_modules</code>.
</p>

<h3>
	كيفية استخدام أو تنفيذ حزمة مثبتة باستخدام npm
</h3>

<p>
	هل تساءلت عن كيفية تضمين واستخدام حزمة مثبَّتة في مجلد node_modules في شيفرتك الخاصة؟ حسنًا، لنفترض أنك ثبَّت مكتبة أدوات جافاسكربت الشائعة <code>lodash</code> باستخدام الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_32" style="">
<span class="pln">npm install lodash</span></pre>

<p>
	سيؤدي ذلك إلى تثبيت الحزمة في مجلد <code>node_modules</code> المحلي التي يمكنك استخدامها في شيفرتك الخاصة من خلال استيرادها في برنامجك باستخدام <code>require</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_34" style="">
<span class="kwd">const</span><span class="pln"> _ </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'lodash)</span></pre>

<p>
	إذا كانت حزمتك الخاصة قابلة للتنفيذ، فسيوضَع الملف القابل للتنفيذ ضمن المجلد <code>node_modules/.bin/‎</code>، وإحدى طرق إثبات ذلك هي استخدام الحزمة <a href="https://www.npmjs.com/package/cowsay" rel="external nofollow">cowsay</a>، حيث توفِّر هذه الحزمة برنامج سطر أوامر يمكن تنفيذه لإنشاء بقرة تقول شيئًا -وحيوانات أخرى أيضًا-، حيث ستثبِّت هذه الحزمة نفسها وعددًا من الاعتماديات في المجلد node_modules عند تثبيتها باستخدام الأمر <code>npm install cowsay</code>:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="91595" href="https://academy.hsoub.com/uploads/monthly_2022_02/02_CowsayInstallation.png.79cccd4c1eca188736a117d9d9eac3a6.png" rel=""><img alt="02_CowsayInstallation.png" class="ipsImage ipsImage_thumbnailed" data-fileid="91595" data-unique="0cvm5ez7x" src="https://academy.hsoub.com/uploads/monthly_2022_02/02_CowsayInstallation.png.79cccd4c1eca188736a117d9d9eac3a6.png"></a>
</p>

<p>
	يوجد مجلد ‎<code>.bin‎</code>‎ مخفي يحتوي على روابط رمزية إلى ملفات cowsay الثنائية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="91596" href="https://academy.hsoub.com/uploads/monthly_2022_02/03_CowsayBinFolder.png.78d97d13ff91d9be90485f9c9f9acc15.png" rel=""><img alt="03_CowsayBinFolder.png" class="ipsImage ipsImage_thumbnailed" data-fileid="91596" data-unique="uaxbqro41" src="https://academy.hsoub.com/uploads/monthly_2022_02/03_CowsayBinFolder.png.78d97d13ff91d9be90485f9c9f9acc15.png"></a>
</p>

<p>
	يمكنك تنفيذ هذه الحزمة من خلال كتابة <code>‎./node_modules/.bin/cowsay</code> لتشغيلها، لكن يُعَدّ <a href="https://flaviocopes.com/npx/" rel="external nofollow">npx</a> المُضمَّن في الإصدارات الأخيرة من npm -منذ الإصدار 5.2- الخيار الأفضل، فما عليك إلا تشغيل الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_36" style="">
<span class="pln">npx cowsay</span></pre>

<p>
	وسيجد npx موقع الحزمة.
</p>

<h3>
	تحديث الحزم
</h3>

<p>
	أصبح التحديث سهلًا أيضًا عن طريق تشغيل الأمر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_38" style="">
<span class="pln">npm update</span></pre>

<p>
	سيتحقّق <code>npm</code> من جميع الحزم بحثًا عن إصدار أحدث يلبي قيود إدارة الأصدارات Versioning الخاصة بك، كما يمكنك تحديد حزمة واحدة لتحديثها أيضًا باستخدام الأمر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_40" style="">
<span class="pln">npm update </span><span class="pun">&lt;</span><span class="pln">package</span><span class="pun">-</span><span class="pln">name</span><span class="pun">&gt;</span></pre>

<p>
	إذا أردت تحديث جميع اعتماديات npm المخزَّنة في الملف package.json -الذي سنشرحه بعد قليل- إلى أحدث إصدار متاح لها، فثبَّتَ حزمةً باستخدام الأمر <code>npm install &lt;packagename&gt;‎</code> الذي سينزِّل أحدث إصدار متاح من الحزمة ويوضَع هذا الإصدار في مجلد <code>node_modules</code>، وستُضاف مدخلة مقابلة إلى الملف <code>package.json</code> والملف <code>package-lock.json</code> الموجودَين في مجلدك الحالي، إذ يحسب npm الاعتماديات ويثبّت أحدث إصدار متاح منها أيضًا.
</p>

<p>
	لنفترض أنك ثبَّتَ الحزمة <a href="https://www.npmjs.com/package/cowsay" rel="external nofollow">cowsay</a>، وهي أداة سطر أوامر رائعة تتيح لك إنشاء بقرة تقول أشياء، فإذا ثبَّتَها باستخدام الأمر <code>npm install cowsay</code>، فستضاف المدخلة التالية إلى ملف <code>package.json</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_42" style="">
<span class="pun">{</span><span class="pln">
  </span><span class="str">"dependencies"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
     </span><span class="str">"cowsay"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^1.3.1"</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يمثّل ما يلي جزءًا من ملف <code>package-lock.json</code>، حيث أزلنا الاعتماديات المتداخلة للتوضيح:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_47" style="">
<span class="pun">{</span><span class="pln">
  </span><span class="str">"requires"</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"lockfileVersion"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"dependencies"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
     </span><span class="str">"cowsay"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="str">"version"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"1.3.1"</span><span class="pun">,</span><span class="pln">
        </span><span class="str">"resolved"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"https://registry.npmjs.org/cowsay/-/cowsay-1.3.1.tgz"</span><span class="pun">,</span><span class="pln">
        </span><span class="str">"integrity"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"sha512-3PVFe6FePVtPj1HTeLin9v8WyLl+VmM1l1H/5P+BTTDkMAjufp+0F9eLjzRnOHzVAYeIYFF5po5NjRrgefnRMQ=="</span><span class="pun">,</span><span class="pln">
        </span><span class="str">"requires"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
           </span><span class="str">"get-stdin"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^5.0.1"</span><span class="pun">,</span><span class="pln">
           </span><span class="str">"optimist"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"~0.6.1"</span><span class="pun">,</span><span class="pln">
           </span><span class="str">"string-width"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"~2.1.1"</span><span class="pun">,</span><span class="pln">
           </span><span class="str">"strip-eof"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^1.0.0"</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
     </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يوضّح هذان الملفان أننا ثبَّتنا الإصدار <code>1.3.1</code> من الحزمة cowsay باستخدام قاعدة التحديثات <code>‎^1.3.1</code>، والتي تعني بالنسبة لقواعد إدارة إصدارات npm أنه يمكن تحديث npm إلى إصدار حزمة التصحيح patch والإصدار الثانوي minor، أي <code>0.13.1</code> و<code>0.14.0</code> وما إلى ذلك، فإذا كان هناك إصدار ثانوي أو إصدار حزمة تصحيح جديد وكتبنا الأمر <code>npm update</code>، فسيُحدَّث الإصدار المثبَّت، وسيُملَأ ملف <code>package-lock.json</code> بالإصدار الجديد، بينما يبقى الملف <code>package.json</code> دون تغيير، كما يمكنك اكتشاف إصدارات الحزم الجديدة من خلال تشغيل الأمر <code>npm outdated</code>، وفيما يلي قائمة ببعض الحزم القديمة في مستودع واحد لم نحدِّثها لفترة طويلة:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="91598" href="https://academy.hsoub.com/uploads/monthly_2022_02/05_OutdatedPackages.png.e5083108d0b50c69458ea971960481d2.png" rel=""><img alt="05_OutdatedPackages.png" class="ipsImage ipsImage_thumbnailed" data-fileid="91598" data-unique="umskdxa78" src="https://academy.hsoub.com/uploads/monthly_2022_02/05_OutdatedPackages.png.e5083108d0b50c69458ea971960481d2.png" style="width: 650px; height: auto;"></a>
</p>

<p>
	تُعَدّ بعض هذه التحديثات إصدارات رئيسية، إذ لن يؤدي تشغيل الأمر <code>npm update</code> إلى تحديثها، فالإصدارات الرئيسية لا تُحدَّث بهذه الطريقة أبدًا لأنها حسب التعريف تقدِّم تغييرات جذرية، ولأن <code>npm</code> يريد توفير المتاعب عليك، في حين يمكنك تحديث جميع الحزم إلى إصدار رئيسي جديد من خلال تثبيت الحزمة تثبيتًا عامًا باستخدام الأمر <code>npm-check-updates</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_50" style="">
<span class="pln">npm install </span><span class="pun">-</span><span class="pln">g npm</span><span class="pun">-</span><span class="pln">check</span><span class="pun">-</span><span class="pln">updates</span></pre>

<p>
	ثم تشغيلها باستخدام الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_52" style="">
<span class="pln">ncu </span><span class="pun">-</span><span class="pln">u</span></pre>

<p>
	سيؤدي ذلك إلى ترقية جميع تلميحات الإصدار في ملف <code>package.json</code> إلى الاعتماديات <code>dependencies</code> و<code>devDependencies</code>، لذلك يستطيع npm تثبيت الإصدار الرئيسي الجديد، ويمكنك الآن تشغيل أمر التحديث كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_54" style="">
<span class="pln">npm update</span></pre>

<p>
	إذا حمّلتَ المشروع بدون اعتماديات <code>node_modules</code> وأردت تثبيت الإصدارات الجديدة أولًا، فما عليك إلا تشغيل الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_56" style="">
<span class="pln">npm install</span></pre>

<h3>
	إدارة الإصدارات وسرد إصدارات الحزم المثبتة
</h3>

<p>
	يدير <code>npm</code> أيضًا -بالإضافة إلى التنزيلات العادية- عملية الأصدَرة versioning، بحيث يمكنك تحديد إصدار معيّن من الحزمة، أو طلب إصدار أحدث أو أقدم مما تحتاجه، وستجد في كثير من الأحيان أنّ المكتبة متوافقة فقط مع إصدار رئيسي لمكتبة أخرى، أو قد تجد خطأً غير مُصحَّح بعد في الإصدار الأخير من مكتبة، مما يسبِّب مشاكلًا، كما يساعد تحديد إصدار صريح من مكتبة أيضًا في إبقاء كل فريق العمل على إصدار الحزمة الدقيق نفسه، بحيث يشغّل الفريق بأكمله الإصدار نفسه حتى تحديث ملف <code>package.json</code>.
</p>

<p>
	تساعد عملية تحديد الإصدار كثيرًا في جميع الحالات السابقة، حيث يتبع <code>npm</code> معيار إدارة الإصدارات الدلالية semantic versioning - أو semver اختصارًا- والذي سنشرحه تاليًا في قسم منفصل، وقد تحتاج عمومًا إلى معرفة إصدار حزمة معينة ثبَّتها في تطبيقك، وهنا يمكنك استخدم الأمر التالي لمعرفة الإصدار الأحدث من جميع حزم npm المثبَّتة بالإضافة إلى اعتمادياتها:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_58" style="">
<span class="pln">npm list</span></pre>

<p>
	إليك المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_62" style="">
<span class="pln">npm list

</span><span class="pun">/</span><span class="typ">Users</span><span class="pun">/</span><span class="pln">flavio</span><span class="pun">/</span><span class="pln">dev</span><span class="pun">/</span><span class="pln">node</span><span class="pun">/</span><span class="pln">cowsay
</span><span class="pun">└─┬</span><span class="pln"> cowsay@1</span><span class="pun">.</span><span class="lit">3.1</span><span class="pln">
  </span><span class="pun">├──</span><span class="pln"> </span><span class="kwd">get</span><span class="pun">-</span><span class="pln">stdin@5</span><span class="pun">.</span><span class="lit">0.1</span><span class="pln">
  </span><span class="pun">├─┬</span><span class="pln"> optimist@0</span><span class="pun">.</span><span class="lit">6.1</span><span class="pln">
  </span><span class="pun">│</span><span class="pln"> </span><span class="pun">├──</span><span class="pln"> minimist@0</span><span class="pun">.</span><span class="lit">0.10</span><span class="pln">
  </span><span class="pun">│</span><span class="pln"> </span><span class="pun">└──</span><span class="pln"> wordwrap@0</span><span class="pun">.</span><span class="lit">0.3</span><span class="pln">
  </span><span class="pun">├─┬</span><span class="pln"> string</span><span class="pun">-</span><span class="pln">width@2</span><span class="pun">.</span><span class="lit">1.1</span><span class="pln">
  </span><span class="pun">│</span><span class="pln"> </span><span class="pun">├──</span><span class="pln"> is</span><span class="pun">-</span><span class="pln">fullwidth</span><span class="pun">-</span><span class="pln">code</span><span class="pun">-</span><span class="pln">point@2</span><span class="pun">.</span><span class="lit">0.0</span><span class="pln">
  </span><span class="pun">│</span><span class="pln"> </span><span class="pun">└─┬</span><span class="pln"> strip</span><span class="pun">-</span><span class="pln">ansi@4</span><span class="pun">.</span><span class="lit">0.0</span><span class="pln">
  </span><span class="pun">│</span><span class="pln"> </span><span class="pun">└──</span><span class="pln"> ansi</span><span class="pun">-</span><span class="pln">regex@3</span><span class="pun">.</span><span class="lit">0.0</span><span class="pln">
  </span><span class="pun">└──</span><span class="pln"> strip</span><span class="pun">-</span><span class="pln">eof@1</span><span class="pun">.</span><span class="lit">0.0</span></pre>

<p>
	يمكنك فتح ملف <code>package-lock.json</code> فقط، ولكنه يحتاج بعض الفحص البصري، حيث يطبق الأمر <code>npm list -g</code> الشيء نفسه ولكن للحزم المثبَّتة تثبيتًا عامًا، كما يمكنك الحصول على حزم المستوى الأعلى فقط، أي الحزم التي طلبتَ من npm تثبيتها وأدرجتَها في ملف <code>package.json</code>، من خلال تشغيل الأمر <code>npm list --depth=0</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_64" style="">
<span class="pln">npm list </span><span class="pun">--</span><span class="pln">depth</span><span class="pun">=</span><span class="lit">0</span><span class="pln">

</span><span class="pun">/</span><span class="typ">Users</span><span class="pun">/</span><span class="pln">flavio</span><span class="pun">/</span><span class="pln">dev</span><span class="pun">/</span><span class="pln">node</span><span class="pun">/</span><span class="pln">cowsay
</span><span class="pun">└──</span><span class="pln"> cowsay@1</span><span class="pun">.</span><span class="lit">3.1</span></pre>

<p>
	يمكنك الحصول على إصدار حزمة معينة عن طريق تحديد اسمها كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_66" style="">
<span class="pln">npm list cowsay

</span><span class="pun">/</span><span class="typ">Users</span><span class="pun">/</span><span class="pln">flavio</span><span class="pun">/</span><span class="pln">dev</span><span class="pun">/</span><span class="pln">node</span><span class="pun">/</span><span class="pln">cowsay
</span><span class="pun">└──</span><span class="pln"> cowsay@1</span><span class="pun">.</span><span class="lit">3.1</span></pre>

<p>
	وتعمل هذه الطريقة أيضًا مع اعتماديات الحزم التي ثبَّتها كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_68" style="">
<span class="pln">npm list minimist

</span><span class="pun">/</span><span class="typ">Users</span><span class="pun">/</span><span class="pln">flavio</span><span class="pun">/</span><span class="pln">dev</span><span class="pun">/</span><span class="pln">node</span><span class="pun">/</span><span class="pln">cowsay
</span><span class="pun">└─┬</span><span class="pln"> cowsay@1</span><span class="pun">.</span><span class="lit">3.1</span><span class="pln">
  </span><span class="pun">└─┬</span><span class="pln"> optimist@0</span><span class="pun">.</span><span class="lit">6.1</span><span class="pln">
     </span><span class="pun">└──</span><span class="pln"> minimist@0</span><span class="pun">.</span><span class="lit">0.10</span></pre>

<p>
	إذا أردت معرفة أحدث إصدار متوفر من الحزمة في مستودع npm، فشغّل الأمر <code>npm view [package_name] version</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_70" style="">
<span class="pln">npm view cowsay version

</span><span class="lit">1.3</span><span class="pun">.</span><span class="lit">1</span></pre>

<h3>
	إلغاء تثبيت حزم npm
</h3>

<p>
	قد تسأل نفسك ماذا لو أردت إلغاء تثبيت حزمة npm المُثبَّتة تثبيتًا محليًا أو عامًا؟ يمكنك إلغاء تثبيت حزمة مثبَّتة مسبقًا محليًا locally باستخدام الأمر <code>npm install &lt;packagename&gt;‎</code> في مجلد <code>node_modules</code> من خلال تشغيل الأمر التالي في مجلد جذر المشروع، أي المجلد الذي يحتوي على مجلد node_modules:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_73" style="">
<span class="pln">npm uninstall </span><span class="pun">&lt;</span><span class="pln">package</span><span class="pun">-</span><span class="pln">name</span><span class="pun">&gt;</span></pre>

<p>
	تُستخدَم الراية <code>‎-S</code> أو <code>‎--save</code> لإزالة جميع المراجع في ملف <code>package.json</code>، فإذا كانت الحزمة عبارة عن اعتمادية تطوير مُدرَجة في اعتماديات devDependencies الخاصة بملف <code>package.json</code>، فيجب عليك استخدام الراية <code>‎-D</code> أو الراية <code>‎--save-dev</code> لإزالتها من الملف كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_75" style="">
<span class="pln">npm uninstall </span><span class="pun">-</span><span class="pln">S </span><span class="pun">&lt;</span><span class="pln">package</span><span class="pun">-</span><span class="pln">name</span><span class="pun">&gt;</span><span class="pln">
npm uninstall </span><span class="pun">-</span><span class="pln">D </span><span class="pun">&lt;</span><span class="pln">package</span><span class="pun">-</span><span class="pln">name</span><span class="pun">&gt;</span></pre>

<p>
	إذا ثُبِّتت الحزمة تثبيتًا عامًا globally، فيجب إضافة الراية <code>‎-g</code> أو الراية <code>‎--global</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_77" style="">
<span class="pln">npm uninstall </span><span class="pun">-</span><span class="pln">g </span><span class="pun">&lt;</span><span class="pln">package</span><span class="pun">-</span><span class="pln">name</span><span class="pun">&gt;</span></pre>

<p>
	إليك المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_81" style="">
<span class="pln">npm uninstall </span><span class="pun">-</span><span class="pln">g webpack</span></pre>

<p>
	كما يمكنك تشغيل هذا الأمر من أي مكان تريده على نظامك لأن المجلد الذي تتواجد فيه حاليًا غير مهم.
</p>

<h2>
	تشغيل مهام وتنفيذ سكربتات من سطر الأوامر
</h2>

<p>
	يدعم ملف package.json تنسيقًا لتحديد مهام سطر الأوامر التي يمكن تشغيلها باستخدام الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_83" style="">
<span class="pln">npm run </span><span class="pun">&lt;</span><span class="pln">task</span><span class="pun">-</span><span class="pln">name</span><span class="pun">&gt;</span></pre>

<p>
	فمثلًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_85" style="">
<span class="pun">{</span><span class="pln">
  </span><span class="str">"scripts"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
     </span><span class="str">"start-dev"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"node lib/server-development"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"start"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"node lib/server-production"</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يشيع استخدام هذه الميزة لتشغيل <a href="https://academy.hsoub.com/programming/workflow/%D8%AF%D9%84%D9%8A%D9%84-webpack-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-r866/" rel="">Webpack</a>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_87" style="">
<span class="pun">{</span><span class="pln">
  </span><span class="str">"scripts"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
     </span><span class="str">"watch"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"webpack --watch --progress --colors --config webpack.conf.js"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"dev"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"webpack --progress --colors --config webpack.conf.js"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"prod"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"NODE_ENV=production webpack -p --config webpack.conf.js"</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يمكنك تشغيل الأوامر التالية بدلًا من كتابة الأوامر الطويلة السابقة التي يسهل نسيانها أو كتابتها بصورة خاطئة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_89" style="">
<span class="pln">$ npm run watch
$ npm run dev
$ npm run prod</span></pre>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="91597" href="https://academy.hsoub.com/uploads/monthly_2022_02/04_CowsayNpx.png.a541ecf10c502aeee4ffddde0dc16a78.png" rel=""><img alt="04_CowsayNpx.png" class="ipsImage ipsImage_thumbnailed" data-fileid="91597" data-unique="1r6nq93at" src="https://academy.hsoub.com/uploads/monthly_2022_02/04_CowsayNpx.png.a541ecf10c502aeee4ffddde0dc16a78.png" style="width: 570px; height: auto;"></a>
</p>

<h2>
	الملف package.json نقطة ارتكاز المشروع
</h2>

<p>
	يُعَدّ ملف package.json عنصرًا أساسيًا في كثير من قواعد شيفرات التطبيقات المستندة إلى نظام <a href="https://wiki.hsoub.com/Node.js" rel="external">Node.js</a> المجتمعي، فإذا استخدمت سابقًا لغة جافاسكريبت أو تعاملت مع مشروع JavaScript أو Node.js أو مشروع واجهة أمامية، فلا بد أنك صادفت ملف package.json، كما يُعَدّ ملف package.json بيانًا manifest لمشروعك، إذ يمكنه تطبيق أشياء غير مرتبطة متعددة، فهو مستودع مركزي لإعداد الأدوات مثلًا، كما أنه المكان الذي يخزّن فيه <code>npm</code> و<code>yarn</code> أسماء وإصدارات الحزم المُثبَّتة.
</p>

<h3>
	معمارية الملف package.json
</h3>

<p>
	فيما يلي مثال لملف package.json:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_93" style="">
<span class="pun">{</span><span class="pln">

</span><span class="pun">}</span></pre>

<p>
	هذا الملف فارغ، إذ لا توجد متطلبات ثابتة لما يجب تواجده في ملف package.json خاص بتطبيقٍ ما، فالشرط الوحيد هو أنه يجب أن يتبع تنسيق <a href="https://academy.hsoub.com/programming/javascript/%D8%AA%D8%B9%D9%84%D9%85-json-r604/" rel="">JSON</a>، وإلّا فلا يمكن أن تقرأه البرامج التي تحاول الوصول إلى خصائصه برمجيًا، وإذا أردت بناء حزمة <a href="https://academy.hsoub.com/programming/javascript/nodejs/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-nodejs-r1463/" rel="">Node.js</a> التي ترغب في توزيعها عبر <code>npm</code>، فسيتغيّر كل شيء جذريًا، إذ يجب أن يكون لديك مجموعة من الخصائص التي ستساعد الأشخاص الآخرين على استخدام هذا الملف، حيث سنتحدّث عن ذلك لاحقًا، وإليك مثال آخر عن ملف package.json:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_97" style="">
<span class="pun">{</span><span class="pln">
  </span><span class="str">"name"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"test-project"</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يعرِّف الملف السابق خاصية الاسم <code>name</code> والتي تعطي اسم التطبيق أو الحزمة الموجودة في المجلد نفسه الذي يوجد فيه هذا الملف، وإليك المثال التالي الأكثر تعقيدًا والمُستخرَج من عينة تطبيق <a href="https://academy.hsoub.com/programming/javascript/vuejs/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-vuejs-r989/" rel="">Vue.js</a>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_101" style="">
<span class="pun">{</span><span class="pln">
  </span><span class="str">"name"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"test-project"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"version"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"1.0.0"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"description"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"A Vue.js project"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"main"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"src/main.js"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"private"</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"scripts"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
     </span><span class="str">"dev"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"webpack-dev-server --inline --progress --config build/webpack.dev.conf.js"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"start"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"npm run dev"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"unit"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"jest --config test/unit/jest.conf.js --coverage"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"test"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"npm run unit"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"lint"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"eslint --ext .js,.vue src test/unit"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"build"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"node build/build.js"</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="str">"dependencies"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
     </span><span class="str">"vue"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^2.5.2"</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="str">"devDependencies"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
     </span><span class="str">"autoprefixer"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^7.1.2"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"babel-core"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^6.22.1"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"babel-eslint"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^8.2.1"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"babel-helper-vue-jsx-merge-props"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^2.0.3"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"babel-jest"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^21.0.2"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"babel-loader"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^7.1.1"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"babel-plugin-dynamic-import-node"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^1.2.0"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"babel-plugin-syntax-jsx"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^6.18.0"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"babel-plugin-transform-es2015-modules-commonjs"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^6.26.0"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"babel-plugin-transform-runtime"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^6.22.0"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"babel-plugin-transform-vue-jsx"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^3.5.0"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"babel-preset-env"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^1.3.2"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"babel-preset-stage-2"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^6.22.0"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"chalk"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^2.0.1"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"copy-webpack-plugin"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^4.0.1"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"css-loader"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^0.28.0"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"eslint"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^4.15.0"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"eslint-config-airbnb-base"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^11.3.0"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"eslint-friendly-formatter"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^3.0.0"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"eslint-import-resolver-webpack"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^0.8.3"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"eslint-loader"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^1.7.1"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"eslint-plugin-import"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^2.7.0"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"eslint-plugin-vue"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^4.0.0"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"extract-text-webpack-plugin"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^3.0.0"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"file-loader"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^1.1.4"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"friendly-errors-webpack-plugin"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^1.6.1"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"html-webpack-plugin"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^2.30.1"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"jest"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^22.0.4"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"jest-serializer-vue"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^0.3.0"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"node-notifier"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^5.1.2"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"optimize-css-assets-webpack-plugin"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^3.2.0"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"ora"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^1.2.0"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"portfinder"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^1.0.13"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"postcss-import"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^11.0.0"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"postcss-loader"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^2.0.8"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"postcss-url"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^7.2.1"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"rimraf"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^2.6.0"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"semver"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^5.3.0"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"shelljs"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^0.7.6"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"uglifyjs-webpack-plugin"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^1.1.1"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"url-loader"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^0.5.8"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"vue-jest"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^1.0.2"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"vue-loader"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^13.3.0"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"vue-style-loader"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^3.0.1"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"vue-template-compiler"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^2.5.2"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"webpack"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^3.6.0"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"webpack-bundle-analyzer"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^2.9.0"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"webpack-dev-server"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^2.9.1"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"webpack-merge"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^4.1.0"</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="str">"engines"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
     </span><span class="str">"node"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"&gt;= 6.0.0"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"npm"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"&gt;= 3.0.0"</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="str">"browserslist"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
     </span><span class="str">"&gt; 1%"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"last 2 versions"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"not ie &lt;= 8"</span><span class="pln">
  </span><span class="pun">]</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	هناك خاصيات متعددة يجب شرحها في المثال السابق:
</p>

<ul>
<li>
		<code>name</code> التي تضبط اسم التطبيق أو الحزمة.
	</li>
	<li>
		<code>version</code> التي تشير إلى الإصدار الحالي.
	</li>
	<li>
		<code>description</code> وهي وصف مختصَر للتطبيق أو للحزمة.
	</li>
	<li>
		<code>main</code> التي تضبط نقطة الدخول للتطبيق.
	</li>
	<li>
		<code>private</code> التي تمنع نشر التطبيق أو الحزمة عن طريق الخطأ على <code>npm</code> إذا ضُبِطت على القيمة <code>true</code>.
	</li>
	<li>
		<code>scripts</code> التي تحدّد مجموعة من سكربتات نود التي يمكنك تشغيلها.
	</li>
	<li>
		<code>dependencies</code> التي تضبط قائمة بحزم <code>npm</code> المثبَّتة كاعتماديات.
	</li>
	<li>
		<code>devDependencies</code> التي تضبط قائمة بحزم <code>npm</code> المثبَّتة كاعتماديات تطوير.
	</li>
	<li>
		<code>engines</code> التي تحدّد إصدار نود الذي تعمل عليه هذه الحزمة أو التطبيق.
	</li>
	<li>
		<code>browserslist</code> التي تُستخدَم لمعرفة المتصفحات وإصداراتها التي تريد دعمها.
	</li>
</ul>
<p>
	تُستخدَم جميع هذه الخصائص إما باستخدام <code>npm</code> أو باستخدام أدوات أخرى.
</p>

<h3>
	خاصيات الملف package.json
</h3>

<p>
	يشرح هذا القسم الخاصيات التي يمكنك استخدامها ضمن الملف package.json بالتفصيل، حيث سنطبّق كل شيء على الحزمة، ولكن يمكن تطبيق الشيء نفسه على التطبيقات المحلية التي لا تستخدِمها على أساس حزم، كما تُستخدَم معظم هذه الخاصيات فقط على <a href="https://www.npmjs.com/" rel="external nofollow">npm</a>، ويُستخدَم البعض الآخر بواسطة السكربتات التي تتفاعل مع شيفرتك مثل <code>npm</code> أو غيره.
</p>

<h4>
	name
</h4>

<p>
	تضبط هذه الخاصية اسم الحزمة مثل المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_103" style="">
<span class="str">"name"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"test-project"</span></pre>

<p>
	يجب أن يتضمّن الاسم أقل من 214 محرفًا وألا يحتوي على مسافات، كما لا يمكن أن يحتوي إلّا على أحرف صغيرة أو واصلات <code>-</code> أو شرطات سفلية <code>_</code>، وذلك لأن الحزمة تحصل على <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%A7-%D9%87%D9%88-%D8%B9%D9%86%D9%88%D8%A7%D9%86-url-%D9%81%D9%8A-%D8%A7%D9%84%D9%88%D9%8A%D8%A8%D8%9F-r1435/" rel="">عنوان URL</a> الخاص بها بناءً على هذه الخاصية عند نشرها على <code>npm</code>، إذا نشرتَ هذه الحزمة علنًا على GitHub، فستكون القيمة المناسبة لهذه الخاصية هي اسم مستودع GitHub.
</p>

<h4>
	author
</h4>

<p>
	تعطي هذه الخاصية اسم مؤلف الحزمة مثل المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_107" style="">
<span class="pun">{</span><span class="pln">
  </span><span class="str">"author"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Flavio Copes &lt;flavio@flaviocopes.com&gt; (https://flaviocopes.com)"</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يمكن استخدامها أيضًا بالتنسيق التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_109" style="">
<span class="pun">{</span><span class="pln">
  </span><span class="str">"author"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
     </span><span class="str">"name"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Flavio Copes"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"email"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"flavio@flaviocopes.com"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"url"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"https://flaviocopes.com"</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<h4>
	contributors
</h4>

<p>
	يمكن أن يكون للمشروع مساهم أو أكثر بالإضافة إلى المؤلف، وهذه الخاصية هي مصفوفة تعطي قائمة المساهمين مثل المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_115" style="">
<span class="pun">{</span><span class="pln">
  </span><span class="str">"contributors"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
     </span><span class="str">"Flavio Copes &lt;flavio@flaviocopes.com&gt; (https://flaviocopes.com)"</span><span class="pln">
  </span><span class="pun">]</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	كما يمكن استخدام هذه الخاصية أيضًا بالتنسيق التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_117" style="">
<span class="pun">{</span><span class="pln">
  </span><span class="str">"contributors"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
     </span><span class="pun">{</span><span class="pln">
        </span><span class="str">"name"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Flavio Copes"</span><span class="pun">,</span><span class="pln">
        </span><span class="str">"email"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"flavio@flaviocopes.com"</span><span class="pun">,</span><span class="pln">
        </span><span class="str">"url"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"https://flaviocopes.com"</span><span class="pln">
     </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">]</span><span class="pln">
</span><span class="pun">}</span></pre>

<h4>
	bugs
</h4>

<p>
	تُستخدَم هذه الخاصية للربط بمتتبّع مشاكل الحزمة، أي بصفحة مشاكل GitHub مثلًا كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_119" style="">
<span class="pun">{</span><span class="pln">
  </span><span class="str">"bugs"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"https://github.com/flaviocopes/package/issues"</span><span class="pln">
</span><span class="pun">}</span></pre>

<h4>
	homepage
</h4>

<p>
	تضبط هذه الخاصية صفحة الحزمة الرئيسية مثل المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_121" style="">
<span class="pun">{</span><span class="pln">
  </span><span class="str">"homepage"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"https://flaviocopes.com/package"</span><span class="pln">
</span><span class="pun">}</span></pre>

<h4>
	version
</h4>

<p>
	تشير هذه الخاصية إلى إصدار الحزمة الحالي مثل المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_123" style="">
<span class="str">"version"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"1.0.0"</span></pre>

<p>
	تتبع هذه الخاصية صيغة إدارة الإصدارات الدلالية semver، مما يعني أنّ الإصدار يُعبَّر عنه دائمًا بثلاثة أعداد: <code>x.x.x</code>، حيث يمثِّل العدد الأول الإصدار الرئيسي، ويمثِّل العدد الثاني الإصدار الثانوي؛ أما العدد الثالث فهو إصدار حزمة التصحيح patch version، فالإصدار الذي يصلح الأخطاء فقط هو إصدار حزمة التصحيح، والإصدار الذي يقدّم تغييرات متوافقة مع الإصدارات السابقة هو الإصدار الثانوي، كما يمكن أن يحتوي الإصدار الرئيسي على تغييرات جذرية.
</p>

<h4>
	license
</h4>

<p>
	تشير إلى رخصة الحزمة مثل المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_125" style="">
<span class="str">"license"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"MIT"</span></pre>

<h4>
	keywords
</h4>

<p>
	تحتوي هذه الخاصية على مصفوفة من الكلمات المفتاحية المرتبطة بما تفعله حزمتك مثل المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_127" style="">
<span class="str">"keywords"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
  </span><span class="str">"email"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"machine learning"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"ai"</span><span class="pln">
</span><span class="pun">]</span></pre>

<p>
	تساعد هذه الخاصية في العثور على حزمتك عند التنقل بين حزم مماثلة، أو عند تصفح موقع <a href="https://www.npmjs.com/" rel="external nofollow">npm</a>.
</p>

<h4>
	description
</h4>

<p>
	تحتوي هذه الخاصية على وصف مختصَر للحزمة مثل المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_129" style="">
<span class="str">"description"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"A package to work with strings"</span></pre>

<p>
	هذه الخاصية مفيدة إذا قرّرت نشر حزمتك على <code>npm</code> لمعرفة معلومات الحزمة.
</p>

<h4>
	repository
</h4>

<p>
	تحدّد هذه الخاصية مكان وجود مستودع الحزمة مثل المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_131" style="">
<span class="str">"repository"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"github:flaviocopes/testing"</span><span class="pun">,</span></pre>

<p>
	لاحظ البادئة <code>github</code>، وهناك خدمات شائعة أخرى مثل gitlab:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_133" style="">
<span class="str">"repository"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"gitlab:flaviocopes/testing"</span><span class="pun">,</span></pre>

<p>
	وأيضًا bitbucket:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_135" style="">
<span class="str">"repository"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"bitbucket:flaviocopes/testing"</span><span class="pun">,</span></pre>

<p>
	يمكنك ضبط نظام التحكم بالإصدارات بصورة صريحة كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_137" style="">
<span class="str">"repository"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="str">"type"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"git"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"url"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"https://github.com/flaviocopes/testing.git"</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	كما يمكنك استخدام أنظمة مختلفة للتحكم بالإصدارات كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_139" style="">
<span class="str">"repository"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="str">"type"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"svn"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"url"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"..."</span><span class="pln">
</span><span class="pun">}</span></pre>

<h4>
	main
</h4>

<p>
	تضبط هذه الخاصية نقطة الدخول إلى الحزمة، وهي المكان الذي سيبحث فيه التطبيق عن عمليات تصدير الوحدة عند استيرادها في أحد التطبيقات مثل المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_141" style="">
<span class="str">"main"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"src/main.js"</span></pre>

<h4>
	private
</h4>

<p>
	إذا ضُبِطت هذه الخاصية على القيمة <code>true</code>، فستمنع نشر التطبيق أو الحزمة عن طريق الخطأ على <code>npm</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_143" style="">
<span class="str">"private"</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span></pre>

<h4>
	scripts
</h4>

<p>
	تحدّد هذه الخاصية مجموعة سكربتات نود التي يمكنك تشغيلها مثل المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_145" style="">
<span class="str">"scripts"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="str">"dev"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"webpack-dev-server --inline --progress --config build/webpack.dev.conf.js"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"start"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"npm run dev"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"unit"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"jest --config test/unit/jest.conf.js --coverage"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"test"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"npm run unit"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"lint"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"eslint --ext .js,.vue src test/unit"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"build"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"node build/build.js"</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تُعَدّ هذه السكربتات تطبيقات سطر الأوامر، حيث يمكنك تشغيلها عن طريق استدعاء الأمر <code>npm run XXXX</code> أو <code>yarn XXXX</code>، حيث <code>XXXX</code> هو اسم الأمر مثل <code>npm run dev</code>، كما يمكنك إعطاء الأمر أيّ اسم تريده، ويمكن للسكربتات فعل أيّ شيء تريده.
</p>

<h4>
	dependencies
</h4>

<p>
	تضبط هذه الخاصية قائمة حزم npm المثبَّتة على أساس اعتماديات. إذا ثبَّتَ حزمةً باستخدام npm أو yarn كما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1124_155" style="">
<span class="pln">npm install </span><span class="tag">&lt;PACKAGENAME&gt;</span><span class="pln">
yarn add </span><span class="tag">&lt;PACKAGENAME&gt;</span></pre>

<p>
	ستُدخَل تلك الحزمة في هذه القائمة تلقائيًا، وإليك المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_149" style="">
<span class="str">"dependencies"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="str">"vue"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^2.5.2"</span><span class="pln">
</span><span class="pun">}</span></pre>

<h4>
	devDependencies
</h4>

<p>
	تضبط هذه الخاصية قائمة حزم npm المثبَّتة على أساس اعتماديات تطوير، وهي تختلف عن الخاصية <code>dependencies</code> لأنها مُخصَّصة للتثبيت على آلة تطوير فقط، وليست ضرورية لتشغيل الشيفرة في عملية الإنتاج، فإذا ثبَّتَ حزمةً باستخدام npm أو yarn:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1124_153" style="">
<span class="pln">npm install --dev </span><span class="tag">&lt;PACKAGENAME&gt;</span><span class="pln">
yarn add --dev </span><span class="tag">&lt;PACKAGENAME&gt;</span></pre>

<p>
	فستُدخَل تلك الحزمة في هذه القائمة تلقائيًا، وإليك المثال التالي:
</p>

<pre class="ipsCode">
"devDependencies": {
  "autoprefixer": "^7.1.2",
  "babel-core": "^6.22.1"
}
</pre>

<h4>
	engines
</h4>

<p>
	تضبط هذه الخاصية إصدارات Node.js والأوامر الأخرى التي تعمل عليها هذه الحزمة أو التطبيق مثل المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_157" style="">
<span class="str">"engines"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="str">"node"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"&gt;= 6.0.0"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"npm"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"&gt;= 3.0.0"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"yarn"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^0.13.0"</span><span class="pln">
</span><span class="pun">}</span></pre>

<h4>
	browserslist
</h4>

<p>
	تُستخدَم هذه الخاصية لمعرفة المتصفحات وإصداراتها التي تريد دعمها، وقد أشارت إليها أدوات Babel وAutoprefixer وأدوات أخرى أنها تُستخدَم لإضافة تعويض نقص دعم المتصفحات polyfills والنسخ الاحتياطية fallbacks اللازمة للمتصفحات التي تستهدفها، وإليك المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_159" style="">
<span class="str">"browserslist"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
  </span><span class="str">"&gt; 1%"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"last 2 versions"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"not ie &lt;= 8"</span><span class="pln">
</span><span class="pun">]</span></pre>

<p>
	يعني الإعداد السابق أنك تريد دعم آخر إصدارين رئيسيين من جميع المتصفحات باستخدام 1‎%‎ على الأقل -من إحصائيات موقع <a href="https://caniuse.com/" rel="external nofollow">caniuse</a>- باستثناء الإصدار IE8 والإصدارات الأقدم (اطلع على المزيد من <a href="https://www.npmjs.com/package/browserslist" rel="external nofollow">موقع حزمة</a>).
</p>

<h4>
	خصائص خاصة بالأوامر
</h4>

<p>
	يمكن أن يستضيف ملف package.json أيضًا إعدادًا خاصًا بالأوامر مثل Babel وESLint وغير ذلك، فلكل منها خاصية معينة مثل <code>eslintConfig</code> و<code>babel</code> وغيرها، وهذه هي الخصائص الخاصة بالأوامر، كما يمكنك العثور على كيفية استخدامها في توثيق الأمر أو المشروع المرتبط بها.
</p>

<h3>
	إصدارات الحزم
</h3>

<p>
	رأيت في الوصف أعلاه أرقام الإصدارات مثل: <code>‎~3.0.0</code> أو <code>‎^0.13.0</code>، حيث يحدِّد الرمز الموجود على يسار رقم الإصدار التحديثات التي تقبلها الحزمة من تلك الاعتمادية، ولنفترض استخدام semver -<a href="https://semver.org/lang/ar/" rel="external nofollow">الأصدَرة الدلالية semantic versioning</a>-، حيث تتضمن جميع الإصدارات 3 خانات عددية، أولها هو الإصدار الرئيسي وثانيها هو الإصدار الثانوي وثالثها هو إصدار حزمة التصحيح patch release، وبالتالي لديك القواعد التالية:
</p>

<ul>
<li>
		<code>~</code>: إذا كتبت <code>‎~0.13.0</code>، فهذا يعني أنك تريد فقط تحديث إصدارات حزمة التصحيح، أي أنّ الإصدار <code>0.13.1</code> مقبول، ولكن الإصدار <code>0.14.0</code> ليس كذلك.
	</li>
	<li>
		<code>^</code>: إذا كتبت <code>‎^0.13.0</code>، فهذا يعني أنك تريد تحديث إصدار حزمة التصحيح والإصدار الثانوي، أي الإصدارات <code>0.13.1</code> و<code>0.14.0</code> وهكذا.
	</li>
	<li>
		<code>*</code>: إذا كتبت <code>*</code>، فهذا يعني أنك تقبل جميع التحديثات بما في ذلك ترقيات الإصدارات الرئيسية.
	</li>
	<li>
		<code>&lt;</code>: أي أنك تقبل أي إصدار أعلى من الإصدار الذي تحدِّده.
	</li>
	<li>
		<code>=‎&lt;</code>: أي أنك تقبل أي إصدار مساوي أو أعلى من الإصدار الذي تحدِّده.
	</li>
	<li>
		<code>=&gt;</code>: أي أنك تقبل أي إصدار مساوي أو أدنى من الإصدار الذي تحدِّده.
	</li>
	<li>
		<code>&gt;</code>: أي أنك تقبل أي إصدار أدنى من الإصدار الذي تحدِّده.
	</li>
</ul>
<p>
	وهناك قواعد أخرى هي:
</p>

<ul>
<li>
		بدون رمز: أي أنك تقبل فقط الإصدار الذي تحدّده.
	</li>
	<li>
		<code>latest</code>: أي أنك تريد استخدام أحدث إصدار متاح.
	</li>
</ul>
<p>
	كما يمكنك دمج معظم ما سبق ضمن مجالات مثل <code>1.0.0‎ || &gt;=1.1.0 &lt;1.2.0</code> لاستخدم إما الإصدار 1.0.0 أو أحد الإصدارات الأعلى أو المساوية للإصدار 1.1.0 والأدنى من الإصدار 1.2.0.
</p>

<h2>
	الملف package-lock.json ودوره في إدارة الإصدارات
</h2>

<p>
	يُنشَأ الملف package-lock.json تلقائيًا عند تثبيت حزم نود، حيث قدَّم npm ملف <code>package-lock.json</code> في الإصدار رقم 5، والهدف من هذا الملف هو تتبّع الإصدار الدقيق لكل حزمة مثبَّتة، وبالتالي فإن المنتج قابل لإعادة الإنتاج بنسبة 100% بالطريقة نفسها حتى إذا حدّث القائمون على الصيانة الحزم، كما يحل ذلك مشكلةً تركَها ملف <code>package.json</code> دون حل، إذ يمكنك في ملف <code>package.json</code> ضبط الإصدارات التي تريد الترقية إليها -أي إصدار حزمة التصحيح أو الإصدار الثانوي- باستخدام صيغة semver كما يلي:
</p>

<ul>
<li>
		إذا كتبت <code>‎~0.13.0</code>، فهذا يعني أنك تريد فقط تحديث إصدار حزمة التصحيح، أي أن الإصدار <code>0.13.1</code> مقبول، ولكن الإصدار <code>0.14.0</code> ليس كذلك.
	</li>
	<li>
		إذا كتبت <code>‎^0.13.0</code>، فهذا يعني أنك تريد تحديث إصدار حزمة التصحيح والإصدار الثانوي، أي الإصدارات <code>0.13.1</code> و<code>0.14.0</code> وهكذا.
	</li>
	<li>
		إذا كتبت <code>0.13.0</code>، فهذا يعني الإصدار الدقيق الذي سيُستخدَم.
	</li>
</ul>
<p>
	لستَ ملزَمًا بتوزيع مجلد node_modules الضخم باستخدام برنامج جت Git، وإذا حاولتَ نسخ المشروع على جهاز آخر باستخدام الأمر <code>npm install</code> -إذا حدّدتَ الصيغة <code>~</code> مع إصدار حزمة التصحيح الخاص بالحزمة- فسيثبَّت هذا الإصدار، كما يحدث الأمر ذاته مع الصيغة <code>^</code> والإصدارات الثانوية.
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		توضيح: إذا حدّدتَ إصدارات معينة مثل الإصدار <code>0.13.0</code>، فلن تتأثر بهذه المشكلة.
	</p>
</blockquote>

<p>
	قد تحاول أنت أو أي شخص آخر تهيئة المشروع على الجانب الآخر من العالم عن طريق تشغيل الأمر <code>npm install</code>، لذلك فإن مشروعك الأصلي والمشروع المُهيَّأ حديثًا مختلفان فعليًا، إذ يجب ألّا يدخِل إصدار حزمة التصحيح أو الإصدار الثانوي تغييرات معطِّلة، ولكننا نعلم أن الأخطاء ممكنة الحدوث وستحدث بالفعل.
</p>

<p>
	يضبط ملف <code>package-lock.json</code> الإصدار المثبَّت حاليًا من كل حزمة باستخدام رمز stone، وسيستخدِم <code>npm</code> هذه الإصدارات المحدَّدة عند تشغيل الأمر <code>npm install</code>، كما أنّ هذا المفهوم ليس بجديد، إذ يستخدِم مديرو حزم لغات البرمجة الأخرى -مثل مكتبات Composer في <a href="https://wiki.hsoub.com/PHP" rel="external">لغة PHP</a>- نظامًا مشابهًا منذ سنوات.
</p>

<p>
	يجب أن يكون ملف <code>package-lock.json</code> ملتزمًا بمستودع Git الخاص بك حتى يجلبه أشخاص آخرون إذا كان المشروع عامًا أو لديك متعاونون أو إذا استخدمت Git على أساس مصدر لعمليات النشر، كما ستُحدَّث إصدارات الاعتماديات في ملف <code>package-lock.json</code> عند تشغيل الأمر <code>npm update</code>.
</p>

<p>
	يوضِّح المثال التالي معمارية ملف <code>package-lock.json</code> التي نحصل عليها عند تشغيل الأمر <code>npm install cowsay</code> في مجلد فارغ:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_162" style="">
<span class="pun">{</span><span class="pln">
  </span><span class="str">"requires"</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"lockfileVersion"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"dependencies"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
     </span><span class="str">"ansi-regex"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="str">"version"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"3.0.0"</span><span class="pun">,</span><span class="pln">
        </span><span class="str">"resolved"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz"</span><span class="pun">,</span><span class="pln">
        </span><span class="str">"integrity"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="str">"cowsay"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
     </span><span class="str">"version"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"1.3.1"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"resolved"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"https://registry.npmjs.org/cowsay/-/cowsay-1.3.1.tgz"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"integrity"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"sha512-3PVFe6FePVtPj1HTeLin9v8WyLl+VmM1l1H/5P+BTTDkMAjufp+0F9eLjzRnOHzVAYeIYFF5po5NjRrgefnRMQ=="</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"requires"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="str">"get-stdin"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^5.0.1"</span><span class="pun">,</span><span class="pln">
        </span><span class="str">"optimist"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"~0.6.1"</span><span class="pun">,</span><span class="pln">
        </span><span class="str">"string-width"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"~2.1.1"</span><span class="pun">,</span><span class="pln">
        </span><span class="str">"strip-eof"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^1.0.0"</span><span class="pln">
     </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="str">"get-stdin"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
     </span><span class="str">"version"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"5.0.1"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"resolved"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"integrity"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g="</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="str">"is-fullwidth-code-point"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
     </span><span class="str">"version"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"2.0.0"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"resolved"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"integrity"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="str">"minimist"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
     </span><span class="str">"version"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"0.0.10"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"resolved"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"integrity"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8="</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="str">"optimist"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
     </span><span class="str">"version"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"0.6.1"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"resolved"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"integrity"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"sha1-2j6nRob6IaGaERwybpDrFaAZZoY="</span><span class="pun">,</span><span class="pln">

     </span><span class="str">"requires"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="str">"minimist"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"~0.0.1"</span><span class="pun">,</span><span class="pln">
        </span><span class="str">"wordwrap"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"~0.0.2"</span><span class="pln">
     </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="str">"string-width"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
     </span><span class="str">"version"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"2.1.1"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"resolved"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"integrity"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw=="</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"requires"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="str">"is-fullwidth-code-point"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^2.0.0"</span><span class="pun">,</span><span class="pln">
        </span><span class="str">"strip-ansi"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^4.0.0"</span><span class="pln">
     </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="str">"strip-ansi"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
     </span><span class="str">"version"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"4.0.0"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"resolved"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"integrity"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"sha1-qEeQIusaw2iocTibY1JixQXuNo8="</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"requires"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="str">"ansi-regex"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^3.0.0"</span><span class="pln">
     </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="str">"strip-eof"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
     </span><span class="str">"version"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"1.0.0"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"resolved"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"integrity"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"sha1-u0P/VZim6wXYm1n80SnJgzE2Br8="</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="str">"wordwrap"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
     </span><span class="str">"version"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"0.0.3"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"resolved"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz"</span><span class="pun">,</span><span class="pln">
     </span><span class="str">"integrity"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"sha1-o9XabNXAvAAI03I0u68b7WMFkQc="</span><span class="pln">
     </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	ثبّتنا حزمة <code>cowsay</code> التي تعتمد على الحزم التالية:
</p>

<ul>
<li>
		<code>get-stdin</code>.
	</li>
	<li>
		<code>optimist</code>.
	</li>
	<li>
		<code>string-width</code>.
	</li>
	<li>
		<code>strip-eof</code>.
	</li>
</ul>
<p>
	تتطلب هذه الحزم حزمًا أخرى مثل الحزم الموجودة في الخاصية <code>requires</code> كما يلي:
</p>

<ul>
<li>
		<code>ansi-regex</code>.
	</li>
	<li>
		<code>is-fullwidth-code-point</code>.
	</li>
	<li>
		<code>minimist</code>.
	</li>
	<li>
		<code>wordwrap</code>.
	</li>
	<li>
		<code>strip-eof</code>.
	</li>
</ul>
<p>
	تُضاف هذه الحزم إلى الملف بالترتيب الأبجدي، ولكل منها حقل <code>version</code>، وحقل <code>resolved</code> يؤشّر إلى موقع الحزمة، وسلسلة نصية <code>integrity</code> يمكننا استخدامها للتحقق من الحزمة.
</p>

<h2>
	قواعد الإدارة الدلالية لنسخ الاعتماديات
</h2>

<p>
	تُعَدّ الإدارة الدلالية لنسخ الاعتماديات Semantic Versioning اصطلاحًا يُستخدَم لتوفير معنى للإصدارات، فإذا كان هناك شيء رائع في حزم Node.js، فهو اتفاق الجميع على استخدام هذا المفهوم لترقيم إصداراتهم، كما يُعَدّ مفهوم الإدارة الدلالية للنسخ بسيطًا للغاية، فلكل الإصدارات 3 خانات عددية <code>x.y.z</code>:
</p>

<ul>
<li>
		العدد الأول هو الإصدار الرئيسي.
	</li>
	<li>
		العدد الثاني هو الإصدار الثانوي.
	</li>
	<li>
		العدد الثالث هو إصدار التصحيح.
	</li>
</ul>
<p>
	إذا أردت إنشاء إصدار جديد، فلن تزيد عددًا كما يحلو لك، بل لديك قواعد يجب الالتزام بها وهي:
</p>

<ul>
<li>
		يُحدَّث الإصدار الرئيسي عند إجراء تغييرات غير متوافقة مع <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-api-r1314/" rel="">واجهة برمجة التطبيقات <abbr title="Application Programming Interface | واجهة برمجية">API</abbr></a>.
	</li>
	<li>
		يُحدَّث الإصدار الثانوي عند إضافة عمليات بطريقة متوافقة مع الإصدارات السابقة.
	</li>
	<li>
		يُحدَّث إصدار تصحيح عند إجراء إصلاحات أخطاء متوافقة مع الإصدارات السابقة.
	</li>
</ul>
<p>
	اُعتمِد هذا المفهوم في جميع لغات البرمجة ومن المهم أن تلتزم بها كل حزمة <code>npm</code> لأن النظام بأكمله يعتمد على ذلك، إذ وضَع <code>npm</code> بعض القواعد التي يمكننا استخدامها في ملف <code>package.json</code> لاختيار الإصدارات التي يمكن تحديث حزمنا إليها عند تشغيل الأمر <code>npm update</code>، وتستخدِم هذه القواعد الرموز التالية:
</p>

<ul>
<li>
		<code>^</code>: إذا كتبت <code>‎^0.13.0</code> عند تشغيل الأمر <code>npm update</code>، فهذا يؤدي إلى تحديث إصدار حزمة التصحيح والإصدار الثانوي، أي الإصدارات <code>0.13.1</code> و<code>0.14.0</code> وهكذا.
	</li>
	<li>
		<code>~</code>: إذا كتبت <code>‎~0.13.0</code> عند تشغيل الأمر <code>npm update</code>، فهذا يؤدي إلى تحديث إصدارات حزمة التصحيح، أي أن الإصدار <code>0.13.1</code> مقبول، ولكن الإصدار <code>0.14.0</code> ليس كذلك.
	</li>
	<li>
		<code>&lt;</code>: أي أنك تقبل أي إصدار أعلى من الإصدار الذي تحدده.
	</li>
	<li>
		<code>=‎&lt;</code>: أي أنك تقبل أي إصدار مساوي أو أعلى من الإصدار الذي تحدّده.
	</li>
	<li>
		<code>=&gt;</code>: أي أنك تقبل أي إصدار مساوي أو أدنى من الإصدار الذي تحدّده.
	</li>
	<li>
		<code>&gt;</code>: أي أنك تقبل أي إصدار أدنى من الإصدار الذي تحدّده.
	</li>
	<li>
		<code>=</code>: أي أنك تقبل الإصدار المحدَّد.
	</li>
	<li>
		<code>-</code>: أي أنك تقبل مجالًا من الإصدارات مثل المجال مثل: <code>2.1.0‎ - 2.6.2</code>.
	</li>
	<li>
		<code>||</code>: يُستخدَم لدمج مجموعات من الإصدارات مثل: <code>‎&lt; 2.1 || &gt; 2.6</code>.
	</li>
</ul>
<p>
	يمكنك دمج بعض القواعد السابقة مثل: <code>1.0.0‎ || &gt;=1.1.0 &lt;1.2.0</code> لاستخدام إما الإصدار 1.0.0 أو أحد الإصدارات الأعلى أو المساوية للإصدار 1.1.0 والأدنى من الإصدار 1.2.0.
</p>

<p>
	هناك قواعد أخرى أيضًا هي:
</p>

<ul>
<li>
		بدون رمز: أي أنك تقبل فقط الإصدار الذي تحدّده مثل: <code>1.2.1</code>.
	</li>
	<li>
		<code>latest</code>: أي أنك تريد استخدام أحدث إصدار متاح.
	</li>
</ul>
<h2>
	أنواع الحزم
</h2>

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

<h3>
	الحزم العامة والحزم المحلية
</h3>

<p>
	الفرق الرئيسي بين الحزم المحلية والعامة هو:
</p>

<ul>
<li>
		تُثبَّت الحزم المحلية في المجلد أو المسار حيث تشغّل الأمر <code>npm install &lt;package-name&gt;‎</code>، وتوضَع في مجلد <code>node_modules</code> ضمن هذا المجلد أو المسار.
	</li>
	<li>
		توضَع جميع الحزم العامة في مكان واحد في نظامك بالاعتماد على إعدادك الخاص بغض النظر عن مكان تشغيل الأمر <code>npm install -g &lt;package-name&gt;‎</code>.
	</li>
</ul>
<p>
	وكلاهما مطلوب بالطريقة نفسها في شيفرتك الخاصة كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_165" style="">
<span class="pln">require</span><span class="pun">(</span><span class="str">'package-name'</span><span class="pun">)</span></pre>

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

<p>
	تحتوي جميع المشاريع على نسختها المحلية الخاصة من الحزمة، فقد يبدو ذلك ضياعًا للموارد، ولكنه ضياع ضئيل بالموازنة مع العواقب السلبية المحتمَلة، كما يجب تثبيت الحزمة العامة عندما توفِّر هذه الحزمة أمرًا قابلًا للتنفيذ بحيث تشغّله من <a href="https://academy.hsoub.com/devops/linux/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%B3%D9%83%D8%B1%D8%A8%D8%AA%D8%A7%D8%AA-%D8%A7%D9%84%D8%B5%D8%AF%D9%81%D8%A9-shell-scripts-r252/" rel="">الصدفة shell</a> أي <a href="https://academy.hsoub.com/programming/php/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%B3%D8%B7%D8%B1-%D8%A7%D9%84%D8%A3%D9%88%D8%A7%D9%85%D8%B1-cli-%D9%81%D9%8A-php-r1176/" rel="">واجهة سطر الأوامر CLI</a>، ويُعاد استخدام هذه الحزمة عبر المشاريع، كما يمكنك أيضًا تثبيت الأوامر القابلة للتنفيذ محليًا وتشغيلها باستخدام npx، ولكن تثبيت الحزم العامة أفضل بالنسبة لبعض الحزم.
</p>

<p>
	فيما يلي أمثلة رائعة عن الحزم العامة الشائعة التي قد تعرفها:
</p>

<ul>
<li>
		<code>npm</code>.
	</li>
	<li>
		<code>create-react-app</code>.
	</li>
	<li>
		<code>vue-cli</code>.
	</li>
	<li>
		<code>grunt-cli</code>.
	</li>
	<li>
		<code>mocha</code>.
	</li>
	<li>
		<code>react-native-cli</code>.
	</li>
	<li>
		<code>gatsby-cli</code>.
	</li>
	<li>
		<code>forever</code>.
	</li>
	<li>
		<code>nodemon</code>.
	</li>
</ul>
<p>
	يُحتمَل أن تكون لديك بعض الحزم العامة المثبَّتة على نظامك التي يمكنك رؤيتها عن طريق تشغيل الأمر التالي في سطر الأوامر الخاص بك:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_169" style="">
<span class="pln">npm list </span><span class="pun">-</span><span class="pln">g </span><span class="pun">--</span><span class="pln">depth </span><span class="lit">0</span></pre>

<h3>
	الاعتماديات الأساسية واعتماديات التطوير
</h3>

<p>
	إذا ثبَّتَ حزمة npm باستخدام الأمر <code>npm install &lt;package-name&gt;‎</code>، فهذا يعني أنك ثبّتها على أساس اعتمادية dependency، حيث تُدرَج الحزمة تلقائيًا في ملف <code>package.json</code> ضمن قائمة <code>dependencies</code> بدءًا من الإصدار npm 5، إذ احتجنا سابقًا إلى تحديد الراية <code>‎--save</code> يدويًا، فإذا أضفتَ الراية <code>‎-D</code> أو الراية <code>‎--save-dev</code>، فهذا يعني أنك تثبّتها على أساس اعتمادية تطوير، وبالتالي ستُضاف إلى قائمة <code>devDependencies</code>.
</p>

<p>
	يُقصَد باعتماديات التطوير أنها حزم للتطوير فقط، وهي غير ضرورية في عملية الإنتاج مثل حزم الاختبار أو حزم webpack أو Babel، فإذا كتبت الأمر <code>npm install</code> واحتوى المجلد على ملف <code>package.json</code> في عملية الإنتاج، فستُثبَّت اعتماديات التطوير، حيث يفترض npm أنّ هذه عملية نشر تطوير، كما يجب ضبط الراية <code>‎--production</code> من خلال الأمر <code>npm install --production</code> لتجنب تثبيت اعتماديات التطوير.
</p>

<h2>
	npx
</h2>

<p>
	يُعَدّ npx طريقةً رائعةً جدًا لتشغيل شيفرة نود، كما يوفِّر ميزات مفيدةً متعددةً، إذ كان متاحًا في npm بدءًا من الإصدار 5.2، الذي صدر في شهر 7 من عام 2017.
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		توضيح: إذا لم ترغب في تثبيت npm، فيمكنك تثبيت <a href="https://www.npmjs.com/package/npx" rel="external nofollow">npx على أساس حزمة قائمة بذاتها</a>.
	</p>
</blockquote>

<p>
	يتيح لك <code>npx</code> تشغيل الشيفرة المُنشَأة باستخدام نود والمنشورة من خلال سجل npm، كما يتميز npx بالميزات التالية:
</p>

<h3>
	تشغيل الأوامر المحلية بسهولة
</h3>

<p>
	اعتاد مطورو نود على نشر معظم الأوامر القابلة للتنفيذ على أساس حزم عامة لتكون هذه الأوامر في المسار الصحيح وقابلةً للتنفيذ مباشرةً، إذ كان هذا أمرًا صعبًا جدًا، لأن تثبيت إصدارات مختلفة من الأمر نفسه غير ممكن، كما يؤدي تشغيل الأمر <code>npx commandname</code> تلقائيًا إلى العثور على مرجع الأمر الصحيح ضمن مجلد <code>node_modules</code> الخاص بالمشروع دون الحاجة إلى معرفة المسار الدقيق، ودون الحاجة إلى تثبيت الحزمة على أنها حزمة عامة وفي مسار المستخدِم.
</p>

<h3>
	تنفيذ الأوامر دون تثبيتها
</h3>

<p>
	هناك ميزة أخرى رائعة في <code>npm</code> تسمح بتشغيل الأوامر دون تثبيتها أولًا، حيث يُعَدّ ذلك مفيدًا جدًا للأسباب التالية:
</p>

<ol>
<li>
		لا تحتاج إلى تثبيت أي شيء.
	</li>
	<li>
		يمكنك تشغيل إصدارات مختلفة من الأمر نفسه باستخدام صيغة ‎.@version
	</li>
</ol>
<p>
	يمكن توضيح استخدام <code>npx</code> من خلال الأمر <code>cowsay</code> الذي سيطبع بقرةً تقول ما تكتبه ضمن الأمر، حيث سيطبع الأمر <code>cowsay "Hello"‎</code> ما يلي على سبيل المثال:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_171" style="">
<span class="pln">_______
</span><span class="pun">&lt;</span><span class="pln"> </span><span class="typ">Hello</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">-------</span><span class="pln">
        \ </span><span class="pun">^</span><span class="pln">__</span><span class="pun">^</span><span class="pln">
        \ </span><span class="pun">(</span><span class="pln">oo</span><span class="pun">)</span><span class="pln">\_______
          </span><span class="pun">(</span><span class="pln">__</span><span class="pun">)</span><span class="pln">\       </span><span class="pun">)</span><span class="pln">\/\
              </span><span class="pun">||----</span><span class="pln">w </span><span class="pun">|</span><span class="pln">
              </span><span class="pun">||</span><span class="pln">     </span><span class="pun">||</span></pre>

<p>
	يحدث ذلك إذا كان الأمر <code>cowsay</code> مثبَّتًا تثبيتًا عامًا من npm سابقًا، وإلا فستحصل على خطأ عند محاولة تشغيل الأمر، كما يسمح لك <code>npx</code> بتشغيل الأمر npm السابق دون تثبيته محليًا كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_173" style="">
<span class="pln">npx cowsay </span><span class="str">"Hello"</span></pre>

<p>
	يُعَدّ الأمر السابق للتسلية فقط ودون فائدة، ولكن يمكنك استخدام npx في حالات مهمة أخرى مثل:
</p>

<ul>
<li>
		تشغيل أداة واجهة سطر الأوامر <code>vue</code> لإنشاء تطبيقات جديدة وتشغيلها باستخدام الأمر <code>npx vue create myvue-app</code>
	</li>
	<li>
		إنشاء تطبيق React جديد باستخدام الأمر <code>npx create-react-app my-react-app</code>.
	</li>
</ul>
<p>
	و حالات أخرى أيضًا، كما ستُمسَح الشيفرة المُنزَّلة لهذه الأوامر بمجرد تنزيلها.
</p>

<h3>
	تشغيل شيفرة باستخدام إصدار نود Node مختلف
</h3>

<p>
	استخدم الرمز <code>@</code> لتحديد الإصدار، وادمج ذلك مع حزمة npm التي هي <a href="https://www.npmjs.com/package/node" rel="external nofollow"><code>node</code></a>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_175" style="">
<span class="pln">npx node@6 </span><span class="pun">-</span><span class="pln">v </span><span class="pun">#</span><span class="pln">v6</span><span class="pun">.</span><span class="lit">14.3</span><span class="pln">
npx node@8 </span><span class="pun">-</span><span class="pln">v </span><span class="pun">#</span><span class="pln">v8</span><span class="pun">.</span><span class="lit">11.3</span></pre>

<p>
	يساعد ذلك في تجنب استخدام أدوات مثل أداة <code>nvm</code> أو أدوات إدارة إصدارات نود الأخرى.
</p>

<h3>
	تشغيل أجزاء شيفرة عشوائية مباشرة من عنوان URL
</h3>

<p>
	لا يقيّدك <code>npx</code> بالحزم المنشورة في سجل npm، إذ يمكنك تشغيل الشيفرة الموجودة في GitHub gist مثل المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1124_177" style="">
<span class="pln">npx https</span><span class="pun">:</span><span class="com">//gist.github.com/zkat/4bc19503fe9e9309e2bfaa2c58074d32</span></pre>

<p>
	يجب أن تكون حذرًا عند تشغيل شيفرة لا تتحكم بها، فالقوة العظمى تستوجب مسؤولية عظمى أيضًا.
</p>

<p>
	ترجمة -وبتصرّف- للفصل Node modules and npm من كتاب The Node.js handbook لصاحبه Flavio Copes.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال التالي: <a href="https://academy.hsoub.com/programming/javascript/nodejs/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D9%86%D9%81%D9%8A%D8%B0-%D8%A7%D9%84%D8%AF%D9%88%D8%A7%D9%84-%D8%AF%D8%A7%D8%AE%D9%84%D9%8A%D8%A7-%D8%B6%D9%85%D9%86-nodejs-r1466/" rel="">كيفية تنفيذ الدوال داخليا ضمن Node.js</a>
	</li>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%88%D8%B6%D8%B9-%D8%A7%D9%84%D8%AA%D9%81%D8%A7%D8%B9%D9%84%D9%8A-%D9%88%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%B3%D8%B7%D8%B1-%D8%A7%D9%84%D8%A3%D9%88%D8%A7%D9%85%D8%B1-%D9%81%D9%8A-nodejs-r1464/" rel="">استخدام الوضع التفاعلي والتعامل مع سطر الأوامر في Node.js</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-nodejs-r1463/" rel="">مقدمة إلى Node.js</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%AA%D8%A3%D9%85%D9%8A%D9%86-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-nodejs-%D9%8A%D8%B9%D9%85%D9%84-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-nginx-%D9%88-let%E2%80%99s-encrypt-%D9%88-compose-docker-r814/" rel="">تأمين تطبيق Node.js يعمل على الحاويات باستخدام Nginx و Let’s Encrypt و Compose Docker</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1465</guid><pubDate>Wed, 09 Feb 2022 11:36:30 +0000</pubDate></item><item><title>&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x627;&#x644;&#x648;&#x636;&#x639; &#x627;&#x644;&#x62A;&#x641;&#x627;&#x639;&#x644;&#x64A; &#x648;&#x627;&#x644;&#x62A;&#x639;&#x627;&#x645;&#x644; &#x645;&#x639; &#x633;&#x637;&#x631; &#x627;&#x644;&#x623;&#x648;&#x627;&#x645;&#x631; &#x641;&#x64A; Node.js</title><link>https://academy.hsoub.com/programming/javascript/nodejs/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%88%D8%B6%D8%B9-%D8%A7%D9%84%D8%AA%D9%81%D8%A7%D8%B9%D9%84%D9%8A-%D9%88%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%B3%D8%B7%D8%B1-%D8%A7%D9%84%D8%A3%D9%88%D8%A7%D9%85%D8%B1-%D9%81%D9%8A-nodejs-r1464/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_02/62035c6b94714_--------.png.76edff849049b5bd22414cee396e7b83.png" /></p>

<p>
	سنتعرّف في هذا المقال على الوضع التفاعلي REPL في Node.js الذي نستخدمه في الطرفية لتقييم تعبير برمجي مكتوب <a href="https://academy.hsoub.com/programming/javascript/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D9%84%D8%BA%D8%A9-javascript-r664/" rel="">بلغة javascript</a>، كما سنتعلّم كيفية التعامل مع سطر الأوامر في Node.js من تمرير وسائط منه وإرسال مخرجات إليه وقبول مدخلات منه.
</p>

<h2>
	استخدام الوضع التفاعلي REPL
</h2>

<p>
	يسمى الوضع التفاعلي في <a href="https://academy.hsoub.com/programming/javascript/nodejs/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-nodejs-r1463/" rel="">Node.js</a> باسم REPL اختصارًا إلى اقرأ-قيّم-اطبع-كرر Read-Evaluate-Print-Loop، أي اقرأ وقدِّر قيمة التعبير البرمجي ثم اطبع الناتج وكرر العملية، وهو طريقة رائعة لاستكشاف ميزات Node.js بطريقة سريعة.
</p>

<p>
	استعملنا الأمر <code>node</code> سابقًا لتشغيل أحد السكربتات:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7818_9" style="">
<span class="pln">node script</span><span class="pun">.</span><span class="pln">js</span></pre>

<p>
	أما إذا حذفنا اسم الملف، فسندخل في الوضع التفاعلي REPL:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7818_11" style="">
<span class="pln">node</span></pre>

<p>
	وعندما تجرب الأمر السابق في الطرفية عندك، فسيحدث ما يلي وسيبقى الأمر في وضع السكون وينتظر منك إدخال تعبير ما:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7818_13" style="">
<span class="pln">$ node
</span><span class="typ">Welcome</span><span class="pln"> to </span><span class="typ">Node</span><span class="pun">.</span><span class="pln">js v14</span><span class="pun">.</span><span class="lit">15.0</span><span class="pun">.</span><span class="pln">
</span><span class="typ">Type</span><span class="pln"> </span><span class="str">".help"</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> more information</span><span class="pun">.</span><span class="pln">
</span><span class="pun">&gt;</span><span class="pln"> </span></pre>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		ملاحظة: إذا لم تكن تعرف كيف تفتح الطرفية عندك، فيمكنك البحث في جوجل عن ذلك مع ذكر نظام التشغيل الخاص بك، وانظر مقال "<a href="https://academy.hsoub.com/devops/linux/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%B7%D8%B1%D9%81%D9%8A%D9%91%D8%A9-%D9%84%D9%8A%D9%86%D9%83%D8%B3-linux-terminal-r18/" rel="">مدخل إلى طرفيّة لينكس</a>".
	</p>
</blockquote>

<p>
	إذا أردنا أن نكون أكثر دقةً، فإن الوضع التفاعلي ينتظر منّا إدخال <a href="https://wiki.hsoub.com/JavaScript" rel="external">شيفرة JavaScript</a>، لذا لنبدأ بسطر بسيط:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7818_15" style="">
<span class="pun">&gt;</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'test'</span><span class="pun">)</span><span class="pln">
test
</span><span class="kwd">undefined</span><span class="pln">
</span><span class="pun">&gt;</span></pre>

<p>
	القيمة الأولى المطبوعة <code>test</code> هي ناتج الأمر الذي طلبنا منه طباعة سلسلة نصية إلى الطرفية، ثم حصلنا على القيمة <code>undefined</code> وهي القيمة المعادة من تنفيذ <code>console.log()‎</code>، ويمكننا الآن إدخال سطر JavaScript جديد.
</p>

<h3>
	استخدام الزر tab للإكمال التلقائي
</h3>

<p>
	من أجمل مزايا الوضع التفاعلي REPL هو أنه تفاعلي، ففي أثناء كتابك للشيفرة إذا ضغطت على زر <code>tab</code>، فسيحاول الوضع التفاعلي الإكمال التلقائي لما كتبته ليوافق اسم متغير عرفته مسبقًا.
</p>

<h3>
	استكشاف كائنات JavaScript
</h3>

<p>
	جرِّب إدخال اسم كائن من <a href="https://academy.hsoub.com/programming/javascript/%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-objects-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r645/" rel="">كائنات JavaScript</a> مثل <code>Number</code> وأضف إليه نقطةً ثم اضغط على <code>tab</code>، إذ سيعرض لك الوضع التفاعلي جميع الخاصيات والتوابع التي يمكنك الوصول إليها في ذاك الكائن.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="91592" href="https://academy.hsoub.com/uploads/monthly_2022_02/62035c68b6fb1_JavaScriptobject1.png.e771917ee4d2cd77553cfc14930a5a91.png" rel=""><img alt="JavaScript object 1.png" class="ipsImage ipsImage_thumbnailed" data-fileid="91592" data-unique="utsc0yktc" src="https://academy.hsoub.com/uploads/monthly_2022_02/62035c68b6fb1_JavaScriptobject1.png.e771917ee4d2cd77553cfc14930a5a91.png" style="width: 750px; height: auto;"></a>
</p>

<h3>
	استكشاف الكائنات العامة
</h3>

<p>
	يمكنك معاينة الكائنات العامة بكتابة <code>global.‎</code> ثم الضغط على الزر <code>tab</code>:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="91593" href="https://academy.hsoub.com/uploads/monthly_2022_02/62035c7a0eff3_JavaScriptobject2.png.6917cf8d0e8bd44bde1ea29f07841b1e.png" rel=""><img alt="JavaScript object 2.png" class="ipsImage ipsImage_thumbnailed" data-fileid="91593" data-unique="s7cl0uhqg" src="https://academy.hsoub.com/uploads/monthly_2022_02/62035c7be03c0_JavaScriptobject2.thumb.png.e3f49f80053b7160b1949ed26826677b.png"></a>
</p>

<h3>
	المتغير الخاص _
</h3>

<p>
	إذا كتبت <code>_</code> بعد شيفرة ما، فسيؤدي ذلك إلى طباعة ناتج آخر عملية.
</p>

<h3>
	الأوامر ذوات النقط
</h3>

<p>
	يملك الوضع التفاعلي REPL بعض الأوامر الخاصة وكلها تبدأ بنقطة <code>.</code> وهي:
</p>

<ul>
<li>
		<code>‎.help</code>: يظهر المساعدة للأوامر ذوات النقط.
	</li>
	<li>
		<code>‎.editor</code>: تفعيل وضع المحرر، وذلك لكتابة شيفرة JavaScript متعددة الأسطر بسهولة، وبعد دخولك في هذا الوضع وكتابتك للشيفرة المطلوبة، فيمكنك إدخال Ctrl+D لتنفيذ الأمر الذي كتبت.
	</li>
	<li>
		<code>‎.break</code>: إذا كتبت الأمر <code>‎.break</code> عند كتابتك لتعبير متعدد الأسطر، فستلغي أي مدخلات قادمة، ومثله مثل الضغط على Ctrl+C.
	</li>
	<li>
		<code>‎.clear</code>: إعادة ضبط سياق الوضع التفاعلي إلى كائن فارغ وإزالة أي تعابير متعددة الأسطر جرت كتابتها.
	</li>
	<li>
		<code>‎.load</code>: تحميل ملف JavaScript بمسار نسبي إلى مجلد العمل الحالي.
	</li>
	<li>
		<code>‎.save</code>: حفظ ما أدخلته في الجلسة التفاعلية إلى ملف مع تمرير مسار الملف بعد كتابة <code>‎.save</code>.
	</li>
	<li>
		<code>‎‎.exit</code>: الخروج من الوضع التفاعلي وهو يماثل الضغط على Ctrl+C مرتين.
	</li>
</ul>
<p>
	يعرِف الوضع التفاعلي REPL متى تكتب تعبيرًا متعدد الأسطر دون الحاجة إلى تشغيل الأمر <code>‎.editor</code>، فإذا بدأت مثلًا بكتابة حلقة تكرار مثل هذه:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7818_22" style="">
<span class="pun">[</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">3</span><span class="pun">].</span><span class="pln">forEach</span><span class="pun">(</span><span class="pln">num </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span></pre>

<p>
	ثم ضغطت على زر <code>enter</code>، فسيعرف الوضع التفاعلي أنك تكتب تعبيرًا متعدد الأسطر ويبدأ سطرًا جديدًا بثلاث نقط <code>…</code>، مما يشير إلى أنك ما زلت تعمل على القسم أو المقطع نفسه:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7818_24" style="">
<span class="pun">...</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">num</span><span class="pun">)</span><span class="pln">
</span><span class="pun">...</span><span class="pln"> </span><span class="pun">})</span></pre>

<p>
	إذا كتبت <code>‎.break</code> في آخر السطر، فسيتوقف وضع تعدد الأسطر ولن يُنفَّذ التعبير الذي كتبته.
</p>

<h2>
	تمرير الوسائط من سطر الأوامر إلى Node.js
</h2>

<p>
	سنشرح في هذا القسم كيفية استقبال وسائط arguments في برنامج Node.js مُمرَّرة من سطر الأوامر، إذ يمكنك تمرير أي عدد من الوسائط أثناء تشغيل برنامج Node.js باستخدام الأمر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7818_26" style="">
<span class="pln">node app</span><span class="pun">.</span><span class="pln">js</span></pre>

<p>
	يمكن أن تكون الوسائط بمفردها، أو على شكل زوج من المفاتيح والقيم مثل:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7818_28" style="">
<span class="pln">node app</span><span class="pun">.</span><span class="pln">js ahmed</span></pre>

<p>
	أو
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7818_30" style="">
<span class="pln">node app</span><span class="pun">.</span><span class="pln">js name</span><span class="pun">=</span><span class="pln">ahmed</span></pre>

<p>
	يجعل هذا طريقة الحصول على القيمة مختلفةً في شيفرة Node.js. إذ أنّ طريقة الحصول على الوسائط هي استخدام الكائن <code>process</code> المبني في Node.js، ففيه الخاصية <code>argv</code> التي هي مصفوفة تحتوي على جميع الوسائط الممررة عبر سطر الأوامر؛ ويكون الوسيط الأول هو المسار الكامل للأمر <code>node</code>، بينما الوسيط الثاني هو مسار الملف المُنفَّذ كاملًا، وجميع الوسائط الإضافية ستكون موجودةً من الموضع الثالث إلى آخره، كما يمكنك المرور على جميع المعاملات بما في ذلك مسار node ومسار الملف المُنفَّذ باستخدام حلقة تكرار:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7818_32" style="">
<span class="pln">process</span><span class="pun">.</span><span class="pln">argv</span><span class="pun">.</span><span class="pln">forEach</span><span class="pun">((</span><span class="pln">val</span><span class="pun">,</span><span class="pln"> index</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(`</span><span class="pln">$</span><span class="pun">{</span><span class="pln">index</span><span class="pun">}:</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">val</span><span class="pun">}`)</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	يمكنك الحصول على الوسائط الإضافية من خلال إنشاء مصفوفة جديدة تستثني أول قيمتين:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7818_34" style="">
<span class="kwd">const</span><span class="pln"> args </span><span class="pun">=</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">argv</span><span class="pun">.</span><span class="pln">slice</span><span class="pun">(</span><span class="lit">2</span><span class="pun">)</span></pre>

<p>
	إذا كان لديك معامل بمفرده دون مفتاح مثل:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7818_37" style="">
<span class="pln">node app</span><span class="pun">.</span><span class="pln">js ahmed</span></pre>

<p>
	فيمكنك الوصول إليه كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7818_40" style="">
<span class="kwd">const</span><span class="pln"> args </span><span class="pun">=</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">argv</span><span class="pun">.</span><span class="pln">slice</span><span class="pun">(</span><span class="lit">2</span><span class="pun">)</span><span class="pln">
args</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span></pre>

<p>
	أما في حالة كان الوسيط هو مفتاح وقيمة كما في:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7818_42" style="">
<span class="pln">node app</span><span class="pun">.</span><span class="pln">js name</span><span class="pun">=</span><span class="pln">ahmed</span></pre>

<p>
	فإن قيمة <code>args[0]‎</code> هي <code>name=ahmed</code>، وستحتاج إلى تفسيرها، وأفضل طريقة هي استخدام المكتبة <code>minimist</code> التي تساعدنا بالتعامل مع الوسائط:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7818_44" style="">
<span class="kwd">const</span><span class="pln"> args </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'minimist'</span><span class="pun">)(</span><span class="pln">process</span><span class="pun">.</span><span class="pln">argv</span><span class="pun">.</span><span class="pln">slice</span><span class="pun">(</span><span class="lit">2</span><span class="pun">))</span><span class="pln">
args</span><span class="pun">[</span><span class="str">'name'</span><span class="pun">]</span><span class="pln"> </span><span class="com">// ahmed</span></pre>

<h2>
	إرسال تطبيق Node.js المخرجات إلى سطر الأوامر
</h2>

<p>
	سنتعلم كيفية الطباعة إلى سطر الأوامر باستخدام Node.js بدءًا من الاستخدام الأساسي للتابع <code>console.log</code> حتى وصولنا إلى الأمور المعقدة.
</p>

<h3>
	طباعة المخرجات باستخدام الوحدة console
</h3>

<p>
	توفِّر Node.js الوحدة <code>console</code> التي توفر عددًا كبيرًا من الطرائق المفيدة في التعامل مع <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%A7-%D9%87%D9%88-%D8%B3%D8%B7%D8%B1-%D8%A7%D9%84%D8%A3%D9%88%D8%A7%D9%85%D8%B1-%D8%9F-r353/" rel="">سطر الأوامر</a>، وهي تشبه إلى حد ما الكائن <code>console</code> الموجود في المتصفحات، والتابع الأساسي الأكثر استخدامًا في هذه الوحدة هو التابع <code>console.log()‎</code>، الذي يطبع <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%B3%D9%84%D8%A7%D8%B3%D9%84-%D8%A7%D9%84%D9%86%D8%B5%D9%8A%D8%A9-strings-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r817/" rel="">السلسلة النصية</a> التي تمررها إليه إلى الطرفية، وإذا مررت كائنًا فسيعرضه على أساس سلسلة نصية، كما يمكنك تمرير قيمًا متعددةً إلى التابع <code>console.log()‎</code> كما يلي، إذ ستطبع Node.js القيمتين x وy معًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7818_48" style="">
<span class="kwd">const</span><span class="pln"> x </span><span class="pun">=</span><span class="pln"> </span><span class="str">'x'</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> y </span><span class="pun">=</span><span class="pln"> </span><span class="str">'y'</span><span class="pln">
console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">)</span></pre>

<p>
	يمكنك أيضًا تنسيق السلاسل النصية بتمرير القيم ومُحدِّد التنسيق كما في المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7818_50" style="">
<span class="pln">console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'My %s has %d years'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'cat'</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span></pre>

<p>
	إذ أنّ محددات التنسيق format specifiers هي:
</p>

<ul>
<li>
		<code>‎%s</code>: تنسيق المتغير على أساس سلسلة نصية.
	</li>
	<li>
		<code>‎</code>%d أو <code>‎</code>%i: تنسيق المتغير على أساس عدد صحيح.
	</li>
	<li>
		<code>‎%f</code>: تنسيق المتغير على أساس عدد ذي فاصلة عشرية.
	</li>
	<li>
		<code>‎</code>%O: تنسيق المتغير على أساس كائن كما في المثال البسيط الآتي:
	</li>
</ul>
<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7818_52" style="">
<span class="pln">console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'%O'</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Number</span><span class="pun">)</span></pre>

<h3>
	مسح محتوى الطرفية
</h3>

<p>
	يمسح التابع <code>console.clear()‎</code> محتوى الطرفية، لكن يختلف سلوكه اعتمادًا على الطرفية المستخدَمة.
</p>

<h3>
	عد العناصر
</h3>

<p>
	يُعَدّ التابع <code>console.count()‎</code> مفيدًا في بعض الحالات التي تريد فيها عدّ المرات التي طُبِعَت فيها السلسلة النصية وإظهار الرقم بجوارها، خذ هذا المثال:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7818_54" style="">
<span class="kwd">const</span><span class="pln"> x </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> y </span><span class="pun">=</span><span class="pln"> </span><span class="lit">2</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> z </span><span class="pun">=</span><span class="pln"> </span><span class="lit">3</span><span class="pln">
console</span><span class="pun">.</span><span class="pln">count</span><span class="pun">(</span><span class="pln">
  </span><span class="str">'The value of x is '</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> x </span><span class="pun">+</span><span class="pln"> </span><span class="str">' and has been checked .. how many times?'</span><span class="pln">
</span><span class="pun">)</span><span class="pln">
console</span><span class="pun">.</span><span class="pln">count</span><span class="pun">(</span><span class="pln">
  </span><span class="str">'The value of x is '</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> x </span><span class="pun">+</span><span class="pln"> </span><span class="str">' and has been checked .. how many times?'</span><span class="pln">
</span><span class="pun">)</span><span class="pln">
console</span><span class="pun">.</span><span class="pln">count</span><span class="pun">(</span><span class="pln">
  </span><span class="str">'The value of y is '</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> z </span><span class="pun">+</span><span class="pln"> </span><span class="str">' and has been checked .. how many times?'</span><span class="pln">
</span><span class="pun">)</span></pre>

<p>
	يمكننا إحصاء عدد البرتقالات والتفاحات التي لدينا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7818_56" style="">
<span class="kwd">const</span><span class="pln"> oranges </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">'orange'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'orange'</span><span class="pun">]</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> apples </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">'just one apple'</span><span class="pun">]</span><span class="pln">
oranges</span><span class="pun">.</span><span class="pln">forEach</span><span class="pun">(</span><span class="pln">fruit </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">count</span><span class="pun">(</span><span class="pln">fruit</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span><span class="pln">
apples</span><span class="pun">.</span><span class="pln">forEach</span><span class="pun">(</span><span class="pln">fruit </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">count</span><span class="pun">(</span><span class="pln">fruit</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span></pre>

<h3>
	طباعة تتبع مكدس الاستدعاء
</h3>

<p>
	قد تكون هنالك حالات من المفيد فيها طباعة تتبع مكدس الاستدعاء call stack trace لإحدى الدوال، وذلك للإجابة مثلًا على السؤال التالي: كيف وصلنا إلى هذا الجزء من الشيفرة، إذ يمكنك استخدام التابع <code>console.trace()‎</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7818_58" style="">
<span class="kwd">const</span><span class="pln"> function2 </span><span class="pun">=</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">trace</span><span class="pun">()</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> function1 </span><span class="pun">=</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> function2</span><span class="pun">()</span><span class="pln">
function1</span><span class="pun">()</span></pre>

<p>
	سيطبع مكدس الاستدعاء ما يلي، وهذا هو الناتج إذا جربنا الشيفرة السابقة في النمط التفاعلي REPL في Node.js:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7818_60" style="">
<span class="typ">Trace</span><span class="pln">
    at function2 </span><span class="pun">(</span><span class="pln">repl</span><span class="pun">:</span><span class="lit">1</span><span class="pun">:</span><span class="lit">33</span><span class="pun">)</span><span class="pln">
    at function1 </span><span class="pun">(</span><span class="pln">repl</span><span class="pun">:</span><span class="lit">1</span><span class="pun">:</span><span class="lit">25</span><span class="pun">)</span><span class="pln">
    at repl</span><span class="pun">:</span><span class="lit">1</span><span class="pun">:</span><span class="lit">1</span><span class="pln">
    at </span><span class="typ">ContextifyScript</span><span class="pun">.</span><span class="typ">Script</span><span class="pun">.</span><span class="pln">runInThisContext </span><span class="pun">(</span><span class="pln">vm</span><span class="pun">.</span><span class="pln">js</span><span class="pun">:</span><span class="lit">44</span><span class="pun">:</span><span class="lit">33</span><span class="pun">)</span><span class="pln">
    at </span><span class="typ">REPLServer</span><span class="pun">.</span><span class="pln">defaultEval </span><span class="pun">(</span><span class="pln">repl</span><span class="pun">.</span><span class="pln">js</span><span class="pun">:</span><span class="lit">239</span><span class="pun">:</span><span class="lit">29</span><span class="pun">)</span><span class="pln">
    at bound </span><span class="pun">(</span><span class="pln">domain</span><span class="pun">.</span><span class="pln">js</span><span class="pun">:</span><span class="lit">301</span><span class="pun">:</span><span class="lit">14</span><span class="pun">)</span><span class="pln">
    at </span><span class="typ">REPLServer</span><span class="pun">.</span><span class="pln">runBound </span><span class="pun">[</span><span class="pln">as </span><span class="kwd">eval</span><span class="pun">]</span><span class="pln"> </span><span class="pun">(</span><span class="pln">domain</span><span class="pun">.</span><span class="pln">js</span><span class="pun">:</span><span class="lit">314</span><span class="pun">:</span><span class="lit">12</span><span class="pun">)</span><span class="pln">
    at </span><span class="typ">REPLServer</span><span class="pun">.</span><span class="pln">onLine </span><span class="pun">(</span><span class="pln">repl</span><span class="pun">.</span><span class="pln">js</span><span class="pun">:</span><span class="lit">440</span><span class="pun">:</span><span class="lit">10</span><span class="pun">)</span><span class="pln">
    at emitOne </span><span class="pun">(</span><span class="pln">events</span><span class="pun">.</span><span class="pln">js</span><span class="pun">:</span><span class="lit">120</span><span class="pun">:</span><span class="lit">20</span><span class="pun">)</span><span class="pln">
    at </span><span class="typ">REPLServer</span><span class="pun">.</span><span class="pln">emit </span><span class="pun">(</span><span class="pln">events</span><span class="pun">.</span><span class="pln">js</span><span class="pun">:</span><span class="lit">210</span><span class="pun">:</span><span class="lit">7</span><span class="pun">)</span></pre>

<h3>
	طباعة الزمن المستغرق
</h3>

<p>
	يمكنك ببساطة حساب مقدار الوقت الذي أخذته الدالة للتنفيذ باستخدام التابعين <code>time()‎</code> و<code>timeEnd()‎</code> كما في المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7818_62" style="">
<span class="kwd">const</span><span class="pln"> doSomething </span><span class="pun">=</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'test'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> measureDoingSomething </span><span class="pun">=</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">time</span><span class="pun">(</span><span class="str">'doSomething()'</span><span class="pun">)</span><span class="pln">
  </span><span class="com">// افعل شيئًا وقس الوقت المستغرق لتنفيذه</span><span class="pln">
  doSomething</span><span class="pun">()</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">timeEnd</span><span class="pun">(</span><span class="str">'doSomething()'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
measureDoingSomething</span><span class="pun">()</span></pre>

<h3>
	مجرى الخرج القياسي stdout والخطأ القياسي stderr
</h3>

<p>
	رأينا أنّ التابع <code>console.log()‎</code> رائع لطباعة الرسائل في الطرفية، وهذا ما يسمى مجرى الخرج القياسي standard output stream أو stdout؛ أما التابع <code>console.error()‎</code> فسيطبع الرسائل المرسَلة إلى مجرى الخطأ القياسي stderr والتي لن تظهر في الطرفية وإنما ستظهر في سجل الخطأ.
</p>

<h3>
	طباعة المخرجات بألوان مختلفة
</h3>

<p>
	يمكنك أن تغيير لون المخرجات النصية في الطرفية باستخدام تسلسلات التهريب escape sequences، وتسلسل التهريب هو مجموعة من المحارف التي تُعرِّف لونًا معينًا في الطرفية مثل:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7818_65" style="">
<span class="pln">console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'\x1b[33m%s\x1b[0m'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'hi!'</span><span class="pun">)</span></pre>

<p>
	يمكنك تجربة ذلك باستخدام النمط التفاعلي في Node.js وستُعرَض السلسلة النصية <code>hi!‎</code> باللون الأصفر، لكن هذه الطريقة ذات مستوى منخفض، فأبسط طريقة لتلوين المخرجات في الطرفية هي باستخدام مكتبة مثل <a href="https://github.com/chalk/chalk" rel="external nofollow">Chalk</a>، حيث يمكنها أيضًا إجراء عمليات التنسيق الأخرى إضافةً إلى تلوينها للنص مثل جعل النص غامقًا أو مائلًا أو مسطرًا تحته، كما يمكنك تثبيتها باستخدام <code>npm install chalk</code> ثم استخدامها:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7818_67" style="">
<span class="kwd">const</span><span class="pln"> chalk </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'chalk'</span><span class="pun">)</span><span class="pln">
console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">chalk</span><span class="pun">.</span><span class="pln">yellow</span><span class="pun">(</span><span class="str">'hi!'</span><span class="pun">))</span></pre>

<p>
	من المؤكد أن استخدام <code>chalk.yellow</code> أكثر راحةً من محاولة تذكُّر شيفرات التهريب وستكون قابلية قراءة الشيفرة أفضل.
</p>

<h3>
	إنشاء شريط تقدم في الطرفية
</h3>

<p>
	تُعَدّ حزمة <a href="https://www.npmjs.com/package/progress" rel="external nofollow">Progress</a> حزمةً رائعةً لإنشاء شريط تقدُّم في الطرفية، حيث يمكنك تثبيتها باستخدام <code>npm install progress</code> ببساطة.
</p>

<p>
	تُنشِئ الشيفرة التالية شريط تقدُّم بعشر خطوات، حيث ستكتمل خطوة كل 100 ميللي ثانية، وحين اكتمال الشريط سنصفِّر العداد:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7818_69" style="">
<span class="kwd">const</span><span class="pln"> </span><span class="typ">ProgressBar</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'progress'</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> bar </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">ProgressBar</span><span class="pun">(</span><span class="str">':bar'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> total</span><span class="pun">:</span><span class="pln"> </span><span class="lit">10</span><span class="pln"> </span><span class="pun">})</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> timer </span><span class="pun">=</span><span class="pln"> setInterval</span><span class="pun">(()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  bar</span><span class="pun">.</span><span class="pln">tick</span><span class="pun">()</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">bar</span><span class="pun">.</span><span class="pln">complete</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    clearInterval</span><span class="pun">(</span><span class="pln">timer</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">},</span><span class="pln"> </span><span class="lit">100</span><span class="pun">)</span></pre>

<h2>
	قبول المدخلات من سطر الأوامر
</h2>

<p>
	يمكننا جعل تطبيق Node.js الذي يعمل من سطر الأوامر تفاعليًا باستخدام وحدة readline المبنية في أساس Node.js.
</p>

<p>
	أصبحت Node.js بدءًا من الإصدار السابع توفِّر الوحدة <code>readline</code> التي تحصل على المدخلات من مجرى قابل للقراءة مثل <code>process.stdin</code> (مجرى الدخل القياسي stdin) سطرًا بسطر والذي يكون أثناء تنفيذ برنامج Node.js من سطر الأوامر هو الدخل المكتوب في الطرفية.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7818_71" style="">
<span class="kwd">const</span><span class="pln"> readline </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'readline'</span><span class="pun">).</span><span class="pln">createInterface</span><span class="pun">({</span><span class="pln">
  input</span><span class="pun">:</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">stdin</span><span class="pun">,</span><span class="pln">
  output</span><span class="pun">:</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">stdout
</span><span class="pun">})</span><span class="pln">


readline</span><span class="pun">.</span><span class="pln">question</span><span class="pun">(`</span><span class="typ">What</span><span class="str">'</span><span class="pln">s your name</span><span class="pun">?`,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">name</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(`</span><span class="typ">Hi</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">name</span><span class="pun">}!`)</span><span class="pln">
  readline</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	تسأل الشيفرة السابقة المستخدِم عن اسمه، وبعد كتابة المستخدِم الاسم والضغط على enter سنرسل تحيةً له.
</p>

<p>
	يُظهِر التابع <code>question()‎</code> المعامل parameter الأول -أي السؤال- وينتظر مدخلات المستخدِم، ثم يستدعي دالة رد نداء عندما يضغط المستخدِم على زر enter. لاحظا أننا أغلقنا واجهة readline في دالة رد النداء، كما توفِّر الواجهة <code>readline</code> توابعًا كثيرةً متعددةً وسنتركك لاستكشافها من توثيقها؛ أما إذا احتجت إلى إدخال كلمة مرور، فمن الأفضل إظهار رمز <code>*</code> بدلًا منها، وأسهل طريقة لذلك هو استخدام الحزمة <a href="https://www.npmjs.com/package/readline-sync" rel="external nofollow">readline-sync</a> التي تشبه readline في الواجهة البرمجية وتستطيع التعامل مع هذه الحالة دون عناء، والخيار الممتاز والمتكامل هو الحزمة <a href="https://github.com/SBoudrias/Inquirer.js" rel="external nofollow">inquirer.js</a>، حيث يمكنك تثبيتها باستخدام npm install inquirer، كما يمكنك إعادة كتابة الشيفرة السابقة كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7818_73" style="">
<span class="kwd">const</span><span class="pln"> inquirer </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'inquirer'</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">var</span><span class="pln"> questions </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[{</span><span class="pln">
  type</span><span class="pun">:</span><span class="pln"> </span><span class="str">'input'</span><span class="pun">,</span><span class="pln">
  name</span><span class="pun">:</span><span class="pln"> </span><span class="str">'name'</span><span class="pun">,</span><span class="pln">
  message</span><span class="pun">:</span><span class="pln"> </span><span class="str">"What's your name?"</span><span class="pun">,</span><span class="pln">
</span><span class="pun">}]</span><span class="pln">

inquirer</span><span class="pun">.</span><span class="pln">prompt</span><span class="pun">(</span><span class="pln">questions</span><span class="pun">).</span><span class="pln">then</span><span class="pun">(</span><span class="pln">answers </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(`</span><span class="typ">Hi</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">answers</span><span class="pun">[</span><span class="str">'name'</span><span class="pun">]}!`)</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	تسمح لك مكتبة inquirer.js بفعل أمور كثيرة مثل الأسئلة متعددة الخيارات وأزرار الانتقاء والتأكيدات وغير ذلك، كما من المفيد معرفة جميع الخيارات المتاحة أمامنا خصوصًا التي توفِّرها Node.js، لكن إذا أردت التعامل مع مدخلات سطر الأوامر كثيرًا، فالخيار الأمثل هو مكتبة inquirer.js.
</p>

<p>
	ترجمة -وبتصرّف- للفصل Command Line من كتاب The Node.js handbook لصاحبه Flavio Copes.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال التالي: <a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%AF%D9%84%D9%8A%D9%84%D9%83-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D9%85%D8%AF%D9%8A%D8%B1-%D8%A7%D9%84%D8%AD%D8%B2%D9%85-npm-%D9%81%D9%8A-nodejs-r1465/" rel="">دليلك الشامل إلى مدير الحزم npm في Node.js</a>
	</li>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/javascript/nodejs/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-nodejs-r1463/" rel="">مقدمة إلى Node.js</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%AA%D8%A3%D9%85%D9%8A%D9%86-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-nodejs-%D9%8A%D8%B9%D9%85%D9%84-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-nginx-%D9%88-let%E2%80%99s-encrypt-%D9%88-compose-docker-r814/" rel="">تأمين تطبيق Node.js يعمل على الحاويات باستخدام Nginx و Let’s Encrypt و Compose Docker</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-nodejs-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D8%AE%D8%AF%D9%85%D8%A9-%D9%87%D9%8A%D8%B1%D9%88%D9%83%D9%88-heroku-%D9%85%D8%AB%D8%A7%D9%84%D9%8B%D8%A7-r1100/" rel="">نشر تطبيق Node.js على الويب: خدمة هيروكو (Heroku) مثالًا</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1464</guid><pubDate>Wed, 09 Feb 2022 11:34:29 +0000</pubDate></item><item><title>&#x645;&#x642;&#x62F;&#x645;&#x629; &#x625;&#x644;&#x649; Node.js</title><link>https://academy.hsoub.com/programming/javascript/nodejs/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-nodejs-r1463/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_02/6203545ca501e_-.png.841cb47c738cbb38ef07eb32cbea8de5.png" /></p>
<p>
	تُعَدّ Node.js بيئة تشغيل JavaScript التي تعمل من طرف الخادم، وهي مفتوحة المصدر ومتعددة المنصات cross-platform -أي تعمل على أكثر من نظام تشغيل- وهي مبنية على محرك جافاسكربت Chrome V8، وتستعمل أساسيًا لإنشاء خوادم الويب، لكنها ليست محدودةً لهذه المهمة فقط، كما أنها لاقت رواجًا بدءًا من انطلاقها في 2009، وتلعب الآن دورًا مهمًا في عالم تطوير الويب، فإذا عددنا أنّ النجوم التي يحصل عليها المشروع في <a href="https://github.com/nodejs/node" rel="external nofollow">GitHub</a> معيارًا لشهرة البرمجية، فاعلم أنّ Node.js قد حصدت أكثر من 84 ألفًا من النجوم حتى الآن، ومن الجدير بالذكر أنّ Node.js مبنية على محرك جافاسكربت Chrome V8، كما تُستعمل بصورة أساسية لإنشاء خوادم الويب، لكنها ليست محدودةً لهذه المهمة فقط.
</p>

<p>
	هذا المقال جزء من سلسلة حول Node.js:
</p>

<ul>
	<li>
		مقدمة إلى Node.js
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%88%D8%B6%D8%B9-%D8%A7%D9%84%D8%AA%D9%81%D8%A7%D8%B9%D9%84%D9%8A-%D9%88%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%B3%D8%B7%D8%B1-%D8%A7%D9%84%D8%A3%D9%88%D8%A7%D9%85%D8%B1-%D9%81%D9%8A-nodejs-r1464/" rel="">استخدام الوضع التفاعلي والتعامل مع سطر الأوامر في Node.js</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%AF%D9%84%D9%8A%D9%84%D9%83-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D9%85%D8%AF%D9%8A%D8%B1-%D8%A7%D9%84%D8%AD%D8%B2%D9%85-npm-%D9%81%D9%8A-nodejs-r1465/" rel="">دليلك الشامل إلى مدير الحزم npm في Node.js</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D9%86%D9%81%D9%8A%D8%B0-%D8%A7%D9%84%D8%AF%D9%88%D8%A7%D9%84-%D8%AF%D8%A7%D8%AE%D9%84%D9%8A%D8%A7-%D8%B6%D9%85%D9%86-nodejs-r1466/" rel="">كيفية تنفيذ الدوال داخليا ضمن Node.js</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%BA%D9%8A%D8%B1-%D8%A7%D9%84%D9%85%D8%AA%D8%B2%D8%A7%D9%85%D9%86%D8%A9-%D9%81%D9%8A-nodejs-r1467/" rel="">البرمجة غير المتزامنة في Node.js</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%B7%D9%84%D8%A8%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D9%8A%D8%A9-%D9%81%D9%8A-nodejs-r1468/" rel="">التعامل مع الطلبيات الشبكية في Node.js</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D9%81%D9%8A-nodejs-r1469/" rel="">التعامل مع الملفات في Node.js</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%88%D8%AD%D8%AF%D8%A7%D8%AA-nodejs-%D8%A7%D9%84%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A9-r1470/" rel="">تعرف على وحدات Node.js الأساسية</a>
	</li>
</ul>

<h2>
	أفضل ميزات Node.js
</h2>

<p>
	تتميز Node.js بالمزايا التالية:
</p>

<h3>
	السرعة
</h3>

<p>
	إحدى الميزات التي تشتهر بها Node.js هي السرعة، فشيفرة JavaScript التي تعمل على Node.js اعتمادًا على اختبارات الأداء benchmark يمكن أن تكون بضعفي سرعة تنفيذ اللغات المصرَّفة compiled مثل C أو Java، وأضعاف سرعة اللغات المفسَّرة مثل بايثون أو روبي بسبب نموذج عدم الحجب non-blocking الذي تستعمله.
</p>

<h3>
	بسيطة
</h3>

<p>
	صدِّقنا عندما نخبرك بأن Node.js بسيطة، بل بسيطة جدًا.
</p>

<h3>
	تستعمل JavaScript
</h3>

<p>
	تشغِّل Node.js شيفرة JavaScript، وهذا يعني أنّ ملايين مبرمجي الواجهات الأمامية الذين يستعملون JavaScript في المتصفح سيستطيعون العمل على شيفرات من طرف الخادم ومن طرف الواجهات الأمامية باستخدام اللغة نفسها، فلا حاجة إلى تعلّم لغة جديدة كليًا، حيث ستكون جميع التعابير التي تستعملها في JavaScript متاحةً في Node.js، واطمئن إلى أنّ آخر معايير ECMAScript مستعملة في Node.js، فلا حاجة إلى انتظار المستخدِمين ليحدِّثوا متصفحاتهم، فأنت صاحب القرار بأي نسخة ECMAScript تريد استخدامها في برنامجك باختيار إصدار Node.js المناسب.
</p>

<h3>
	محرك V8
</h3>

<p>
	يمكنك الاستفادة من عمل آلاف المهندسين الذين جعلوا -ويستمروا بجعل- محرك JavaScript الخاص بمتصفح Chrome سريعًا للغاية، وذلك باعتماد Node.js على محرك Chrome V8 المفتوح المصدر.
</p>

<h3>
	منصة غير متزامنة
</h3>

<p>
	تُعَدّ جميع الأوامر البرمجية في <a href="https://academy.hsoub.com/programming/general/%D9%84%D8%BA%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9/" rel="">لغات البرمجة</a> التقليدية حاجبة blocking افتراضيًا مثل سي C وجافا Java وبايثون وبي إتش بي PHP إلا إذا تدخّلتَ بصورة صريحة لإنشاء عمليات غير متزامنة؛ فإذا أجريت مثلًا طلبًا شبكيًا لقراءة ملف JSON، فسيتوقف التنفيذ حتى يكون الرد response جاهزًا.
</p>

<p>
	تسمح JavaScript بكتابة شيفرات غير متزامنة asynchronous وغير حاجبة non-blocking بطريقة سهلة جدًا باستخدام خيط thread وحيد ودوال رد النداء callback functions والبرمجة التي تعتمد على الأحداث event-driven، حيث نمرر دالة رد نداء والتي ستُستدعَى حين نتمكن من إكمال معالجة العملية وذلك في كل مرة تحدث عملية تستهلك الموارد، كما أننا لن ننتظر الانتهاء من ذلك قبل الاستمرار في تنفيذ بقية البرنامج.
</p>

<p>
	أُخِذت هذه الآلية من المتصفح، فلا يمكننا انتظار تحميل شيء ما عبر طلب AJAX قبل أن نكون قادرين على التعامل مع أحداث النقر على عناصر الصفحة، فيجب حدوث كل شيء في الوقت الحقيقي لتوفير تجربة جيدة للمستخدِم، مما يسمح بمعالجة آلاف الاتصالات بخادم Node.js وحيد دون الدخول في تعقيدات إدارة الخيوط threads، والتي تكون سببًا رئيسيًا للعلل في البرامج.
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p data-gramm="false">
		ملاحظة: إذا أنشأتَ المعالج onclick لصفحة الويب، فأنت تستعمل تقنيات البرمجة غير المتزامنة باستخدام معالجات الأحداث في JavaScript.
	</p>
</blockquote>

<p>
	توفِّر Node.js تعاملًا غير حاجب مع الدخل والخرج I/O، وتكون المكتبات في Node.js عمومًا مكتوبةً بمنهجية عدم الحجب، مما يجعل سلوك الحجب في Node.js استثناءً للقاعدة وليس شيئًا طبيعيًا، كما تكمل Node.js العمليات عند وصول الرد عندما تريد إجراء عملية دخل أو خرج مثل القراءة من الشبكة أو الوصول إلى قاعدة البيانات أو نظام الملفات بدلًا من حجب الخيط blocking the thread وإهدار طاقة المعالج بالانتظار.
</p>

<h3>
	عدد هائل من المكتبات
</h3>

<p>
	ساعد مدير الحزم npm ببنيته البسيطة النظام العام في node.js، إذ يستضيف npm ما يقرب من 500 ألف حزمة مفتوحة المصدر تستطيع استخدامها بحرّية.
</p>

<h2>
	مثال عن تطبيق Node.js
</h2>

<p>
	المثال الأكثر شيوعًا عن تطبيق Node.js هو خادم ويب يعرض العبارة الشهيرة Hello World:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_957_11" style=""><span class="kwd">const</span><span class="pln"> http </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'http'</span><span class="pun">)</span><span class="pln"> 
</span><span class="kwd">const</span><span class="pln"> hostname </span><span class="pun">=</span><span class="pln"> </span><span class="str">'127.0.0.1'</span><span class="pln"> 
</span><span class="kwd">const</span><span class="pln"> port </span><span class="pun">=</span><span class="pln"> </span><span class="lit">3000</span><span class="pln"> 
</span><span class="kwd">const</span><span class="pln"> server </span><span class="pun">=</span><span class="pln"> http</span><span class="pun">.</span><span class="pln">createServer</span><span class="pun">((</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> 
res</span><span class="pun">.</span><span class="pln">statusCode </span><span class="pun">=</span><span class="pln"> </span><span class="lit">200</span><span class="pln"> 
res</span><span class="pun">.</span><span class="pln">setHeader</span><span class="pun">(</span><span class="str">'Content-Type'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'text/plain'</span><span class="pun">)</span><span class="pln"> 
res</span><span class="pun">.</span><span class="pln">end</span><span class="pun">(</span><span class="str">'Hello World\n'</span><span class="pun">)</span><span class="pln"> 
</span><span class="pun">})</span><span class="pln"> 
server</span><span class="pun">.</span><span class="pln">listen</span><span class="pun">(</span><span class="pln">port</span><span class="pun">,</span><span class="pln"> hostname</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> 
console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(`</span><span class="typ">Server</span><span class="pln"> running at http</span><span class="pun">:</span><span class="com">//${hostname}:${port}/`) </span><span class="pln">
</span><span class="pun">})</span><span class="pln"> </span></pre>

<p>
	احفظ الشيفرة البسيطة السابقة في ملف باسم server.js ثم نفِّذ <code>node server.js</code> في الطرفية الخاصة بك وذلك من أجل تنفيذ تلك الشيفرة.
</p>

<p>
	تبدأ الشيفرة السابقة بتضمين <a href="https://wiki.hsoub.com/Node.js/http" rel="external">وحدة http</a>‏، إذ تمتلك <a href="https://wiki.hsoub.com/Node.js" rel="external">Node.js</a> مكتبةً قياسيةً رائعةً بما في ذلك دعم التعامل مع الشبكات؛ أما التابع <code>createServer()‎</code> الخاص بوحدة <code>http</code> فيُنشئ خادم HTTP جديد ويعيده، كما أنّ الخادم قد ضُبِط للاستماع إلى منفذ وعنوان شبكي محدَّدين، وعندما يجهز الخادم فستُستدعى دالة رد النداء والتي تخبرنا في هذه الحالة أنّ الخادم جاهز، وكلما استقبل الخادم طلبًا request جديدًا، فسيُطلَق <a href="https://wiki.hsoub.com/Node.js/http#.D8.A7.D9.84.D8.AD.D8.AF.D8.AB_.27request.27" rel="external">الحدث request</a> الذي يوفِّر كائنين هما الطلب أي كائن <a href="https://wiki.hsoub.com/Node.js/http#.D8.A7.D9.84.D8.B5.D9.86.D9.81_http.IncomingMessage" rel="external"><code>http.IncomingMessage</code></a> والرد أي كائن <a href="https://wiki.hsoub.com/Node.js/http#.D8.A7.D9.84.D8.B5.D9.86.D9.81_http.ServerResponse" rel="external"><code>http.ServerResponse</code></a>، ويُعَدّ هذان الكائنان أساسًا للتعامل مع استدعاء HTTP.
</p>

<p>
	يوفِّر الكائن الأول معلومات الطلبية لكننا لم نستعمله في هذا المثال البسيط، إلا أنه يمكنك الوصول إلى ترويسات الطلب وإلى بيانات الطلب؛ أما الكائن الثاني فيعيد البيانات إلى صاحب الطلب caller، وفي هذه الحالة باستخدامنا للسطر التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_957_13" style=""><span class="pln">res</span><span class="pun">.</span><span class="pln">statusCode </span><span class="pun">=</span><span class="pln"> </span><span class="lit">200</span></pre>

<p>
	ضبطنا قيمة الخاصية statusCode إلى 200 والتي تعني أنّ الرد ناجح، ثم ضبطنا ترويسة Content-Type كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_957_15" style=""><span class="pln">res</span><span class="pun">.</span><span class="pln">setHeader</span><span class="pun">(</span><span class="str">'Content-Type'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'text/plain'</span><span class="pun">)</span></pre>

<p>
	ثم ننهي الطلب بإضافة المحتوى على أساس وسيط argument إلى التابع ‎<code>end()</code>‎:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_957_19" style=""><span class="pln">res</span><span class="pun">.</span><span class="pln">end</span><span class="pun">(</span><span class="str">'Hello World\n'</span><span class="pun">)</span></pre>

<h2>
	أدوات Node.js وأطر عملها
</h2>

<p>
	تُعَدّ Node.js منصةً منخفضة المستوى، كما توجد آلاف المكتبات المكتوبة باستخدام Node.js لتسهيل الأمور على المطوِّرين وجعلها أكثر متعةً وسلاسةً، إذ أصبح عدد كبير من هذه المكتبات شائعًا بين المطوِّرين، وهذه قائمة غير شاملة للمكتبات التي نراها تستحق التعلم:
</p>

<ul>
	<li>
		<a href="https://expressjs.com/" rel="external nofollow">Express</a>: أحد أبسط وأقوى الطرق لإنشاء خادم ويب، ويكون تركيزه على البساطة وعدم الانحياز والميزات الأساسية لخادم الويب هو مفتاح نجاحه.
	</li>
	<li>
		Meteor: <a href="https://academy.hsoub.com/programming/general/%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-framework/" rel="">إطار عمل Framework</a> قوي جدًا ومتكامل، يسمح لك ببناء التطبيقات باستخدام JavaScript ومشاركة الشيفرة بين العميل والخادم، كما أصبح الآن يتكامل بسلاسة مع مكتبات واجهة المستخدِم مثل React و Vue و Angular، في حين يمكن استخدامه أيضًا لإنشاء تطبيقات الويب.
	</li>
	<li>
		<a href="http://koajs.com/" rel="external nofollow">koa</a>: بناه فريق Express نفسه ويطمح إلى أن يكون أصغر وأبسط اعتمادًا على سنوات الخبرة الطويلة للفريق، إذ بدأ هذا المشروع الجديد للحاجة إلى إنشاء تغييرات غير متوافقة مع ما سبقها دون تخريب ما أُنجِز في المشروع.
	</li>
	<li>
		Next.js: إطار عمل يستعمل التصيير rendering من طرف الخادم لتطبيقات React.
	</li>
	<li>
		<a href="https://github.com/zeit/micro" rel="external nofollow">Micro</a>: خادم خفيف جدًا لإنشاء خدمات HTTP مصغرة microservices غير متزامنة.
	</li>
	<li>
		<a href="https://socket.io/" rel="external nofollow">Socket.io</a>: محرك تواصل في الوقت الحقيقي لبناء تطبيقات الشبكة.
	</li>
</ul>

<h2>
	تاريخ موجز عن Node.js
</h2>

<p>
	لننظر إلى تاريخ Node.js من عام 2009 حتى الآن.
</p>

<p>
	أُنشِئت لغة JavaScript في شركة Netscape على أساس أداة لتعديل صفحات الويب داخل متصفحها <a href="https://ar.wikipedia.org/wiki/%D9%86%D8%AA%D8%B3%D9%83%D9%8A%D8%A8_%D9%86%D8%A7%D9%81%D9%8A%D8%AC%D8%A7%D8%AA%D9%88%D8%B1" rel="external nofollow">Netscape Navigator</a>، كما يُعَدّ بيع خوادم الويب جزءًا من نموذج الأعمال في Netscape والتي تحتوي على بيئة باسم Netscape LiveWire التي تستطيع إنشاء صفحات آلية باستخدام JavaScript من طرف الخادم؛ أي أنّ فكرة استخدام JavaScript من طرف الخادم لم تبدأ من Node.js وإنما هي قديمة قدم JavaScript، إلا أنها لم تكن ناجحةً في تلك الفترة.
</p>

<p>
	أحد العوامل الرئيسية لاشتهار Node.js هو التوقيت، إذ بدأت <a href="https://academy.hsoub.com/programming/javascript/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D9%84%D8%BA%D8%A9-javascript-r664/" rel="">لغة JavaScript</a> تُعَدّ لغةً حقيقيةً، وذلك بفضل تطبيقات Web 2.0 التي أظهرت للعالم كيف تكون التجربة الحديثة للويب مثل Google Maps أو GMail، كما أنّ أداء محركات JavaScript قد ارتفع بشدة بفضل حرب المتصفحات، إذ تعمل فرق التطوير خلف كل متصفح رئيسي بجدّ كل يوم لتحسين الأداء، وكان هذا فوزًا عظيمًا لمنصة JavaScript، علمًا أنّ محرك V8 الذي تستعمله Node.js هو محرك متصفح Chrome، وبالطبع لم تكن شهرة Node.js محض صدفة أو توقيت جيد، إذ أضافت مفاهيم ثورية في كيفية البرمجة باستخدام JavaScript من طرف الخادم.
</p>

<ul>
	<li>
		عام 2009: ولدت Node.js وأُنشِئت أول نسخة من npm.
	</li>
	<li>
		عام 2010: ولد كل من Express وSocket.io.
	</li>
	<li>
		عام 2011: وصل npm إلى الإصدار 1.0 وبدأت الشركات الكبيرة بتبني Node.js مثل LinkedIn، كما ولد <a href="http://hapi.dev/" rel="external nofollow">Hapi</a>.
	</li>
	<li>
		عام 2012: استمرت عملية تبنّي Node.js بسرعة كبيرة.
	</li>
	<li>
		عام 2013: أُنشِئت أول منصة تدوين باستخدام Node.js: ولدت <a href="https://koajs.com/" rel="external nofollow">Koa</a>.
	</li>
	<li>
		عام 2014: اشتق مشروع IO.js من Node.js بهدف إضافة دعم ES6 والتطوير بوتيرة أسرع.
	</li>
	<li>
		عام 2015: أُسست منظمة Node.js Foundation، ودمج مشروع IO.js مع Node.js مجددًا، كما أصبح npm يدعم الوحدات الخاصة private modules، وأُصدرت نسخة Node 4 (ولم تُصدَر النسخة 1 أو 2 أو 3 من قبل).
	</li>
	<li>
		عام 2016: ولد <a href="https://blog.npmjs.org/post/141577284765/kik-left-pad-and-npm" rel="external nofollow">مشروع Yarn</a>، وأُصدِرت Node 6.
	</li>
	<li>
		عام 2017: ركّز npm على الحماية أكثر، وأُصدرت Node 8، وأضاف محرك V8 وحدة HTTP/2 بنسخة تجريبية.
	</li>
	<li>
		عام 2018: أُصدرت Node 10، وأضيف دعم تجريبي لوحدات ES بلاحقة <code>‎.mjs</code>.
	</li>
	<li>
		عام 2019: أُصدرت Node 12 وNode 13.
	</li>
	<li>
		عام 2020: أُصدرت Node 14.
	</li>
	<li>
		عام 2021: الإصدار الحالي هو 16 والذي أصبح الإصدار المستقر طويل الدعم LTS حاليًا.
	</li>
</ul>

<h2>
	كيفية تثبيت Node.js
</h2>

<p>
	يمكن تثبيت Node.js بطرائق مختلفة، وسنشرح في هذا الفصل أشهر الطرائق وأسهلها لتثبيتها، كما أنّ الحزم الرسمية لجميع أنظمة التشغيل الرئيسية متوافرة على الرابط <a href="https://nodejs.org/en/download/" rel="external nofollow">nodejs.org/en/download</a>.
</p>

<p>
	إحدى الطرائق المناسبة لتثبيت Node.js هي استعمال مدير الحزم، حيث يملك كل نظام تشغيل مدير حزم خاص به، ففي نظام macOS يكون مدير الحزم <a href="https://brew.sh/" rel="external nofollow">Homebrew</a> هو مدير الحزم الأساسي، ويسمح بعد تثبيته بتثبيت Node.js بسهولة، وذلك بتنفيذ الأمر التالي في سطر الأوامر داخل الطرفية، ولن نذكر بالتفصيل مدراء الحزم المتاحة للينكس أو ويندوز منعًا للإطالة، لكنها موجودة بالتفصيل في موقع <a href="https://nodejs.org/en/download/package-manager/" rel="external nofollow">nodejs.org</a>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_957_29" style=""><span class="pln">brew install node</span></pre>

<p>
	يُعَدّ <code>nvm</code> الطريق الشائع لتشغيل Node.js، إذ يسمح لك بتبديل إصدار Node.js بسهولة وتثبيت الإصدارات الجديدة لتجربتها ثم العودة إلى الإصدار القديم إذا لم يعمل كل شيء على ما يرام، فمن المفيد جدًا على سبيل المثال تجربة الشيفرة الخاصة ببرنامج على الإصدارات القديمة من Node.js، كما ننصحك بمراجعة <a href="https://github.com/creationix/nvm" rel="external nofollow">github.com/creationix/nvm</a> لمزيد من المعلومات حول هذا الخيار، وننصحك بصورة شخصية باستعمال المثبِّت الرسمي إذا أنت حديث العهد على Node.js ولا تريد استخدام مدير الحزم الخاص بنظام تشغيلك، لكن على أي حال، سيكون البرنامج التنفيذي node متاحًا في سطر الأوامر بعد تثبيت Node.js بأي طريقة من الطرائق السابقة.
</p>

<h2>
	ماذا عليك معرفته في JavaScript لاستخدام Node.js
</h2>

<p>
	إذا بدأت لتوّك مع JavaScript، فما مدى عمق المعلومات التي تلزمك لاستخدام Node.js؟ من الصعب الوصول إلى النقطة التي تكون فيها واثقًا من قدراتك البرمجية كفاية، فحين تعلّمك كتابة الشيفرات، فقد تكون محتارًا متى تنتهي شيفرة JavaScript ومتى تبدأ Node.js وبالعكس، لذا ننصحك أن تكون متمكنًا تمكنًا جيدًا من المفاهيم الأساسية في JavaScript قبل التعمق في Node.js:
</p>

<ul>
	<li>
		<a href="https://wiki.hsoub.com/JavaScript#.D8.A8.D9.86.D9.8A.D8.A9_.D8.A7.D9.84.D9.84.D8.BA.D8.A9" rel="external">بنية البرنامج</a>.
	</li>
	<li>
		<a href="https://wiki.hsoub.com/JavaScript#.D8.A7.D9.84.D8.AA.D8.B9.D8.A7.D8.A8.D9.8A.D8.B1_.D8.A7.D9.84.D8.A8.D8.B1.D9.85.D8.AC.D9.8A.D8.A9" rel="external">التعابير</a>.
	</li>
	<li>
		الأنواع.
	</li>
	<li>
		المتغيرات
	</li>
	<li>
		<a href="https://wiki.hsoub.com/JavaScript#.D8.A7.D9.84.D8.AF.D9.88.D8.A7.D9.84" rel="external">الدوال والدوال السهمية</a>.
	</li>
	<li>
		الكلمة المحجوزة <a href="https://wiki.hsoub.com/JavaScript/this" rel="external"><code>this</code></a>.
	</li>
	<li>
		حلقات التكرار والمجالات scopes.
	</li>
	<li>
		المصفوفات.
	</li>
	<li>
		الفواصل المنقوطة (نعم، هذا المحرف ; <span class="ipsEmoji">?</span> )
	</li>
	<li>
		الوضع الصارم.
	</li>
	<li>
		إصدارات ECMAScript مثل ES6 وES2016 وES2017.
	</li>
</ul>

<p>
	ستكون بعد تعلّمك للمفاهيم السابقة في طريقك لتصبح مطور JavaScript محترف في بيئة المتصفح وNode.js، وفيما يلي مفاهيم أساسية لفهم البرمجة غير المتزامنة asynchronous programming والتي هي جزء أساسي من Node.js:
</p>

<ul>
	<li>
		مفهوم البرمجة غير المتزامنة ورد النداء callbacks.
	</li>
	<li>
		المؤقتات Timers.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-promise-%D9%81%D9%8A-javascript-r740/" rel="">الوعود Promises</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D9%84%D8%A7%D8%AA%D8%B2%D8%A7%D9%85%D9%86-%D9%88%D8%A7%D9%84%D8%A7%D9%86%D8%AA%D8%B8%D8%A7%D8%B1-asyncawait-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r921/" rel="">الكلمتان المحجوزتان Async وAwait</a>.
	</li>
	<li>
		التعابير المغلقة Closures.
	</li>
	<li>
		حلقات الأحداث.
	</li>
</ul>

<p>
	توجد <a href="https://academy.hsoub.com/programming/javascript/" rel="">مقالات كثيرة وكتب في أكاديمية حسوب</a> بالإضافة إلى <a href="https://wiki.hsoub.com/JavaScript" rel="external">توثيق JavaScript في موسوعة حسوب</a> عن جميع المواضيع السابقة.
</p>

<h2>
	الاختلافات بين Node.js والمتصفح
</h2>

<p>
	كيف تختلف كتابة تطبيقات JavaScript في Node.js عن البرمجة للويب داخل المتصفح؟ يستخدِم كل من المتصفح وNode.js لغة JavaScript للبرمجة؛ لكن بناء التطبيقات التي تعمل في المتصفح مختلف تمامًا عن بناء تطبيقات Node.js، فعلى الرغم من أنهما يستعملان لغة البرمجة نفسها JavaScript، إلا أنه هنالك اختلافات جوهرية تجعل الفرق بينهما كبيرًا،.
</p>

<p>
	يملك مطور واجهات المستخدِم الذي يكتب تطبيقات Node.js ميزةً رائعةً ألا وهي استخدامه للغة البرمجة نفسها، فمن المعروف أنه من الصعب تعلّم لغة برمجة جديدة بإتقان، لكن باستعمال لغة البرمجة نفسها لإجراء كل العمل على موقع الويب سواءً للواجهة الأمامية أو الخادم، حيث توجد أفضلية واضحة في هذه النقطة؛ إلا أنّ بيئة العمل هي التي تختلف.
</p>

<p>
	ستتعامل أغلب الوقت في المتصفح مع شجرة DOM أو غيرها من الواجهات البرمجية الخاصة بالمتصفح Web Platform APIs مثل ملفات الارتباط Cookies والتي لا توجد في Node.js، وبالطبع لن تكون الكائنات <code>document</code> و<code>window</code> وغيرها من كائنات المتصفح متوافرةً، كما لن نحصل على الواجهات البرمجية APIs التي توفرها Node.js عبر وحداتها مثل الوصول إلى نظام الملفات.
</p>

<p>
	يوجد اختلاف كبير آخر وهو أنك تستطيع التحكم في البيئة التي تعمل فيها Node.js ما لم تبني تطبيقًا مفتوح المصدر يمكن لأي شخص نشره في أيّ مكان، فأنت تعلم ما هي نسخة Node.js التي سيعمل عليها تطبيقك؛ فليس لديك الحرية في اختيار المتصفح الذي يستخدمه زوار موقعك بموازنة ذلك مع بيئة المتصفح، وهذا يعني أنك يمكنك أن تكتب شيفرات ES6-7-8-9 التي تدعمها نسخة Node.js عندك.
</p>

<p>
	ستكون ملزمًا باستخدام إصدارات JavaScript/ECMAScript القديمة على الرغم من تطوّر JavaScript بسرعة كبيرة، وذلك لأن المتصفحات أبطأ منها والمستخدِمون أبطأ بالتحديث، وصحيحٌ أنك تستطيع استخدام Babel لتحويل شيفرتك إلى نسخة موافقة لمعيار ECMAScript 5 قبل إرسالها إلى زوار موقعك، لكنك لن تحتاج إلى ذلك في Node.js.
</p>

<p>
	يوجد اختلاف آخر هو استخدام Node.js لنظام الوحدات CommonJS، بينما بدأنا نرى أنّ المتصفحات تستخدم معيار وحدات ES Modules؛ وهذا يعني عمليًا أنه عليك استخدام <code>require()‎</code> في Node.js، و<code>import</code> في المتصفح حاليًا.
</p>

<h2>
	محرك V8
</h2>

<p>
	V8 هو اسم محرك JavaScript الذي يُشغِّل متصفح Chrome، حيث يأخذ شيفرات JavaScript وينفذها أثناء التصفح عبر Chrome، إذ يوفر بيئة التشغيل اللازمة لتنفيذ شيفرات JavaScript، في حين يوفِّر المتصفح شجرة DOM وغيرها من الواجهات البرمجية للويب، كما يُعَدّ استقلال محرك JavaScript عن المتصفح الذي يستخدمه الميزة الرئيسية التي سببت بانتشار Node.js.
</p>

<p>
	اختار مجتمع مطوري Node.js محرك V8 في 2009، وبعد أن ذاع صيت Node.js صار محرك V8 هو المحرك الذي يشغِّل عددًا غير محصور من شيفرات JavaScript التي تعمل من طرف الخادم، وخذ بالحسبان أنّ بيئة تشغيل Node.js كبيرة جدًا ويعود الفضل إليها في أنّ محرك V8 أصبح يشغِّل <a href="https://academy.hsoub.com/programming/general/%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%B3%D8%B7%D8%AD-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D8%A8/" rel="">تطبيقات سطح المكتب</a> عن طريق مشاريع مثل Electron.js.
</p>

<h3>
	محركات JavaScript الأخرى
</h3>

<p>
	تملك المتصفحات الأخرى محركات JavaScript مختلفة منها:
</p>

<ul>
	<li>
		يملك متصفح Firefox محرك <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey" rel="external nofollow">SpiderMonkey</a>.
	</li>
	<li>
		يملك متصفح Safari محرك JavaScriptCore ويسمى Nitro أيضًا.
	</li>
	<li>
		يملك متصفح Edge محرك Chakra.
	</li>
</ul>

<p>
	تطبِّق جميع هذه المحركات معيار ECMA ES-262 والذي يسمى ECMAScript أيضًا، وهو المعيار المستخدَم في لغة JavaScript.
</p>

<h3>
	السعي إلى الأداء الأفضل
</h3>

<p>
	كُتِب محرك V8 بلغة C++‎ ويُحسَّن باستمرار، وهو محمول portable ويعمل على ماك ولينكس وويندوز وغيرها من أنظمة التشغيل، كما لن نخوض في تفاصيل محرك V8 لأنها موجودة في مواقع كثيرة مثل موقع V8 الرسمي وتتغير مع مرور الوقت، وعادةً تكون التغييرات كبيرةً، إذ يتطور محرك V8 دومًا كما في غيره في محركات JavaScript لتسريع الويب وبيئة Node.js، كما يجري سباق في عالم الويب للأداء الأفضل منذ سنوات، ونحن -المستخدِمون والمطوِّرون- نستفيد كثيرًا من هذه المنافسة لأنها تعطينا أداءً أسرع وأفضل سنةً بعد سنة.
</p>

<h3>
	التصريف Compilation
</h3>

<p>
	تُعَدّ لغة JavaScript عمومًا لغةً مفسَّرةً interpreted؛ لكن محركات JavaScript الحديثة لم تَعُد تفسِّر شيفرات JavaScript وحسب، وإنما تُصرِّفها compile، فقد حدث ذلك منذ عام 2009، عندما أُضيف مُصرِّف SpuderMonkey إلى متصفح Firefox 3.5، ثم اتبع الجميع هذه الفكرة، حيث تُصرَّف JavaScript داخليًا في V8 حين اللزوم بتصريف JIT (اختصارًا لـ just-in-time) لتسريع عملية التنفيذ.
</p>

<p>
	قد ترى أنّ ذلك مناف للمنطق، لكن تطورت لغة JavaScript من لغة تنفِّذ عادةً بضعة مئات من الأسطر إلى لغة تشغِّل تطبيقات كاملة تحتوي على آلاف أو حتى مئات الآلاف من الأسطر البرمجية التي تعمل في المتصفح وذلك منذ إطلاق Google Maps في 2004، فيمكن لتطبيقاتنا الآن أن تعمل لساعات في المتصفح بدلًا من سكربتات بسيطة للتحقق من صحة المدخلات أو إجراء أفعال بسيطة معينة، ففي هذا العالم الجديد أصبح من المنطقي تمامًا تصريف شيفرات JavaScript؛ وصحيحٌ أنها قد تأخذ بعض الوقت القليل لجهوزية شيفرة JavaScript، إلا أننا سنرى أداءً أفضل من الشيفرة المفسَّرة فقط.
</p>

<h2>
	تشغيل تطبيق Node.js والخروج منه
</h2>

<p>
	الطريقة التقليدية لتشغيل برنامج Node.js هي استخدام الأمر <code>node</code> المتاح في نظام التشغيل بعد تثبيت Node.js ثم تمرير اسم الملف الذي تريد تنفيذه إلى الأمر، فلو كان تطبيق Node.js عندك موجود في ملف باسم <code>app.js</code>، فيمكنك تشغيله بكتابة الأمر التالي في سطر الأوامر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_957_31" style=""><span class="pln">node app</span><span class="pun">.</span><span class="pln">js</span></pre>

<p>
	لنتعلم كيف يمكننا إنهاء تطبيق Node.js بأفضل الطرائق الممكنة، حيث توجد عدة طرق لإنهائه، فعندما تشغِّل برنامج في سطر الأوامر، يمكنك أن توقفه بالضغط على <code>Ctrl+c</code>، لكن ما نريد الحديث عنه هو إنهاء التطبيق برمجيًا، ولنبدأ بأكثر طريقة قاسية لإنهاء التطبيق، ولنرَ لماذا لا يفترض بك استعمالها.
</p>

<p>
	توفِّر الوحدة الأساسية core module المسماة <code>process</code> تابعًا يسمح لك بالخروج برمجيًا من تطبيق Node.js وهو <code>process.exit()‎</code>، إذ سيؤدي تشغيل Node.js هذا السطر إلى إغلاق العملية process مباشرةً، وهذا يعني أنه سيتوقف أيّ رد نداء معلَّق، أو أيّ طلب شبكي لا يزال مُرسَلًا، أو أيّ وصول إلى نظام ملفات، أو عمليات تكتب إلى مجرى الخرج القياسي <code>stdout</code> أو الخطأ القياسي <code>stderr</code> توقفًا مباشرًا دون سابق إنذار، وإذا لم تجد حرجًا في ذلك، فيمكنك تمرير عدد صحيح إلى التابع ليخبر نظام التشغيل ما هي حالة الخروج exit code:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_957_33" style=""><span class="pln">process</span><span class="pun">.</span><span class="pln">exit</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span></pre>

<p>
	تكون حالة الخروج الافتراضية هي 0، والتي تعني نجاح تنفيذ البرنامج، حيث تمتلك حالات الخروج المختلفة معانٍ خاصة والتي يمكنك استخدامها في نظامك للتواصل مع بقية البرامج، كما يمكنك أيضًا ضبط قيمة الخاصية <code>process.exitCode</code> بحيث تُعيد Node.js حالة الخروج المضبوطة إلى هذه الخاصية عند انتهاء تنفيذ البرنامج:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_957_35" style=""><span class="pln">process</span><span class="pun">.</span><span class="pln">exitCode </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span></pre>

<p>
	ينتهي البرنامج بسلام عند انتهاء تنفيذ جميع الشيفرات فيه في الحالة الطبيعية، وكثيرًا ما نُنشِئ خوادم باستخدام Node.js مثل خادم <a href="https://academy.hsoub.com/programming/general/%d9%85%d8%af%d8%ae%d9%84-%d8%a5%d9%84%d9%89-http-r73/" rel="">HTTP</a> التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_957_37" style=""><span class="kwd">const</span><span class="pln"> express </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'express'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> app </span><span class="pun">=</span><span class="pln"> express</span><span class="pun">()</span><span class="pln">
app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
 res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">'Hi!'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">listen</span><span class="pun">(</span><span class="lit">3000</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'Server ready'</span><span class="pun">))</span></pre>

<p>
	لن ينتهي هذا البرنامج أبدًا، فإذا استدعيت التابع ‎<code>process.exit()‎</code>‎، فستنتهي جميع الطلبيات قيد التنفيذ أو المعلَّقة، وهذا ليس أمرًا جميلًا صدقًا، حيث ستحتاج في هذه الحالة إلى إرسال الإشارة SIGTERM إلى الأمر، وسنتعامل مع الأمر باستخدام معالج إشارة العملية process signal handler.
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p data-gramm="false">
		ملاحظة: لا حاجة إلى تضمين الوحدة <code>process</code> باستخدام require، فهي متاحة تلقائيًا.
	</p>
</blockquote>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_957_39" style=""><span class="kwd">const</span><span class="pln"> express </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'express'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> app </span><span class="pun">=</span><span class="pln"> express</span><span class="pun">()</span><span class="pln">
app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
 res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">'Hi!'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">listen</span><span class="pun">(</span><span class="lit">3000</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'Server ready'</span><span class="pun">))</span><span class="pln">
process</span><span class="pun">.</span><span class="pln">on</span><span class="pun">(</span><span class="str">'SIGTERM'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
 app</span><span class="pun">.</span><span class="pln">close</span><span class="pun">(()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'Process terminated'</span><span class="pun">)</span><span class="pln">
 </span><span class="pun">})</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	قد تتساءل ما هي الإشارات؟ الإشارات هي نظام تواصل داخلي في معيار POSIX، وهو إشعار يُرسَل إلى العملية لإخبارها أنّ حدثًا ما قد حدث، فالإشارة <code>SIGKILL</code> هي الإشارة التي تخبر العملية بالتوقف عن العمل فورًا، وهي تعمل مثل ‎<code>process.exit()</code>‎؛ أما الإشارة <code>SIGTERM</code> فهي الإشارة التي تخبر العملية بأن تنتهي بلطف، وهي الإشارة التي تُرسَل من مدراء العمليات في أنظمة التشغيل، كما يمكنك إرسال هذه الإشارة من داخل البرنامج باستخدام دالة تابع آخر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_957_41" style=""><span class="pln">process</span><span class="pun">.</span><span class="pln">kill</span><span class="pun">(</span><span class="pln">process</span><span class="pun">.</span><span class="pln">pid</span><span class="pun">,</span><span class="pln"> </span><span class="str">'SIGTERM'</span><span class="pun">)</span><span class="pln"> </span></pre>

<p>
	أو من برنامج Node.js آخر، أو من أيّ برنامج يعمل على النظام يعرف مُعرِّف العملية <abbr title="Process IDentifier | معرّف العملية أو البرنامج"><abbr title="Process IDentifier | معرّف العملية أو البرنامج">PID</abbr></abbr> الخاص بالعملية التي تريد إنهاءها.
</p>

<h2>
	متغيرات البيئة: الفرق بين التطوير والإنتاج
</h2>

<p>
	متغير البيئة هو اصطلاح مُستخدَم على نطاق واسع في المكتبات الخارجية أيضًا، وسنتعلم كيفية قراءة واستخدام متغيرات البيئة في برنامج Node.js، حيث توفِّر الوحدة الأساسية <code>process</code> في Node.js الخاصية <code>env</code> التي تحتوي على جميع متغيرات البيئة المضبوطة في لحظة تشغيل العملية، وفيما يلي مثال يصل إلى متغير البيئة <code>NODE_ENV</code> المضبوط إلى القيمة development افتراضيًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_957_43" style=""><span class="pln">process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">NODE_ENV </span><span class="com">// "development"</span></pre>

<p>
	ضبطه إلى القيمة production قبل تشغيل السكربت سيخبر Node.js أنّ هذه بيئة إنتاجية وليست تطويرية، كما يمكنك بالطريقة نفسها الوصول إلى أيّ متغيرات بيئة خاصة تضبطها.
</p>

<p>
	يمكن أن يكون لديك إعدادات مختلفة لبيئات الإنتاج والتطوير، حيث يفترض Node أنه يعمل دائمًا في بيئة تطوير، ولكن يمكنك إعلام Node.js بأنك تعمل في بيئة إنتاج من خلال ضبط متغير البيئة <code>NODE_ENV=production</code> عن طريق تنفيذ الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_957_45" style=""><span class="kwd">export</span><span class="pln"> NODE_ENV</span><span class="pun">=</span><span class="pln">production</span></pre>

<p>
	لكن يُفضَّل في الصدَفة <a href="https://academy.hsoub.com/devops/linux/%d8%af%d9%84%d9%8a%d9%84-%d9%85%d9%8a%d9%8e%d8%b3%d9%91%d9%8e%d8%b1-%d9%84%d9%83%d8%aa%d8%a7%d8%a8%d8%a9-%d8%b3%d9%83%d8%b1%d8%a8%d8%aa%d8%a7%d8%aa-shell-r56/" rel="">shell</a> وضعه في ملف إعداد الصدَفة مثل <code>‎.bash_profile</code> مع صدفة Bash، وذلك لأن الإعداد بخلاف ذلك لا يستمر في حالة إعادة تشغيل النظام، كما يمكنك تطبيق متغير البيئة عن طريق وضعه في بداية أمر تهيئة تطبيقك كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_957_47" style=""><span class="pln">NODE_ENV</span><span class="pun">=</span><span class="pln">production node app</span><span class="pun">.</span><span class="pln">js</span></pre>

<p>
	يضمن ضبط البيئة على القيمة <code>production</code> ما يلي:
</p>

<ul>
	<li>
		الاحتفاظ بتسجيل الدخول إلى المستوى الأدنى الأساسي.
	</li>
	<li>
		إجراء مزيد من مستويات التخبئة أو التخزين المؤقت caching لتحسين الأداء.
	</li>
</ul>

<p>
	تطبِّق مكتبة القوالب Pug التي يستخدمها إطار عمل <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-nodejs-%D9%88express-r1099/" rel="">Express</a> على سبيل المثال عملية التصريف في وضع تنقيح الأخطاء، إذا لم يُضبَط المتغير <code>NODE_ENV</code> على القيمة <code>production</code>، حيث تُصرَّف عروض Express في كل طلب في وضع التطوير، بينما تُخزَّن مؤقتًا في وضع الإنتاج، كما يوفِّر إطار Express خطّافات إعداد configuration hooks خاصة بالبيئة تُستدعَى تلقائيًا بناءً على قيمة المتغير <code>NODE_ENV</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_957_51" style=""><span class="pln">app</span><span class="pun">.</span><span class="pln">configure</span><span class="pun">(</span><span class="str">'development'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
 </span><span class="com">//...</span><span class="pln">
</span><span class="pun">})</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">configure</span><span class="pun">(</span><span class="str">'production'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
 </span><span class="com">//...</span><span class="pln">
</span><span class="pun">})</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">configure</span><span class="pun">(</span><span class="str">'production'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'staging'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
 </span><span class="com">//...</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	يمكنك استخدام ذلك مثلًا لضبط معالجات أخطاء مختلفة في وضع مختلف كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_957_53" style=""><span class="pln">app</span><span class="pun">.</span><span class="pln">configure</span><span class="pun">(</span><span class="str">'development'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
 app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">express</span><span class="pun">.</span><span class="pln">errorHandler</span><span class="pun">({</span><span class="pln"> dumpExceptions</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln"> showStack</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln"> </span><span class="pun">}));</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

app</span><span class="pun">.</span><span class="pln">configure</span><span class="pun">(</span><span class="str">'production'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
 app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">express</span><span class="pun">.</span><span class="pln">errorHandler</span><span class="pun">())</span><span class="pln">
</span><span class="pun">})</span></pre>

<h2>
	استضافة مشاريع Node.js
</h2>

<p>
	يمكن استضافة تطبيقات Node.js في أماكن عديدة اعتمادًا على احتياجاتك، وسنذكر لك قائمةً بالخيارات المتاحة أمامك، وهي قائمة غير شاملة بالخيارات التي يمكنك استخدامها لنشر تطبيقك وجعله متاحًا للعامة، وسنرتِّبها من الأبسط والأقل مزايا إلى الأعقد والأقوى:
</p>

<ul>
	<li>
		أسهل الخيارات على الإطلاق: نفق محلي local tunnel.
	</li>
	<li>
		نشر التطبيقات دون أي ضبط.
	</li>
	<li>
		Glitch.
	</li>
	<li>
		Codepen.
	</li>
	<li>
		الخيارات عديمة الخوادم Serverless.
	</li>
	<li>
		المنصة على أساس خدمة PAAS.
	</li>
	<li>
		Zeit Now.
	</li>
	<li>
		Nanobox.
	</li>
	<li>
		Heroku.
	</li>
	<li>
		Microsoft Azure.
	</li>
	<li>
		Google Cloud Platform.
	</li>
	<li>
		خادم خاص افتراضي Virtual Private Server أي VPS.
	</li>
	<li>
		خادم حقيقي Bare metal.
	</li>
</ul>

<h3>
	نفق محلي
</h3>

<p>
	يمكنك نشر تطبيقك وتُخديم الطلبات من حاسوبك باستخدام نفق محلي local tunnel حتى إذا كان لديك عنوان IP ديناميكي، أو كنت تحت NAT، فهذا الخيار مناسب لإجراء بعض الاختبارات السريعة، أو تجربة المنتج أو مشاركة التطبيق مع مجموعة صغيرة من الأشخاص، وهنالك أداة رائعة لذلك متاحة لجميع المنصات اسمها <a href="https://ngrok.com/" rel="external nofollow">ngrok</a>، فكل ما عليك فعله لاستعمالها هو كتابة <code>ngrok PORT</code>، إذ إنَّ PORT هو المنفذ الذي تريد نشره على الإنترنت، وستحصل على نطاق من ngrok.io، لكن سيسمح لك الاشتراك المدفوع بالحصول على عنوان URL مخصص إضافةً إلى خيارات حماية إضافة (تذكَّر أنك تفتح جهازك إلى الإنترنت)، وهنالك خدمة أخرى يمكنك استخدامها وهي <a href="https://github.com/localtunnel/localtunnel" rel="external nofollow">github.com/localtunnel/localtunnel</a>.
</p>

<h3>
	نشر التطبيقات دون أي ضبط
</h3>

<p>
	هناك خيارات متاحة لنشر تطبيقات Node.js دون أيّ ضبط يُذكر، وسنذكر من هذه الخيارات منصة Glitch ومنصة Codepen.
</p>

<h4>
	Glitch
</h4>

<p>
	يُعَدّ <a href="https://glitch.com/" rel="external nofollow">Glitch</a> بيئةً تسمح لك ببناء تطبيقاتك بسرعة كبيرة، ورؤيتها حيةً على النطاق الفرعي الخاص بك على glitch.com، فلا يمكنك حاليًا الحصول على نطاق مخصص وهنالك بعض <a href="https://glitch.com/faq#restrictions" rel="external nofollow">المحدوديات</a>، لكن ستبقى مع ذلك بيئةً رائعةً؛ فهي تحتوي على كامل ميزات Node.js وCDN ومكان تخزين آمن للمعلومات الحساسة، بالإضافة إلى الاستيراد والتصدير من GitHub، كما أنّ هذه الخدمة موفَّرة من الشركة التي تقف خلف FogBugz وTrello والمشاركين في إنشاء StackOverflow.
</p>

<h4>
	Codepen
</h4>

<p>
	منصة <a href="https://codepen.io/" rel="external nofollow">Codepen</a> رائعة، فهي تسمح لك بإنشاء مشروع متعدد الملفات ونشره بنطاق مخصص.
</p>

<h3>
	الخيارات عديمة الخوادم Serverless
</h3>

<p>
	إحدى الطرائق لنشر تطبيقك وعدم الحاجة إلى خادم لإدارته هي استخدام إحدى الخيارات <a href="https://academy.hsoub.com/devops/cloud-computing/%D8%B3%D8%A8%D8%B9-%D9%85%D9%86%D8%B5%D8%A7%D8%AA-%D9%85%D9%81%D8%AA%D9%88%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D8%B5%D8%AF%D8%B1-%D9%84%D9%84%D8%A8%D8%AF%D8%A1-%D9%81%D9%8A-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%AD%D9%88%D8%B3%D8%A8%D8%A9-%D8%AE%D9%81%D9%8A%D8%A9-%D8%A7%D9%84%D8%AE%D9%88%D8%A7%D8%AF%D9%85-serverless-r408/" rel="">عديمة الخوادم Serverless</a> التي هي منهجية لنشر تطبيقاتك على أساس وظائف functions، وهي ترد على نقطة نهاية شبكية network endpoint، وهذا يسمى أيضًا FAAS أي الوظيفة على أساس خدمة Function As A Service، ومن الخيارات الشائعة جدًا هي:
</p>

<ul>
	<li>
		<a href="https://serverless.com/framework/" rel="external nofollow">Serverless Framework</a>
	</li>
	<li>
		<a href="https://stdlib.com/" rel="external nofollow">Standard Library</a>
	</li>
</ul>

<p>
	يوفِّر كلا الخيارين طبقة تجريدية abstraction layer لنشر التطبيقات على حلول مثل AWS Lambda وغيرها من حلول FAAS المبنية على Azure أو Google Cloud.
</p>

<h3>
	المنصة على أساس خدمة PAAS
</h3>

<p>
	تُعَدّ PASS اختصارًا لـ Platform AS A Service أي المنصة على أساس خدمة، وتحمل عنك هذه المنصات عناء التفكير في كثير من الأمور عند نشر تطبيقك.
</p>

<h4>
	Zeit Now
</h4>

<p>
	يُعَدّ Zeit خيارًا مثيرًا للاهتمام، فعندما تكتب الأمر <code>now</code> في الطرفية، فسيتولى أمر نشر تطبيقك كله؛ وهنالك نسخة مجانية مع محدوديات ونسخة مدفوعة بميزات أكثر، حيث ستنسى أنّ هنالك خادم وكل ما عليك فعله هو نشر التطبيق.
</p>

<h4>
	Nanobox
</h4>

<p>
	سنحيلك إلى موقع Nanobox لتأخذ فكرةً عنه <a href="https://nanobox.io/" rel="external nofollow">nanobox.io</a>.
</p>

<h4>
	Heroku
</h4>

<p>
	يُعَدّ Heroku منصةً رائعةً، وهنالك سلسلة فيديوهات عن <a href="https://academy.hsoub.com/tags/heroku/" rel="">كيفية نشر التطبيقات عبر Heroku</a> منها فيديو <a href="https://academy.hsoub.com/devops/deployment/%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-reactjs-%D8%B0%D9%88-%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%AE%D9%84%D9%81%D9%8A%D8%A9-nodejs-%D8%B9%D9%84%D9%89-%D9%85%D9%86%D8%B5%D8%A9-heroku-r512/" rel="">نشر تطبيق React.js ذو واجهات خلفية Node.js على منصة Heroku</a>.
</p>

<h4>
	Microsoft Azure
</h4>

<p>
	خدمة Azure توفرها Microsoft Cloud، وهنالك مقالة أجنبية تفصيلية عن <a href="https://docs.microsoft.com/en-us/microsoftteams/platform/get-started/get-started-nodejs" rel="external nofollow">إنشاء تطبيق Node.js في Azure</a>.
</p>

<h4>
	Google Cloud Platform
</h4>

<p>
	تُعَدّ منصة Google Cloud خيارًا رائعًا لتنظيم تطبيقاتك، ولديهم <a href="https://cloud.google.com/node/" rel="external nofollow">توثيق جيد عن Node.js</a>.
</p>

<h3>
	خادم خاص افتراضي VPS
</h3>

<p>
	ستجد في هذا القسم الخيارات الشائعة التي قد تعرفها من قبل، وهي مرتبة من أكثرها سهولةً للمستخدِم:
</p>

<ul>
	<li>
		<a href="https://www.digitalocean.com/" rel="external nofollow">DigitalOcean</a>
	</li>
	<li>
		<a href="https://www.linode.com/" rel="external nofollow">Linode</a>
	</li>
	<li>
		<a href="https://aws.amazon.com/" rel="external nofollow">Amazon Web Services</a>، ونذكر خصوصًا خدمة Amazon Elastic Beanstalk فهي تسهل بعضًا من تعقيدات AWS.
	</li>
</ul>

<p>
	ولمّا كانت هذه الخدمات توفِّر لك خادم لينكس فارغ يمكنك العمل عليه، فلن نوصي بمقالةٍ أو دليلٍ محدد لهذه الخدمات؛ وهذه ليست جميع الشركات التي توفر خدمات VPS، لكنها هي التي استعملناها سابقًا وننصح بها.
</p>

<h3>
	خادم حقيقي
</h3>

<p>
	يوجد خيار آخر هو خادم حقيقي bare metal، بحيث تثبت عليه توزيعة لينكس وتصله بالإنترنت أو يمكنك استئجار واحد شهريًا، كما في خدمة <a href="https://www.vultr.com/pricing/baremetal/" rel="external nofollow">Vultr Bare Metal</a>.
</p>

<p>
	ترجمة -وبتصرّف- للفصلين Introduction و Basics من كتاب The Node.js handbook لصاحبه Flavio Copes.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال التالي: <a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%88%D8%B6%D8%B9-%D8%A7%D9%84%D8%AA%D9%81%D8%A7%D8%B9%D9%84%D9%8A-%D9%88%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%B3%D8%B7%D8%B1-%D8%A7%D9%84%D8%A3%D9%88%D8%A7%D9%85%D8%B1-%D9%81%D9%8A-nodejs-r1464/" rel="">استخدام الوضع التفاعلي والتعامل مع سطر الأوامر في Node.js</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%A8%D9%86%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-nodejs-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-docker-r809/" rel="">بناء تطبيق Node.js باستخدام Docker</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-nodejs-%D9%84%D8%B3%D9%8A%D8%B1-%D8%B9%D9%85%D9%84-%D9%8A%D8%B9%D8%AA%D9%85%D8%AF-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-docker-compose-r811/" rel="">إعداد تطبيق node.js لسير عمل يعتمد على الحاويات باستخدام Docker Compose</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%AA%D8%A3%D9%85%D9%8A%D9%86-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-nodejs-%D9%8A%D8%B9%D9%85%D9%84-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-nginx-%D9%88-let%E2%80%99s-encrypt-%D9%88-compose-docker-r814/" rel="">تأمين تطبيق Node.js يعمل على الحاويات باستخدام Nginx و Let’s Encrypt و Compose Docker</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-nodejs-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D8%AE%D8%AF%D9%85%D8%A9-%D9%87%D9%8A%D8%B1%D9%88%D9%83%D9%88-heroku-%D9%85%D8%AB%D8%A7%D9%84%D9%8B%D8%A7-r1100/" rel="">نشر تطبيق Node.js على الويب: خدمة هيروكو (Heroku) مثالًا</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1463</guid><pubDate>Sat, 05 Feb 2022 16:00:00 +0000</pubDate></item><item><title>&#x625;&#x646;&#x634;&#x627;&#x621; &#x645;&#x642;&#x64A;&#x62F; &#x644;&#x645;&#x639;&#x62F;&#x644; &#x627;&#x644;&#x62A;&#x631;&#x627;&#x633;&#x644; &#x641;&#x64A; &#x628;&#x64A;&#x626;&#x629; Node.js</title><link>https://academy.hsoub.com/programming/javascript/nodejs/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%82%D9%8A%D8%AF-%D9%84%D9%85%D8%B9%D8%AF%D9%84-%D8%A7%D9%84%D8%AA%D8%B1%D8%A7%D8%B3%D9%84-%D9%81%D9%8A-%D8%A8%D9%8A%D8%A6%D8%A9-nodejs-r1450/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_01/61efb788c9828_------Node.js---App-Platform.png.28ccd61bc65e65323a128d5997c61de7.png" /></p>

<p>
	يسمح تقييد معدل التراسل Rate limiting بإدارة حركة مرور البيانات في الشبكة، ويحد من عدد المرات التي يكرر فيها المستخدم عملية ما خلال مدة معينة، مثل استخدام واجهة برمجة التطبيقات <abbr title="Application Programming Interface | واجهة برمجية">API</abbr>. تُعد الخدمات التي لا تحتوي على إجراءات أمان لتقيد من معدل التراسل معدل التراسل عرضة لزيادة التحميل وإعاقة العمل السليم للتطبيق.
</p>

<p>
	سنتعلم في هذا المقال كيفية إنشاء خادم <a href="https://academy.hsoub.com/devops/linux/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-nodejs-%D8%B9%D9%84%D9%89-%D9%86%D8%B8%D8%A7%D9%85-%D8%A3%D8%A8%D9%88%D9%86%D8%AA%D9%88-1804-r419/" rel="">Node.js</a> قادر على التحقق من <a href="https://academy.hsoub.com/devops/networking/%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A7%D8%AA-%D8%A7%D9%84%D9%81%D8%B1%D8%B9%D9%8A%D8%A9-%D9%88%D8%A7%D9%84%D8%B9%D9%86%D8%A7%D9%88%D9%8A%D9%86-%D9%88%D8%A7%D9%84%D8%A3%D8%AE%D8%B7%D8%A7%D8%A1-%D9%81%D9%8A-%D8%A8%D8%B1%D9%88%D8%AA%D9%88%D9%83%D9%88%D9%84-ip-r498/" rel="">عنوان IP</a> الخاص بالطلب وحساب معدل هذه الطلبات من خلال مقارنة العلامات الزمنية للطلبات لكل مستخدم، فإذا تجاوز عنوان IP الحد المعين الذي اخترناه في تطبيقنا، فسوف نتصل بواجهة برمجة تطبيقات <a href="https://api.cloudflare.com/" rel="external nofollow">Cloudflare <abbr title="Application Programming Interface | واجهة برمجية">API</abbr></a> ونضيف عنوان IP إلى القائمة، بعدها سننشئ قاعدة <a href="https://academy.hsoub.com/devops/security/firewalls/%D9%85%D8%A7-%D9%87%D9%88-%D8%A7%D9%84%D8%AC%D8%AF%D8%A7%D8%B1-%D8%A7%D9%84%D9%86%D8%A7%D8%B1%D9%8A-%D9%88%D9%83%D9%8A%D9%81-%D9%8A%D8%B9%D9%85%D9%84%D8%9F-r114/" rel="">جدار حماية</a> Cloudflare Firewall تحظر جميع الطلبات الواردة من عناوين IP الموجودة في تلك القائمة.
</p>

<p>
	إذًا سننشئ مشروع Node.js على منصة تطبيقات <a href="https://academy.hsoub.com/devops/cloud-computing/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%AA%D8%A8%D8%B9-%D8%A3%D8%AF%D8%A7%D8%A1-%D8%A7%D9%84%D8%AE%D9%88%D8%A7%D8%AF%D9%8A%D9%85-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D8%AE%D8%B7%D8%B7%D8%A7%D8%AA-digitalocean-r309/" rel="">DigitalOcean</a> التي تدعي App Platform والتي تحمي النطاقات المسجلة والمضافة إلى حسابك في Cloudflare بتقيد معدل التراسل.
</p>

<p>
	ستحتاج خلال هذا المقال إلى:
</p>

<ul>
<li>
		حساب <a href="https://www.cloudflare.com/" rel="external nofollow">Cloudflare</a>، ويفي الحساب المجاني Free plan من Cloudflare بالغرض. احرص اختيار الحساب المجاني عند إنشاء حساب جديد.
	</li>
	<li>
		نطاق مسجل ومضاف إلى حسابك في Cloudflare. يمكنك الإطلاع على <a href="https://academy.hsoub.com/devops/security/%25D9%2583%25D9%258A%25D9%2581-%25D9%2586%25D8%25AE%25D9%2581%25D9%2581-%25D9%2585%25D9%2586-%25D9%2587%25D8%25AC%25D9%2585%25D8%25A7%25D8%25AA-ddos-%25D8%25B6%25D8%25AF-%25D9%2585%25D9%2588%25D9%2582%25D8%25B9%25D9%2586%25D8%25A7-%25D8%25A8%25D8%25A7%25D8%25B3%25D8%25AA%25D8%25AE%25D8%25AF%25D8%25A7%25D9%2585-cloudflare-r136/" rel="">كيف نخفف من هجمات DDoS ضد موقعنا باستخدام CloudFlare</a> مع Cloudflare، وننصح بقراءة <a href="https://academy.hsoub.com/devops/servers/%25D9%2585%25D9%2582%25D8%25AF%25D9%2591%25D9%2585%25D8%25A9-%25D8%25A5%25D9%2584%25D9%2589-%25D9%2585%25D9%258F%25D8%25B5%25D8%25B7%25D9%258E%25D9%2584%25D8%25AD%25D8%25A7%25D8%25AA-%25D9%2588%25D8%25B9%25D9%2586%25D8%25A7%25D8%25B5%25D8%25B1-%25D9%2588%25D9%2585%25D9%2581%25D8%25A7%25D9%2587%25D9%258A%25D9%2585-%25D9%2586%25D8%25B8%25D8%25A7%25D9%2585-%25D8%25A3%25D8%25B3%25D9%2585%25D8%25A7%25D8%25A1-%25D8%25A7%25D9%2584%25D9%2586%25D8%25B7%25D8%25A7%25D9%2582%25D8%25A7%25D8%25AA-r5" rel="">هذا المقال</a> للتعرف على أساسيات نظام <a href="https://academy.hsoub.com/devops/servers/dns/%D8%AA%D9%86%D8%B5%D9%8A%D8%A8-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%AE%D8%AF%D9%85%D8%A9-%D8%A7%D8%B3%D9%85-%D8%A7%D9%84%D9%86%D8%B7%D8%A7%D9%82-dns-%D8%B9%D9%84%D9%89-%D8%AE%D9%88%D8%A7%D8%AF%D9%8A%D9%85-%D8%A3%D9%88%D8%A8%D9%86%D8%AA%D9%88-r176/" rel="">أسماء النطاقات DNS</a> ومكوناته.
	</li>
	<li>
		خادم Express مع بيئة Node.js، اتبع الخطوات في المقال التالي لإعداد خادم <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%AD%D8%AF%D9%8A%D8%AF-%D8%A7%D9%84%D9%85%D8%B3%D8%A7%D8%B1%D8%A7%D8%AA-%D9%88%D8%A3%D9%86%D9%88%D8%A7%D8%B9-%D8%B7%D9%84%D8%A8%D8%A7%D8%AA-http-%D9%81%D9%8A-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-express-r1444/" rel="">Express</a>.
	</li>
	<li>
		حساب على <a href="https://github.com/" rel="external nofollow">GitHub</a> ونسخة مثبتة من <a href="https://academy.hsoub.com/devops/servers/%25D9%2585%25D9%2582%25D8%25AF%25D9%2591%25D9%2585%25D8%25A9-%25D8%25A5%25D9%2584%25D9%2589-%25D9%2585%25D9%258F%25D8%25B5%25D8%25B7%25D9%258E%25D9%2584%25D8%25AD%25D8%25A7%25D8%25AA-%25D9%2588%25D8%25B9%25D9%2586%25D8%25A7%25D8%25B5%25D8%25B1-%25D9%2588%25D9%2585%25D9%2581%25D8%25A7%25D9%2587%25D9%258A%25D9%2585-%25D9%2586%25D8%25B8%25D8%25A7%25D9%2585-%25D8%25A3%25D8%25B3%25D9%2585%25D8%25A7%25D8%25A1-%25D8%25A7%25D9%2584%25D9%2586%25D8%25B7%25D8%25A7%25D9%2582%25D8%25A7%25D8%25AA-r5" rel="">برنامج git</a>على جهازك، يُعد ذلك ضروريًا لأننا سنرسل الشيفرة من GitHub على منصة App Platform من DigitalOcean.
	</li>
	<li>
		حساب على موقع <a href="https://www.digitalocean.com/products/app-platform/" rel="external nofollow">DigitalOcean</a>.
	</li>
</ul>
<h2>
	إعداد المشروع والنشر على منصة App Platform
</h2>

<p>
	سنوسع خادم Express الأساسي الذي أنشأناه في مقال <a href="%D8%B1%D8%A7%D8%A8%D8%B7" rel="">دليل استخدام Node.js وإطار العمل Express للمبتدئين</a>، في هذه الخطوة، ونرسل الشيفرة إلى مستودع GitHub Repository، ثم ننشر التطبيق على منصة App Platform.
</p>

<p>
	افتح مجلد مشروع خادم Express بواسطة إحدى محررات النصوص البرمجية، ثم أنشئ ملفًا جديدًا في المجلد الرئيسي للمشروع باسم <code>getignore.</code>، وأضف الأسطر التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4847_11" style="">
<span class="pln">node_modules</span><span class="pun">/</span><span class="pln">
</span><span class="pun">.</span><span class="pln">env</span></pre>

<p>
	لاحظ أن السطر الأول في ملف <code>getignore.</code> يوجه git لعدم تتبع المجلد node_modules، ويتيح ذلك عدم زيادة حجم المستودع، إذ يُولَد المجلد node-modules عند الحاجة باستخدام الأمر <code>npm install</code>، أما السطر الثاني فيمنع تتبع ملف <a href="https://academy.hsoub.com/devops/linux/%D8%A7%D9%84%D9%85%D8%AA%D8%BA%D9%8A%D8%B1%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D8%AB%D9%88%D8%A7%D8%A8%D8%AA-%D9%88%D8%AA%D8%B9%D9%88%D9%8A%D8%B6-%D8%A7%D9%84%D8%A3%D9%88%D8%A7%D9%85%D8%B1-%D9%81%D9%8A-%D8%B3%D9%83%D8%B1%D8%A8%D8%AA%D8%A7%D8%AA-%D8%A7%D9%84%D8%B5%D8%AF%D9%81%D8%A9-shell-scripts-r256/" rel="">متغيرات</a> البيئة، وسوف ننشئ الملف <code>env.</code> في الخطوات التالية.
</p>

<p>
	انتقل إلى ملف server.js في محرر النصوص البرمجية وعدل الأسطر التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4847_15" style="">
<span class="pun">...</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">listen</span><span class="pun">(</span><span class="pln">process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">PORT </span><span class="pun">||</span><span class="pln"> </span><span class="lit">3000</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(`</span><span class="typ">Example</span><span class="pln"> app is listening on port $</span><span class="pun">{</span><span class="pln">process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">PORT </span><span class="pun">||</span><span class="pln"> </span><span class="lit">3000</span><span class="pun">}`);</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	يتيح الاستخدام الشرطي للمتغير <code>PORT</code> تشغيل الخادم ديناميكيًا على المنفذ المعين <code>PORT</code> في متغير البيئة <code>process.env</code> أو استخدام المنفذ 3000 كمنفذ احتياطي.
</p>

<p>
	لاحظ أن السلسلة الموجودة في التابع <code>console.log</code> ليست محاطة بعلامة اقتباس عادية وإنما بعلامة اقتباس مائلة (`) ، إذ يتيح ذلك استخدام <a href="https://wiki.hsoub.com/JavaScript/Template_Literals" rel="external">القوالب النصية template literals</a> التي تسمح باستخدام التعابير expressions ضمن السلاسل.
</p>

<p>
	شَغّل التطبيق من واجهة الطرفية Terminal باستخدام الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4847_17" style="">
<span class="pln">node server</span><span class="pun">.</span><span class="pln">js</span></pre>

<p>
	لاحظ أن واجهة المتصفح ستعرض الرسالة التالية: "Successful response" أي أنه نجحت الاستجابة، وسيظهر الخرج التالي في واجهة الطرفية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4847_19" style="">
<span class="typ">Example</span><span class="pln"> app is listening on port </span><span class="lit">3000</span></pre>

<p>
	سننشر الآن التطبيق على منصة App platform بعد أن تأكدنا من عمل خادم Express بنجاح.
</p>

<p>
	أولاً ، هيئ <code>git</code> في المجلد الرئيسي للمشروع وأرسل الشيفرة إلى حسابك في GitHub، ثم انتقل إلى <a href="https://cloud.digitalocean.com/apps/" rel="external nofollow">لوحة التحكم Dashboard</a> في منصة App Platform في المتصفح وانقر على زر إنشاء التطبيق <strong>Create App</strong>. ثم اضغط على خيار <strong>GitHub</strong> وتوثيق مع GitHub، ثم اختر مستودع مشروعك من القائمة المنسدلة.
</p>

<p>
	راجع الإعدادات، ثم سمي التطبيق، وبما أننا سنعمل على مرحلة تطوير التطبيق في هذا المقال يُفضل أن نختار الخطة الأساسية <strong>Basic Plan</strong>، ثم اضغط على زر تشغيل التطبيق <strong>Launch App</strong>.
</p>

<p>
	انتقل بعد ذلك إلى صفحة الإعدادات <strong>Settings</strong> واضغط على قسم النطاقات <strong>Domains</strong> ثم أضف النطاق الخاص بك الموجه بواسطة Cloudflare في حقل اسم النطاق الرئيسي أو الفرعي <strong>Domain or Subdomain Name</strong>، ثم اضغط على خيار إدارة النطاق الخاص بك <strong>You manage your domain</strong> وانسخ سجل <code>CNAME</code> لتضيفه إلى حساب DNS الخاص بنطاقك في Cloudflare.
</p>

<p>
	افتح لوحة تحكم النطاق في Cloudflare في علامة تبويب جديدة، ثم اضغط على تبويبة <strong>DNS</strong>، واضغط بعدها على زر إضافة سجل <strong>Add Record</strong> واختر <strong>CNAME</strong> عند خيار النوع <strong>Type</strong>، اكتب الرمز <strong>@</strong> ثم الصق سجل <code>CNAME</code> الذي نسخته سابقًا من منصة App Platform. اضغط على زر الحفظ <strong>Save</strong> ثم انتقل إلى صفحة الإعدادات <strong>Settings</strong> في لوحة تحكم App platform واضغط بعدها على قسم النطاقات <strong>Domains</strong>، ثم اضغط على زر إضافة نطاق <strong>Add Domain</strong>.
</p>

<p>
	اضغط على علامة تبويب عمليات النشر <strong>Deployments</strong> لمشاهدة تفاصيل التطبيقات المنشورة. يمكنك الضغط على نطاقك <code>your_domain</code> لاستعراضه في <a href="https://academy.hsoub.com/programming/javascript/%D8%A3%D9%81%D8%B9%D8%A7%D9%84-%D8%A7%D9%84%D9%85%D8%AA%D8%B5%D9%81%D8%AD-%D8%A7%D9%84%D8%A7%D9%81%D8%AA%D8%B1%D8%A7%D8%B6%D9%8A%D8%A9-%D9%84%D9%84%D8%A3%D8%AD%D8%AF%D8%A7%D8%AB-%D9%88%D8%B6%D8%A8%D8%B7%D9%87%D8%A7-%D8%B9%D8%A8%D8%B1-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1143/" rel="">المتصفح</a>، حالما ينتهي النشر. لاحظ أن واجهة المتصفح ستعرض الرسالة التالية: نجحت الاستجابة "successful response".
</p>

<p>
	ستحصل على الرسالة التالية عند الانتقال إلى علامة تبويب سجلات التشغيل الحالية <strong>Runtime Logs</strong> في لوحة تحكم App Platform:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4847_22" style="">
<span class="typ">Example</span><span class="pln"> app is listening on port </span><span class="lit">8080</span></pre>

<p>
	لاحظ أن المنفذ <code>8080</code> هو المنفذ الافتراضي في App Platform، يمكنك تغييره من الإعدادات قبل النشر.
</p>

<p>
	الآن، سنتعلم كيفية تحديد ذاكرة التخزين المؤقت cache لحساب الطلبات الواردة إلى مُقيِّد معدل التراسل Rate limiter.
</p>

<h2>
	تخزين عنوان IP المستخدم وحساب عدد الطلبات في الثانية
</h2>

<p>
	سنخزن في هذه الخطوة عنوان IP المستخدم في ذاكرة تخزين مؤقتة cache مع مجموعة من الطوابع الزمنية timestamps لمراقبة عدد الطلبات في الثانية من عنوان IP لكل مستخدم.
</p>

<p>
	تُخزن ذاكرة التخزين المؤقت البيانات التي يستخدمها تطبيق ما بشكل متكرر، ويُحتفظ عادةً بالبيانات الموجودة في ذاكرة <a href="https://academy.hsoub.com/programming/php/%D8%A7%D9%84%D8%AA%D8%AE%D8%B2%D9%8A%D9%86-%D8%A7%D9%84%D9%85%D8%A4%D9%82%D8%AA-cache-%D9%88%D9%85%D9%82%D8%A7%D8%A8%D8%B3-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-webscockets-%D9%81%D9%8A-php-r1180/" rel="">التخزين المؤقت</a> في أجهزة الوصول السريع مثل ذاكرة الوصول العشوائي RAM، فالغرض الأساسي منها هو تسريع استرداد البيانات عن طريق تقليل الحاجة إلى استردادها من وسائط التخزين الأبطأ.
</p>

<p>
	سنحتاج إلى ثلاثة حزم من برنامج مدير الحزم npm وهي <code>node-cache</code> و<code>is-ip</code> و <code>request-ip</code>. فتلتقط الحزمة <code>request-ip</code> عنوان IP المستخدم الذي يطلب الخادم، أما الحزمة <code>node-cache</code> فتنشئ ذاكرة تخزين مؤقتة تستخدم لتتبع طلبات المستخدم، وتستخدم الحزمة <code>is-ip</code> للتحقق فيما إذا كان عنوان IP هو عنوان IPv6.
</p>

<p>
	ثبت الحزم <code>node-cache</code> و<code>is-ip</code> و <code>request-ip</code> في الطرفية باستخدام مدير الحزم npm عن طريق الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4847_26" style="">
<span class="pln">npm i node</span><span class="pun">-</span><span class="pln">cache is</span><span class="pun">-</span><span class="pln">ip request</span><span class="pun">-</span><span class="pln">ip</span></pre>

<p>
	افتح ملف server.js بواسطة إحدى محررات النصوص البرمجية وأضف الأسطر التالية بعد سطر <code>const express = require('express');‎</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4847_28" style="">
<span class="pun">...</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> requestIP </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'request-ip'</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> nodeCache </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'node-cache'</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> isIp </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'is-ip'</span><span class="pun">);</span><span class="pln">
</span><span class="pun">...</span></pre>

<p>
	يحضّر السطر الأول وحدة <code>requestIP</code> من حزمة <code>request-ip</code> التي ثبتها سابقًا، فتقوم هذه الوحدة بإلتقاط عنوان IP المستخدم الذي يطلب الخادم.
</p>

<p>
	أما السطر الثاني فيحضّر وحدة <code>nodeCache</code> من حزمة <code>node-cache</code>، التي تنشئ ذاكرة تخزين مؤقتة تستخدم لتتبع طلبات المستخدم في الثانية. ويحضّر السطر الثالث وحدة <code>isIp</code> من الحزمة <code>is-ip</code>، ويتحقق فيما إذا كان عنوان IP من الإصدار السادس IPv6 الذي سوف تصوغه وفق ترميز <a href="https://academy.hsoub.com/devops/networking/%D8%A7%D9%84%D8%A5%D8%B5%D8%AF%D8%A7%D8%B1-%D8%A7%D9%84%D8%B3%D8%A7%D8%AF%D8%B3-%D9%85%D9%86-%D8%A8%D8%B1%D9%88%D8%AA%D9%88%D9%83%D9%88%D9%84-ip-r504/" rel="">CIDR</a> المعتمد في Cloudflare.
</p>

<p>
	عَرِّف المتغيرات الثابتة في ملف server.js:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4847_30" style="">
<span class="pun">...</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> TIME_FRAME_IN_S </span><span class="pun">=</span><span class="pln"> </span><span class="lit">10</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> TIME_FRAME_IN_MS </span><span class="pun">=</span><span class="pln"> TIME_FRAME_IN_S </span><span class="pun">*</span><span class="pln"> </span><span class="lit">1000</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> MS_TO_S </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> </span><span class="lit">1000</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> RPS_LIMIT </span><span class="pun">=</span><span class="pln"> </span><span class="lit">2</span><span class="pun">;</span></pre>

<p>
	يحدد المتغير الثابت <code>TIME_FRAME_IN_S</code> الفترة التي يحسب خلالها تطبيقك متوسط الطوابع الزمنية للمستخدم، ويجدر بالذكر أن زيادة الفترة الزمنية سيؤدي إلى زيادة حجم الذاكرة المؤقتة، وبالتالي سيؤدي إلى استهلاك حجم أكبر من الذاكرة.
</p>

<p>
	ويحدد المتغير الثابت <code>TIME_FRAME_IN_MS</code> الفترة التي يحسب خلالها تطبيقك متوسط الطوابع الزمنية للمستخدم، ولكن بالميللي ثانية، وسنستخدم معامل التحويل <code>MS_TO_S</code> لتحويل الزمن من صيغة الميللي ثانية إلى صيغة الثواني. يحدد المتغير <code>RPS_LIMIT</code> حد العتبة الذي سيؤدي إلى تشغيل مقيد معدل التراسل، ويغير قيمة العتبة وفقًا لمتطلبات التطبيق، وقد أسندنا له القيمة 2 والتي تعتبر قيمة معتدلة، في المتغير <code>RPS_LIMIT</code> أثناء مرحلة التطوير.
</p>

<p>
	يمكنك باستخدام إطار العمل Express كتابة دوال وسيطة middleware لها وصول إلى جميع <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-http-%D8%B4%D8%B1%D8%AD-%D8%A7%D9%84%D8%AA%D8%AE%D8%A7%D8%B7%D8%A8-%D8%A8%D9%8A%D9%86-%D8%A7%D9%84%D8%B9%D9%85%D9%8A%D9%84-%D9%88%D8%A7%D9%84%D8%AE%D8%A7%D8%AF%D9%85-r74/" rel="">طلبات HTTP</a> الواردة إلى خادمك.
</p>

<p>
	استدعِ التابع <code>()app.use</code> لتعريف دالة وسيطة، ومررها ضمنه، باتباع هذه الطريقة أنشئ دالة وسيطة باسم <code>ipMiddleware</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4847_32" style="">
<span class="pun">...</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> ipMiddleware </span><span class="pun">=</span><span class="pln"> async </span><span class="kwd">function</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let clientIP </span><span class="pun">=</span><span class="pln"> requestIP</span><span class="pun">.</span><span class="pln">getClientIp</span><span class="pun">(</span><span class="pln">req</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">isIp</span><span class="pun">.</span><span class="pln">v6</span><span class="pun">(</span><span class="pln">clientIP</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        clientIP </span><span class="pun">=</span><span class="pln"> clientIP</span><span class="pun">.</span><span class="pln">split</span><span class="pun">(</span><span class="str">':'</span><span class="pun">).</span><span class="pln">splice</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">4</span><span class="pun">).</span><span class="pln">join</span><span class="pun">(</span><span class="str">':'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">'::/64'</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    next</span><span class="pun">();</span><span class="pln">
</span><span class="pun">};</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">ipMiddleware</span><span class="pun">);</span><span class="pln">
</span><span class="pun">...</span></pre>

<p>
	لاحظ أن الدالة <code>()getClientIP</code> تستخدم الكائن <code>req</code> من الدالة الوسيطة كمعامل.
</p>

<p>
	نستدعي الدالة <code>()v6.</code> مع الوحدة <code>is-ip</code>، وتعطي نتيجة صحيحة <code>true</code> إذا كان الوسيط الممرر إليها عنوان IPv6. يجب عليك صياغة عناوين IPv6 وفق قناع شبكة <code>64/</code> حسب ترميز CIDR، كالآتي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4847_34" style="">
<span class="pln">aaaa</span><span class="pun">:</span><span class="pln">bbbb</span><span class="pun">:</span><span class="pln">cccc</span><span class="pun">:</span><span class="pln">dddd</span><span class="pun">::/</span><span class="lit">64</span></pre>

<p>
	ينشئ التابع <code>(':')split.</code> مصفوفة من السلسلة التي تحتوي على عناوين IP ويفصلهم بواسطة المحرف<code>:</code>.
</p>

<p>
	يرجع التابع <code>(splice(0,4.</code> أول أربعة وسطاء في المصفوفة. أما التابع <code>(':')join.</code> فيرجع سلسلة من المصفوفة مع المحرف <code>:</code>.
</p>

<p>
	يوجه التابع <code>()next</code> الدالة الوسيطة للانتقال إلى الدالة الوسيطة التالية، إن وجدت، ونلاحظ في مثالنا أنها تنقل الطلب إلى المسار <code>/</code> من طلب GET. احرص على تضمين ذلك في التابع، وإلا فلن ينتقل الطلب من الدالة الوسيطة.
</p>

<p>
	هيئ نسخة instance من الوحدة <code>node-cache</code> عن طريق إضافة المتغير التالي بعد الثوابت:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4847_36" style="">
<span class="pun">...</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">IPCache</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> nodeCache</span><span class="pun">({</span><span class="pln"> stdTTL</span><span class="pun">:</span><span class="pln"> TIME_FRAME_IN_S</span><span class="pun">,</span><span class="pln"> deleteOnExpire</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">,</span><span class="pln"> checkperiod</span><span class="pun">:</span><span class="pln"> TIME_FRAME_IN_S </span><span class="pun">});</span><span class="pln">
</span><span class="pun">...</span></pre>

<p>
	يمكننا إعادة تعريف المعاملات overriding الأساسية الخاصة بوحدة <code>nodecache</code> وتبديلها إلى خصائص محددة custom properties باستخدام المتغير الثابت <code>IPCache</code>:
</p>

<ul>
<li>
		<code>stdTTL</code>: يعبر عن الفاصل الزمني بالثواني الذي يُحذَف بعده زوج مفتاح-قيمة key-value من عناصر <a href="https://academy.hsoub.com/programming/php/%D8%A7%D9%84%D8%AA%D8%AE%D8%B2%D9%8A%D9%86-%D8%A7%D9%84%D9%85%D8%A4%D9%82%D8%AA-cache-%D9%88%D9%85%D9%82%D8%A7%D8%A8%D8%B3-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-webscockets-%D9%81%D9%8A-php-r1180/" rel="">ذاكرة التخزين المؤقت</a>. <code>TTL</code> تعني وقت الحياة Time To Live، وهي مقياس للوقت الذي تنتهي بعده ذاكرة التخزين المؤقت.
	</li>
	<li>
		<code>deleteOnExpire</code>: اضبطه على <code>false</code>حيث أننا سنكتب تابع استدعاء فيما بعد ليتعامل مع الحدث <code>expired</code>.
	</li>
</ul>
<p>
	*<code>checkperiod</code> يعبر عن الفاصل الزمني بالثواني الذي يُشغَل بعده فحص تلقائي للعناصر منتهية الصلاحية. تكون القيمة الإفتراضية <code>600</code>، لكن فحص انتهاء الصلاحية في تطبيقك سيحدث في وقت أقصر، لأن القيمة المحددة أصغر.
</p>

<p>
	يمكنك الإطلاع على توثيق <a href="https://www.npmjs.com/package/node-cache" rel="external nofollow">node-cache</a> للحصول على مزيد من المعلومات عن المعاملات الإفتراضية لحزمة <code>node-cache</code>. سيساعدك المخطط التالي على تصور كيفية تخزين ذاكرة التخزين المؤقت للبيانات:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="90629" href="https://academy.hsoub.com/uploads/monthly_2022_01/image2.png.25c2af8dacf03f7da8157295c832d80c.png" rel=""><img alt="image2.png" class="ipsImage ipsImage_thumbnailed" data-fileid="90629" data-unique="dtnpy7rjh" src="https://academy.hsoub.com/uploads/monthly_2022_01/image2.thumb.png.2248531c6c2e02b3bb64f8445977cc68.png" style="width: 500px; height: auto;"></a>
</p>

<p>
	سننشئ الآن زوج مفتاح-قيمة لعنوان IP الجديد ونضيفه للزوج القديم في حال وجود عنوان IP في الذاكرة المؤقتة. تكون القيمة عبارة عن مصفوفة من العلامات الزمنية timestamps المقابلة لكل طلب وارد إلى تطبيقك.
</p>

<p>
	أنشئ الدالة <code>()updateCache</code> بعد المتغير الثابت <code>IPCache</code> لإضافة علامات زمنية للطوابع في الذاكرة المؤقتة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3501_7" style="">
<span class="pun">...</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> updateCache </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">ip</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let </span><span class="typ">IPArray</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="typ">IPCache</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="pln">ip</span><span class="pun">)</span><span class="pln"> </span><span class="pun">||</span><span class="pln"> </span><span class="pun">[];</span><span class="pln">
    </span><span class="typ">IPArray</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">());</span><span class="pln">
    </span><span class="typ">IPCache</span><span class="pun">.</span><span class="kwd">set</span><span class="pun">(</span><span class="pln">ip</span><span class="pun">,</span><span class="pln"> </span><span class="typ">IPArray</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="typ">IPCache</span><span class="pun">.</span><span class="pln">getTtl</span><span class="pun">(</span><span class="pln">ip</span><span class="pun">)</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">.</span><span class="pln">now</span><span class="pun">())</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> MS_TO_S </span><span class="pun">||</span><span class="pln"> TIME_FRAME_IN_S</span><span class="pun">);</span><span class="pln">
</span><span class="pun">};</span><span class="pln">
</span><span class="pun">...</span></pre>

<p>
	لاحظ أن السطر الأول في الدالة يحضر مجموعة العلامات الزمنية لعنوان IP المحدد أو يهيئ مصفوفة فارغة إذا كان فارغًا. نقوم في السطر التالي بإضافة العلامة الزمنية الحالية التي التقطت بواسطة الدالة <code>()new Date</code> إلى المصفوفة.
</p>

<p>
	تأخذ الدالة <code>()set.</code> ثلاثة وسطاء هي : <code>key</code> و <code>value</code> و<code>TTL</code>، فيعيد الوسيط الأخير تعريف قيمة TTL القياسية عن طريق استبدال قيمة <code>stdTTL</code> في المتغير <code>IPCache</code>، ويستخدم <code>TTL</code> الحالية إذا كان عنوان IP موجودًا في الذاكرة المؤقتة، عدا عن ذلك تُحَدد قيمة <code>TTL</code> من المتغير <code>TIME_FRAME_IN_S</code>.
</p>

<p>
	تُحسَب قيمة TTL لزوج مفتاح-قيمة الحالي عن طريق طرح قيمة العلامة الزمنية الحالية من العلامة الزمنية لانتهاء الصلاحية، وتُحَول النتيجة إلى ثواني ثم تُمَرر إلى حقل الوسيط الثالث في الدالة <code>()set.</code>.
</p>

<p>
	تقبل الدالة <code>()getTtl.</code> مفتاحًا وعنوان IP كوسطاء وترجع زمن TTL للزوج مفتاح-قيمة كعلامة زمنية، وفي حال عدم وجود عنوان IP في الذاكرة المؤقتة، تُرجِع <code>undefined</code> وتستخدم القيمة الاحتياطية في المتغير <code>TIME_FRAME_IN_S</code>.
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<div class="ipsQuote_contents ipsClearfix">
		<p>
			<strong>ملاحظة:</strong> ستحتاج التحويل من صيغة الميللي ثانية إلى صيغة الثواني، إذ أن <a href="https://wiki.hsoub.com/JavaScript" rel="external">لغة جافاسكربت JavaScript</a> تستخدم العلامات الزمنية بصيغة الميللي ثانية، أما وحدة <code>node-cache</code> فتستخدم الثواني.
		</p>
	</div>
</blockquote>

<p>
	أضف الأسطر التالية في الدالة الوسيطة <code>ipMiddleware</code> بعد تعليمة <code>if (isIp.v6(clientIP))‎</code> لحساب عدد الطلبات في الثانية من عنوان IP الذي يطلب التطبيق:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3501_9" style="">
<span class="pun">...</span><span class="pln">
    updateCache</span><span class="pun">(</span><span class="pln">clientIP</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> </span><span class="typ">IPArray</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="typ">IPCache</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="pln">clientIP</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="typ">IPArray</span><span class="pun">.</span><span class="pln">length </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">1</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">const</span><span class="pln"> rps </span><span class="pun">=</span><span class="pln"> </span><span class="typ">IPArray</span><span class="pun">.</span><span class="pln">length </span><span class="pun">/</span><span class="pln"> </span><span class="pun">((</span><span class="typ">IPArray</span><span class="pun">[</span><span class="typ">IPArray</span><span class="pun">.</span><span class="pln">length </span><span class="pun">-</span><span class="pln"> </span><span class="lit">1</span><span class="pun">]</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="typ">IPArray</span><span class="pun">[</span><span class="lit">0</span><span class="pun">])</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> MS_TO_S</span><span class="pun">);</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">rps </span><span class="pun">&gt;</span><span class="pln"> RPS_LIMIT</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'You are hitting limit'</span><span class="pun">,</span><span class="pln"> clientIP</span><span class="pun">);</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">...</span></pre>

<p>
	يضيف السطر الأول الطابع الزمني للطلب الوارد من عنوان IP إلى ذاكرة التخزين المؤقت عن طريق استدعاء دالة <code>()updateCache</code>، ويجمع السطر الثاني مجموعة العلامات الزمنية لعنوان IP. إذا كان عدد العناصر في مصفوفة العلامات الزمنية أكبر من واحد (إذ يحتاج حساب الطلبات في الثانية إلى علامتين زمنيتين على الأقل)، وعدد الطلبات في الثانية هو أكبر من قيمة العتبة التي حددناها في الثوابت، إذا سوف يطبع عنوان IP في لوحة التحكم console. يحسب المتغير <code>rps</code> عدد الطلبات في الثانية عن طريق تقسيم عدد الطلبات على فارق الفاصل الزمني، ثم يحول النتيجة إلى الثانية.
</p>

<p>
	بما أننا اسندنا القيمة <code>false</code> إلى الخاصية <code>deleteOnExpire</code> في المتغير <code>IPCache</code>، علينا الآن التعامل يدويُا مع الحدث <code>expired</code>، فتوفر وحدة <code>node-cache</code> دالة استدعاء تُشَغل الحدث <code>expired</code>.
</p>

<p>
	أضف الأسطر التالية بعد المتغير <code>IPCache</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3501_11" style="">
<span class="pun">...</span><span class="pln">
</span><span class="typ">IPCache</span><span class="pun">.</span><span class="pln">on</span><span class="pun">(</span><span class="str">'expired'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">key</span><span class="pun">,</span><span class="pln"> value</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">()</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> value</span><span class="pun">[</span><span class="pln">value</span><span class="pun">.</span><span class="pln">length </span><span class="pun">-</span><span class="pln"> </span><span class="lit">1</span><span class="pun">]</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> TIME_FRAME_IN_MS</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="typ">IPCache</span><span class="pun">.</span><span class="pln">del</span><span class="pun">(</span><span class="pln">key</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">});</span><span class="pln">
</span><span class="pun">...</span></pre>

<p>
	تقبل دالة رد النداء<code>()on.</code> مفتاح <code>key</code> وقيمة <code>value</code> العنصر منتهي الصلاحية كوسطاء، وتكون <code>value</code> عبارة عن مصفوفة علامات زمنية للطلبات مخزنة في الذاكرة المؤقتة.
</p>

<p>
	يتحقق السطر المحدد الثاني من العنصر الأخير في مصفوفة القيم بمقارنته مع المتغير<code>TIME_FRAME_IN_S</code>، إذ يطرح آخر عنصر في مصفوفة القيم <code>value</code> من الدالة <code>new Date()‎</code> (الوقت الحالي)، فإذا كانت النتيجة أصغر من قيمة المتغير <code>TIME_FRAME_IN_S</code> تأخذ الدالة <code>‎.del()‎</code> المفتاح<code>key</code> كوسيط وتحذف العنصر منتهي الصلاحية من ذاكرة التخزين المؤقت.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3501_13" style="">
<span class="pun">...</span><span class="pln">
    </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">const</span><span class="pln"> updatedValue </span><span class="pun">=</span><span class="pln"> value</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="kwd">function</span><span class="pln"> </span><span class="pun">(</span><span class="pln">element</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">()</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> element </span><span class="pun">&lt;</span><span class="pln"> TIME_FRAME_IN_MS</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">});</span><span class="pln">
        </span><span class="typ">IPCache</span><span class="pun">.</span><span class="kwd">set</span><span class="pun">(</span><span class="pln">key</span><span class="pun">,</span><span class="pln"> updatedValue</span><span class="pun">,</span><span class="pln"> TIME_FRAME_IN_S </span><span class="pun">-</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">()</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> updatedValue</span><span class="pun">[</span><span class="lit">0</span><span class="pun">])</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> MS_TO_S</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">...</span></pre>

<p>
	يوفر تابع المصفوفات المحلي <code>filter()‎</code> في جافاسكريبت دالة استدعاء لترشيح العناصر الموجودة في مصفوفة العلامات الزمنية.
</p>

<p>
	يتحقق السطر الثالث من الشيفرة السابقة من نتيجة طرح العناصر من الدالة <code>new Date()‎</code> إذا كانت أصغر من المتغير <code>TIME_FRAME_IN_S</code> ويخزن النتيجة في المتغير <code>updatedValue</code> ثم تضاف العناصر التي تمت تصفيتها إلى المتغير <code>updatedValue</code>. يؤدي ذلك إلى تحديث ذاكرة التخزين المؤقت بالعناصر التي تمت تصفيتها في المتغير <code>updatedValue</code> وزمن TTL الجديد.
</p>

<p>
	تحفز قيم <code>TTL</code> التي تطابق العنصر الأول من المتغير <code>updatedValue</code> تشغيل دالة <code>‎.on('expired')‎</code>عندما تحذف الذاكرة المؤقتة العنصر التالي. وتحسب قيمة زمن TTL الجديد عن طريق حساب الفرق بين المتغير <code>TIME_FRAME_IN_S</code> والوقت منتهي الصلاحية منذ أول علامة زمنية للطلب في المتغير <code>updatedValue</code>.
</p>

<p>
	شَغّل التطبيق من واجهة الطرفية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3501_15" style="">
<span class="pln">node server</span><span class="pun">.</span><span class="pln">js</span></pre>

<p>
	انتقل إلى العنوان <code>localhost:3000</code> في متصفح الويب، ولاحظ أن المتصفح سوف يعرض الرسالة: "Successful response"، ثم جرب حَدِث الصفحة عدة مرات حتى تتجاوز قيمة المتغير <code>RPS_LIMIT</code> ولاحظ النتيجة التي ستعرضها الطرفية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3501_17" style="">
<span class="typ">Example</span><span class="pln"> app is listening on port </span><span class="lit">3000</span><span class="pln">
</span><span class="typ">You</span><span class="pln"> are hitting limit </span><span class="pun">::</span><span class="lit">1</span></pre>

<p>
	<strong>ملاحظة:</strong> يظهر عنوان localhost IP على الشكل ‎::1، لكن ذلك سيتغير، إذ يلتقط تطبيقك العنوان العام Public IP للمستخدم عند نشره خارج الخادم المحلي localhost.
</p>

<p>
	أصبح بإمكان تطبيقك تتبع طلبات المستخدم وتخزين العلامات الزمنية في ذاكرة التخزين المؤقت.
</p>

<p>
	سنتعلم في الخطوة التالية كيفية استخدام واجهة برمجة تطبيقات Cloudflare لإعداد <a href="https://academy.hsoub.com/devops/security/firewalls/%D9%85%D8%A7-%D9%87%D9%88-%D8%A7%D9%84%D8%AC%D8%AF%D8%A7%D8%B1-%D8%A7%D9%84%D9%86%D8%A7%D8%B1%D9%8A-%D9%88%D9%83%D9%8A%D9%81-%D9%8A%D8%B9%D9%85%D9%84%D8%9F-r114/" rel="">جدار الحماية Firewall</a>.
</p>

<h2>
	إعداد جدار حماية Cloudflare
</h2>

<p>
	سنتعلم في هذه الخطوة كيفية إعداد جدار حماية Cloudflare لإيقاف عناوين IP عند الوصول إلى الحد المعين، وكيفية إنشاء متغيرات البيئة environment variables، واستدعاء <a href="https://academy.hsoub.com/programming/java/%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A7%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D8%AD%D8%B2%D9%85-%D9%88%D8%A7%D9%84%D9%88%D8%AD%D8%AF%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D8%AA%D9%88%D8%AB%D9%8A%D9%82-javadoc-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1091/" rel="">واجهة برمجة تطبيقات</a> Cloudflare.
</p>

<p>
	سجل الدخول في لوحة تحكم Cloudflare، ثم اذهب إلى الصفحة الرئيسية لحسابك ومنها اضغط على علامة تبويب <strong>الإعدادات</strong> <strong>Configurations</strong>، ثم <strong>قوائم</strong> <strong>Lists</strong>. أنشئ قائمة جديدة باسم <code>your_list</code>.
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<div class="ipsQuote_contents ipsClearfix">
		<p>
			<strong>ملاحظة:</strong> يتوفر خيار القوائم <strong>Lists</strong> في لوحة تحكم الحساب على Cloudflare، وليس من صفحة لوحة تحكم النطاق.
		</p>
	</div>
</blockquote>

<p>
	افتح لوحة تحكم النطاق الخاص بك <code>your_domain</code> من الصفحة الرئيسية <strong>Home</strong>، ثم اضغط على علامة تبويب الجدار الناري <strong>Firewall</strong> واضغط على قواعد الجدار الناري <strong>Firewall Rules</strong> ثم على إنشاء قاعدة جديدة <strong>Create a Firewall rule</strong> وسَمِها باسم تختاره مثل <code>your_rule_name</code> مجازًا.
</p>

<p>
	اختر عنوان المصدر <code>IP Source Address</code> من القائمة المنسدلة، واختر <code>is in list</code> عند خيار المُشغِل <strong>Operator</strong>، و <code>your_list</code> عند خيار القيمة <strong>Value</strong>، ثم اضغط على حجب <strong>Block</strong> من القائمة المنسدلة للخيار <strong>Choose an action</strong>، واضغط بعدها على زر نشر <strong>Deploy</strong>.
</p>

<p>
	أنشئ ملفًا بصيغة <code>env.</code> في المجلد الرئيسي للمشروع وأضف عليه الأسطر التالية حتى نتمكن من استدعاء واجهة تطبيقات Cloudflare من تطبيقنا:
</p>

<pre class="ipsCode">
ACCOUNT_MAIL=your_cloudflare_login_mail
API_KEY=your_api_key
ACCOUNT_ID=your_account_id
LIST_ID=your_list_id
</pre>

<p>
	انتقل إلى قسم الملف الشخصي <strong>My Profile</strong> في لوحة تحكم Cloudflare، ثم اضغط على علامة تبويب وحدات واجهة التطبيقات <strong><abbr title="Application Programming Interface | واجهة برمجية">API</abbr> Tokens</strong> للحصول على قيمة <code>API_KEY</code>، ثم اضغط على عرض <strong>View</strong> في قسم Global <abbr title="Application Programming Interface | واجهة برمجية">API</abbr> Key واكتب كلمة المرور الخاصة بك.
</p>

<p>
	انتقل إلى علامة تبويب الإعدادات <strong>Configuration</strong> ثم إلى قسم القوائم <strong>Lists</strong>، واضغط على زر تعديل <strong>Edit</strong> بجانب القائمة <code>your_list</code> التي أنشأتها. يمكنك الحصول على معرف الحساب <code>ACCOUNT_ID</code>ومعرف القائمة <code>LIST_ID</code> من عنوان URL من المتصفح الذي يكون على الصيغة التالية:
</p>

<pre class="ipsCode">
https://dash.cloudflare.com/your_account_id/configurations/lists/your_list_id
</pre>

<p>
	<strong>تنبيه:</strong> احرص على أن يكون محتوى الملف <code>env.</code> سريًا وليس متاحًا للجميع، وذلك عن طريق إضافته ضمن ملف <code>gitignore.</code> الذي أنشأناه في الخطوة الأولى.
</p>

<p>
	ثبت حزمة <code>axios</code> و <code>dotenv</code> في الطرفية بواسطة مدير الحزم:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3501_20" style="">
<span class="pln">npm i axios dotenv</span></pre>

<p>
	أضف الأسطر التالية بعد سطر المتغير <code>nodeCache</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3501_22" style="">
<span class="pun">...</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> axios </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'axios'</span><span class="pun">);</span><span class="pln">
require</span><span class="pun">(</span><span class="str">'dotenv'</span><span class="pun">).</span><span class="pln">config</span><span class="pun">();</span><span class="pln">
</span><span class="pun">...</span></pre>

<p>
	يحضر السطر الأول وحدة <code>axios</code> من حزمة <code>axios</code> المثبتة، وسنستخدم هذه الوحدة لإجراء استدعاءات إلى واجهة برمجة تطبيقات Cloudflare. يُعِد السطر الثاني وحدة <code>dotenv</code> ويفَعّل المتغير العام <code>process.env</code> الذي سيعرف قيم ملف <code>env.</code> في ملف <code>server.js</code>.
</p>

<p>
	أضف الأسطر البرمجية التالية إلى تعليمة If الشرطية التالية <code>if (rps &gt; RPS_LIMIT)‎</code> فوق السطر:<code>console.log('You are hitting limit', clientIP)‎</code> حتى تتمكن من استدعاء واجهة برمجة تطبيقات Cloudflare:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3501_25" style="">
<span class="pun">...</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> url </span><span class="pun">=</span><span class="pln"> </span><span class="pun">`</span><span class="pln">https</span><span class="pun">:</span><span class="com">//api.cloudflare.com/client/v4/accounts/${process.env.ACCOUNT_ID}/rules/lists/${process.env.LIST_ID}/items`;</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> body </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[{</span><span class="pln"> ip</span><span class="pun">:</span><span class="pln"> clientIP</span><span class="pun">,</span><span class="pln"> comment</span><span class="pun">:</span><span class="pln"> </span><span class="str">'your_comment'</span><span class="pln"> </span><span class="pun">}];</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> headers </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="str">'X-Auth-Email'</span><span class="pun">:</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">ACCOUNT_MAIL</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'X-Auth-Key'</span><span class="pun">:</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">API_KEY</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'Content-Type'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'application/json'</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">};</span><span class="pln">
    </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        await axios</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="pln">url</span><span class="pun">,</span><span class="pln"> body</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> headers </span><span class="pun">});</span><span class="pln">
    </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="pln">error</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">error</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">...</span></pre>

<p>
	أصبح بإمكاننا الآن الاتصال بواجهة برمجة تطبيقات Cloudflare عن طريق العنوان URL لإضافة عنصر، وفي حالتنا سنضيف عنوان IP إلى القائمة <code>your_list</code>، إذ تظهر واجهة برمجة تطبيقات Cloudflare المتغيران <code>ACCOUNT_MAIL</code> و <code>API_KEY</code> في ترويسة الطلب على الشكل <code>X-Auth-Email</code> و <code>X-Auth-Key</code>. أما جسم الطلب فيتألف من عنوان IP الذي سيضاف إلى القائمة وتعليق <code>comment</code> له القيمة <code>your_comment</code>. ضع في الحسبان أنه يمكنك استبدال التعليق <code>comment</code> بالتعليق الذي يحلو لك لتمييز القيمة المدخلة.
</p>

<p>
	يوضع الطلب <code>POST</code> المستدعى على الشكل <code>()axios.post</code> ضمن الكتلة البرمجية try-catch لمعالجة الأخطاء، إن وجدت، فتحتاج الدالة <code>axios.post</code> إلى الوسطاء التالية: <code>url</code>، <code>body</code>، <code>headers</code> لإنشاء الطلب.
</p>

<p>
	يجب عليك تغيير قيمة المتغير <code>clientIP</code> عند تجريب الطلبات إلى عنوان IP تجريبي مثل: <code>198.51.100.0/24</code> لأن Cloudflare لايقبل عنوان الخادم المحلي localhost:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3501_27" style="">
<span class="pun">...</span><span class="pln">
let clientIP </span><span class="pun">=</span><span class="pln"> </span><span class="str">'198.51.100.0/24'</span><span class="pun">;</span><span class="pln">
</span><span class="pun">...</span></pre>

<p>
	شغل التطبيق من واجهة الطرفية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3501_29" style="">
<span class="pln">node server</span><span class="pun">.</span><span class="pln">js</span></pre>

<p>
	انتقل إلى العنوان <code>localhost:3000</code> في المتصفح وستلاحظ ظهور الرسالة: "Successful response".
</p>

<p>
	حَدِث الصفحة عدة مرات حتى تتجاوز قيمة المتغير <code>RPS_LIMIT</code> ولاحظ النتيجة التي ستعرضها الطرفية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3501_31" style="">
<span class="typ">Example</span><span class="pln"> app is listening on port </span><span class="lit">3000</span><span class="pln">
</span><span class="typ">You</span><span class="pln"> are hitting limit </span><span class="pun">::</span><span class="lit">1</span></pre>

<p>
	انتقل الآن إلى صفحة لوحة تحكم Cloudflare ثم إلى صفحة القوائم <code>your_list</code> حيث سيظهر عنوان IP الذي أضفناه في القائمة <code>your_list</code>، ثم ستظهر صفحة الجدار الناري بعد إرسال التغييرات إلى GitHub.
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<div class="ipsQuote_contents ipsClearfix">
		<p>
			<strong>ملاحظة:</strong> احرص على تغيير قيم المتغير <code>clientIP</code> إلى <code>(requestIP.getClientIp(req</code> قبل إرسال الشيفرة إلى GitHub.
		</p>
	</div>
</blockquote>

<p>
	انشر تطبيقك بعد حفظ التعديلات وارفع الشيفرة على GitHub، وبنا أننا فعّلنا خيار النشر التلقائي auto-deploy سيرسل الكود تلقائيًا من GitHub إلى منصة App Platform لنشره.
</p>

<p>
	ستحتاج إلى إضافة ملف <code>env.</code> إلى منصة App Platform في قسم متغيرات بيئة التطبيق <strong>App-Level Environment Variables</strong> من تبويبة الإعدادات <strong>Settings</strong>. أضف زوج مفتاح-قيمة إلى ملف <code>env.</code> كي يستطيع تطبيقك الوصول إلى محتوياته من App Platform.
</p>

<p>
	انتقل إلى نطاقك في المتصفح بعد انتهاء النشر وحَدِث الصفحة باستمرار حتى تصل إلى الحد <code>RPS_LIMIT</code>، عندها سيظهر المستعرض صفحة الجدار الناري من Cloudflare:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="90628" href="https://academy.hsoub.com/uploads/monthly_2022_01/image1.png.47299fc669680594008d816c14116304.png" rel=""><img alt="image1.png" class="ipsImage ipsImage_thumbnailed" data-fileid="90628" data-unique="0k1jf8ufz" src="https://academy.hsoub.com/uploads/monthly_2022_01/image1.thumb.png.9ec228adb6e12d94a000980a17769f38.png" style="width: 500px; height: auto;"></a>
</p>

<p>
	ستحصل على الرسالة التالية عند الانتقال إلى علامة تبويب سجلات التشغيل الحالية <strong>Runtime Logs</strong> في لوحة تحكم App Platform:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3501_33" style="">
<span class="typ">You</span><span class="pln"> are hitting limit your_public_ip</span></pre>

<p>
	يمكنك التأكد من أن الجدار الناري يحظر عنوان IP المحدد في القائمة <code>your_list</code> فقط، عن طريق الدخول إلى نطاقك <code>your_domain</code> من جهاز أخر أو عبر اتصال <abbr title="Virtual Private Network | الشبكة الخاصة الافتراضية">VPN</abbr>، ويمكنك حذف عنوان IP من لوحة تحكم Cloudflare.
</p>

<p>
	قد يتطلب الحصول على رد من الجدار الناري بضع ثوان بسبب الرد المخزن سابقُا في المتصفح.
</p>

<p>
	مبارك! لقد أعددت جدار Cloudflare الناري لحظر عناوين IP عندما يصل المستخدمون إلى حد معدل التراسل عن طريق استدعاء واجهة برمجة تطبيقات Cloudflare.
</p>

<h2>
	ختامًا
</h2>

<p>
	تعلمنا في هذا المقال كيفية إنشاء مشروع Node.js ونشره على منصة App Platform من DigitalOcean وتوجيهه للاتصال بنطاقك Cloudflare، وطبقنا إجراء حماية للنطاق من زيادة حد معدل التراسل عن طريق إعداد <a href="https://academy.hsoub.com/devops/security/firewalls/%D9%85%D8%A7-%D9%87%D9%88-%D8%A7%D9%84%D8%AC%D8%AF%D8%A7%D8%B1-%D8%A7%D9%84%D9%86%D8%A7%D8%B1%D9%8A-%D9%88%D9%83%D9%8A%D9%81-%D9%8A%D8%B9%D9%85%D9%84%D8%9F-r114/" rel="">الجدار الناري</a> في Cloudflare.
</p>

<p>
	يمكنك، فيما بعد، تعديل قاعدة الجدار الناري لإظهار تحدٍ JS Challenge أو رمز تحقق كابتشا CAPTCHA بدلاً من حظر المستخدم، اطلع على <a href="https://developers.cloudflare.com/firewall/cf-firewall-rules/actions#supported-actions" rel="external nofollow">مستندات Cloudflare</a> لمزيد من التفاصيل حول ذلك.
</p>

<p>
	وللحصول على المساعدة والدعم يمكنك إضافة سؤالك في قسم الأسئلة والأجوبة في <a href="https://academy.hsoub.com/questions/" rel="">أكاديمية حسوب</a>.
</p>

<p>
	ترجمة- وبتصرف للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-build-a-rate-limiter-with-node-js-on-app-platform" rel="external nofollow">How To Build a Rate Limiter With Node.js on App Platform</a> لصاحبه Abel Mathew
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%A8%D9%86%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-nodejs-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-docker-r809/" rel="">بناء تطبيق Node.js باستخدام Docker</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%AF%D9%85%D8%AC-%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-mongodb-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D9%83-node-r810/" rel="">دمج قاعدة البيانات MongoDB في تطبيقك Node</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85%D9%8A%D9%86-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-nodejs-%D8%A8%D8%A7%D8%B3%D8%AA%D8%B9%D9%85%D8%A7%D9%84-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-mongodb-%D9%88%D9%85%D9%83%D8%AA%D8%A8%D8%A9-mongoose-r1134/" rel="">إدارة المستخدمين في تطبيق Node.js باستعمال قواعد بيانات MongoDB ومكتبة Mongoose</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%AD%D9%81%D8%B8-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-nodejs-%D9%81%D9%8A-%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-mongodb-r1101/" rel="">حفظ بيانات تطبيقات Node.js في قاعدة بيانات MongoDB</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D8%AE%D9%84%D9%81%D9%8A%D8%A9-%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-nodejs-%D8%B9%D8%A8%D8%B1-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-jest-r1133/" rel="">اختبار الواجهة الخلفية لتطبيقات Node.js عبر مكتبة Jest</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1450</guid><pubDate>Sat, 29 Jan 2022 07:27:01 +0000</pubDate></item><item><title>&#x645;&#x634;&#x631;&#x648;&#x639; &#x628;&#x646;&#x627;&#x621; &#x645;&#x648;&#x642;&#x639; &#x644;&#x645;&#x634;&#x627;&#x631;&#x643;&#x629; &#x627;&#x644;&#x645;&#x647;&#x627;&#x631;&#x627;&#x62A; &#x628;&#x627;&#x633;&#x62A;&#x639;&#x645;&#x627;&#x644; Node.js</title><link>https://academy.hsoub.com/programming/javascript/nodejs/%D9%85%D8%B4%D8%B1%D9%88%D8%B9-%D8%A8%D9%86%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%84%D9%85%D8%B4%D8%A7%D8%B1%D9%83%D8%A9-%D8%A7%D9%84%D9%85%D9%87%D8%A7%D8%B1%D8%A7%D8%AA-%D8%A8%D8%A7%D8%B3%D8%AA%D8%B9%D9%85%D8%A7%D9%84-nodejs-r1403/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2021_12/Node-01-01.jpg.63ee49dfdb393b18f24e1ba072023fea.jpg" /></p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		لا أعلم بعد النبوة أفضل من بث العلم.
	</p>

	<p>
		ـــ عبد الله بن المبارك.
	</p>
</blockquote>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="85700" href="https://academy.hsoub.com/uploads/monthly_2021_12/chapter_picture_21.jpg.836b036df73fc459bcbd62a8eb47b0b3.jpg" rel=""><img alt="chapter_picture_21.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="85700" data-unique="dr511lxl4" src="https://academy.hsoub.com/uploads/monthly_2021_12/chapter_picture_21.jpg.836b036df73fc459bcbd62a8eb47b0b3.jpg"></a>
</p>

<p>
	يوجد ما يسمى بفعاليات مشاركة المهارات، حيث يتحدث الناس في كلمات موجزة غير رسمية عما يفعلونه لينفعوا غيرهم به، فإذا كانت الفعالية حول مشاركة مهارات الزراعة مثلًا فربما يتحدث أحدهم عن زراعة الكرفس، أو إذا كنا في مجموعة برمجية فربما تخبر الناس عن <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-nodejs-%D9%88express-r1099/" rel="">Node.js</a>، كما تسمى مثل تلك الاجتماعات بمجموعات المستخدِمين إذا كانت تتعلق بالحوسبة والتقنية، وهي طريقة فعالة لتوسيع الأفق ومعرفة جديد التطورات، أو التعرف على أشخاص جدد لهم الاهتمامات نفسها، وسيكون هدفنا في هذا المقال الأخير إعداد موقع لإدارة الكلمات المقدمة في اجتماع لمشاركة المهارات.
</p>

<p>
	لنتخيل مجموعةً صغيرةً من الناس تجتمع بانتظام في مكتب أحد أعضائها للحديث عن ركوب الدراجات ذات العجلة الواحدة مثلًا، وقد انتقل من كان ينظم تلك الاجتماعات إلى مدينة أخرى ولم يشغل أحد مكانه، لذا نريد هنا إنشاء نظام يسمح للمشاركين بطلب الحديث ومناقشة الكلمات بين بعضهم بعضًا دون منظِّم مركزي لهم، كما ستكون بعض الشيفرة التي سنكتبها في هذا المقال موجهةً لبيئة Node.js كما فعلنا في <a href="https://academy.hsoub.com/programming/javascript/%D8%A8%D9%8A%D8%A6%D8%A9-nodejs-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-%D8%AE%D8%A7%D8%B1%D8%AC-%D8%A7%D9%84%D9%85%D8%AA%D8%B5%D9%81%D8%AD-r1402/" rel="">المقال السابق</a>، فلن تعمل مباشرةً في صفحة HTML العادية، ويمكن تحميل الشيفرة الكاملة للمشروع من <a href="https://eloquentjavascript.net/code/skillsharing.zip" rel="external nofollow"> ملف zip</a>.
</p>

<h2>
	التصميم
</h2>

<p>
	سيحتوي هذا المشروع على جزء يعمل على الخادم مكتوب لبيئة Node وجزء للعميل مكتوب من أجل المتصفح، ويخزن الخادم بيانات النظام ويعطيها إلى العميل، كما يخدِّم الملفات التي تستخدِم النظام الخاص بجانب العميل، حيث يحتفظ الخادم بقائمة من الكلمات المقترحة للاجتماع التالي ويعرض العميل تلك القائمة، ويكون لكل كلمة اسم مقدِّمها وعنوانها وملخصها ومصفوفة من التعليقات المرتبطة بها، كما يسمح العميل للمستخدِمين باقتراح كلمات جديدة -أي إضافتها إلى القائمة- وحذف الكلمات والتعليق أيضًا على الكلمات الموجودة، فكلما نفّذ المستخدِم شيئًا من هؤلاء فسينشئ العميل طلب HTTP ليخبر الخادم بذلك.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="85701" href="https://academy.hsoub.com/uploads/monthly_2021_12/skillsharing.png.1b052bd24d510981c2a85488e7b92586.png" rel=""><img alt="skillsharing.png" class="ipsImage ipsImage_thumbnailed" data-fileid="85701" data-unique="0fxi40q98" src="https://academy.hsoub.com/uploads/monthly_2021_12/skillsharing.png.1b052bd24d510981c2a85488e7b92586.png"></a>
</p>

<p>
	يهيَّأ التطبيق ليعرض الكلمات المقترحة وتعليقاتها عرضًا حيًا، وكلما أرسل أحد كلمةً جديدةً في مكان ما أو أضاف تعليقًا فيجب على كل من تكون الصفحة مفتوحة عنده رؤية ذلك الحدث، وهنا محل التحدي إذ لا توجد طريقة يفتح بها الخادم اتصالًا مع عميل ولا توجد طريقة مناسبة لنعرف مَن من العملاء ينظرون الآن إلى الموقع، ويسمى حل تلك المشكلة <a href="https://academy.hsoub.com/programming/javascript/%D8%A2%D9%84%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D8%A7%D8%AA%D8%B5%D8%A7%D9%84-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-%D9%85%D8%B9-%D8%A7%D9%84%D8%AE%D8%A7%D8%AF%D9%85-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1301/" rel="">بالاستطلاع المفتوح long polling</a> وهو أحد بواعث تصميم بيئة Node من البداية.
</p>

<h2>
	الاستطلاع المفتوح
</h2>

<p>
	نحتاج إلى اتصال بين العميل والخادم كي يستطيع الخادم إخبار العميل مباشرةً بتغير شيء ما، لكن لا تقبل متصفحات الويب الاتصالات عادةً، كما أنّ موجِّهات الانترنت routers تحجب عادةً مثل تلك الاتصالات عن العملاء، لذا لن نستطيع جعل الخادم يبدأ ذلك الاتصال، لكن نستطيع تهيئة الأمر كي يفتح العميل الاتصال ويحتفظ به لفترة كي يستطيع الخادم استخدامه من أجل إرسال معلومات عند الحاجة، غير أنّ طلب <a href="https://academy.hsoub.com/programming/general/%d9%85%d8%af%d8%ae%d9%84-%d8%a5%d9%84%d9%89-http-r73/" rel="">HTTP</a> يسمح بتدفق معلومات بسيطة مثل إرسال العميل لطلب ما ورد الخادم عليه باستجابة لذلك الطلب وحسب.
</p>

<p>
	أما إذا أردنا أكثر من ذلك فثَم تقنية اسمها WebSockets تدعمها أغلب المتصفحات الحديثة وتسهل فتح الاتصالات من أجل تبادل البيانات عشوائي، غير أنها صعبة قليلًا في الاستفادة منها لحالتنا، والبديل الذي سنستخدِمه في هذا المقال سيكون تقنيةً أبسط وهي الاستطلاع المفتوح، حيث يطلب العميل من الخادم معلومات جديدة باستمرار باستخدام طلبات HTTP العادية، ويماطل الخادم في الاستجابة لتلك الطلبات إذا لم يكن ثمة شيء جديد لإبلاغه، وطالما أنّ العميل يضمن وجود طلب استطلاع وجس مفتوح دائمًا، فسيستقبل معلومات من الخادم بسرعة بعد توفرها، فإذا كان تطبيق مشاركة المهارات مفتوحًا لدى فاطمة في متصفحها، فسيكون ذلك المتصفح قد أنشأ طلبًا من أجل التحديثات وسيكون منتظرًا استجابةً لذلك الطلب، فإذا أرسلت إيمان كلمةً عن قيادة الدراجة هبوطًا على تل شديد الانحدار، فسيلاحظ الخادم انتظار فاطمة تحديثات، ويرسل استجابةً تحتوي على الكلمة الجديدة إلى طلبها المنتظِر، وسيستلم متصفح فاطمة تلك البيانات ويحدِّث الشاشة ليعرض الكلمة.
</p>

<p>
	المعتاد لمثل تلك الطلبات والاتصالات أنها تنقطع بعد مهلة محددة تسمى timeout إذا لم يكن ثمة نشاط أو رد، ولكي نمنع حدوث ذلك هنا فإنّ تقنيات الاستطلاع المفتوح تعيّن وقتًا أقصى لكل طلب، حيث يستجيب الخادم بعده ولا بد حتى لو لم يكن ثمة شيء يبلِّغه، ثم ينشئ العميل بعد ذلك طلبًا جديدًا، وإعادة التشغيل الدورية تلك تجعل التقنية أكثر ثباتًا لتسمح للعملاء بالعودة للاتصال بعد فشل مؤقت في الشبكة أو مشاكل في الخادم، وإذا كان لدينا خادمًا يستخدِم الاستطلاع المفتوح فقد يكون لديه آلاف الطلبات التي تنتظره، مما يعني أنّ اتصالات TCP مفتوحة، وهنا تأتي ميزة Node، إذ تسهِّل إدارة عدة اتصالات دون إنشاء خيط تحكم منفصل لكل اتصال منها.
</p>

<h2>
	واجهة HTTP
</h2>

<p>
	ينبغي النظر أولًا قبل تصميم الخادم أو العميل إلى النقطة التي يتلاقى فيها كل منهما، وهي واجهة HTTP التي يتواصلان من خلالها، حيث سنستخدم JSON على أساس صيغة لطلبنا وعلى أساس متن للاستجابة أيضًا، كما سنستفيد من توابع HTTP وترويساته كما في خادم الملفات من المقال السابق المشار إليه سلفًا، وبما أنّ الواجهة تتمحور حول مسار <code>‎/talks</code>، فستُستخدَم المسارات التي لا تبدأ بـ <code>‎/talks</code> لخدمة الملفات الساكنة، وهي شيفرة HTML وجافاسكربت لنظام جانب العميل، فإذا أرسلنا طلب <code>GET</code> إلى <code>/talks</code> فسيعيد مستند JSON يشبه ما يلي:
</p>

<pre class="ipsCode">
[{"title": "Unituning",
  "presenter": "Jamal",
  "summary": "Modifying your cycle for extra style",
  "comments": []}]
</pre>

<p>
	تُنشأ الكلمة الجديدة بإنشاء طلب <code>PUT</code> إلى رابط مثل <code>‎/talks/Unituning</code>، حيث يكون الجزء الذي بعد الشرطة الثانية هو عنوان الكلمة، ويجب احتواء متن طلب <code>PUT</code> على كائن JSON يستخدِم الخاصيتين <code>presenter</code> و<code>summary</code>، وبما أنّ عناوين الكلمات تحتوي على مسافات ومحارف قد لا تظهر كما يجب لها في الرابط، فيجب ترميز سلاسل العناوين النصية بدالة <code>encodeURIComponent</code> عند بناء مثل تلك الروابط.
</p>

<pre class="ipsCode">
console.log("/talks/" + encodeURIComponent("How to Idle"));
// → /talks/How%20to%20Idle
</pre>

<p>
	قد يبدو طلب إنشاء كلمة عن الوقوف بالدراجة كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_72_6" style="">
<span class="pln">PUT </span><span class="pun">/</span><span class="pln">talks</span><span class="pun">/</span><span class="typ">How</span><span class="pun">%</span><span class="lit">20to</span><span class="pun">%</span><span class="lit">20Idle</span><span class="pln"> HTTP</span><span class="pun">/</span><span class="lit">1.1</span><span class="pln">
</span><span class="typ">Content</span><span class="pun">-</span><span class="typ">Type</span><span class="pun">:</span><span class="pln"> application</span><span class="pun">/</span><span class="pln">json
</span><span class="typ">Content</span><span class="pun">-</span><span class="typ">Length</span><span class="pun">:</span><span class="pln"> </span><span class="lit">92</span><span class="pln">

</span><span class="pun">{</span><span class="str">"presenter"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Hasan"</span><span class="pun">,</span><span class="pln">
 </span><span class="str">"summary"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Standing still on a unicycle"</span><span class="pun">}</span></pre>

<p>
	تدعم مثل تلك الروابط طلبات <code>GET</code> لجلب تمثيل JSON لكلمة ما وطلبات <code>DELETE</code> لحذف الكلمة، كما تضاف التعليقات إلى الكلمة باستخدام طلب <code>POST</code> إلى رابط مثل <code>‎/talks/Unituning/comments</code> مع متن JSON يحتوي على الخاصيتين <code>author</code> و<code>message</code>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_72_8" style="">
<span class="pln">POST </span><span class="pun">/</span><span class="pln">talks</span><span class="pun">/</span><span class="typ">Unituning</span><span class="pun">/</span><span class="pln">comments HTTP</span><span class="pun">/</span><span class="lit">1.1</span><span class="pln">
</span><span class="typ">Content</span><span class="pun">-</span><span class="typ">Type</span><span class="pun">:</span><span class="pln"> application</span><span class="pun">/</span><span class="pln">json
</span><span class="typ">Content</span><span class="pun">-</span><span class="typ">Length</span><span class="pun">:</span><span class="pln"> </span><span class="lit">72</span><span class="pln">

</span><span class="pun">{</span><span class="str">"author"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Iman"</span><span class="pun">,</span><span class="pln">
 </span><span class="str">"message"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Will you talk about raising a cycle?"</span><span class="pun">}</span></pre>

<p>
	قد تحتوي طلبات <code>GET</code> إلى <code>‎/talks</code> على ترويسات إضافية تخبر الخادم بتأخير الإجابة إذا لم تتوفر معلومات جديدة، وذلك من أجل دعم الاستطلاع المفتوح، كما سنستخدِم زوجًا من الترويسات صُممتا أساسًا من أجل إدارة التخزين المؤقت وهما <code>ETag</code> و<code>If-None-Match</code>، وقد تدرِج الخوادم ترويسة <code>ETag</code> -التي تشير إلى وسم الكتلة Entity Tag- في الاستجابة، بحيث تكون قيمتها <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%B3%D9%84%D8%A7%D8%B3%D9%84-%D8%A7%D9%84%D9%86%D8%B5%D9%8A%D8%A9-strings-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r817/" rel="">سلسلةً نصيةً</a> تعرِّف الإصدار الحالي للمورِد، وقد تنشئ العملاء طلبًا إضافيًا عندما تطلب هذا المورد مرةً ثانيةً من خلال إدراج ترويسة <code>If-None-Match</code> التي تحمل قيمتها السلسلة نفسها؛ أما إذا لم يتغير المورد، فسيستجيب الخادم برمز الحالة 304 والذي يعني "غير معدَّل not modified"، ليخبر العميل أنّ إصداره المخزَّن لا زال هو الإصدار الحالي؛ أما إذا لم يطابق الوسم، فسيستجيب الاستجابة العادية.
</p>

<p>
	نحتاج إلى مثل ذلك النظام لأننا نريد تمكين العميل من إخبار الخادم بإصدار قائمة الكلمات التي لديه، وألا يستجيب الخادم إلا عند تغير تلك القائمة، لكن ينبغي على الخادم تأخير الإجابة وعدم الإعادة نهائيًا إلا عند توفر شيء جديد أو مرور مهلة زمنية محددة بدلًا من إعادة 304 مباشرةً، وعليه فمن أجل تمييز طلبات الاستطلاع المفتوح عن الطلبات الشرطية العادية، فإننا نعطيها ترويسةً أخرى هي <code>Prefer: wait=90</code> التي تخبر الخادم باستعداد العميل لانتظار الاستجابة مدةً قدرها 90 ثانية، كما سيحتفظ الخادم برقم إصدار version number يحدِّثه في كل مرة تتغير فيها كلمة ما، وسيستخدم ذلك على أساس قيمة لوسم <code>ETag</code>، ويمكن للعملاء إنشاء طلبات مثل هذا ليتم إشعارها عند حدوث تغيير في الكلمة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_72_10" style="">
<span class="pln">GET </span><span class="pun">/</span><span class="pln">talks HTTP</span><span class="pun">/</span><span class="lit">1.1</span><span class="pln">
</span><span class="typ">If</span><span class="pun">-</span><span class="typ">None</span><span class="pun">-</span><span class="typ">Match</span><span class="pun">:</span><span class="pln"> </span><span class="str">"4"</span><span class="pln">
</span><span class="typ">Prefer</span><span class="pun">:</span><span class="pln"> wait</span><span class="pun">=</span><span class="lit">90</span><span class="pln">

</span><span class="pun">(</span><span class="pln">time passes</span><span class="pun">)</span><span class="pln">

HTTP</span><span class="pun">/</span><span class="lit">1.1</span><span class="pln"> </span><span class="lit">200</span><span class="pln"> OK
</span><span class="typ">Content</span><span class="pun">-</span><span class="typ">Type</span><span class="pun">:</span><span class="pln"> application</span><span class="pun">/</span><span class="pln">json
</span><span class="typ">ETag</span><span class="pun">:</span><span class="pln"> </span><span class="str">"5"</span><span class="pln">
</span><span class="typ">Content</span><span class="pun">-</span><span class="typ">Length</span><span class="pun">:</span><span class="pln"> </span><span class="lit">295</span><span class="pln">

</span><span class="pun">[....]</span></pre>

<p>
	لا يقوم البروتوكول في حالتنا هذه بأيّ تحكم في الوصول، إذ يستطيع أيّ أحد تعليق أو تعديل الكلمات أو يحذفها، وليس من الحكمة وضع نظام مثل هذا على الويب دون حماية إضافية.
</p>

<h2>
	الخادم
</h2>

<p>
	لنبدأ ببناء جانب الخادم من البرنامج، حيث ستعمل الشيفرة في هذا القسم على <a href="https://wiki.hsoub.com/Node.js" rel="external">Node.js</a>.
</p>

<h3>
	التوجيه Routing
</h3>

<p>
	سيستخدم خادمنا <code>createServer</code> من أجل بدء خادم HTTP، ويجب علينا التفريق في الدالة التي تعالج طلبًا جديدًا بين أنواع الطلبات المختلفة التي ندعمها وفقًا للتابع والمسار، وصحيح أنه يمكن تنفيذ ذلك بسلسلة طويلة من تعليمات <code>if</code>، إلا أنّ طريقة التوجيه أفضل، فالموجّه هو مكون يساعد في إرسال طلب إلى الدالة التي تستطيع معالجته، فنستطيع إخباره أنّ طلبات <code>PUT</code> مثلًا التي يطابق مسارها التعبير النمطي <code>‎/^\/talks\/([^\/]+)$/‎</code> -يشير إلى <code>‎/talks/‎</code> متبوعًا بعنوان الكلمة-، يمكن معالجتها بدالة ما، كما يساعد على استخراج أجزاء مفيدة من المسار -عنوان الكلمة في حالتنا- مغلَّفًا بين أقواس في التعبير النمطي ثم يمررها إلى الدالة المعالجة.
</p>

<p>
	هناك عدة حزم موجهات جيدة على <a href="https://academy.hsoub.com/programming/javascript/%D9%81%D9%8A%D8%AF%D9%8A%D9%88-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D8%AF%D9%8A%D8%B1-%D8%A7%D9%84%D8%AD%D8%B2%D9%85-npm-r1225/" rel="">NPM</a>، لكننا سنكتب واحدةً بأنفسنا لتوضيح الفكرة، وتوضِّح الشيفرة التالية <code>router.js</code> الذي سنطلبه من وحدة الخادم الخاص بنا عن طريق <code>require</code> لاحقًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_72_12" style="">
<span class="kwd">const</span><span class="pln"> </span><span class="pun">{</span><span class="pln">parse</span><span class="pun">}</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"url"</span><span class="pun">);</span><span class="pln">

module</span><span class="pun">.</span><span class="pln">exports </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Router</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  constructor</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">routes </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[];</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  add</span><span class="pun">(</span><span class="pln">method</span><span class="pun">,</span><span class="pln"> url</span><span class="pun">,</span><span class="pln"> handler</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">routes</span><span class="pun">.</span><span class="pln">push</span><span class="pun">({</span><span class="pln">method</span><span class="pun">,</span><span class="pln"> url</span><span class="pun">,</span><span class="pln"> handler</span><span class="pun">});</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  resolve</span><span class="pun">(</span><span class="pln">context</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let path </span><span class="pun">=</span><span class="pln"> parse</span><span class="pun">(</span><span class="pln">request</span><span class="pun">.</span><span class="pln">url</span><span class="pun">).</span><span class="pln">pathname</span><span class="pun">;</span><span class="pln">

    </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let </span><span class="pun">{</span><span class="pln">method</span><span class="pun">,</span><span class="pln"> url</span><span class="pun">,</span><span class="pln"> handler</span><span class="pun">}</span><span class="pln"> of </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">routes</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      let match </span><span class="pun">=</span><span class="pln"> url</span><span class="pun">.</span><span class="pln">exec</span><span class="pun">(</span><span class="pln">path</span><span class="pun">);</span><span class="pln">
      </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">match </span><span class="pun">||</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">method </span><span class="pun">!=</span><span class="pln"> method</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">continue</span><span class="pun">;</span><span class="pln">
      let urlParts </span><span class="pun">=</span><span class="pln"> match</span><span class="pun">.</span><span class="pln">slice</span><span class="pun">(</span><span class="lit">1</span><span class="pun">).</span><span class="pln">map</span><span class="pun">(</span><span class="pln">decodeURIComponent</span><span class="pun">);</span><span class="pln">
      </span><span class="kwd">return</span><span class="pln"> handler</span><span class="pun">(</span><span class="pln">context</span><span class="pun">,</span><span class="pln"> </span><span class="pun">...</span><span class="pln">urlParts</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	تصدِّر الوحدة صنف <code>Router</code>، كما يسمح كائن الموجّه بتسجيل معالِجات جديدة باستخدام التابع <code>add</code>، ويمكن حل الطلبات باستخدام التابع <code>resolve</code> الخاص به، حيث سيعيد هذا التابع استجابةً عند العثور على معالج، وإذا لم يعثر فسيعيد قيمةً غير معرَّفة <code>null</code>، ويجرب طريقًا واحدًا في كل مرة بالترتيب الذي عرِّفَت به تلك الطرق إلى أن يعثر على تطابق، كما تُستدعَى الدوال المعالجة بقيمة <code>context</code> التي ستكون نسخة الخادم في حالتنا وسلاسل المطابقة لأيّ مجموعة تعرّفها في تعبيرنا النمطي وكائن الطلب، كما يجب فك تشفير روابط السلاسل النصية بما أنّ الرابط الخام قد يحتوي على رموز من تنسيق <code>‎%20</code>.
</p>

<h3>
	تقديم الملفات
</h3>

<p>
	إذا لم يطابق الطلب أي نوع معرّف في موجهنا فيجب على الخادم تفسير ذلك على أنه طلب لملف في مجلد <code>public</code>، ومن الممكن هنا استخدام خادم الملفات المعرَّف في المقال السابق لتقديم مثل تلك الملفات، لكننا لا نحتاج ولا نريد دعم طلبات <code>PUT</code> أو <code>DELETE</code> على الملفات، ونرغب أن يكون لدينا ميزات مثل دعم التخزين، وعليه فسنستخدم خادم ملفات ساكنة مجرَّبًا من NPM وليكن <code>ecstatic</code> مثلًا، رغم أنه ليس الوحيد على NPM ولكنه يعمل جيدًا ومناسب لأغراضنا.
</p>

<p>
	تصدِّر حزمة <code>ecstatic</code> دالةً يمكن استدعاؤها مع كائن تهيئة configuration object لإنتاج دالة معالجة طلبات، وسنستخدِم الخيار <code>root</code> لنخبر الخادم بالمكان الذي يجب أعليه البحث فيه عن الملفات، كما تقبل الدالة المعالِجة المعاملَين <code>request</code> و<code>response</code> ويمكن تمريرهما مباشرةً إلى <code>createServer</code> لإنشاء خادم لا يقدم لنا إلا الملفات فقط، كما نريد التحقق أولًا من الطلبات التي يجب معالجتها معالجةً خاصةً، لذا نغلفها في دالة أخرى.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_72_14" style="">
<span class="kwd">const</span><span class="pln"> </span><span class="pun">{</span><span class="pln">createServer</span><span class="pun">}</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"http"</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">Router</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"./router"</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> ecstatic </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"ecstatic"</span><span class="pun">);</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> router </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Router</span><span class="pun">();</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> defaultHeaders </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="str">"Content-Type"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"text/plain"</span><span class="pun">};</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">SkillShareServer</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  constructor</span><span class="pun">(</span><span class="pln">talks</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">talks </span><span class="pun">=</span><span class="pln"> talks</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">version </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">waiting </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[];</span><span class="pln">

    let fileServer </span><span class="pun">=</span><span class="pln"> ecstatic</span><span class="pun">({</span><span class="pln">root</span><span class="pun">:</span><span class="pln"> </span><span class="str">"./public"</span><span class="pun">});</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">server </span><span class="pun">=</span><span class="pln"> createServer</span><span class="pun">((</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      let resolved </span><span class="pun">=</span><span class="pln"> router</span><span class="pun">.</span><span class="pln">resolve</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">);</span><span class="pln">
      </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">resolved</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        resolved</span><span class="pun">.</span><span class="kwd">catch</span><span class="pun">(</span><span class="pln">error </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
          </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">error</span><span class="pun">.</span><span class="pln">status </span><span class="pun">!=</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">return</span><span class="pln"> error</span><span class="pun">;</span><span class="pln">
          </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">{</span><span class="pln">body</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</span><span class="pun">(</span><span class="pln">error</span><span class="pun">),</span><span class="pln"> status</span><span class="pun">:</span><span class="pln"> </span><span class="lit">500</span><span class="pun">};</span><span class="pln">
        </span><span class="pun">}).</span><span class="pln">then</span><span class="pun">(({</span><span class="pln">body</span><span class="pun">,</span><span class="pln">
                  status </span><span class="pun">=</span><span class="pln"> </span><span class="lit">200</span><span class="pun">,</span><span class="pln">
                  headers </span><span class="pun">=</span><span class="pln"> defaultHeaders</span><span class="pun">})</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
          response</span><span class="pun">.</span><span class="pln">writeHead</span><span class="pun">(</span><span class="pln">status</span><span class="pun">,</span><span class="pln"> headers</span><span class="pun">);</span><span class="pln">
          response</span><span class="pun">.</span><span class="pln">end</span><span class="pun">(</span><span class="pln">body</span><span class="pun">);</span><span class="pln">
        </span><span class="pun">});</span><span class="pln">
      </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        fileServer</span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">);</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">});</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  start</span><span class="pun">(</span><span class="pln">port</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">server</span><span class="pun">.</span><span class="pln">listen</span><span class="pun">(</span><span class="pln">port</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  stop</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">server</span><span class="pun">.</span><span class="pln">close</span><span class="pun">();</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	نستخدِم هنا طريقةً للاستجابات تشبه خادم الملفات الذي رأيناه في المقال السابق، إذ تعيد المعالِجات وعودًا تُحل إلى كائنات تصف الاستجابة، وتغلِّف الخادم في كائن يحمل حالته كذلك.
</p>

<h3>
	الكلمات على أساس موارد
</h3>

<p>
	تخزَّن الكلمات المقترحة في الخاصية <code>talks</code> للخادم، وهو كائن تكون أسماء خصائصه عناوين الكلمات، كما ستُكشف على أساس موارد HTTP تحت <code>‎/talks/[title]‎</code>، لذا نحتاج إلى إضافة معالجات إلى الموجه الخاص بنا تستخدِم التوابع المختلفة التي تستطيع العملاء استخدامها كي تعمل معها، كما يجب على معالج طلبات <code>GET</code> التي تطلب كلمة بعينها البحث عن تلك الكلمة، ويستجيب ببيانات JSON لها أو باستجابة خطأ 404.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_72_16" style="">
<span class="kwd">const</span><span class="pln"> talkPath </span><span class="pun">=</span><span class="pln"> </span><span class="str">/^\/talks\/([^\/]+)$/</span><span class="pun">;</span><span class="pln">

router</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="str">"GET"</span><span class="pun">,</span><span class="pln"> talkPath</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">server</span><span class="pun">,</span><span class="pln"> title</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">title in server</span><span class="pun">.</span><span class="pln">talks</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">{</span><span class="pln">body</span><span class="pun">:</span><span class="pln"> JSON</span><span class="pun">.</span><span class="pln">stringify</span><span class="pun">(</span><span class="pln">server</span><span class="pun">.</span><span class="pln">talks</span><span class="pun">[</span><span class="pln">title</span><span class="pun">]),</span><span class="pln">
            headers</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="str">"Content-Type"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"application/json"</span><span class="pun">}};</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">{</span><span class="pln">status</span><span class="pun">:</span><span class="pln"> </span><span class="lit">404</span><span class="pun">,</span><span class="pln"> body</span><span class="pun">:</span><span class="pln"> </span><span class="pun">`</span><span class="typ">No</span><span class="pln"> talk </span><span class="str">'${title}'</span><span class="pln"> found</span><span class="pun">`};</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	تُحذَف الكلمة بحذفها من الكائن <code>talks</code>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_72_18" style="">
<span class="pln">router</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="str">"DELETE"</span><span class="pun">,</span><span class="pln"> talkPath</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">server</span><span class="pun">,</span><span class="pln"> title</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">title in server</span><span class="pun">.</span><span class="pln">talks</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">delete</span><span class="pln"> server</span><span class="pun">.</span><span class="pln">talks</span><span class="pun">[</span><span class="pln">title</span><span class="pun">];</span><span class="pln">
    server</span><span class="pun">.</span><span class="pln">updated</span><span class="pun">();</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">{</span><span class="pln">status</span><span class="pun">:</span><span class="pln"> </span><span class="lit">204</span><span class="pun">};</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	يرسل التابع <code>updated</code> -الذي سنعرِّفه لاحقًا- إشعارات إلى طلبات الاستطلاع المفتوح المنتظرة بشأن التغيير، ولجلب محتوى متن الطلب فإننا نعرِّف دالةً تدعى <code>readStream</code> تقرأ كل المحتوى من بث قابل للقراءة وتعيد وعدًا يُحل إلى سلسلة نصية.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_72_20" style="">
<span class="kwd">function</span><span class="pln"> readStream</span><span class="pun">(</span><span class="pln">stream</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Promise</span><span class="pun">((</span><span class="pln">resolve</span><span class="pun">,</span><span class="pln"> reject</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let data </span><span class="pun">=</span><span class="pln"> </span><span class="str">""</span><span class="pun">;</span><span class="pln">
    stream</span><span class="pun">.</span><span class="pln">on</span><span class="pun">(</span><span class="str">"error"</span><span class="pun">,</span><span class="pln"> reject</span><span class="pun">);</span><span class="pln">
    stream</span><span class="pun">.</span><span class="pln">on</span><span class="pun">(</span><span class="str">"data"</span><span class="pun">,</span><span class="pln"> chunk </span><span class="pun">=&gt;</span><span class="pln"> data </span><span class="pun">+=</span><span class="pln"> chunk</span><span class="pun">.</span><span class="pln">toString</span><span class="pun">());</span><span class="pln">
    stream</span><span class="pun">.</span><span class="pln">on</span><span class="pun">(</span><span class="str">"end"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> resolve</span><span class="pun">(</span><span class="pln">data</span><span class="pun">));</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	أحد المعالِجات التي تحتاج إلى قراءة متون الطلبات هو <code>PUT</code> المستخدَم في إنشاء كلمات جديدة، ويجب عليه التحقق من إذا كانت البيانات المعطاة لها الخصائص <code>presenter</code> و<code>summary</code> والتي تكون سلاسل نصية، فقد تكون أيّ بيانات قادمة من خارج النظام غير منطقية، ولا نريد إفساد نموذج بياناتنا الداخلية أو تعطيله إذا أتت طلبات سيئة bad requests، وإذا بدت البيانات صالحةً، فسيخزِّن المعالِج كائنًا يمثِّل الكلمة الجديدة في كائن <code>talks</code>، وهذا سيكتب فوق كلمة موجودة سلفًا في العنوان نفسه ويستدعي <code>updated</code> مرةً أخرى.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_72_22" style="">
<span class="pln">router</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="str">"PUT"</span><span class="pun">,</span><span class="pln"> talkPath</span><span class="pun">,</span><span class="pln">
           async </span><span class="pun">(</span><span class="pln">server</span><span class="pun">,</span><span class="pln"> title</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let requestBody </span><span class="pun">=</span><span class="pln"> await readStream</span><span class="pun">(</span><span class="pln">request</span><span class="pun">);</span><span class="pln">
  let talk</span><span class="pun">;</span><span class="pln">
  </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> talk </span><span class="pun">=</span><span class="pln"> JSON</span><span class="pun">.</span><span class="pln">parse</span><span class="pun">(</span><span class="pln">requestBody</span><span class="pun">);</span><span class="pln"> </span><span class="pun">}</span><span class="pln">
  </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="pln">_</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">{</span><span class="pln">status</span><span class="pun">:</span><span class="pln"> </span><span class="lit">400</span><span class="pun">,</span><span class="pln"> body</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Invalid JSON"</span><span class="pun">};</span><span class="pln"> </span><span class="pun">}</span><span class="pln">

  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">talk </span><span class="pun">||</span><span class="pln">
      </span><span class="kwd">typeof</span><span class="pln"> talk</span><span class="pun">.</span><span class="pln">presenter </span><span class="pun">!=</span><span class="pln"> </span><span class="str">"string"</span><span class="pln"> </span><span class="pun">||</span><span class="pln">
      </span><span class="kwd">typeof</span><span class="pln"> talk</span><span class="pun">.</span><span class="pln">summary </span><span class="pun">!=</span><span class="pln"> </span><span class="str">"string"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">{</span><span class="pln">status</span><span class="pun">:</span><span class="pln"> </span><span class="lit">400</span><span class="pun">,</span><span class="pln"> body</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Bad talk data"</span><span class="pun">};</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  server</span><span class="pun">.</span><span class="pln">talks</span><span class="pun">[</span><span class="pln">title</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">title</span><span class="pun">,</span><span class="pln">
                         presenter</span><span class="pun">:</span><span class="pln"> talk</span><span class="pun">.</span><span class="pln">presenter</span><span class="pun">,</span><span class="pln">
                         summary</span><span class="pun">:</span><span class="pln"> talk</span><span class="pun">.</span><span class="pln">summary</span><span class="pun">,</span><span class="pln">
                         comments</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[]};</span><span class="pln">
  server</span><span class="pun">.</span><span class="pln">updated</span><span class="pun">();</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">{</span><span class="pln">status</span><span class="pun">:</span><span class="pln"> </span><span class="lit">204</span><span class="pun">};</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	تعمل إضافة تعليق إلى كلمة ما بصورة مشابهة، إذ نستخدِم <code>readStream</code> لنحصل على محتوى الطلب ونتحقق من البيانات الناتجة ونخزِّنها على هيئة تعليق إذا كانت صالحةً.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_72_24" style="">
<span class="pln">router</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="str">"POST"</span><span class="pun">,</span><span class="pln"> </span><span class="str">/^\/talks\/([^\/]+)\/comments$/</span><span class="pun">,</span><span class="pln">
           async </span><span class="pun">(</span><span class="pln">server</span><span class="pun">,</span><span class="pln"> title</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let requestBody </span><span class="pun">=</span><span class="pln"> await readStream</span><span class="pun">(</span><span class="pln">request</span><span class="pun">);</span><span class="pln">
  let comment</span><span class="pun">;</span><span class="pln">
  </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> comment </span><span class="pun">=</span><span class="pln"> JSON</span><span class="pun">.</span><span class="pln">parse</span><span class="pun">(</span><span class="pln">requestBody</span><span class="pun">);</span><span class="pln"> </span><span class="pun">}</span><span class="pln">
  </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="pln">_</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">{</span><span class="pln">status</span><span class="pun">:</span><span class="pln"> </span><span class="lit">400</span><span class="pun">,</span><span class="pln"> body</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Invalid JSON"</span><span class="pun">};</span><span class="pln"> </span><span class="pun">}</span><span class="pln">

  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">comment </span><span class="pun">||</span><span class="pln">
      </span><span class="kwd">typeof</span><span class="pln"> comment</span><span class="pun">.</span><span class="pln">author </span><span class="pun">!=</span><span class="pln"> </span><span class="str">"string"</span><span class="pln"> </span><span class="pun">||</span><span class="pln">
      </span><span class="kwd">typeof</span><span class="pln"> comment</span><span class="pun">.</span><span class="pln">message </span><span class="pun">!=</span><span class="pln"> </span><span class="str">"string"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">{</span><span class="pln">status</span><span class="pun">:</span><span class="pln"> </span><span class="lit">400</span><span class="pun">,</span><span class="pln"> body</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Bad comment data"</span><span class="pun">};</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">title in server</span><span class="pun">.</span><span class="pln">talks</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    server</span><span class="pun">.</span><span class="pln">talks</span><span class="pun">[</span><span class="pln">title</span><span class="pun">].</span><span class="pln">comments</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">comment</span><span class="pun">);</span><span class="pln">
    server</span><span class="pun">.</span><span class="pln">updated</span><span class="pun">();</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">{</span><span class="pln">status</span><span class="pun">:</span><span class="pln"> </span><span class="lit">204</span><span class="pun">};</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">{</span><span class="pln">status</span><span class="pun">:</span><span class="pln"> </span><span class="lit">404</span><span class="pun">,</span><span class="pln"> body</span><span class="pun">:</span><span class="pln"> </span><span class="pun">`</span><span class="typ">No</span><span class="pln"> talk </span><span class="str">'${title}'</span><span class="pln"> found</span><span class="pun">`};</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	إذا حاولنا إضافة تعليق إلى كلمة غير موجودة فسنحصل على الخطأ 404.
</p>

<h3>
	دعم الاستطلاع المفتوح
</h3>

<p>
	يُعَدّ الجزء المتعلق بمعالجة الاستطلاع المفتوح في هذا الخادم أمرًا مثيرًا، فقد يكون الطلب <code>GET</code> الآتي إلى <code>‎/talks</code> طلبًا عاديًا أو طلب استطلاع مفتوح، وسيكون لدينا أماكن عدة يجب علينا فيها إرسال مصفوفة من الكلمات talks إلى العميل، لذا سنعرِّف تابعًا مساعدًا يبني مثل تلك المصفوفة ويدرِج ترويسة <code>ETag</code> في الاستجابة.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_72_26" style="">
<span class="typ">SkillShareServer</span><span class="pun">.</span><span class="pln">prototype</span><span class="pun">.</span><span class="pln">talkResponse </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let talks </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[];</span><span class="pln">
  </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let title of </span><span class="typ">Object</span><span class="pun">.</span><span class="pln">keys</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">talks</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    talks</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">talks</span><span class="pun">[</span><span class="pln">title</span><span class="pun">]);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    body</span><span class="pun">:</span><span class="pln"> JSON</span><span class="pun">.</span><span class="pln">stringify</span><span class="pun">(</span><span class="pln">talks</span><span class="pun">),</span><span class="pln">
    headers</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="str">"Content-Type"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"application/json"</span><span class="pun">,</span><span class="pln">
              </span><span class="str">"ETag"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">`</span><span class="str">"${this.version}"</span><span class="pun">`,</span><span class="pln">
              </span><span class="str">"Cache-Control"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"no-store"</span><span class="pun">}</span><span class="pln">
  </span><span class="pun">};</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	يجب على المعالج النظر في ترويسات الطلب ليرى إذا كانت الترويستان <code>If-None-Match</code> و<code>Prefer</code> موجودتين أم لا، كما تخزِّن Node الترويسات التي تكون أسماؤها حساسةً لحالة الأحرف بأسماء ذات أحرف صغيرة.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_72_28" style="">
<span class="pln">router</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="str">"GET"</span><span class="pun">,</span><span class="pln"> </span><span class="str">/^\/talks$/</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">server</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let tag </span><span class="pun">=</span><span class="pln"> </span><span class="str">/"(.*)"/</span><span class="pun">.</span><span class="pln">exec</span><span class="pun">(</span><span class="pln">request</span><span class="pun">.</span><span class="pln">headers</span><span class="pun">[</span><span class="str">"if-none-match"</span><span class="pun">]);</span><span class="pln">
  let wait </span><span class="pun">=</span><span class="pln"> </span><span class="str">/\bwait=(\d+)/</span><span class="pun">.</span><span class="pln">exec</span><span class="pun">(</span><span class="pln">request</span><span class="pun">.</span><span class="pln">headers</span><span class="pun">[</span><span class="str">"prefer"</span><span class="pun">]);</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">tag </span><span class="pun">||</span><span class="pln"> tag</span><span class="pun">[</span><span class="lit">1</span><span class="pun">]</span><span class="pln"> </span><span class="pun">!=</span><span class="pln"> server</span><span class="pun">.</span><span class="pln">version</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> server</span><span class="pun">.</span><span class="pln">talkResponse</span><span class="pun">();</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">wait</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">{</span><span class="pln">status</span><span class="pun">:</span><span class="pln"> </span><span class="lit">304</span><span class="pun">};</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> server</span><span class="pun">.</span><span class="pln">waitForChanges</span><span class="pun">(</span><span class="typ">Number</span><span class="pun">(</span><span class="pln">wait</span><span class="pun">[</span><span class="lit">1</span><span class="pun">]));</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	إذا لم يُعط أيّ وسم أو كان الوسم المعطى لا يطابق إصدار الخادم الحالي، فسيستجيب المعالج بقائمة من الكلمات، وإذا كان الطلب شرطيًا ولم تتغير الكلمات، فسننظر في الترويسة <code>Prefer</code> لنرى إذا كان يجب علينا تأخير الاستجابة أم نستجيب فورًا، كما تخزَّن دوال رد النداء للطلبات المؤجلة في مصفوفة <code>waiting</code> الخاصة بالخادم كي يستطيع إشعارها عند حدوث شيء ما، ويضبط التابع <code>waitForChanges</code> مؤقتًا على الفور للاستجابة برمز الحالة 304 إذا انتظر الطلب لفترة طويلة.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_72_30" style="">
<span class="typ">SkillShareServer</span><span class="pun">.</span><span class="pln">prototype</span><span class="pun">.</span><span class="pln">waitForChanges </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">time</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Promise</span><span class="pun">(</span><span class="pln">resolve </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">waiting</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">resolve</span><span class="pun">);</span><span class="pln">
    setTimeout</span><span class="pun">(()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">waiting</span><span class="pun">.</span><span class="pln">includes</span><span class="pun">(</span><span class="pln">resolve</span><span class="pun">))</span><span class="pln"> </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
      </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">waiting </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">waiting</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="pln">r </span><span class="pun">=&gt;</span><span class="pln"> r </span><span class="pun">!=</span><span class="pln"> resolve</span><span class="pun">);</span><span class="pln">
      resolve</span><span class="pun">({</span><span class="pln">status</span><span class="pun">:</span><span class="pln"> </span><span class="lit">304</span><span class="pun">});</span><span class="pln">
    </span><span class="pun">},</span><span class="pln"> time </span><span class="pun">*</span><span class="pln"> </span><span class="lit">1000</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	يزيد تسجيل التغيير بالتابع <code>updated</code> قيمة الإصدار التي هي قيمة الخاصية <code>version</code> ويوقظ جميع الطلبات المنتظِرة.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_72_32" style="">
<span class="typ">SkillShareServer</span><span class="pun">.</span><span class="pln">prototype</span><span class="pun">.</span><span class="pln">updated </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">version</span><span class="pun">++;</span><span class="pln">
  let response </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">talkResponse</span><span class="pun">();</span><span class="pln">
  </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">waiting</span><span class="pun">.</span><span class="pln">forEach</span><span class="pun">(</span><span class="pln">resolve </span><span class="pun">=&gt;</span><span class="pln"> resolve</span><span class="pun">(</span><span class="pln">response</span><span class="pun">));</span><span class="pln">
  </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">waiting </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[];</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	هكذا تكون شيفرة الخادم قد تمت، فإذا أنشأنا نسخةً من <code>SkillShareServer</code> وبدأناها عند المنفَذ 8000، فسيخدم خادم HTTP الناتج الملفات من المجلد الفرعي <code>public</code> مع واجهة لإدارة الكلمات تحت رابط <code>‎/talks</code>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_72_34" style="">
<span class="kwd">new</span><span class="pln"> </span><span class="typ">SkillShareServer</span><span class="pun">(</span><span class="typ">Object</span><span class="pun">.</span><span class="pln">create</span><span class="pun">(</span><span class="kwd">null</span><span class="pun">)).</span><span class="pln">start</span><span class="pun">(</span><span class="lit">8000</span><span class="pun">);</span></pre>

<h2>
	العميل
</h2>

<p>
	يتكون جانب العميل من موقع لمشاركة المهارات من ثلاثة ملفات هي صفحة <a href="https://wiki.hsoub.com/HTML" rel="external">HTML</a> صغيرة وورقة تنسيقات style sheet وملف جافاسكربت.
</p>

<h3>
	HTML
</h3>

<p>
	يُعَدّ تقديم ملف اسمه <code>index.html</code> إحدى الطرق المستخدَمة بكثرة في خوادم الويب عند إنشاء طلب مباشرة إلى مسار موافق لمجلد ما، وتدعم وحدة خادم الملفات التي نستخدمها <code>exstatic</code> تلك الطريقة، فإذا أنشئ طلب إلى المسار <code>/</code> فسيبحث الخادم عن الملف <code>‎./public/index.html</code>، حيث يكون <code>‎./public</code> الجذر الذي أعطيناه إليه، ثم يعيد ذلك الملف إذا وجده، وعلى ذلك فإذا أردنا لصفحة أن تظهر عندما يوجَّه متصفح ما إلى خادمنا، فيجب علينا وضعها في <code>public/index.html</code>، حيث يكون ملف index الخاص بنا كما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_72_36" style="">
<span class="dec">&lt;!doctype html&gt;</span><span class="pln">
</span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">charset</span><span class="pun">=</span><span class="atv">"utf-8"</span><span class="tag">&gt;</span><span class="pln">
</span><span class="tag">&lt;title&gt;</span><span class="pln">Skill Sharing</span><span class="tag">&lt;/title&gt;</span><span class="pln">
</span><span class="tag">&lt;link</span><span class="pln"> </span><span class="atn">rel</span><span class="pun">=</span><span class="atv">"stylesheet"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"skillsharing.css"</span><span class="tag">&gt;</span><span class="pln">

</span><span class="tag">&lt;h1&gt;</span><span class="pln">Skill Sharing</span><span class="tag">&lt;/h1&gt;</span><span class="pln">

</span><span class="tag">&lt;script</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"skillsharing_client.js"</span><span class="tag">&gt;&lt;/script&gt;</span></pre>

<p>
	يعرِّف هذا الملف عنوان المستند، ويتضمن ورقة تنسيقات تعرِّف بعض التنسيقات لضمان وجود مسافة بين الكلمات، إضافة إلى أمور أخرى، كما يضيف في النهاية عنوانًا في قمة الصفحة ويحمِّل السكربت التي تحتوي على تطبيق جانب العميل.
</p>

<h3>
	الإجراءات
</h3>

<p>
	تتكون حالة التطبيق من قائمة من الكلمات واسم المستخدم، كما سنخزِّن ذلك في الكائن <code>{talks,user}</code>، ولا نريد السماح لواجهة المستخدِم بتعديل الحالة أو إرسال طلبات HTTP، بل قد تطلق إجراءات تصف ما الذي يحاول المستخدِم فعله، في حين تأخذ دالة <code>handleAction</code> مثل هذا الإجراء وتجعله يحدُث، كما تعالَج تغيرات الحالة في الدالة نفسها بما أنّ تحديثات حالتنا بسيطة جدًا.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_72_38" style="">
<span class="kwd">function</span><span class="pln"> handleAction</span><span class="pun">(</span><span class="pln">state</span><span class="pun">,</span><span class="pln"> action</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">action</span><span class="pun">.</span><span class="pln">type </span><span class="pun">==</span><span class="pln"> </span><span class="str">"setUser"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    localStorage</span><span class="pun">.</span><span class="pln">setItem</span><span class="pun">(</span><span class="str">"userName"</span><span class="pun">,</span><span class="pln"> action</span><span class="pun">.</span><span class="pln">user</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="typ">Object</span><span class="pun">.</span><span class="pln">assign</span><span class="pun">({},</span><span class="pln"> state</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">user</span><span class="pun">:</span><span class="pln"> action</span><span class="pun">.</span><span class="pln">user</span><span class="pun">});</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">action</span><span class="pun">.</span><span class="pln">type </span><span class="pun">==</span><span class="pln"> </span><span class="str">"setTalks"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="typ">Object</span><span class="pun">.</span><span class="pln">assign</span><span class="pun">({},</span><span class="pln"> state</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">talks</span><span class="pun">:</span><span class="pln"> action</span><span class="pun">.</span><span class="pln">talks</span><span class="pun">});</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">action</span><span class="pun">.</span><span class="pln">type </span><span class="pun">==</span><span class="pln"> </span><span class="str">"newTalk"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fetchOK</span><span class="pun">(</span><span class="pln">talkURL</span><span class="pun">(</span><span class="pln">action</span><span class="pun">.</span><span class="pln">title</span><span class="pun">),</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      method</span><span class="pun">:</span><span class="pln"> </span><span class="str">"PUT"</span><span class="pun">,</span><span class="pln">
      headers</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="str">"Content-Type"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"application/json"</span><span class="pun">},</span><span class="pln">
      body</span><span class="pun">:</span><span class="pln"> JSON</span><span class="pun">.</span><span class="pln">stringify</span><span class="pun">({</span><span class="pln">
        presenter</span><span class="pun">:</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">user</span><span class="pun">,</span><span class="pln">
        summary</span><span class="pun">:</span><span class="pln"> action</span><span class="pun">.</span><span class="pln">summary
      </span><span class="pun">})</span><span class="pln">
    </span><span class="pun">}).</span><span class="kwd">catch</span><span class="pun">(</span><span class="pln">reportError</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">action</span><span class="pun">.</span><span class="pln">type </span><span class="pun">==</span><span class="pln"> </span><span class="str">"deleteTalk"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fetchOK</span><span class="pun">(</span><span class="pln">talkURL</span><span class="pun">(</span><span class="pln">action</span><span class="pun">.</span><span class="pln">talk</span><span class="pun">),</span><span class="pln"> </span><span class="pun">{</span><span class="pln">method</span><span class="pun">:</span><span class="pln"> </span><span class="str">"DELETE"</span><span class="pun">})</span><span class="pln">
      </span><span class="pun">.</span><span class="kwd">catch</span><span class="pun">(</span><span class="pln">reportError</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">action</span><span class="pun">.</span><span class="pln">type </span><span class="pun">==</span><span class="pln"> </span><span class="str">"newComment"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fetchOK</span><span class="pun">(</span><span class="pln">talkURL</span><span class="pun">(</span><span class="pln">action</span><span class="pun">.</span><span class="pln">talk</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">"/comments"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      method</span><span class="pun">:</span><span class="pln"> </span><span class="str">"POST"</span><span class="pun">,</span><span class="pln">
      headers</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="str">"Content-Type"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"application/json"</span><span class="pun">},</span><span class="pln">
      body</span><span class="pun">:</span><span class="pln"> JSON</span><span class="pun">.</span><span class="pln">stringify</span><span class="pun">({</span><span class="pln">
        author</span><span class="pun">:</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">user</span><span class="pun">,</span><span class="pln">
        message</span><span class="pun">:</span><span class="pln"> action</span><span class="pun">.</span><span class="pln">message
      </span><span class="pun">})</span><span class="pln">
    </span><span class="pun">}).</span><span class="kwd">catch</span><span class="pun">(</span><span class="pln">reportError</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> state</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	سنخزن اسم المستخدِم في <code>localStorage</code> كي يمكن استعادتها عند تحميل الصفحة؛ أما الإجراءات التي تحتاج إلى إنشاء الخادم طلبات شبكية باستخدام <code>fetch</code> إلى واجهة HTTP التي وصفناها من قبل فسنستخدِم دالةً مغلِّفةً هي <code>fetchOk</code> تتأكد من أنّ الوعد المعاد مرفوض إذا أعاد الخادم رمز خطأ.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_72_42" style="">
<span class="kwd">function</span><span class="pln"> fetchOK</span><span class="pun">(</span><span class="pln">url</span><span class="pun">,</span><span class="pln"> options</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> fetch</span><span class="pun">(</span><span class="pln">url</span><span class="pun">,</span><span class="pln"> options</span><span class="pun">).</span><span class="pln">then</span><span class="pun">(</span><span class="pln">response </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">status </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">400</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">return</span><span class="pln"> response</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">throw</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Error</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">statusText</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تُستخدَم الدالة المساعدة التالية لبناء رابط لكلمة لها عنوان محدَّد.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_72_44" style="">
<span class="kwd">function</span><span class="pln"> talkURL</span><span class="pun">(</span><span class="pln">title</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="str">"talks/"</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> encodeURIComponent</span><span class="pun">(</span><span class="pln">title</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	إذا فشل الطلب فلا نريد أن تظل صفحتنا ساكنةً لا تفعل شيء دون تفسير، لذا نعرِّف دالةً تدعى <code>reportError</code> تعرض للمستخدِم صندوقًا حواريًا يخبره أنّ شيئًا خاطئًا قد حدث.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_72_46" style="">
<span class="kwd">function</span><span class="pln"> reportError</span><span class="pun">(</span><span class="pln">error</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  alert</span><span class="pun">(</span><span class="typ">String</span><span class="pun">(</span><span class="pln">error</span><span class="pun">));</span><span class="pln">
</span><span class="pun">}</span></pre>

<h3>
	إخراج المكونات Rendering Components
</h3>

<p>
	سنستخدِم منظورًا يشبه الذي رأيناه في مقال <a href="https://academy.hsoub.com/programming/javascript/%D8%A5%D9%86%D8%AC%D8%A7%D8%B2-%D9%85%D8%B4%D8%B1%D9%88%D8%B9-%D9%85%D8%AD%D8%B1%D8%B1-%D8%B1%D8%B3%D9%88%D9%85-%D9%86%D9%82%D8%B7%D9%8A%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1401/" rel="">إنجاز مشروع محرر رسوم نقطية باستخدام جافاسكربت</a> والذي يقسِّم التطبيق إلى مكونات، لكن بما أن بعض تلك المكونات قد لا تحتاج إلى تحديث أبدًا أو تُرسم من جديد في كل مرة تُحدَّث فيها، فسنعرِّف أولئك على أساس دوال تعيد <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D8%B3%D8%AA%D9%83%D8%B4%D8%A7%D9%81-%D8%AE%D8%A7%D8%B5%D9%8A%D8%A7%D8%AA-%D8%B9%D9%82%D8%AF-dom-%D8%A8%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-%D8%A7%D9%84%D8%B5%D9%86%D9%81-%D9%88%D8%A7%D9%84%D9%88%D8%B3%D9%85-%D9%88%D8%A7%D9%84%D9%85%D8%AD%D8%AA%D9%88%D9%8A%D8%A7%D8%AA-r1107/" rel="">عقدة DOM</a> مباشرةً وليس على أساس أصناف، ويوضِّح المثال التالي مكونًا يعرض حقلًا يمكن للمستخدِم إدخال اسمه فيه.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_72_48" style="">
<span class="kwd">function</span><span class="pln"> renderUserField</span><span class="pun">(</span><span class="pln">name</span><span class="pun">,</span><span class="pln"> dispatch</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> elt</span><span class="pun">(</span><span class="str">"label"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{},</span><span class="pln"> </span><span class="str">"Your name: "</span><span class="pun">,</span><span class="pln"> elt</span><span class="pun">(</span><span class="str">"input"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    type</span><span class="pun">:</span><span class="pln"> </span><span class="str">"text"</span><span class="pun">,</span><span class="pln">
    value</span><span class="pun">:</span><span class="pln"> name</span><span class="pun">,</span><span class="pln">
    onchange</span><span class="pun">(</span><span class="pln">event</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      dispatch</span><span class="pun">({</span><span class="pln">type</span><span class="pun">:</span><span class="pln"> </span><span class="str">"setUser"</span><span class="pun">,</span><span class="pln"> user</span><span class="pun">:</span><span class="pln"> event</span><span class="pun">.</span><span class="pln">target</span><span class="pun">.</span><span class="pln">value</span><span class="pun">});</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">}));</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	الدالة <code>elt</code> المستخدَمة لبناء عناصر DOM هي نفسها التي استخدمناها في مقال إنجاز مشروع محرر رسوم نقطية باستخدام جافاسكربت المشار إليه أعلاه، وتُستخدَم دالة شبيهة بها لإخراج الكلمات، حيث تتضمن قائمةً من التعليقات واستمارةً من أجل إضافة تعليق جديد.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_72_51" style="">
<span class="kwd">function</span><span class="pln"> renderTalk</span><span class="pun">(</span><span class="pln">talk</span><span class="pun">,</span><span class="pln"> dispatch</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> elt</span><span class="pun">(</span><span class="pln">
    </span><span class="str">"section"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">className</span><span class="pun">:</span><span class="pln"> </span><span class="str">"talk"</span><span class="pun">},</span><span class="pln">
    elt</span><span class="pun">(</span><span class="str">"h2"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">,</span><span class="pln"> talk</span><span class="pun">.</span><span class="pln">title</span><span class="pun">,</span><span class="pln"> </span><span class="str">" "</span><span class="pun">,</span><span class="pln"> elt</span><span class="pun">(</span><span class="str">"button"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      type</span><span class="pun">:</span><span class="pln"> </span><span class="str">"button"</span><span class="pun">,</span><span class="pln">
      onclick</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        dispatch</span><span class="pun">({</span><span class="pln">type</span><span class="pun">:</span><span class="pln"> </span><span class="str">"deleteTalk"</span><span class="pun">,</span><span class="pln"> talk</span><span class="pun">:</span><span class="pln"> talk</span><span class="pun">.</span><span class="pln">title</span><span class="pun">});</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">},</span><span class="pln"> </span><span class="str">"Delete"</span><span class="pun">)),</span><span class="pln">
    elt</span><span class="pun">(</span><span class="str">"div"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">,</span><span class="pln"> </span><span class="str">"by "</span><span class="pun">,</span><span class="pln">
        elt</span><span class="pun">(</span><span class="str">"strong"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">,</span><span class="pln"> talk</span><span class="pun">.</span><span class="pln">presenter</span><span class="pun">)),</span><span class="pln">
    elt</span><span class="pun">(</span><span class="str">"p"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">,</span><span class="pln"> talk</span><span class="pun">.</span><span class="pln">summary</span><span class="pun">),</span><span class="pln">
    </span><span class="pun">...</span><span class="pln">talk</span><span class="pun">.</span><span class="pln">comments</span><span class="pun">.</span><span class="pln">map</span><span class="pun">(</span><span class="pln">renderComment</span><span class="pun">),</span><span class="pln">
    elt</span><span class="pun">(</span><span class="str">"form"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      onsubmit</span><span class="pun">(</span><span class="pln">event</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        event</span><span class="pun">.</span><span class="pln">preventDefault</span><span class="pun">();</span><span class="pln">
        let form </span><span class="pun">=</span><span class="pln"> event</span><span class="pun">.</span><span class="pln">target</span><span class="pun">;</span><span class="pln">
        dispatch</span><span class="pun">({</span><span class="pln">type</span><span class="pun">:</span><span class="pln"> </span><span class="str">"newComment"</span><span class="pun">,</span><span class="pln">
                  talk</span><span class="pun">:</span><span class="pln"> talk</span><span class="pun">.</span><span class="pln">title</span><span class="pun">,</span><span class="pln">
                  message</span><span class="pun">:</span><span class="pln"> form</span><span class="pun">.</span><span class="pln">elements</span><span class="pun">.</span><span class="pln">comment</span><span class="pun">.</span><span class="pln">value</span><span class="pun">});</span><span class="pln">
        form</span><span class="pun">.</span><span class="pln">reset</span><span class="pun">();</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">},</span><span class="pln"> elt</span><span class="pun">(</span><span class="str">"input"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">type</span><span class="pun">:</span><span class="pln"> </span><span class="str">"text"</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"comment"</span><span class="pun">}),</span><span class="pln"> </span><span class="str">" "</span><span class="pun">,</span><span class="pln">
       elt</span><span class="pun">(</span><span class="str">"button"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">type</span><span class="pun">:</span><span class="pln"> </span><span class="str">"submit"</span><span class="pun">},</span><span class="pln"> </span><span class="str">"Add comment"</span><span class="pun">)));</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	معالِج الحدث <code>"submit"</code> يستدعي <code>form.reset</code> لمسح محتوى الاستمارة بعد إنشاء الإجراء <code>"newcomment"</code>، وعند إنشاء أجزاء متوسطة التعقيد من DOM، فسيبدو هذا التنسيق من البرمجة فوضويًا، وهناك امتداد جافاسكربت واسع الاستخدام رغم أنه ليس قياسيًا ويسمى JSX، حيث يسمح لنا بكتابة HTML في السكربتات الخاصة بك مباشرةً مما يحسِّن من مظهر الشيفرة، لكن يجب علينا تشغيل برنامج ما قبل تشغيل الشيفرة نفسها ليحوّل شيفرة HTML الوهمية تلك إلى استدعاءات <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%AF%D9%88%D8%A7%D9%84-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r781/" rel="">لدوال جافاسكربت</a> مثل تلك التي نستخدمها ها هنا؛ أما التعليقات فستكون أبسط في الإخراج.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_72_53" style="">
<span class="kwd">function</span><span class="pln"> renderComment</span><span class="pun">(</span><span class="pln">comment</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> elt</span><span class="pun">(</span><span class="str">"p"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">className</span><span class="pun">:</span><span class="pln"> </span><span class="str">"comment"</span><span class="pun">},</span><span class="pln">
             elt</span><span class="pun">(</span><span class="str">"strong"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">,</span><span class="pln"> comment</span><span class="pun">.</span><span class="pln">author</span><span class="pun">),</span><span class="pln">
             </span><span class="str">": "</span><span class="pun">,</span><span class="pln"> comment</span><span class="pun">.</span><span class="pln">message</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	أخيرًا، تُخرَج الاستمارة التي يستطيع المستخدِم استخدامها في إنشاء الكلمة كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_72_55" style="">
<span class="kwd">function</span><span class="pln"> renderTalkForm</span><span class="pun">(</span><span class="pln">dispatch</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let title </span><span class="pun">=</span><span class="pln"> elt</span><span class="pun">(</span><span class="str">"input"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">type</span><span class="pun">:</span><span class="pln"> </span><span class="str">"text"</span><span class="pun">});</span><span class="pln">
  let summary </span><span class="pun">=</span><span class="pln"> elt</span><span class="pun">(</span><span class="str">"input"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">type</span><span class="pun">:</span><span class="pln"> </span><span class="str">"text"</span><span class="pun">});</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> elt</span><span class="pun">(</span><span class="str">"form"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    onsubmit</span><span class="pun">(</span><span class="pln">event</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      event</span><span class="pun">.</span><span class="pln">preventDefault</span><span class="pun">();</span><span class="pln">
      dispatch</span><span class="pun">({</span><span class="pln">type</span><span class="pun">:</span><span class="pln"> </span><span class="str">"newTalk"</span><span class="pun">,</span><span class="pln">
                title</span><span class="pun">:</span><span class="pln"> title</span><span class="pun">.</span><span class="pln">value</span><span class="pun">,</span><span class="pln">
                summary</span><span class="pun">:</span><span class="pln"> summary</span><span class="pun">.</span><span class="pln">value</span><span class="pun">});</span><span class="pln">
      event</span><span class="pun">.</span><span class="pln">target</span><span class="pun">.</span><span class="pln">reset</span><span class="pun">();</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">},</span><span class="pln"> elt</span><span class="pun">(</span><span class="str">"h3"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Submit a Talk"</span><span class="pun">),</span><span class="pln">
     elt</span><span class="pun">(</span><span class="str">"label"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Title: "</span><span class="pun">,</span><span class="pln"> title</span><span class="pun">),</span><span class="pln">
     elt</span><span class="pun">(</span><span class="str">"label"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Summary: "</span><span class="pun">,</span><span class="pln"> summary</span><span class="pun">),</span><span class="pln">
     elt</span><span class="pun">(</span><span class="str">"button"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">type</span><span class="pun">:</span><span class="pln"> </span><span class="str">"submit"</span><span class="pun">},</span><span class="pln"> </span><span class="str">"Submit"</span><span class="pun">));</span><span class="pln">
</span><span class="pun">}</span></pre>

<h3>
	الاستطلاع
</h3>

<p>
	نحتاج إلى قائمة الكلمات الحالية إذا أردنا بدء التطبيق، وبما أن التحميل الابتدائي متعلق للغاية بعملية الاستطلاع المفتوح إذ يجب استخدام <code>ETag</code> من الحمل عند الاستطلاع، فسنكتب دالةً تظل تستطلع الخادم لـ <code>‎/talks</code> وتستدعي دالة رد نداء عند توفر مجموعة كلمات جديدة.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_72_57" style="">
<span class="pln">async </span><span class="kwd">function</span><span class="pln"> pollTalks</span><span class="pun">(</span><span class="pln">update</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let tag </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">undefined</span><span class="pun">;</span><span class="pln">
  </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(;;)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let response</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      response </span><span class="pun">=</span><span class="pln"> await fetchOK</span><span class="pun">(</span><span class="str">"/talks"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        headers</span><span class="pun">:</span><span class="pln"> tag </span><span class="pun">&amp;&amp;</span><span class="pln"> </span><span class="pun">{</span><span class="str">"If-None-Match"</span><span class="pun">:</span><span class="pln"> tag</span><span class="pun">,</span><span class="pln">
                         </span><span class="str">"Prefer"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"wait=90"</span><span class="pun">}</span><span class="pln">
      </span><span class="pun">});</span><span class="pln">
    </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="pln">e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Request failed: "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> e</span><span class="pun">);</span><span class="pln">
      await </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Promise</span><span class="pun">(</span><span class="pln">resolve </span><span class="pun">=&gt;</span><span class="pln"> setTimeout</span><span class="pun">(</span><span class="pln">resolve</span><span class="pun">,</span><span class="pln"> </span><span class="lit">500</span><span class="pun">));</span><span class="pln">
      </span><span class="kwd">continue</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">status </span><span class="pun">==</span><span class="pln"> </span><span class="lit">304</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">continue</span><span class="pun">;</span><span class="pln">
    tag </span><span class="pun">=</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">headers</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"ETag"</span><span class="pun">);</span><span class="pln">
    update</span><span class="pun">(</span><span class="pln">await response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">());</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	بما أن هذه الدالة هي دالة <code>async</code> فمن السهل تنفيذ تكرار حلقي وانتظار الطلب، وهي تشغِّل حلقةً تكراريةً لا نهائيةً تجلب قائمةً من الكلمات في كل تكرار إما جلبًا عاديًا أو مع تضمين الترويسات التي تجعله طلب استطلاع مفتوح إذا لم يكن هذا هو الطلب الأول، حيث تنتظر الدالة عند فشل الطلب لحظةً ثم تحاول مرةً أخرى وهكذا، فإذا انقطع الاتصال لدينا لوهلة ثم عادة مرةً أخرى فسيستطيع البرنامج أن يتعافى ويتابع التحديث، ويكون الوعد المحلول بواسطة <code>setTimeout</code> طريقةً لإجبار دالة <code>async</code> على الانتظار.
</p>

<p>
	إذا أعاد الخادم استجابة 304 فهذا يعني انتهاء المهلة الزمنية المحددة لطلب استطلاع مفتوح، لذا يجب أن تبدأ الدالة الطلب التالي، فإذا كانت الاستجابة هي 200 العادية، فسيُقرأ متنها على أنه <a href="https://academy.hsoub.com/programming/javascript/%D9%83%D9%8A%D9%81-%D8%AA%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-json-%D9%81%D9%8A-javascript-r548/" rel="">JSON</a> ويمرَّر إلى رد النداء، كما تخزَّن قيمة الترويسة <code>ETag</code> من أجل التكرار التالي.
</p>

<h3>
	التطبيق
</h3>

<p>
	يربط المكون التالي واجهة المستخدِم كلها بعضها ببعض:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_72_59" style="">
<span class="kwd">class</span><span class="pln"> </span><span class="typ">SkillShareApp</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  constructor</span><span class="pun">(</span><span class="pln">state</span><span class="pun">,</span><span class="pln"> dispatch</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dispatch </span><span class="pun">=</span><span class="pln"> dispatch</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">talkDOM </span><span class="pun">=</span><span class="pln"> elt</span><span class="pun">(</span><span class="str">"div"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">className</span><span class="pun">:</span><span class="pln"> </span><span class="str">"talks"</span><span class="pun">});</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dom </span><span class="pun">=</span><span class="pln"> elt</span><span class="pun">(</span><span class="str">"div"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">,</span><span class="pln">
                   renderUserField</span><span class="pun">(</span><span class="pln">state</span><span class="pun">.</span><span class="pln">user</span><span class="pun">,</span><span class="pln"> dispatch</span><span class="pun">),</span><span class="pln">
                   </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">talkDOM</span><span class="pun">,</span><span class="pln">
                   renderTalkForm</span><span class="pun">(</span><span class="pln">dispatch</span><span class="pun">));</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">syncState</span><span class="pun">(</span><span class="pln">state</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  syncState</span><span class="pun">(</span><span class="pln">state</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">state</span><span class="pun">.</span><span class="pln">talks </span><span class="pun">!=</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">talks</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">talkDOM</span><span class="pun">.</span><span class="pln">textContent </span><span class="pun">=</span><span class="pln"> </span><span class="str">""</span><span class="pun">;</span><span class="pln">
      </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let talk of state</span><span class="pun">.</span><span class="pln">talks</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">talkDOM</span><span class="pun">.</span><span class="pln">appendChild</span><span class="pun">(</span><span class="pln">
          renderTalk</span><span class="pun">(</span><span class="pln">talk</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dispatch</span><span class="pun">));</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
      </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">talks </span><span class="pun">=</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">talks</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	إذا تغيرت الكلمات فسيُعيد هذا المكون رسمها جميعًا، وهذا أمر بسيط حقًا لكنه مضيعة للوقت وسنعود إليه في التدريبات، إذ نستطيع بدء التطبيق كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_72_61" style="">
<span class="kwd">function</span><span class="pln"> runApp</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let user </span><span class="pun">=</span><span class="pln"> localStorage</span><span class="pun">.</span><span class="pln">getItem</span><span class="pun">(</span><span class="str">"userName"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">||</span><span class="pln"> </span><span class="str">"Anon"</span><span class="pun">;</span><span class="pln">
  let state</span><span class="pun">,</span><span class="pln"> app</span><span class="pun">;</span><span class="pln">
  </span><span class="kwd">function</span><span class="pln"> dispatch</span><span class="pun">(</span><span class="pln">action</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    state </span><span class="pun">=</span><span class="pln"> handleAction</span><span class="pun">(</span><span class="pln">state</span><span class="pun">,</span><span class="pln"> action</span><span class="pun">);</span><span class="pln">
    app</span><span class="pun">.</span><span class="pln">syncState</span><span class="pun">(</span><span class="pln">state</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  pollTalks</span><span class="pun">(</span><span class="pln">talks </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">app</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      state </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">user</span><span class="pun">,</span><span class="pln"> talks</span><span class="pun">};</span><span class="pln">
      app </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">SkillShareApp</span><span class="pun">(</span><span class="pln">state</span><span class="pun">,</span><span class="pln"> dispatch</span><span class="pun">);</span><span class="pln">
      document</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">appendChild</span><span class="pun">(</span><span class="pln">app</span><span class="pun">.</span><span class="pln">dom</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      dispatch</span><span class="pun">({</span><span class="pln">type</span><span class="pun">:</span><span class="pln"> </span><span class="str">"setTalks"</span><span class="pun">,</span><span class="pln"> talks</span><span class="pun">});</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">}).</span><span class="kwd">catch</span><span class="pun">(</span><span class="pln">reportError</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

runApp</span><span class="pun">();</span></pre>

<p>
	إذا شغلنا الخادم وفتحنا نافذتَي متصفح لـ <a href="http://localhost:8000" ipsnoembed="false" rel="external nofollow">http://localhost:8000</a> جنبًا إلى جنب، فسيمكنك رؤية كيف أنّ الإجراءات الذي تحدِثه في إحدى النافذتين تظهر مباشرةً في الأخرى.
</p>

<h2>
	تدريبات
</h2>

<p>
	ستتضمن التدريبات التالية تعديل النظام المعرّف في هذا المقال، ولكي تعمل عليها تأكد من تحميل الشيفرة أولًا من <a href="https://eloquentjavascript.net/code/skillsharing.zip" rel="external nofollow">هذا الرابط</a> وتكون قد ثبّتَّ Node لديك من <a href="https://nodejs.org" rel="external nofollow">موقعها الرسمي</a>، وكذلك اعتماديات المشروع باستخدام الأمر <code>npm install</code>.
</p>

<h3>
	ثبات القرص
</h3>

<p>
	يحتفظ خادم مشاركة المهارات ببياناته في الذاكرة، وهذا يعني أنه ستضيع كل الكلمات والتعليقات عند تعطله أو إعادة تشغيله لأيّ سبب كان، لذا وسِّع الخادم ليخزِّن بيانات الكلمات في القرص، ويعيد تحميل البيانات تلقائيًا عند إعادة تشغيله، ولا تقلق بشأن الكفاءة وإنما افعل أبسط شيء يؤدي الغرض.
</p>

<h4>
	إرشادات الحل
</h4>

<p>
	أبسط حل لهذا هو ترميز كائن <code>talks</code> كله على أنه JSON وإلقائه في ملف بواسطة <code>writeFile</code>، وهناك تابع <code>update</code> بالفعل يُستدعى في كل مرة تتغير فيها بيانات الخادم، حيث يمكن توسيعه لكتابة البيانات الجديدة على القرص.
</p>

<p>
	اختر اسم ملف وليكن <code>‎./talks,json</code>، ويمكن للخادم أن يحاول في قراءة هذا الملف باستخدام <code>readFile</code> عند بدء عمله، وإذا نجح فيمكن للخادم أن يستخدِم محتويات الملف على أساس تاريخ بدء له؛ لكن احذر، فكائن <code>talks</code> بدأ على أساس كائن ليس له نموذج أولي كي يمكن استخدام العامل <code>in</code> بصورة موثوقة.
</p>

<p>
	ستعيد <code>JSON.parse</code> كائنات عادية يكون نموذجها الأولي هو <code>Object.prototype</code>، فإذا استخدمت JSON على أساس صيغة ملفات لك، فيجب عليك نسخ خصائص الكائن المعاد بواسطة <code>JSON.parse</code> في كائن جديد ليس له نموذج أولي.
</p>

<h3>
	إعادة ضبط حقول التعليقات
</h3>

<p>
	تعمل إعادة رسم الكلمات كلها لأنك لا تستطيع عادةً معرفة الفرق بين عقدة DOM وبديلها التوأم، لكن هناك استثناءات لهذا، فإذا بدأت كتابة شيء ما في حقل التعليق لكلمة ما في نافذة متصفح ثم أضفت تعليقًا إلى الكلمة نفسها من متصفح آخر، فسيعاد رسم الحقل في النافذة الأولى ليحذف محتواه وتركيزه focus معًا، وسيكون هذا مزعجًا للغاية إذا كان لدينا نقاشًا بين عدة مستخدِمِين من حواسيب مختلفة ومتصفحات عدة يضيفون تعليقات في الوقت نفسه، فهل تستطيع إيجاد طريقة لحل هذه المشكلة؟
</p>

<h4>
	إرشادات الحل
</h4>

<p>
	إنّ أفضل حل لهذا هو جعل مكونات الكلمات كائنات لها التابع <code>syncState</code> كي يمكن تحديثها لتعرض نسخةً معدلةً من الكلمة، وتكون الطريقة الوحيدة التي يمكن بها تغير كلمة ما هي بإضافة تعليقات أكثر، وعليه يكون التابع <code>syncState</code> بسيطًا نسبيًا هنا؛ أما الجزء الصعب فهو عند تغير قائمة الكلمات، إذ يجب إصلاح قائمة مكونات DOM الموجودة بكلمات من القائمة الجديدة، مما يعني حذف المكونات التي حُذفت كلماتها وتحديث المكونات التي تغيرت كلماتها.
</p>

<p>
	من المفيد عند تنفيذ ذلك الاحتفاظ بهيكل بيانات يخزن مكونات الكلمات تحت عناوين الكلمات نفسها كي تستطيع معرفة إذا كان مكون ما موجودًا بالنسبة لكلمة معطاة أم لا، ثم تكرِّر حلقيًا على المصفوفة الجديدة للكلمات وتزامن المكون الموجود سلفًا لكل واحدة منها أو تنشئ واحدًا جديدًا، ولحذف المكونات بالنسبة للكلمات المحذوفة فيجب عليك التكرار حلقيًا أيضًا على المكونات وتنظر هل لا زالت الكلمات الموافقة لها موجودةً أم لا.
</p>

<p>
	ترجمة -بتصرف- <a href="https://eloquentjavascript.net/21_skillsharing.html" rel="external nofollow">للفصل الحادي والعشرين من كتاب Elequent Javascript</a> لصاحبه Marijn Haverbeke.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%8A%D8%B2%D9%8A%D8%AF-%D9%85%D9%86-%D9%85%D8%B9%D8%AF%D9%84-%D8%A7%D9%84%D8%A7%D8%AD%D8%AA%D9%81%D8%A7%D8%B8-%D8%A8%D8%A7%D9%84%D8%B9%D9%85%D9%84%D8%A7%D8%A1-%D8%B9%D8%A8%D8%B1-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%B2%D8%AF-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r1320/" rel="">تطوير تطبيق عملي يزيد من معدل الاحتفاظ بالعملاء عبر واجهة زد البرمجية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/%D9%87%D9%8A%D9%83%D9%84-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D8%AE%D9%84%D9%81%D9%8A%D8%A9-%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1%D8%A7%D8%AA-unit-tests-r1132/" rel="">هيكل تطبيقات الواجهة الخلفية: مدخل إلى الاختبارات (unit tests)</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D8%AD%D8%B1%D9%83-%D8%A8%D8%AD%D8%AB-%D9%84%D8%A8%D9%84%D8%A8-%D8%A7%D9%84%D8%B9%D8%B1%D8%A8%D9%8A-%D9%83%D9%85%D8%AD%D8%B1%D9%83-%D8%A8%D8%AD%D8%AB-%D8%AF%D8%A7%D8%AE%D9%84%D9%8A-%D9%84%D9%85%D8%AD%D8%AA%D9%88%D9%8A%D8%A7%D8%AA-%D9%85%D9%88%D9%82%D8%B9%D9%83-r1347/" rel="">استخدام محرك بحث لبلب العربي كمحرك بحث داخلي لمحتويات موقعك</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1403</guid><pubDate>Fri, 17 Dec 2021 09:11:29 +0000</pubDate></item><item><title>&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x645;&#x62D;&#x631;&#x643; &#x628;&#x62D;&#x62B; &#x644;&#x628;&#x644;&#x628; &#x627;&#x644;&#x639;&#x631;&#x628;&#x64A; &#x643;&#x645;&#x62D;&#x631;&#x643; &#x628;&#x62D;&#x62B; &#x62F;&#x627;&#x62E;&#x644;&#x64A; &#x644;&#x645;&#x62D;&#x62A;&#x648;&#x64A;&#x627;&#x62A; &#x645;&#x648;&#x642;&#x639;&#x643;</title><link>https://academy.hsoub.com/programming/javascript/nodejs/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D8%AD%D8%B1%D9%83-%D8%A8%D8%AD%D8%AB-%D9%84%D8%A8%D9%84%D8%A8-%D8%A7%D9%84%D8%B9%D8%B1%D8%A8%D9%8A-%D9%83%D9%85%D8%AD%D8%B1%D9%83-%D8%A8%D8%AD%D8%AB-%D8%AF%D8%A7%D8%AE%D9%84%D9%8A-%D9%84%D9%85%D8%AD%D8%AA%D9%88%D9%8A%D8%A7%D8%AA-%D9%85%D9%88%D9%82%D8%B9%D9%83-r1347/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2021_11/61864318d9d3b_-----------------.png.9a105a3615601cb87cbf7a16c7c1f560.png" /></p>

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

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

<p>
	سنبني في هذا المقال موقع ويب يعرض مقالات، بشكل مبسط، باستخدام Node.js وقاعدة بيانات MySQL (أو MariaDB) وسنرى معًا الفارق بين وظيفة البحث التي سننشئها يدويًا، وبين نتائج البحث التي يعطينا إياها لبلب عند استخدامه في الموقع، فهيا بنا.
</p>

<h2>
	جدول المحتويات
</h2>

<ul>
<li>
		<a data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="#%D8%A7%D9%84%D8%AA%D9%87%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%A3%D9%88%D9%84%D9%8A%D8%A9-%D9%84%D9%84%D9%85%D8%B4%D8%B1%D9%88%D8%B9" rel="">التهيئة الأولية للمشروع</a>
	</li>
	<li>
		<a data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="#%D8%AA%D8%B5%D9%85%D9%8A%D9%85-%D8%A7%D9%84%D8%B5%D9%81%D8%AD%D8%A7%D8%AA" rel="">تصميم الصفحات</a>
	</li>
	<li>
		<a data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="#%D8%AA%D9%87%D9%8A%D8%A6%D8%A9-%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%88%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%A7%D9%84%D9%85%D9%88%D8%AC%D9%87%D8%A7%D8%AA" rel="">تهيئة إدارة قواعد البيانات وإنشاء الموجهات Routes</a>
	</li>
	<li>
		<a data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="#%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-%D8%A7%D9%84%D9%85%D9%82%D8%A7%D9%84%D8%A7%D8%AA" rel="">إضافة المقالات</a>
	</li>
	<li>
		<a data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="#%D8%A8%D9%86%D8%A7%D8%A1-%D8%B9%D9%85%D9%84%D9%8A%D8%A9-%D8%A7%D9%84%D8%A8%D8%AD%D8%AB-%D8%A7%D8%B9%D8%AA%D9%85%D8%A7%D8%AF%D8%A7-%D8%B9%D9%84%D9%89-%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA" rel="">بناء عملية البحث اعتمادًا على قاعدة البيانات</a>
	</li>
	<li>
		<a data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="#%D9%85%D9%81%D8%A7%D9%87%D9%8A%D9%85-%D9%85%D8%AA%D8%B9%D9%84%D9%82%D8%A9-%D8%A8%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%D9%84%D8%A8%D8%AD%D8%AB-%D8%A7%D9%84%D8%AF%D8%A7%D8%AE%D9%84%D9%8A" rel="">مفاهيم متعلقة بمحرك البحث الداخلي</a>
	</li>
	<li>
		<a data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="#%D8%AA%D9%87%D9%8A%D8%A6%D8%A9-%D8%AD%D8%B2%D9%85%D8%A9-%D9%84%D8%A8%D9%84%D8%A8" rel="">تهيئة حزمة لبلب</a>
	</li>
	<li>
		<a data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="#%D8%A8%D9%86%D8%A7%D8%A1-%D8%B9%D9%85%D9%84%D9%8A%D8%A9-%D8%A7%D9%84%D9%81%D9%87%D8%B1%D8%B3%D8%A9" rel="">بناء عملية الفهرسة Indexing</a>
	</li>
	<li>
		<a data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="#%D8%AA%D8%B9%D8%AF%D9%8A%D9%84-%D8%B9%D9%85%D9%84%D9%8A%D8%A9-%D8%A7%D9%84%D8%A8%D8%AD%D8%AB-%D9%88%D8%AA%D8%AC%D8%A7%D8%B1%D8%A8-%D8%A7%D9%84%D8%A8%D8%AD%D8%AB" rel="">تعديل عملية البحث وتجارب البحث</a>
	</li>
	<li>
		<a data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="#%D8%AA%D8%AC%D8%A7%D8%B1%D8%A8-%D8%A7%D9%84%D8%A8%D8%AD%D8%AB-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%A8%D9%84%D8%A8" rel="">تجارب البحث باستخدام لبلب</a>
	</li>
	<li>
		<a data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="#%D8%A7%D9%84%D8%AE%D9%84%D8%A7%D8%B5%D8%A9" rel="">الخلاصة</a>
	</li>
</ul>
<h2 id="التهيئة-الأولية-للمشروع">
	التهيئة الأولية للمشروع
</h2>

<ul></ul>
<p>
	سننشئ مجلدًا جديدًا باسم my-blog للمشروع ونفتحه باستخدام محرر Visual Studio Code وبعدها نفتح نافذة سطر الأوامر، وننفذ الأمر التالي فيها لإنشاء مشروع Node.js جديد:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2287_7" style="">
<span class="pln">npm init</span></pre>

<p>
	اختر ملف الانطلاق app.js عند السؤال عن ذلك ثم أنشئه بعدها في جذر المجلد بإنشاء ملف جديد ضمن محرر الأكواد vscode. يجب أن ننتبه إلى أهمية تنظيم ملفات عمل المشروع، فمن المهم جدًا أن تكون أسماء المجلدات دالةً على وظيفتها، وأن تكون الملفات مقسمةً تقسيمًا صحيحًا.
</p>

<p>
	علينا أن ننشِئ مجلدًا للضبط باسم config ونضع بداخله ملفًا لإنشاء قاعدة البيانات والجدول <code>articles</code> إن لم تكن موجودةً، لكن قبل ذلك، علينا أن نثبت بعض الحزم من <a href="https://academy.hsoub.com/programming/javascript/%D9%81%D9%8A%D8%AF%D9%8A%D9%88-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D8%AF%D9%8A%D8%B1-%D8%A7%D9%84%D8%AD%D8%B2%D9%85-npm-r1225/" rel="">مدير الحزم npm</a> لتضمين الوحدات modules التي تلزمنا في عملنا. اكتب ما يلي في سطر الأوامر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2287_9" style="">
<span class="pln">npm install express express</span><span class="pun">-</span><span class="pln">session body</span><span class="pun">-</span><span class="pln">parser sequelize uuid ejs mysql</span></pre>

<p>
	سنشرح سريعًا وظيفة كل حزمة من الحزم السابقة:
</p>

<ul>
<li>
		express: هي إطار العمل المساعد في تطبيقات node.js، وحزمة express-session هي حزمة تعريف الجلسة لكل مستخدم حيث ستفيدنا في عملية البحث.
	</li>
	<li>
		body-parser: هي الأداة التي ستساعدنا في قراءة الطلبات requests.
	</li>
	<li>
		sequelize: هي عبارة عن orm (وسيلة تواصل بين الخادم وقاعدة البيانات تتولى جميع الاستفسارات بين التطبيق والخادم وقاعدة البيانات) سيساعدنا في إدارة عمليات قاعدة البيانات.
	</li>
	<li>
		uuid: هي أداة تولد معرفات فريدة عالميًا.
	</li>
	<li>
		ejs: هي محرك العرض الذي سنستخدمه لعرض صفحاتنا.
	</li>
	<li>
		mysql: هي حزمة للتواصل مع قواعد البيانات.
	</li>
</ul>
<p>
	يمكنك الرجوع دائمًا إلى موقع npm لمعرفة المزيد عن هذه الحزم.
</p>

<p>
	سننشِئ <a data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="https://academy.hsoub.com/programming/sql/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%B9%D9%86-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-r584/" rel="">قاعدة بيانات</a> باسم myBlog، سنبدأ بكتابة الملف dbCreation.js ضمن مجلد config كما يلي، لا تنسَ تغيير معلومات الاتصال من المستخدم الجذر root أو أي مستخدم آخر وتغيير كلمة المرور password إلى كلمة المرور الخاصة بك:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2287_11" style="">
<span class="com">//تنفذ الأوامر الموجودة فى هذا الملف فى بداية إنشاء المشروع فقط</span><span class="pln">
let mysql </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'mysql'</span><span class="pun">);</span><span class="pln">

</span><span class="com">//معلومات الاتصال بالخادم المحلى </span><span class="pln">
let con </span><span class="pun">=</span><span class="pln"> mysql</span><span class="pun">.</span><span class="pln">createConnection</span><span class="pun">({</span><span class="pln">
  host</span><span class="pun">:</span><span class="pln"> </span><span class="str">"localhost"</span><span class="pun">,</span><span class="pln">
  user</span><span class="pun">:</span><span class="pln"> </span><span class="str">"root"</span><span class="pun">,</span><span class="pln">
  password</span><span class="pun">:</span><span class="pln"> </span><span class="str">"password"</span><span class="pun">,</span><span class="pln">
  multipleStatements</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln"> 
</span><span class="pun">});</span><span class="pln">

</span><span class="com">//تعريف أمر إنشاء قاعدة البيانات وإنشاء الجدول المستخدم فى التطبيق</span><span class="pln">
let sqlCommand </span><span class="pun">=</span><span class="pln"> </span><span class="pun">`</span><span class="pln">
CREATE DATABASE IF NOT EXISTS myBlog CHARACTER SET utf8 COLLATE utf8_general_ci</span><span class="pun">;</span><span class="pln">

use myBlog</span><span class="pun">;</span><span class="pln">

CREATE TABLE IF NOT EXISTS articles </span><span class="pun">(</span><span class="pln">
    id </span><span class="kwd">int</span><span class="pln"> AUTO_INCREMENT</span><span class="pun">,</span><span class="pln">
    title varchar</span><span class="pun">(</span><span class="lit">200</span><span class="pun">),</span><span class="pln">
    author varchar</span><span class="pun">(</span><span class="lit">100</span><span class="pun">),</span><span class="pln">
    content text</span><span class="pun">,</span><span class="pln">
    tags varchar</span><span class="pun">(</span><span class="lit">500</span><span class="pun">),</span><span class="pln">
    createdAt datetime</span><span class="pun">,</span><span class="pln">
    updatedAt datetime</span><span class="pun">,</span><span class="pln">
    PRIMARY KEY </span><span class="pun">(</span><span class="pln">id</span><span class="pun">)</span><span class="pln">
</span><span class="pun">);</span><span class="pln">
</span><span class="pun">`</span><span class="pln">
con</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="kwd">function</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

    </span><span class="com">//تنفيذ إنشاء قاعدة البيانات </span><span class="pln">
    con</span><span class="pun">.</span><span class="pln">query</span><span class="pun">(</span><span class="pln">sqlCommand</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> </span><span class="pun">(</span><span class="pln">err</span><span class="pun">,</span><span class="pln"> result</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">throw</span><span class="pln"> err</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">});</span><span class="pln">

</span><span class="pun">});</span></pre>

<p>
	تخزن قاعدة البيانات معلومات كل مقال كما يوضح الكود إذ تحتوي على جدول فيه الحقول الآتية:
</p>

<ul>
<li>
		حقل <code>id</code> رقمي من النوع int والذي يمثل معرِّف المقال وهو المفتاح الرئيسي primary key للجدول، ويزداد تلقائيًا.
	</li>
	<li>
		الحقول <code>title</code> و <code>author</code> و <code>tags</code> لتخزين عنوان المقال واسم كتابها والوسوم المستعملة فيها وذلك على التتالي وبالترتيب، وهي من النوع <code>varchar</code>.
	</li>
	<li>
		الحقل <code>content</code> من النوع <code>text</code> الذي يمثل محتوى المقال.
	</li>
	<li>
		الحقلان <code>createdAt</code> و <code>updatedAt</code> لتخزين متى أُنشِئت المقالة ومتى حدثت، وهما من النوع <code>datetime</code>.
	</li>
</ul>
<p>
	بعد إنشاء قاعدة البيانات وضبطها، لنشغل الخادم عبر كتابة الكود التالي في ملف app.js:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2287_13" style="">
<span class="kwd">const</span><span class="pln"> express </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"express"</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> bodyParser </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"body-parser"</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> session </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"express-session"</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> uuid </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"uuid"</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> errorHandler </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"./middleware/errorHandler"</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> db </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"./config/dbCreation"</span><span class="pun">);</span><span class="pln">

</span><span class="com">// ‫لإنشاء التطبيق باستخدام express</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> app </span><span class="pun">=</span><span class="pln"> express</span><span class="pun">();</span><span class="pln">

</span><span class="com">//تحديد محرك العرض</span><span class="pln">
app</span><span class="pun">.</span><span class="kwd">set</span><span class="pun">(</span><span class="str">"views"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">`</span><span class="pln">$</span><span class="pun">{</span><span class="pln">__dirname</span><span class="pun">}/</span><span class="pln">views</span><span class="pun">`);</span><span class="pln">
app</span><span class="pun">.</span><span class="kwd">set</span><span class="pun">(</span><span class="str">"view engine"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"ejs"</span><span class="pun">);</span><span class="pln">

</span><span class="com">// ‫‫خيارات حزمة body-parser</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">bodyParser</span><span class="pun">.</span><span class="pln">urlencoded</span><span class="pun">({</span><span class="pln"> extended</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln"> </span><span class="pun">}));</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">bodyParser</span><span class="pun">.</span><span class="pln">json</span><span class="pun">({</span><span class="pln"> extended</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln"> </span><span class="pun">}));</span><span class="pln">

</span><span class="com">// تعيين المجلد الحاوي للملفات القابلة للإرسال</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="str">'/public'</span><span class="pun">,</span><span class="pln"> express</span><span class="pun">.</span><span class="kwd">static</span><span class="pun">(</span><span class="str">'public'</span><span class="pun">));</span><span class="pln">

</span><span class="com">// تحديد المنفذ</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> port </span><span class="pun">=</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">PORT </span><span class="pun">||</span><span class="pln"> </span><span class="lit">5000</span><span class="pun">;</span><span class="pln">

</span><span class="com">// ‫تحديد session secret</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> sessionSecret </span><span class="pun">=</span><span class="pln"> </span><span class="str">"keyboard cat"</span><span class="pun">;</span><span class="pln">

</span><span class="com">// استخدام حزمة الجلسة لتمييز كل مستخدم</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">
  session</span><span class="pun">({</span><span class="pln">
    genid</span><span class="pun">:</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">return</span><span class="pln"> uuid</span><span class="pun">.</span><span class="pln">v4</span><span class="pun">();</span><span class="pln">
    </span><span class="pun">},</span><span class="pln">
    secret</span><span class="pun">:</span><span class="pln"> sessionSecret</span><span class="pun">,</span><span class="pln">
    resave</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
    saveUninitialized</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">
</span><span class="pun">);</span><span class="pln">

</span><span class="com">// ‫استدعاء المستقبل article routes</span><span class="pln">
require</span><span class="pun">(</span><span class="str">"./routes/article.routes"</span><span class="pun">)(</span><span class="pln">app</span><span class="pun">);</span><span class="pln">

</span><span class="com">// معالجة الأخطاء</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">errorHandler</span><span class="pun">);</span><span class="pln">


app</span><span class="pun">.</span><span class="pln">listen</span><span class="pun">(</span><span class="pln">port</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(`</span><span class="pln">server is up</span><span class="pun">,</span><span class="pln"> listening on port $</span><span class="pun">{</span><span class="pln">port</span><span class="pun">}`);</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	بناء على ما كتبناه هنا، علينا إنشاء مجلد views على جذر المشروع وهو ما سيحوي صفحات العرض والتي سيكون امتدادها ejs حيث خصصنا ejs كمحرك للعرض، كما سننشئ مجلدًا اسمه public يحوي ملفًا اسمه style.css حيث عيننا هذا المجلد على أن محتوياته قابلة للإرسال إلى حاسوب العميل.
</p>

<h2 id="تصميم-الصفحات">
	تصميم الصفحات
</h2>

<p>
	سنحتاج في مشروعنا هذا إلى الصفحات التالية:
</p>

<ul>
<li>
		الصفحة الرئيسية (فيها عرض للمقالات أو لنتائج البحث).
	</li>
	<li>
		صفحة عرض المقالة.
	</li>
	<li>
		صفحة إنشاء المقالة.
	</li>
</ul>
<p>
	إن أمعنا النظر فسنجد أن الترويسة والتذييل لهذه الصفحات نفسها، فلا حاجة لتكرار الشيفرات نفسها في أكثر من ملف، لذا سنقسم الملفات إلى الملفات التي سيلي ذكرها في هذا القسم.
</p>

<p>
	سننشئ ضمن المجلد views ملفًا نسميه header.ejs يحوي الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_2287_15" style="">
<span class="dec">&lt;!DOCTYPE html&gt;</span><span class="pln">
</span><span class="tag">&lt;html</span><span class="pln"> </span><span class="atn">lang</span><span class="pun">=</span><span class="atv">"ar"</span><span class="tag">&gt;</span><span class="pln">

</span><span class="tag">&lt;head&gt;</span><span class="pln">
    </span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">charset</span><span class="pun">=</span><span class="atv">"UTF-8"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"viewport"</span><span class="pln"> </span><span class="atn">content</span><span class="pun">=</span><span class="atv">"width=device-width, initial-scale=1.0"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;title&gt;</span><span class="pln">مدونتي</span><span class="tag">&lt;/title&gt;</span><span class="pln">
    </span><span class="tag">&lt;link</span><span class="pln"> </span><span class="atn">rel</span><span class="pun">=</span><span class="atv">"stylesheet"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css"</span><span class="pln"> </span><span class="atn">integrity</span><span class="pun">=</span><span class="atv">"sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk"</span><span class="pln"> </span><span class="atn">crossorigin</span><span class="pun">=</span><span class="atv">"anonymous"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;link</span><span class="pln"> </span><span class="atn">rel</span><span class="pun">=</span><span class="atv">"stylesheet"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.14.0/css/all.min.css"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;link</span><span class="pln"> </span><span class="atn">rel</span><span class="pun">=</span><span class="atv">"stylesheet"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"../public/style.css"</span><span class="tag">&gt;</span><span class="pln">

</span><span class="tag">&lt;/head&gt;</span><span class="pln">

</span><span class="tag">&lt;body&gt;</span><span class="pln">
    </span><span class="tag">&lt;nav</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar fixed-top navbar-expand-md "</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"container"</span><span class="tag">&gt;</span><span class="pln">
          </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar-brand mr-0 ml-4 "</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"/"</span><span class="tag">&gt;</span><span class="pln">مدونتي</span><span class="tag">&lt;/a&gt;</span><span class="pln">
          </span><span class="tag">&lt;button</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar-toggler"</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"button"</span><span class="pln"> </span><span class="atn">data-toggle</span><span class="pun">=</span><span class="atv">"collapse"</span><span class="pln"> </span><span class="atn">data-target</span><span class="pun">=</span><span class="atv">"#navbarNav"</span><span class="pln"> </span><span class="atn">aria-controls</span><span class="pun">=</span><span class="atv">"navbarNav"</span><span class="pln"> </span><span class="atn">aria-expanded</span><span class="pun">=</span><span class="atv">"false"</span><span class="pln"> </span><span class="atn">aria-label</span><span class="pun">=</span><span class="atv">"Toggle navigation"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;i</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"fas fa-bars"</span><span class="tag">&gt;&lt;/i&gt;</span><span class="pln">
          </span><span class="tag">&lt;/button&gt;</span><span class="pln">
          </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"collapse navbar-collapse"</span><span class="pln"> </span><span class="atn">id</span><span class="pun">=</span><span class="atv">"navbarNav"</span><span class="tag">&gt;</span><span class="pln">

            </span><span class="tag">&lt;ul</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar-nav mr-md-1"</span><span class="tag">&gt;</span><span class="pln">
              </span><span class="tag">&lt;li</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"nav-item ml-md-2 active"</span><span class="tag">&gt;</span><span class="pln">
                </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"nav-link"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"/article/create"</span><span class="tag">&gt;</span><span class="pln">
                  </span><span class="tag">&lt;i</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"fas fa-plus ml-1"</span><span class="tag">&gt;&lt;/i&gt;</span><span class="pln">
                  إنشاء مقال 
                  </span><span class="tag">&lt;span</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"sr-only"</span><span class="tag">&gt;</span><span class="pln">(current)</span><span class="tag">&lt;/span&gt;</span><span class="pln">
                </span><span class="tag">&lt;/a&gt;</span><span class="pln">
              </span><span class="tag">&lt;/li&gt;</span><span class="pln">
            </span><span class="tag">&lt;/ul&gt;</span><span class="pln">

            </span><span class="tag">&lt;form</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"form-inline mt-2 mt-md-0"</span><span class="pln"> </span><span class="atn">action</span><span class="pun">=</span><span class="atv">"/search"</span><span class="pln"> </span><span class="atn">method</span><span class="pun">=</span><span class="atv">"GET"</span><span class="pln"> </span><span class="tag">&gt;</span><span class="pln">
              </span><span class="tag">&lt;input</span><span class="pln"> </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"q"</span><span class="pln"> </span><span class="atn">id</span><span class="pun">=</span><span class="atv">"q"</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"form-control ml-sm-2"</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"text"</span><span class="pln"> </span><span class="atn">placeholder</span><span class="pun">=</span><span class="atv">"عن ماذا تريد أن تبحث ؟"</span><span class="pln"> </span><span class="atn">aria-label</span><span class="pun">=</span><span class="atv">"Search"</span><span class="pln"> </span><span class="atn">required</span><span class="tag">&gt;</span><span class="pln">
              </span><span class="tag">&lt;button</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"btn btn-primary my-2 my-sm-0"</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"submit"</span><span class="tag">&gt;</span><span class="pln">ابحث</span><span class="tag">&lt;/button&gt;</span><span class="pln">
            </span><span class="tag">&lt;/form&gt;</span><span class="pln">

          </span><span class="tag">&lt;/div&gt;</span><span class="pln">
        </span><span class="tag">&lt;/div&gt;</span><span class="pln">
      </span><span class="tag">&lt;/nav&gt;</span></pre>

<p>
	وهذه هي ترويسة الصفحة، استدعينا فيها <a href="https://wiki.hsoub.com/Bootstrap" rel="external">مكتبة Bootstrap</a> من نظام توصيل المحتوى CDN، واستدعينا فيها مكتبة fontawesome من نظام توصيل المحتوى CDN، واستدعينا ملف style.css الذي أنشأناه، كما بنينا شريط التنقل للموقع.
</p>

<p>
	سننشئ ضمن المجلد views ملفًا آخر اسمه footer.ejs وهو تذييل الصفحة، ونكتب فيه التالي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1547_9" style="">
<span class="tag">&lt;/div&gt;</span><span class="pln">
    </span><span class="tag">&lt;script</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"https://code.jquery.com/jquery-3.5.1.min.js"</span><span class="pln"> </span><span class="atn">integrity</span><span class="pun">=</span><span class="atv">"sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="</span><span class="pln"> </span><span class="atn">crossorigin</span><span class="pun">=</span><span class="atv">"anonymous"</span><span class="tag">&gt;&lt;/script&gt;</span><span class="pln">
    </span><span class="tag">&lt;script</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js"</span><span class="pln"> </span><span class="atn">integrity</span><span class="pun">=</span><span class="atv">"sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI"</span><span class="pln"> </span><span class="atn">crossorigin</span><span class="pun">=</span><span class="atv">"anonymous"</span><span class="tag">&gt;&lt;/script&gt;</span><span class="pln">
    </span><span class="tag">&lt;script</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"../public/script.js"</span><span class="tag">&gt;&lt;/script&gt;</span><span class="pln">
</span><span class="tag">&lt;/body&gt;</span><span class="pln">

</span><span class="tag">&lt;/html&gt;</span></pre>

<p>
	وهنا أيضًا استدعينا مكتبة jQuery وملف JavaScript الخاص <a data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="https://academy.hsoub.com/programming/html/bootstrap/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D8%A8%D9%88%D8%AA%D8%B3%D8%AA%D8%B1%D8%A7%D8%A8-5-r1315/" rel="">بمكتبة Bootstrap</a>، وملف script.js الذى يحتوى على السكربت الخاص بالتطبيق، وأغلقنا الأوسمة المفتوحة الموجودة في ملف header.ejs.
</p>

<p>
	سنهيئ الآن الصفحة الرئيسية في الموقع والتي ستمكننا من عرض المقالات. لننشئ الآن ملف index.ejs أيضًا في مجلد views ولنكتب فيه:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1547_14" style="">
<span class="pun">&lt;%-</span><span class="pln"> include</span><span class="pun">(</span><span class="str">'_header'</span><span class="pun">)</span><span class="pln"> %&gt;

</span><span class="tag">&lt;main</span><span class="pln"> </span><span class="atn">role</span><span class="pun">=</span><span class="atv">"main"</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"container-fluid"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"jumbotron"</span><span class="tag">&gt;</span><span class="pln">
      </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"container"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;h1</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"text-center"</span><span class="tag">&gt;</span><span class="pln">أحدث المقالات</span><span class="tag">&lt;/h1&gt;</span><span class="pln">
      </span><span class="tag">&lt;/div&gt;</span><span class="pln">
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"container pt-4 pb-4"</span><span class="tag">&gt;</span><span class="pln">
      </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"row"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="pun">&lt;%</span><span class="pln"> </span><span class="kwd">if</span><span class="pun">(!</span><span class="pln">articles </span><span class="pun">||</span><span class="pln"> articles</span><span class="pun">.</span><span class="pln">length </span><span class="pun">==</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> %&gt;
          </span><span class="tag">&lt;h5</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"col mt-5 pt-5 text-center"</span><span class="tag">&gt;</span><span class="pln">لا توجد مقالات لعرضها</span><span class="tag">&lt;/h5&gt;</span><span class="pln">
          </span><span class="pun">&lt;%</span><span class="pln"> </span><span class="pun">}</span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> %&gt;
          </span><span class="pun">&lt;%</span><span class="pln"> articles</span><span class="pun">.</span><span class="pln">forEach</span><span class="pun">(</span><span class="pln">article </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> %&gt;
            </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"mb-4 col-md-6 col-sm-12"</span><span class="tag">&gt;</span><span class="pln">
              </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"card shadow-sm p-3 bg-white rounded"</span><span class="tag">&gt;</span><span class="pln">
                </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"card-body"</span><span class="tag">&gt;</span><span class="pln">
                  </span><span class="tag">&lt;h2</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"card-title mb-3"</span><span class="tag">&gt;</span><span class="pln">
                    &lt;a class="text-primary text-decoration-none" href="/article/</span><span class="pun">&lt;%=</span><span class="pln"> article</span><span class="pun">.</span><span class="pln">id %&gt;"&gt;</span><span class="pun">&lt;%=</span><span class="pln"> article</span><span class="pun">.</span><span class="pln">title %&gt;</span><span class="tag">&lt;/a&gt;</span><span class="pln">
                  </span><span class="tag">&lt;/h2&gt;</span><span class="pln">
                  </span><span class="tag">&lt;p</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"card-text mb-4"</span><span class="tag">&gt;&lt;i</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"fas fa-user-circle"</span><span class="tag">&gt;&lt;/i&gt;</span><span class="pln">  </span><span class="pun">&lt;%=</span><span class="pln"> article</span><span class="pun">.</span><span class="pln">author %&gt;</span><span class="tag">&lt;/p&gt;</span><span class="pln">
                  &lt;button class="delete-btn btn btn-danger btn-sm" data-id="</span><span class="pun">&lt;%=</span><span class="pln"> article</span><span class="pun">.</span><span class="pln">id %&gt;"&gt;حذف المقال</span><span class="tag">&lt;/button&gt;</span><span class="pln">
                </span><span class="tag">&lt;/div&gt;</span><span class="pln">
              </span><span class="tag">&lt;/div&gt;</span><span class="pln">
            </span><span class="tag">&lt;/div&gt;</span><span class="pln">
          </span><span class="pun">&lt;%</span><span class="pln"> </span><span class="pun">})</span><span class="pln"> %&gt;
          </span><span class="pun">&lt;%</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> %&gt;
      </span><span class="tag">&lt;/div&gt;</span><span class="pln">
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
  </span><span class="tag">&lt;/main&gt;</span><span class="pln">



</span><span class="pun">&lt;%-</span><span class="pln"> include</span><span class="pun">(</span><span class="str">'_footer'</span><span class="pun">)</span><span class="pln"> %&gt;</span></pre>

<p>
	الشيفرات المحاطة بالإشارتين <code>&lt;%</code> و <code>%&gt;</code> هي شيفرات JavaScript يقرؤها <a data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="https://academy.hsoub.com/questions/16497-%D8%A5%D8%B8%D9%87%D8%A7%D8%B1-%D8%B5%D9%81%D8%AD%D8%A9-html-%D8%B6%D9%85%D9%86-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-expressjs-%D9%81%D9%8A-nodejs/" rel="">ejs</a> لتنفيذ ما ضمنها، وفي حالتنا هنا نستورد الترويسة والتذييل للصفحات، ونضع ما يعبر عن المقالات في الكائن الذي سنوفره عند تنفيذ الصفحة حتى نعرض بياناته.
</p>

<p>
	لنضع تنسيقًا لتحسين شكل الصفحة، إذ سنكتب شيفرة التنسيق التالية في الملف style.css الذي أنشأناه في المجلد public:
</p>

<pre class="ipsCode prettyprint lang-css prettyprinted" id="ips_uid_2287_22" style="">
<span class="com">/*-الخط المستخدم بالموقع-*/</span><span class="pln">
</span><span class="lit">@import</span><span class="pln"> url</span><span class="pun">(</span><span class="str">'https://fonts.googleapis.com/css2?family=Almarai&amp;display=swap'</span><span class="pun">);</span><span class="pln">
</span><span class="lit">@import</span><span class="pln"> url</span><span class="pun">(</span><span class="str">'https://fonts.googleapis.com/css2?family=Almarai:wght@700&amp;display=swap'</span><span class="pun">);</span><span class="pln">

</span><span class="com">/*-الإعدادات العامة للموقع-*/</span><span class="pln">
body </span><span class="pun">{</span><span class="pln">
    direction</span><span class="pun">:</span><span class="pln"> rtl</span><span class="pun">;</span><span class="pln">
    text</span><span class="pun">-</span><span class="pln">align</span><span class="pun">:</span><span class="pln"> right</span><span class="pun">;</span><span class="pln">
    font</span><span class="pun">-</span><span class="pln">family</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Almarai'</span><span class="pun">,</span><span class="pln"> sans</span><span class="pun">-</span><span class="pln">serif</span><span class="pun">;</span><span class="pln">
    font</span><span class="pun">-</span><span class="pln">size</span><span class="pun">:</span><span class="pln"> </span><span class="lit">16px</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="pun">.</span><span class="pln">bg</span><span class="pun">-</span><span class="pln">light</span><span class="pun">{</span><span class="pln">
    background</span><span class="pun">-</span><span class="pln">color</span><span class="pun">:</span><span class="pln"> </span><span class="com">#e9ecef !important;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

textarea</span><span class="pun">{</span><span class="pln">
    resize</span><span class="pun">:</span><span class="pln"> none</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="pun">.</span><span class="pln">container</span><span class="pun">--</span><span class="pln">error</span><span class="pun">{</span><span class="pln">
    margin</span><span class="pun">-</span><span class="pln">top</span><span class="pun">:</span><span class="pln"> </span><span class="lit">70px</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="pun">.</span><span class="pln">container</span><span class="pun">--</span><span class="pln">error p</span><span class="pun">{</span><span class="pln">
    font</span><span class="pun">-</span><span class="pln">weight</span><span class="pun">:</span><span class="pln"> </span><span class="lit">700</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="pun">.</span><span class="pln">container</span><span class="pun">-</span><span class="pln">fluid</span><span class="pun">{</span><span class="pln">
    padding</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> </span><span class="pun">!</span><span class="pln">important</span><span class="pun">;</span><span class="pln">
    margin</span><span class="pun">-</span><span class="pln">top</span><span class="pun">:</span><span class="pln"> </span><span class="lit">56px</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="pun">.</span><span class="pln">navbar</span><span class="pun">{</span><span class="pln">
    background</span><span class="pun">-</span><span class="pln">color</span><span class="pun">:</span><span class="pln"> </span><span class="com">#e9ecef;</span><span class="pln">
    padding</span><span class="pun">:</span><span class="pln"> </span><span class="lit">15px</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="pun">.</span><span class="pln">navbar</span><span class="pun">-</span><span class="pln">toggler</span><span class="pun">{</span><span class="pln">
    border</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1px</span><span class="pln"> solid </span><span class="com">#212529;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="pun">.</span><span class="pln">navbar</span><span class="pun">-</span><span class="pln">toggler</span><span class="pun">:</span><span class="pln">focus</span><span class="pun">{</span><span class="pln">
    border</span><span class="pun">:</span><span class="lit">1px</span><span class="pln"> solid </span><span class="com">#212529 !important; </span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="pun">.</span><span class="pln">navbar</span><span class="pun">-</span><span class="pln">brand</span><span class="pun">{</span><span class="pln">
    color</span><span class="pun">:</span><span class="pln"> </span><span class="com">#212529 !important;</span><span class="pln">
    font</span><span class="pun">-</span><span class="pln">weight</span><span class="pun">:</span><span class="pln"> </span><span class="lit">700</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="pun">.</span><span class="pln">navbar </span><span class="pun">.</span><span class="pln">navbar</span><span class="pun">-</span><span class="pln">collapse form</span><span class="pun">{</span><span class="pln">
    margin</span><span class="pun">-</span><span class="pln">right</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">auto</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="pun">.</span><span class="pln">navbar </span><span class="pun">.</span><span class="pln">navbar</span><span class="pun">-</span><span class="pln">nav</span><span class="pun">{</span><span class="pln">
    padding</span><span class="pun">-</span><span class="pln">right</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="pun">.</span><span class="pln">navbar </span><span class="pun">.</span><span class="pln">navbar</span><span class="pun">-</span><span class="pln">nav </span><span class="pun">.</span><span class="pln">nav</span><span class="pun">-</span><span class="pln">link</span><span class="pun">{</span><span class="pln">
    color</span><span class="pun">:</span><span class="pln"> </span><span class="com">#212529;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="pun">.</span><span class="pln">navbar </span><span class="pun">.</span><span class="pln">navbar</span><span class="pun">-</span><span class="pln">nav </span><span class="pun">.</span><span class="pln">nav</span><span class="pun">-</span><span class="pln">link i</span><span class="pun">{</span><span class="pln">
    font</span><span class="pun">-</span><span class="pln">size</span><span class="pun">:</span><span class="pln"> </span><span class="lit">13px</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="pun">.</span><span class="pln">jumbotron</span><span class="pun">{</span><span class="pln">
    border</span><span class="pun">-</span><span class="pln">radius</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
    padding</span><span class="pun">:</span><span class="pln"> </span><span class="lit">6rem</span><span class="pln"> </span><span class="lit">2rem</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="pun">.</span><span class="pln">card</span><span class="pun">{</span><span class="pln">
    border</span><span class="pun">:</span><span class="pln"> none</span><span class="pun">;</span><span class="pln">
    height</span><span class="pun">:</span><span class="pln"> </span><span class="lit">100</span><span class="pun">%;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="pun">.</span><span class="pln">card</span><span class="pun">-</span><span class="pln">title a</span><span class="pun">{</span><span class="pln">
    transition</span><span class="pun">:</span><span class="pln"> all </span><span class="lit">0.3s</span><span class="pln"> ease</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="pun">.</span><span class="pln">form</span><span class="pun">-</span><span class="pln">control</span><span class="pun">{</span><span class="pln">
    border</span><span class="pun">:</span><span class="pln"> none</span><span class="pun">;</span><span class="pln">
    height</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">auto</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="pun">.</span><span class="pln">article</span><span class="pun">-</span><span class="pln">head</span><span class="pun">{</span><span class="pln">
    display</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">inline</span><span class="pun">-</span><span class="pln">flex</span><span class="pun">;</span><span class="pln">
    align</span><span class="pun">-</span><span class="pln">items</span><span class="pun">:</span><span class="pln"> flex</span><span class="pun">-</span><span class="pln">start</span><span class="pun">;</span><span class="pln">
    justify</span><span class="pun">-</span><span class="pln">content</span><span class="pun">:</span><span class="pln"> center</span><span class="pun">;</span><span class="pln">
    flex</span><span class="pun">-</span><span class="pln">direction</span><span class="pun">:</span><span class="pln"> column</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="pun">.</span><span class="pln">article</span><span class="pun">-</span><span class="pln">head h5</span><span class="pun">{</span><span class="pln">
    color</span><span class="pun">:</span><span class="pln"> </span><span class="com">#868686;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="com">/*-responsive-*/</span><span class="pln">
</span><span class="lit">@media</span><span class="pln"> </span><span class="pun">(</span><span class="pln">min</span><span class="pun">-</span><span class="pln">width</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1100px</span><span class="pun">){</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">container</span><span class="pun">--</span><span class="pln">article</span><span class="pun">-</span><span class="pln">create</span><span class="pun">{</span><span class="pln">
        max</span><span class="pun">-</span><span class="pln">width</span><span class="pun">:</span><span class="pln"> </span><span class="lit">900px</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="pun">.</span><span class="pln">container</span><span class="pun">--</span><span class="pln">article</span><span class="pun">-</span><span class="pln">view</span><span class="pun">{</span><span class="pln">
        max</span><span class="pun">-</span><span class="pln">width</span><span class="pun">:</span><span class="pln"> </span><span class="lit">900px</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">


</span><span class="lit">@media</span><span class="pln"> </span><span class="pun">(</span><span class="pln">max</span><span class="pun">-</span><span class="pln">width</span><span class="pun">:</span><span class="pln"> </span><span class="lit">768px</span><span class="pun">){</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">navbar</span><span class="pun">-</span><span class="pln">nav </span><span class="pun">&gt;</span><span class="pln"> li</span><span class="pun">:</span><span class="pln">first</span><span class="pun">-</span><span class="pln">child</span><span class="pun">{</span><span class="pln">
        margin</span><span class="pun">-</span><span class="pln">top</span><span class="pun">:</span><span class="pln"> </span><span class="lit">20px</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">navbar  </span><span class="pun">&gt;</span><span class="pln"> </span><span class="pun">.</span><span class="pln">container</span><span class="pun">{</span><span class="pln">
        padding</span><span class="pun">-</span><span class="pln">right</span><span class="pun">:</span><span class="pln"> </span><span class="lit">15px</span><span class="pun">;</span><span class="pln">
        padding</span><span class="pun">-</span><span class="pln">left</span><span class="pun">:</span><span class="pln"> </span><span class="lit">15px</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="lit">@media</span><span class="pln"> </span><span class="pun">(</span><span class="pln">max</span><span class="pun">-</span><span class="pln">width</span><span class="pun">:</span><span class="pln"> </span><span class="lit">576px</span><span class="pun">){</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">navbar</span><span class="pun">-</span><span class="pln">collapse form button</span><span class="pun">[</span><span class="pln">type</span><span class="pun">=</span><span class="pln">submit</span><span class="pun">]{</span><span class="pln">
        width</span><span class="pun">:</span><span class="pln"> </span><span class="lit">100</span><span class="pun">%;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">navbar  </span><span class="pun">&gt;</span><span class="pln"> </span><span class="pun">.</span><span class="pln">container</span><span class="pun">{</span><span class="pln">
        padding</span><span class="pun">-</span><span class="pln">right</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
        padding</span><span class="pun">-</span><span class="pln">left</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	أنشأنا حتى الآن الصفحة الرئيسية التي ستعرض كل المقالات أو نتائج البحث، ولننشئ الآن الصفحة التي ستمكننا من إضافة مقالات جديدة؛ سننشئ في مجلد views ملفًا اسمه create_article.ejs ونكتب فيه:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2287_24" style="">
<span class="pun">&lt;%-</span><span class="pln"> include</span><span class="pun">(</span><span class="str">'_header'</span><span class="pun">);</span><span class="pln"> </span><span class="pun">%&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">main role</span><span class="pun">=</span><span class="str">"main"</span><span class="pln"> </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"container-fluid"</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"jumbotron"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"container"</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">h1 </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"text-center"</span><span class="pun">&gt;أنشئ</span><span class="pln"> </span><span class="pun">مقالًا&lt;/</span><span class="pln">h1</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"container container--article-create pt4 pb-4"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">form action</span><span class="pun">=</span><span class="str">"/article"</span><span class="pln"> </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"pt-5 pb-5"</span><span class="pln"> method</span><span class="pun">=</span><span class="str">"POST"</span><span class="pln"> id</span><span class="pun">=</span><span class="str">"create-article-form"</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"form-group"</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">label </span><span class="kwd">for</span><span class="pun">=</span><span class="str">"title"</span><span class="pun">&gt;عنوان</span><span class="pln"> </span><span class="pun">المقال&lt;/</span><span class="pln">label</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">input type</span><span class="pun">=</span><span class="str">"text"</span><span class="pln"> </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"form-control pt-2 pb-2 mt-1 bg-light"</span><span class="pln"> id</span><span class="pun">=</span><span class="str">"title"</span><span class="pln"> name</span><span class="pun">=</span><span class="str">"title"</span><span class="pln">
                    placeholder</span><span class="pun">=</span><span class="str">"أدخل عنوانًا مناسبًا لمقالك"</span><span class="pln"> required</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"form-group"</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">label </span><span class="kwd">for</span><span class="pun">=</span><span class="str">"author "</span><span class="pun">&gt;الكاتب&lt;/</span><span class="pln">label</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">input type</span><span class="pun">=</span><span class="str">"text"</span><span class="pln"> </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"form-control pt-2 pb-2 mt-1 bg-light"</span><span class="pln"> id</span><span class="pun">=</span><span class="str">"author"</span><span class="pln"> name</span><span class="pun">=</span><span class="str">"author"</span><span class="pln"> placeholder</span><span class="pun">=</span><span class="str">"ما اسم كاتب المقال؟"</span><span class="pln"> required</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"form-group"</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">label </span><span class="kwd">for</span><span class="pun">=</span><span class="str">"content"</span><span class="pun">&gt;نص</span><span class="pln"> </span><span class="pun">المقال&lt;/</span><span class="pln">label</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">textarea </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"form-control pt-2 pb-2 mt-1 bg-light"</span><span class="pln"> id</span><span class="pun">=</span><span class="str">"content"</span><span class="pln"> name</span><span class="pun">=</span><span class="str">"content"</span><span class="pln"> placeholder</span><span class="pun">=</span><span class="str">"نص المقال هنا"</span><span class="pln">
                    rows</span><span class="pun">=</span><span class="str">"7"</span><span class="pln"> required</span><span class="pun">&gt;&lt;/</span><span class="pln">textarea</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"form-group"</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">label </span><span class="kwd">for</span><span class="pun">=</span><span class="str">"tags"</span><span class="pun">&gt;الوسوم&lt;/</span><span class="pln">label</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">input type</span><span class="pun">=</span><span class="str">"text"</span><span class="pln"> </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"form-control pt-2 pb-2 mt-1 bg-light"</span><span class="pln"> id</span><span class="pun">=</span><span class="str">"tags"</span><span class="pln"> name</span><span class="pun">=</span><span class="str">"tags"</span><span class="pln"> placeholder</span><span class="pun">=</span><span class="str">"مثال على الوسوم: الكتابة,العمل"</span><span class="pln"> required</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"form-group text-center"</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">input type</span><span class="pun">=</span><span class="str">"submit"</span><span class="pln"> </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"btn btn-lg btn-block mt-3 btn-primary"</span><span class="pln"> value</span><span class="pun">=</span><span class="str">"أنشئ المقال"</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">form</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">main</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;%-</span><span class="pln"> include</span><span class="pun">(</span><span class="str">'_footer'</span><span class="pun">);</span><span class="pln"> </span><span class="pun">%&gt;</span></pre>

<p>
	والآن لننشئ الصفحة التي ستعرض مقالًا عند النقر على عنوانه في الصفحة الرئيسية؛ سننشئ ضمن المجلد views ملفًا اسمه article.ejs ونكتب فيه:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2287_26" style="">
<span class="pun">&lt;%-</span><span class="pln"> include</span><span class="pun">(</span><span class="str">'_header'</span><span class="pun">);</span><span class="pln"> </span><span class="pun">%&gt;</span><span class="pln">

</span><span class="pun">&lt;</span><span class="pln">article </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"container-fluid"</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"jumbotron"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"container text-center"</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;%</span><span class="pln"> </span><span class="kwd">if</span><span class="pun">(!</span><span class="pln">article</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="pun">%&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">h1 </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"text-center"</span><span class="pun">&gt;لا</span><span class="pln"> </span><span class="pun">يوجد</span><span class="pln"> </span><span class="pun">مقالات!&lt;/</span><span class="pln">h1</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;%</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="pun">%&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"article-head text-right"</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">h1 </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"text-right mb-3"</span><span class="pun">&gt;&lt;%=</span><span class="pln"> article</span><span class="pun">.</span><span class="pln">title </span><span class="pun">%&gt;&lt;/</span><span class="pln">h1</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">h5 </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"text-right mt-1"</span><span class="pun">&gt;الكاتب:</span><span class="pln"> </span><span class="pun">&lt;%=</span><span class="pln"> article</span><span class="pun">.</span><span class="pln">author </span><span class="pun">%&gt;&lt;/</span><span class="pln">h5</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">

            </span><span class="pun">&lt;%</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> </span><span class="pun">%&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"container container--article-view"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;%</span><span class="pln"> </span><span class="kwd">if</span><span class="pun">(!</span><span class="pln">article</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="pun">%&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">h1</span><span class="pun">&gt;المقال</span><span class="pln"> </span><span class="pun">الذي</span><span class="pln"> </span><span class="pun">طلبته</span><span class="pln"> </span><span class="pun">غير</span><span class="pln"> </span><span class="pun">موجود!&lt;/</span><span class="pln">h1</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">a href</span><span class="pun">=</span><span class="str">"/"</span><span class="pln"> </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"btn btn-secondary btn-sm"</span><span class="pun">&gt;عد</span><span class="pln"> </span><span class="pun">إلى</span><span class="pln"> </span><span class="pun">الصفحة</span><span class="pln"> </span><span class="pun">الرئيسية&lt;/</span><span class="pln">a</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;%</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="pun">%&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;%-</span><span class="pln">article</span><span class="pun">.</span><span class="pln">content</span><span class="pun">%&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;%</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> </span><span class="pun">%&gt;</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">article</span><span class="pun">&gt;</span><span class="pln">

</span><span class="pun">&lt;%-</span><span class="pln"> include</span><span class="pun">(</span><span class="str">'_footer'</span><span class="pun">);</span><span class="pln"> </span><span class="pun">%&gt;</span></pre>

<p>
	والآن لننشئ الصفحة التي ستعرض الخطأ للمستخدم في حالة قام بكتابة رابط غير موجود، سننشئ ضمن المجلد views ملفًا اسمه error.ejs ونكتب فيه:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2287_28" style="">
<span class="pun">&lt;%-</span><span class="pln"> include</span><span class="pun">(</span><span class="str">"_header"</span><span class="pun">);</span><span class="pln"> </span><span class="pun">%&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"container-fluid"</span><span class="pun">&gt;</span><span class="pln">

    </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"jumbotron"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"container"</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">h1 </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"text-center"</span><span class="pun">&gt;حدث</span><span class="pln"> </span><span class="pun">خطأ</span><span class="pln"> </span><span class="pun">ما!&lt;/</span><span class="pln">h1</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"container container--error text-center"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">h2 </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"text-center"</span><span class="pun">&gt;أعد</span><span class="pln"> </span><span class="pun">المحاولة</span><span class="pln"> </span><span class="pun">لاحقًا</span><span class="pln"> </span><span class="pun">&lt;/</span><span class="pln">h2</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">a href</span><span class="pun">=</span><span class="str">"/"</span><span class="pln"> </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"btn btn-lg mt-3 btn-primary"</span><span class="pun">&gt;عد</span><span class="pln"> </span><span class="pun">إلى</span><span class="pln"> </span><span class="pun">الصفحة</span><span class="pln"> </span><span class="pun">الرئيسية&lt;/</span><span class="pln">a</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">

</span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;%-</span><span class="pln"> include</span><span class="pun">(</span><span class="str">"_footer"</span><span class="pun">);</span><span class="pln"> </span><span class="pun">%&gt;</span></pre>

<h2 id="تهيئة-إدارة-قواعد-البيانات-وإنشاء-الموجهات">
	تهيئة إدارة قواعد البيانات وإنشاء الموجهات Routes
</h2>

<p>
	بعد أن انتهينا من تصميم الصفحات، حان الوقت الآن لإنشاء طريقة <a data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="https://academy.hsoub.com/programming/sql/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D8%A5%D8%AF%D8%AE%D8%A7%D9%84%D8%8C-%D8%A7%D9%84%D8%AD%D8%B0%D9%81-%D9%88%D8%A7%D9%84%D8%AA%D8%B9%D8%AF%D9%8A%D9%84-%D9%81%D9%8A-sql-r587/" rel="">التعامل مع قواعد البيانات</a>، وكما تحدثنا سابقًا، سنستخدم Sequelize التي هي ORM شهيرة في <a href="https://wiki.hsoub.com/Node.js" rel="external">Node.js</a>.
</p>

<p>
	سننشِئ ملفًا جديدًا باسم sequelize.js، وسنضع فيه الشيفرة الآتية لتهيئة عملية الاتصال بقاعدة البيانات، وأذكِّرك بتغيير اسم المستخدم وكلمة المرور وفقًا لبيئة العمل الخاصة بك:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2287_30" style="">
<span class="kwd">const</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="typ">Sequelize</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"sequelize"</span><span class="pun">);</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> DATABASE_NAME </span><span class="pun">=</span><span class="pln"> </span><span class="str">"myBlog"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> USER_NAME </span><span class="pun">=</span><span class="pln"> </span><span class="str">"root"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> PASSWORD </span><span class="pun">=</span><span class="pln"> </span><span class="str">"password"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> DIALECT </span><span class="pun">=</span><span class="pln"> </span><span class="str">"mariadb"</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> sequelize </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Sequelize</span><span class="pun">(</span><span class="pln">DATABASE_NAME</span><span class="pun">,</span><span class="pln"> USER_NAME</span><span class="pun">,</span><span class="pln"> PASSWORD</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    dialect</span><span class="pun">:</span><span class="pln"> DIALECT</span><span class="pun">,</span><span class="pln">
    dialectOptions</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> connectTimeout</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1000</span><span class="pln"> </span><span class="pun">},</span><span class="pln">
    logging</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">,</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

module</span><span class="pun">.</span><span class="pln">exports </span><span class="pun">=</span><span class="pln"> sequelize</span><span class="pun">;</span></pre>

<p>
	لتستطيع حزمة sequelize التعرف على قاعدة البيانات من نوع MariaDB (أو MySQL)، علينا تثبيت الحزمة المُخصَّصة لها بتنفيذ الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2287_32" style="">
<span class="pln">npm install mariadb</span></pre>

<p>
	وبهذا أصبح المشروع جاهزًا لكتابة الموجهات routes؛ لننشئ مجلدًا باسم routes بداخله ملف article.routes.js يحوي الشيفرة الآتية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2287_34" style="">
<span class="kwd">const</span><span class="pln"> controller </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"../controllers/article.controller"</span><span class="pun">);</span><span class="pln">
module</span><span class="pun">.</span><span class="pln">exports </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">app</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/"</span><span class="pun">,</span><span class="pln"> controller</span><span class="pun">.</span><span class="pln">viewIndex</span><span class="pun">);</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	ولكن يجب أن ننشئ مجلدًا باسم controllers وننشئ ملف باسم article.controller.js ونكتب فيه ما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2287_36" style="">
<span class="kwd">const</span><span class="pln"> </span><span class="typ">Article</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"../models/article"</span><span class="pun">);</span><span class="pln">

exports</span><span class="pun">.</span><span class="pln">viewIndex </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="typ">Article</span><span class="pun">.</span><span class="pln">findAll</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">then</span><span class="pun">((</span><span class="pln">articles</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">"index"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> articles</span><span class="pun">:</span><span class="pln"> articles </span><span class="pun">});</span><span class="pln">
    </span><span class="pun">})</span><span class="pln">
    </span><span class="pun">.</span><span class="kwd">catch</span><span class="pun">((</span><span class="pln">error</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      next</span><span class="pun">(</span><span class="pln">error</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">});</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	هيأنا هنا الموجه الرئيسي، وعند طلب العنوان localhost:5000 في المتصفح فسيجلب الخادم كل المقالات من قاعدة البيانات، ويفسر الصفحة index.ejs ويعطيها الكائن articles حتى يعرض معلومات المقالات.
</p>

<p>
	نشغل المشروع عن طريق كتابة الأمر التالي في ملف المشروع في سطر الأوامر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2287_38" style="">
<span class="pln">npm start</span></pre>

<p>
	يجدر بالذكر أننا عدلنا القسم <code>scripts</code> في ملف package.json ووضعنا سكربت start لبدء تشغيل التطبيق عبر <code>node</code> للملف app.js.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2287_40" style="">
<span class="pun">[...]</span><span class="pln">
  </span><span class="str">"scripts"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">"test"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"echo \"Error: no test specified\" &amp;&amp; exit 1"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"start"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"node app.js"</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
</span><span class="pun">[...]</span></pre>

<p>
	في الخطوة التالية لنهيئ المستقبل الذي يوجهنا إلى نموذج أو استمارة إنشاء مقال؛ سنضيف داخل ملف article.routes.js بعد الشيفرة السابقة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2287_42" style="">
<span class="pln">  app</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">"/article"</span><span class="pun">,</span><span class="pln"> controller</span><span class="pun">.</span><span class="pln">create</span><span class="pun">);</span><span class="pln">
  app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/article/create"</span><span class="pun">,</span><span class="pln"> controller</span><span class="pun">.</span><span class="pln">viewCreate</span><span class="pun">);</span></pre>

<p>
	بعد ذلك نضيف الشيفرة الآتية داخل ملف article.controller.js:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2287_44" style="">
<span class="pln">exports</span><span class="pun">.</span><span class="pln">viewCreate </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">"create_article"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">};</span><span class="pln">

exports</span><span class="pun">.</span><span class="pln">create </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="typ">Article</span><span class="pun">.</span><span class="pln">create</span><span class="pun">(</span><span class="pln">req</span><span class="pun">.</span><span class="pln">body</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">then</span><span class="pun">((</span><span class="pln">article</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      article</span><span class="pun">.</span><span class="pln">save</span><span class="pun">();</span><span class="pln">
      res</span><span class="pun">.</span><span class="pln">redirect</span><span class="pun">(</span><span class="str">"/"</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">})</span><span class="pln">
    </span><span class="pun">.</span><span class="kwd">catch</span><span class="pun">((</span><span class="pln">error</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      next</span><span class="pun">(</span><span class="pln">error</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">});</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	وبهذا نستطيع الآن إنشاء مقالات جديدة، واستعراضها من الصفحة الرئيسية، وحتى نرى المقال بعد الضغط على بطاقته في الصفحة الرئيسية، سنضيف ما يلي داخل ملف article.routes.js بعد الشيفرة السابقة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2287_46" style="">
<span class="pln">  app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/article/:id"</span><span class="pun">,</span><span class="pln"> controller</span><span class="pun">.</span><span class="pln">viewArticle</span><span class="pun">);</span></pre>

<p>
	بعد ذلك نضيف الشيفرة الآتية داخل ملف article.controller.js:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2287_48" style="">
<span class="pln">exports</span><span class="pun">.</span><span class="pln">viewArticle </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="typ">Article</span><span class="pun">.</span><span class="pln">findOne</span><span class="pun">({</span><span class="pln">
    where</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      id</span><span class="pun">:</span><span class="pln"> req</span><span class="pun">.</span><span class="pln">params</span><span class="pun">.</span><span class="pln">id</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">},</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">then</span><span class="pun">((</span><span class="pln">article</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">"article"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> article</span><span class="pun">:</span><span class="pln"> article </span><span class="pun">});</span><span class="pln">
    </span><span class="pun">})</span><span class="pln">
    </span><span class="pun">.</span><span class="kwd">catch</span><span class="pun">((</span><span class="pln">error</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      next</span><span class="pun">(</span><span class="pln">error</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">});</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	وبهذا أصبح المشروع جاهزًا لإضافة وعرض المقالات من قاعدة البيانات.
</p>

<p>
	لا تنسَ حفظ كل شيء ثم تشغيل المشروع، أو استخدم حزمة nodemon لتعيد تشغيل المشروع تلقائيًا عند كل تعديل تجريه على شيفراتك.
</p>

<h2 id="إضافة-المقالات">
	إضافة المقالات
</h2>

<p>
	سننشئ بداية مقالًا للتحقق من صحة الإدخال إلى قاعدة البيانات، وليكن بالمعلومات التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="80280" data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="https://academy.hsoub.com/uploads/monthly_2021_10/617122729a161_.png.dcf2ea9832aaec23e504b9facd3e46c6.png" rel=""><img alt="إضافة المقالات.png" class="ipsImage ipsImage_thumbnailed" data-fileid="80280" data-unique="sqkiwyejy" src="https://academy.hsoub.com/uploads/monthly_2021_10/61712277212ba_.thumb.png.12d500ac3c8662adcfed925f6533ef86.png" style="width: 600px; height: auto;"></a>
</p>

<p>
	وبعد أن تحققت من صحة العمليات التي بنيتها، سأنشئ مجموعة من المقالات ذات المواضيع المتقاربة من أكاديمية حسوب، ولا بد أن نتحدث عن كل المقالات التي أدخلناها حتى نفهم نتائج البحث.
</p>

<p>
	سأدخل المقال الأول بعنوان «<a data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="https://academy.hsoub.com/entrepreneurship/tips/%D9%83%D9%8A%D9%81-%D8%AA%D9%82%D8%AF%D9%91%D8%B1-%D8%A3%D8%AC%D8%B1-%D9%85%D8%B3%D8%A7%D8%B9%D8%AF%D9%83-%D8%B9%D9%86-%D8%A8%D8%B9%D8%AF-r525/" rel="">كيف تقدّر أجر مساعدك عن بعد</a>» وسأدخل اسم الكاتب عدنان، وفي نص المقال سأنسخ فقط المقدمة، وفي صندوق الوسوم سأدخل كلمة أجر مساعدك.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="80281" data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="https://academy.hsoub.com/uploads/monthly_2021_10/6171227abea14_.png.fa56088edf974b820d6c4789375dfd47.png" rel=""><img alt="إنشاء مقال.png" class="ipsImage ipsImage_thumbnailed" data-fileid="80281" data-unique="70ohuim5k" src="https://academy.hsoub.com/uploads/monthly_2021_10/6171227f66f75_.thumb.png.150898df498bdb38de790694dbe2df0c.png" style="width: 600px; height: auto;"></a>
</p>

<p>
	المقال الثاني بعنوان «<a data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="https://academy.hsoub.com/entrepreneurship/tips/%D8%A3%D8%B3%D8%B3-%D8%A7%D9%84%D9%82%D9%8A%D8%A7%D8%AF%D8%A9-%D8%A7%D9%84%D8%AA%D9%82%D9%86%D9%8A%D8%A9-r452/" rel="">أسس القيادة التقنية</a>» والكاتب ابراهيم سليمان، وسأنسخ فقط المقدمة، وفي صندوق الوسوم سأدخل أسس القيادة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="80277" data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="https://academy.hsoub.com/uploads/monthly_2021_10/617122566169f_.png.78b1b0dc9557034a976cdada37ecd64e.png" rel=""><img alt="أسس القيادة التقنية.png" class="ipsImage ipsImage_thumbnailed" data-fileid="80277" data-unique="gwqddm5ij" src="https://academy.hsoub.com/uploads/monthly_2021_10/6171225bba8ec_.thumb.png.42d3b89e4329da72f85ef9a5732b3813.png" style="width: 600px; height: auto;"></a>
</p>

<p>
	والمقال الثالث بعنوان «<a data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="https://academy.hsoub.com/entrepreneurship/tips/%D9%85%D8%B9%D8%B6%D9%84%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%A7%D8%AC%D9%8A%D8%A9-r423/" rel="">معضلة الإنتاجية</a> والكاتب هو عمر، وسأنسخ فقط المقدمة، وفي صندوق الوسوم سأدخل الإنتاجية.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="80278" data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="https://academy.hsoub.com/uploads/monthly_2021_10/6171225fd8951_.png.ab46623327fac434981346a8897fb609.png" rel=""><img alt="إنشاء مقالة معضلة الإنتاجية.png" class="ipsImage ipsImage_thumbnailed" data-fileid="80278" data-unique="tcnxy49x6" src="https://academy.hsoub.com/uploads/monthly_2021_10/61712264ea22c_.thumb.png.83be237251b43af640de7c9762d072cf.png" style="width: 600px; height: auto;"></a>
</p>

<p>
	إذًا حصلنا الآن على ثلاثة مقالات، مواضيعها مختلفة، بالإضافة إلى المقال الأول التجريبي، وأصبح لدينا ثلاث كُتَّاب لكل منهم مقال، ولنبن الآن مستَقبِل البحث.
</p>

<h2 id="بناء-عملية-البحث-اعتمادا-على-قاعدة-البيانات">
	بناء عملية البحث اعتمادًا على قاعدة البيانات
</h2>

<p>
	يعتمد معظم المطورين على المعامل <a data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="https://wiki.hsoub.com/SQL/like" rel="external"><code>like</code></a> في عبارات <a href="https://academy.hsoub.com/programming/sql/%D8%A7%D9%84%D9%85%D8%B1%D8%AC%D8%B9-%D8%A7%D9%84%D9%85%D8%AA%D9%82%D8%AF%D9%85-%D8%A5%D9%84%D9%89-%D9%84%D8%BA%D8%A9-sql-r961/" rel="">SQL</a> لبحثهم، العملية كافية بالنسبة للبحث في شيء محدد يدركه المستخدم مسبقًا ولا مجال فيه للاختلاف الكبير بين آراء مختلف المستخدمين وصياغتها لعبارة البحث، وكل مافي الأمر هنا أن تبحث باستخدام هذا المعامل عن ما يشبه عبارة البحث في حقل العنوان أو حقل الوسوم tags للمقال، وتوفير مربع بحث آخر للبحث حسب الكاتب.
</p>

<p>
	وتُنفّذ العملية ببساطة بمقارنة السلاسل النصية المطلوبة مع عبارة البحث التي أدخلها المستخدم، دون أدنى شكل من أشكال تحليل محتوى المقال أو تعرف أداة البحث على موضوعه ومدى فائدته للمستخدم بناء على عبارة البحث.
</p>

<p>
	حتى أنشئ وظيفة البحث التقليدية، سأذهب إلى قسم الموجهات routes في ملف article.routes.js وأضيف الموجه التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2287_54" style="">
<span class="pln">  app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/search"</span><span class="pun">,</span><span class="pln"> controller</span><span class="pun">.</span><span class="pln">search</span><span class="pun">);</span></pre>

<p>
	بعد ذلك نضيف الشيفرة الآتية داخل ملف article.controller.js:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2287_56" style="">
<span class="pln">exports</span><span class="pun">.</span><span class="pln">search </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="typ">Article</span><span class="pun">.</span><span class="pln">findAll</span><span class="pun">({</span><span class="pln">
    where</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="pun">[</span><span class="typ">Op</span><span class="pun">.</span><span class="pln">or</span><span class="pun">]:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        title</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="pun">[</span><span class="typ">Op</span><span class="pun">.</span><span class="pln">like</span><span class="pun">]:</span><span class="pln"> </span><span class="pun">`%</span><span class="pln">$</span><span class="pun">{</span><span class="pln">req</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">q</span><span class="pun">}%`</span><span class="pln"> </span><span class="pun">},</span><span class="pln">
        tags</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="pun">[</span><span class="typ">Op</span><span class="pun">.</span><span class="pln">like</span><span class="pun">]:</span><span class="pln"> </span><span class="pun">`%</span><span class="pln">$</span><span class="pun">{</span><span class="pln">req</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">q</span><span class="pun">}%`</span><span class="pln"> </span><span class="pun">},</span><span class="pln">
        content</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="pun">[</span><span class="typ">Op</span><span class="pun">.</span><span class="pln">like</span><span class="pun">]:</span><span class="pln"> </span><span class="pun">`%</span><span class="pln">$</span><span class="pun">{</span><span class="pln">req</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">q</span><span class="pun">}%`</span><span class="pln"> </span><span class="pun">},</span><span class="pln">
      </span><span class="pun">},</span><span class="pln">
    </span><span class="pun">},</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">then</span><span class="pun">((</span><span class="pln">articles</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">"index"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> articles</span><span class="pun">:</span><span class="pln"> articles </span><span class="pun">});</span><span class="pln">
    </span><span class="pun">})</span><span class="pln">
    </span><span class="pun">.</span><span class="kwd">catch</span><span class="pun">((</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      next</span><span class="pun">(</span><span class="pln">err</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">});</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	سيبحث هذا التابع في حقول العنوان والمحتوى والوسوم عن ما يتضمن حرفيًا ما أدخلناه في عبارة البحث، فإذا بحثنا باستخدام إحدى العبارات: أسس القيادة التقنية، معضلة الإنتاجية، مقال تجريبي؛ فإنه سيعيد لنا نتيجة واحدة كانت قيمة وسومها تحتوي العبارة. لكن ماذا إذا بحثنا مثلًا عن "أشياء إنتاجيّة"؟ نذكر أن أحد مقالاتنا كان يتحدث عن الإنتاج وفيه فقرة عن أشياء إنتاجيّة حقيقيّة، وتعلم أن تحصيلك لنتيجة بحث دقيقة تعطي المستخدم ما هو مفيد له فقط بناء على الدالة like شيء مستحيل، جرب مثلًا البحث عن "اشياء إنتاجيّة" دون همزة ستجد أنه لا يعيد لك أي نتائج.
</p>

<p>
	ولربما تبحث عن أمر آخر، كلمة أسس مكتوبة مع همزة في المقال الذي عنوانه أسس القيادة التقنية، ابحث عن "اسس" دون همزة، وستجد أنه لا يعيد لك أي نتائج!
</p>

<h2 id="مفاهيم-متعلقة-بمحرك-البحث-الداخلي">
	مفاهيم متعلقة بمحرك البحث الداخلي
</h2>

<p>
	يأتي هنا دور <a data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="https://lableb.com/ar" rel="external nofollow">محرك البحث لبلب</a> والذي له القدرة على تحليل النص ومعرفة موضوعه ومضمونه، وجدولة نتائج البحث على أساس هذا التحليل، لنجرب الآن بناء البحث باستخدام لبلب لنرى إن كان سينجح في إعادة نتائج لنا بناء على العبارات التي لم تعد نتائج في البحث السابق، ولكن قبل أن نبدأ ببناء الوظائف، لنفهم بعض المفاهيم الأساسية.
</p>

<p>
	محرك البحث لبلب هو محرك بحث سحابي، يعمل على جدولة أو فهرسة indexing المحتوى الذي يصله من الموقع، ومن ثم التفاعل مع عبارة البحث التي تصله وتوفير النتائج التي يجدها مناسبة. من هنا نعرف أنه لدينا عمليتين حتى نصل إلى المطلوب: الأولى جدولة المحتوى، والثانية هي عملية البحث.
</p>

<p>
	يوفِّر محرك البحث لبلب <a data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-api-r1314/" rel="">واجهة برمجية Web <abbr title="Application Programming Interface | واجهة برمجية">API</abbr></a> للتعامل معها بشكل مباشر، كما يوفر حزمة برمجية SDK لكل من المنصات: Node.js و PHP Laravel و WordPress، حتى تسهل التعامل مع <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-api-r1314/" rel="">الواجهة البرمجية <abbr title="Application Programming Interface | واجهة برمجية">API</abbr></a> الخاصة به.
</p>

<p>
	يمكنك الرجوع إلى <a data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="https://lableb.com/doc" rel="external nofollow">التوثيق الرسمي</a> للواجهة البرمجية وللحزم البرمجية لأي تفاصيل ومعلومات إضافية. يتطلب التعامل مع <a data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="https://dashboard.lableb.com" rel="external nofollow">واجهة لبلب البرمجية</a> حسابًا على موقع لبلب، ويتم الحفاظ على وثوقية وأمان المعلومات عند التعامل مع لبلب من خلال استخدام مفاتيح خاصة بمشروعك <abbr title="Application Programming Interface | واجهة برمجية">API</abbr> Key تستطيع توليد مفاتيح جديدة من خلال لوحة التحكم الخاصة بمشروعك (من خلال الدخول الى لوحة التحكم -&gt; ثم الضغط على اسم المشروع الخاص بك -&gt; ثم الدخول الى قسم <abbr title="Application Programming Interface | واجهة برمجية">API</abbr> Keys) ونلاحظ وجود مفتاحين: الأول للقيام بعملية الفهرسة عند لبلب والثاني لإجراء عمليات البحث من بإستخدام لبلب.
</p>

<h2 id="تهيئة-حزمة-لبلب">
	تهيئة حزمة لبلب
</h2>

<p>
	لدي مشروع اسمه academyhsoub في حسابي على لبلب، ولهذا المشروع جدول collection اسمه posts وله البنية التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="80279" data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="https://academy.hsoub.com/uploads/monthly_2021_10/617122694075c_posts.png.b27ca84b89d7fa5aa1833357c0ca3004.png" rel=""><img alt="بنية posts.png" class="ipsImage ipsImage_thumbnailed" data-fileid="80279" data-unique="ydumlqszj" src="https://academy.hsoub.com/uploads/monthly_2021_10/6171226ee10d1_posts.thumb.png.a380b7c8d91d6f6740bbf4ae4a01f469.png" style="width: 600px; height: auto;"></a>
</p>

<p>
	أستطيع من هنا التحكم في حقول هذا الجدول بإضافة حقول جديدة ومنحها أسماء أو حذف حقول موجودة، وجعلها إلزامية بتفعيل الخاصية required لها.
</p>

<p>
	سنثبت حزمة لبلب بالأمر الآتي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2287_59" style="">
<span class="pln">npm install </span><span class="lit">@lableb</span><span class="pun">/</span><span class="pln">javascript</span><span class="pun">-</span><span class="pln">sdk</span></pre>

<p>
	وبعدها ننشئ ملفًا باسم <code>lableb.js</code> داخل المجلد <code>config</code> ونضع به الشيفرة الآتية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2287_61" style="">
<span class="kwd">const</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="typ">LablebClient</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"@lableb/javascript-sdk"</span><span class="pun">);</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> PROJECT_NAME </span><span class="pun">=</span><span class="pln"> </span><span class="str">"academyhsoub"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> INDEXING_TOKEN </span><span class="pun">=</span><span class="pln"> </span><span class="str">"محتوى هذا الحقل من حسابك على لبلب"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> SEARCH_TOKEN </span><span class="pun">=</span><span class="pln"> </span><span class="str">"محتوى هذا الحقل من حسابك على لبلب "</span><span class="pun">;</span><span class="pln">

let lableb</span><span class="pun">;</span><span class="pln">

</span><span class="typ">LablebClient</span><span class="pun">({</span><span class="pln">
    platformName</span><span class="pun">:</span><span class="pln"> PROJECT_NAME</span><span class="pun">,</span><span class="pln">
    </span><span class="typ">APIKey</span><span class="pun">:</span><span class="pln"> SEARCH_TOKEN</span><span class="pun">,</span><span class="pln">
    indexingAPIKey</span><span class="pun">:</span><span class="pln"> INDEXING_TOKEN
</span><span class="pun">})</span><span class="pln">
</span><span class="pun">.</span><span class="pln">then</span><span class="pun">(</span><span class="pln">lablebClient </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    lableb </span><span class="pun">=</span><span class="pln"> lablebClient</span><span class="pun">;</span><span class="pln">
</span><span class="pun">})</span><span class="pln">
</span><span class="pun">.</span><span class="kwd">catch</span><span class="pun">(</span><span class="pln">error </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    console</span><span class="pun">.</span><span class="pln">error</span><span class="pun">(</span><span class="pln">error</span><span class="pun">);</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

module</span><span class="pun">.</span><span class="pln">exports </span><span class="pun">=</span><span class="pln"> lableb</span><span class="pun">;</span></pre>

<p>
	وهكذا سنستطيع استخدام المتغير <code>lableb</code> لمختلف العمليات. هيأنا هذا الثابت بإعطائه اسم المشروع ومفاتيح الواجهة <abbr title="Application Programming Interface | واجهة برمجية">API</abbr> KEYs الخاصة بالجدولة والبحث، حيث أن الحزمة ستوجه الطلبات إلى رابط واجهة لبلب البرمجية بالشكل التالي في حال عملية الجدولة:
</p>

<pre class="ipsCode prettyprint lang-ruby prettyprinted" id="ips_uid_8977_15" style="">
<span class="pln">https</span><span class="pun">:/</span><span class="str">/api.lableb.com/</span><span class="pln">api</span><span class="pun">/</span><span class="pln">v2</span><span class="pun">/</span><span class="pln">$</span><span class="pun">{</span><span class="pln">PROJECT_NAME</span><span class="pun">}/</span><span class="pln">indices</span><span class="pun">/</span><span class="pln">$</span><span class="pun">{</span><span class="pln">COLLECTION_NAME</span><span class="pun">}/</span><span class="pln">documents</span><span class="pun">?</span><span class="pln">apikey</span><span class="pun">=</span><span class="pln">$</span><span class="pun">{</span><span class="pln">INDEXING_TOKEN</span><span class="pun">}</span></pre>

<p>
	وبالشكل التالي في عملية البحث:
</p>

<pre class="ipsCode prettyprint lang-ruby prettyprinted" id="ips_uid_8977_13" style="">
<span class="pln">https</span><span class="pun">:/</span><span class="str">/api.lableb.com/</span><span class="pln">api</span><span class="pun">/</span><span class="pln">v2</span><span class="pun">/</span><span class="pln">$</span><span class="pun">{</span><span class="pln">PROJECT_NAME</span><span class="pun">}/</span><span class="pln">indices</span><span class="pun">/</span><span class="pln">$</span><span class="pun">{</span><span class="pln">COLLECTION_NAME</span><span class="pun">}/</span><span class="pln">search</span><span class="pun">/</span><span class="pln">default</span><span class="pun">?</span><span class="pln">q</span><span class="pun">=</span><span class="pln">$</span><span class="pun">{</span><span class="pln">SEARCH_QUERY</span><span class="pun">}&amp;</span><span class="pln">cat</span><span class="pun">=</span><span class="typ">Lifestyle</span><span class="pun">&amp;</span><span class="pln">limit</span><span class="pun">=</span><span class="lit">1</span><span class="pun">&amp;</span><span class="pln">apikey</span><span class="pun">=</span><span class="pln">$</span><span class="pun">{</span><span class="pln">SEARCH_TOKEN</span><span class="pun">}</span></pre>

<p>
	وبذلك تختصر علينا الحزمة كتابة هذه العناوين وتشكيلها يدويًا.
</p>

<h2 id="بناء-عملية-الفهرسة">
	بناء عملية الفهرسة Indexing
</h2>

<p>
	سأضيف الآن عمليات الفهرسة بتوسيع extend صنف قاعدة البيانات حتى نعمل ببنية صحيحة. سنبني عملية فهرسة Indexing مرتبطة بالصنف <code>Article</code> وظيفتها جدولة كل المقالات السابقة في قاعدة البيانات، وستفيدنا هذه العملية في التأكد من فهرسة المقالات التي أضيفت إلى قاعدة البيانات قبل استخدام محرك البحث، أو عندما يكون قد جرى تعديل على المقالات ولم تتم فهرسته.
</p>

<p>
	وعملية الفهرسة مرتبطة بالكائن <code>article</code> عند إنشاء كل كائن جديد حتى تتم فهرسته بعد الإنشاء مباشرة. ولبناء العمليتان، ننشئ مجلدًا جديدًا باسم models وبداخله ننشئ ملف باسم article.js ونكتب بداخله الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2287_67" style="">
<span class="kwd">const</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="typ">Sequelize</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Model</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"sequelize"</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> sequelize </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"../config/sequelize"</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> lableb </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"../config/lableb"</span><span class="pun">);</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Article</span><span class="pln"> extends </span><span class="typ">Model</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="com">//إنشاء مستند قابل للفهرسة خارج الكائن </span><span class="pln">
  _document</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">


    </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      id</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">id</span><span class="pun">,</span><span class="pln">
      title</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">title</span><span class="pun">,</span><span class="pln">
      content</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">content</span><span class="pun">,</span><span class="pln">
      url</span><span class="pun">:</span><span class="pln"> </span><span class="pun">`/</span><span class="pln">article</span><span class="pun">/</span><span class="pln">$</span><span class="pun">{</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">id</span><span class="pun">}`,</span><span class="pln">
      tags</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">tags</span><span class="pun">,</span><span class="pln">
      authors</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">author</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">};</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  </span><span class="com">// إنشاء مستندات قابلة للفهرسة لجميع المقالات</span><span class="pln">
  </span><span class="kwd">static</span><span class="pln"> async _findAllDocuments</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="typ">Article</span><span class="pun">.</span><span class="pln">findAll</span><span class="pun">().</span><span class="pln">map</span><span class="pun">((</span><span class="pln">a</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> a</span><span class="pun">.</span><span class="pln">_document</span><span class="pun">());</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  </span><span class="com">// فهرسة مستند واحد</span><span class="pln">
  _index</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> lableb</span><span class="pun">.</span><span class="pln">index</span><span class="pun">({</span><span class="pln">
            documents</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">_document</span><span class="pun">()]</span><span class="pln">
        </span><span class="pun">});</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  </span><span class="com">// حذف مستند واحد من الفهرسة</span><span class="pln">
  async _deleteIndex</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> lableb</span><span class="pun">.</span><span class="kwd">delete</span><span class="pun">({</span><span class="pln">
            documentId</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">id
        </span><span class="pun">});</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  </span><span class="com">// فهرسة جميع المقالات</span><span class="pln">
  </span><span class="kwd">static</span><span class="pln"> async indexAll</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> documents </span><span class="pun">=</span><span class="pln"> await </span><span class="typ">Article</span><span class="pun">.</span><span class="pln">_findAllDocuments</span><span class="pun">();</span><span class="pln">

     </span><span class="kwd">return</span><span class="pln"> lableb</span><span class="pun">.</span><span class="pln">index</span><span class="pun">({</span><span class="pln">
            documents</span><span class="pun">:</span><span class="pln"> documents
        </span><span class="pun">});</span><span class="pln">

  </span><span class="pun">}</span><span class="pln">

  </span><span class="com">// تحويل نتائج البحث القادمة من لبلب إلى مقالات قابلة للعرض</span><span class="pln">
  </span><span class="kwd">static</span><span class="pln"> async search</span><span class="pun">(</span><span class="pln">query</span><span class="pun">,</span><span class="pln"> limit</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

        </span><span class="kwd">const</span><span class="pln"> lablebResult </span><span class="pun">=</span><span class="pln"> await lableb</span><span class="pun">.</span><span class="pln">search</span><span class="pun">({</span><span class="pln">
            query</span><span class="pun">:</span><span class="pln"> encodeURIComponent</span><span class="pun">(</span><span class="pln">query</span><span class="pun">),</span><span class="pln">
            limit</span><span class="pun">:</span><span class="pln"> limit
        </span><span class="pun">});</span><span class="pln">

        </span><span class="kwd">const</span><span class="pln"> searchResults </span><span class="pun">=</span><span class="pln"> lablebResult</span><span class="pun">.</span><span class="pln">response</span><span class="pun">.</span><span class="pln">results</span><span class="pun">;</span><span class="pln">


    </span><span class="kwd">return</span><span class="pln"> searchResults</span><span class="pun">.</span><span class="pln">map</span><span class="pun">((</span><span class="pln">searchResult</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">({</span><span class="pln">
      id</span><span class="pun">:</span><span class="pln"> searchResult</span><span class="pun">.</span><span class="pln">id</span><span class="pun">,</span><span class="pln">
      title</span><span class="pun">:</span><span class="pln"> searchResult</span><span class="pun">.</span><span class="pln">title</span><span class="pun">,</span><span class="pln">
      content</span><span class="pun">:</span><span class="pln"> searchResult</span><span class="pun">.</span><span class="pln">content</span><span class="pun">,</span><span class="pln">
      author</span><span class="pun">:</span><span class="pln"> searchResult</span><span class="pun">.</span><span class="pln">authors</span><span class="pun">[</span><span class="lit">0</span><span class="pun">],</span><span class="pln">
      tags</span><span class="pun">:</span><span class="pln"> searchResult</span><span class="pun">.</span><span class="pln">tags</span><span class="pun">[</span><span class="lit">0</span><span class="pun">],</span><span class="pln">
    </span><span class="pun">}));</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="typ">Article</span><span class="pun">.</span><span class="pln">init</span><span class="pun">(</span><span class="pln">
  </span><span class="pun">{</span><span class="pln">
    title</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Sequelize</span><span class="pun">.</span><span class="pln">STRING</span><span class="pun">,</span><span class="pln">
    author</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Sequelize</span><span class="pun">.</span><span class="pln">STRING</span><span class="pun">,</span><span class="pln">
    content</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Sequelize</span><span class="pun">.</span><span class="pln">STRING</span><span class="pun">,</span><span class="pln">
    tags</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Sequelize</span><span class="pun">.</span><span class="pln">STRING</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="pun">{</span><span class="pln">
    hooks</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      afterSave</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> </span><span class="pun">(</span><span class="pln">article</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        article</span><span class="pun">.</span><span class="pln">_index</span><span class="pun">();</span><span class="pln">
      </span><span class="pun">},</span><span class="pln">
      beforeDestroy</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">article</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        article</span><span class="pun">.</span><span class="pln">_deleteIndex</span><span class="pun">();</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">},</span><span class="pln">
    sequelize</span><span class="pun">,</span><span class="pln">
    modelName</span><span class="pun">:</span><span class="pln"> </span><span class="str">"article"</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">);</span><span class="pln">

module</span><span class="pun">.</span><span class="pln">exports </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Article</span><span class="pun">;</span></pre>

<p>
	سنشرح الشيفرة السابقة؛ أنشأنا في البداية صنفًا يحتوي على الدالة <code>document_</code> ليكون وسيطًا معبرًا عن المقال الواجب جدولته.
</p>

<p>
	بعد ذلك أنشأنا التابع <code>findAllDocuments_</code> لإنشاء مستندات قابلة للفهرسة لجميع المقالات وأنشأنا بعد ذلك التابع <code>index_</code> لفهرسة مستند واحد والتابع <code>indexAll</code> لفهرسة جميع المقالات، ولا بد أن نذكر هنا أن خدمة لبلب تتعامل مع المعلومات المفهرسة فيها عبر حقل <code>id</code>، فإذا تم إرسال المقالات إلى لبلب وكانت مُعرِّفاتُها موجودة مسبقًا، فسيتم تحديثها لا تكرارها. ونلاحظ أثناء بناء المصفوفة <code>documents_</code> أننا خزننا حتى روابط هذه المقالات.
</p>

<p>
	ونُذكِّر أننا أضفنا هذا الجزء من قبل وهو مسؤول عن عرض وجدولة كل المقالات:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2287_69" style="">
<span class="pln">exports</span><span class="pun">.</span><span class="pln">viewIndex </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="typ">Article</span><span class="pun">.</span><span class="pln">findAll</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">then</span><span class="pun">((</span><span class="pln">articles</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">"index"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> articles</span><span class="pun">:</span><span class="pln"> articles </span><span class="pun">});</span><span class="pln">
    </span><span class="pun">})</span><span class="pln">
    </span><span class="pun">.</span><span class="kwd">catch</span><span class="pun">((</span><span class="pln">error</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      next</span><span class="pun">(</span><span class="pln">error</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">});</span><span class="pln">
</span><span class="pun">};</span></pre>

<h2 id="تعديل-عملية-البحث-وتجارب-البحث">
	تعديل عملية البحث وتجارب البحث
</h2>

<p>
	رأينا في السابق نتائج البحث التي لم تكن ذات جدوى عالية، والآن لنعلق دالة البحث القديمة بحيث تصبح بدون تأثير على التطبيق ونضع الدالة الآتية بدلًا منها فى الملف <code>article.controller.js</code> وجعلها كالآتي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2287_71" style="">
<span class="pln">exports</span><span class="pun">.</span><span class="pln">search </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="typ">Article</span><span class="pun">.</span><span class="pln">search</span><span class="pun">(</span><span class="pln">req</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">q</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">then</span><span class="pun">((</span><span class="pln">articles</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">"index"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> articles</span><span class="pun">:</span><span class="pln"> articles </span><span class="pun">});</span><span class="pln">
    </span><span class="pun">})</span><span class="pln">
    </span><span class="pun">.</span><span class="kwd">catch</span><span class="pun">((</span><span class="pln">error</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      next</span><span class="pun">(</span><span class="pln">error</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">});</span><span class="pln">
</span><span class="pun">};</span><span class="pln">

exports</span><span class="pun">.</span><span class="pln">indexAll </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="typ">Article</span><span class="pun">.</span><span class="pln">indexAll</span><span class="pun">();</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	ونحصل من دالة بحث لبلب على نتائج البحث لنتصرف فيها.
</p>

<p>
	إذا علقنا كل محتوى القديم وكتبنا الأسطر التي تنفذ عملية البحث باستخدام لبلب، وتعتمد على نتائج بحث لبلب في عرض المقالات، عوضًا عن قاعدة بياناتنا وهكذا بنينا كامل عملية البحث.
</p>

<h2 id="تجارب-البحث-باستخدام-لبلب">
	تجارب البحث باستخدام لبلب
</h2>

<p>
	لدينا ثلاثة مقالات تتحدث عن مواضيع مختلفة، وموضوع واحد بعنوان مقال تجريبي، لنجرب البحث عن كلمة "اشياء إنتاجيّة":
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="80262" data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="https://academy.hsoub.com/uploads/monthly_2021_10/6171222e5239d_.png.2faf7bf9efbec6232303c8e018a20614.png" rel=""><img alt="تجارب البحث باستخدام لبلب.png" class="ipsImage ipsImage_thumbnailed" data-fileid="80262" data-unique="765q0dbel" src="https://academy.hsoub.com/uploads/monthly_2021_10/6171222ec2a41_.thumb.png.633a0319d680695b781c241cb98496cf.png" style="width: 600px; height: auto;"></a>
</p>

<p>
	ونلاحظ أنه أعاد لنا المقالة التى تتحدث عن أشياء إنتاجية وهى "معضلة الإنتاجية "على الرغم أنها لا تبدأ بهمزة، وأعاد أيضًا مقال أسس القيادة التقنية بعدها لأنها تحتوى على كلمة الأشياء.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="80263" data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="https://academy.hsoub.com/uploads/monthly_2021_10/6171222fefe00_.png.5185327cfe81529350cee71827547572.png" rel=""><img alt="البحث باستخدام لبلب.png" class="ipsImage ipsImage_thumbnailed" data-fileid="80263" data-unique="ethdkdxbs" src="https://academy.hsoub.com/uploads/monthly_2021_10/6171223250d21_.thumb.png.9529765d7dc6156ee9f74eed61486991.png" style="width: 600px; height: auto;"></a>
</p>

<p>
	جرب الآن البحث عن كلمة "اسس" دون همزة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="80264" data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="https://academy.hsoub.com/uploads/monthly_2021_10/617122338549f_.png.98281b9a5fe72bdc73551cba11498e06.png" rel=""><img alt="البحث بدون همزة.png" class="ipsImage ipsImage_thumbnailed" data-fileid="80264" data-unique="ncxt0t8kh" src="https://academy.hsoub.com/uploads/monthly_2021_10/61712233cb07d_.thumb.png.58399f46f2525a431bd58ef456afe3f2.png" style="width: 600px; height: auto;"></a>
</p>

<p>
	و"أسس" مع همزة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="80265" data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="https://academy.hsoub.com/uploads/monthly_2021_10/6171223451996_.png.0776acdfd58a57be20a000a03bf26d03.png" rel=""><img alt="البحث مع همزة.png" class="ipsImage ipsImage_thumbnailed" data-fileid="80265" data-unique="jk422p6o0" src="https://academy.hsoub.com/uploads/monthly_2021_10/61712234747f2_.thumb.png.709231258df4029dcd1e73e029d9684e.png" style="width: 600px; height: auto;"></a>
</p>

<p>
	ونلاحظ هنا أنه جلب لنا مقال أسس القيادة التقنية فى كلتا الحالتين، هل كنا سنستطيع برمجة دالة بحث تعطينا نفس الدقة في النتائج؟
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="80266" data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="https://academy.hsoub.com/uploads/monthly_2021_10/617122351e261_.png.60c9ed35d15725879bdbc5ab4d674524.png" rel=""><img alt="برمجة دالة بحث.png" class="ipsImage ipsImage_thumbnailed" data-fileid="80266" data-unique="cvlgcduaa" src="https://academy.hsoub.com/uploads/monthly_2021_10/61712236921aa_.thumb.png.07f91b3434ae6d9a2963ceae9b6d7e02.png" style="width: 600px; height: auto;"></a>
</p>

<p>
	حسنٌ، لنبحث عن عبارة مخصصة جدًا في مقال كيف تقدّر أجر مساعدك عن بعد، لنبحث عن عبارة "ميزانية مشروعك"
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="80267" data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="https://academy.hsoub.com/uploads/monthly_2021_10/61712237668d5_.png.bddbdab0a6cf4eea1ce82e612b8b3550.png" rel=""><img alt="البحث عن عبارة مخصصة.png" class="ipsImage ipsImage_thumbnailed" data-fileid="80267" data-unique="so831x67b" src="https://academy.hsoub.com/uploads/monthly_2021_10/617122380ee88_.thumb.png.7cb7b808580910cccf73c47b20c733a3.png" style="width: 600px; height: auto;"></a>
</p>

<p>
	ونلاحظ أننا حصلنا فقط على نتيجة واحدة وهي مقالة كيف تقدّر أجر مساعدك عن بعد، ويجب أن تتذكر أننا وضعنا المقدمة فقط فى المحتوى الخاص بالمقالة، لذلك اخترت عبارة من المقدمة وبحثت عنها، وسنفعل المثل فى عمليات البحث القادمة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="80268" data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="https://academy.hsoub.com/uploads/monthly_2021_10/61712238c88cd_.png.1613bc42e9f0af37996b13fdfbb21d6a.png" rel=""><img alt="نتائج البحث عن عبارة مخصصة.png" class="ipsImage ipsImage_thumbnailed" data-fileid="80268" data-unique="rzzb28v8n" src="https://academy.hsoub.com/uploads/monthly_2021_10/6171223d6b859_.thumb.png.1028378d8bdfa1a19b0d8d8e5d160a86.png" style="width: 600px; height: auto;"></a>
</p>

<p>
	لنبحث عن كلمة "نيويوركر"
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="80269" data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="https://academy.hsoub.com/uploads/monthly_2021_10/6171223fbd913_.png.7cf00b81c95e4da2bd262f05817505ac.png" rel=""><img alt="نيويوركر.png" class="ipsImage ipsImage_thumbnailed" data-fileid="80269" data-unique="1fbaett89" src="https://academy.hsoub.com/uploads/monthly_2021_10/6171224061821_.thumb.png.98a236db705a18bad02638e8757d9457.png" style="width: 600px; height: auto;"></a>
</p>

<p>
	ونلاحظ مجددًا أنه أعاد مقالة "معضلة الإنتاجية" فقط إذ أنَّها المقالة الوحيدة لدينا التى تحتوى على هذه الكلمة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="80270" data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="https://academy.hsoub.com/uploads/monthly_2021_10/61712241d81f0_.png.1d3d42bde05b33a251190f4b25f7beec.png" rel=""><img alt="نتائج البحث عن نيويوركر.png" class="ipsImage ipsImage_thumbnailed" data-fileid="80270" data-unique="053x0zbus" src="https://academy.hsoub.com/uploads/monthly_2021_10/61712242cc8c2_.thumb.png.e7b64a6c45a5b891a1f025479e06b279.png" style="width: 600px; height: auto;"></a>
</p>

<p>
	لنبحث الآن باستخدام عبارة أقرب لما يكتبه البشر عادة، لنبحث عن "ما هى المعضلة الانتاجية؟"
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="80271" data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="https://academy.hsoub.com/uploads/monthly_2021_10/61712243ca812_.png.ac03aa89f5159454f4a8c02e386574c5.png" rel=""><img alt="بحث ما هى المعضلة الانتاجية.png" class="ipsImage ipsImage_thumbnailed" data-fileid="80271" data-unique="go5ecrc7v" src="https://academy.hsoub.com/uploads/monthly_2021_10/6171224461450_.thumb.png.3bf3abc3ce53c6a451ca96e8b8665507.png" style="width: 600px; height: auto;"></a>
</p>

<p>
	وسنرى أنه أعاد لنا مقالة معضلة الإنتاجية فقط، واستثنى المقالات الأخرى:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="80272" data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="https://academy.hsoub.com/uploads/monthly_2021_10/617122460d921_.png.27ac55979f4671becc23887e606018d9.png" rel=""><img alt="نتائج البحث عن ما هى المعضلة الانتاجية.png" class="ipsImage ipsImage_thumbnailed" data-fileid="80272" data-unique="lukfqef3u" src="https://academy.hsoub.com/uploads/monthly_2021_10/617122470604f_.thumb.png.5ea6c14412b10f01ac1e9ef3df3e0c9b.png" style="width: 600px; height: auto;"></a>
</p>

<p>
	نستنتج أن محرك البحث استطاع عرض نتيجة بناء على عبارة سؤال طبيعي.
</p>

<p>
	لنجرب البحث عن عبارة "مقال تجريبي"
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="80273" data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="https://academy.hsoub.com/uploads/monthly_2021_10/61712247d6d08_.png.ba615fac62ccc6d956264f050e240418.png" rel=""><img alt="بحث مقال تجريبي.png" class="ipsImage ipsImage_thumbnailed" data-fileid="80273" data-unique="s6f92higy" src="https://academy.hsoub.com/uploads/monthly_2021_10/6171224878a8d_.thumb.png.c08282b15085c14e6adec478a2356d8d.png" style="width: 600px; height: auto;"></a>
</p>

<p>
	وسنجد أنه أعاد المقالات التى تحتوى على كلمة مقال أو تجريبى ولكن "مقال تجريبى" فى بداية الترتيب.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="80274" data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="https://academy.hsoub.com/uploads/monthly_2021_10/6171224a06b94_.png.badd628bd0b0fd4389caa17758089aaa.png" rel=""><img alt="نتائج بحث مقال تجريبي.png" class="ipsImage ipsImage_thumbnailed" data-fileid="80274" data-unique="xi32gj9fh" src="https://academy.hsoub.com/uploads/monthly_2021_10/6171224d4f1fb_.thumb.png.91105d3adb2efddea7572104263b37c4.png" style="width: 600px; height: auto;"></a>
</p>

<p>
	لنبحث عن اسم الكاتب "عدنان":
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="80275" data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="https://academy.hsoub.com/uploads/monthly_2021_10/6171224ecd1e7_.png.587e127dfe9f47e887da4109a9d1b861.png" rel=""><img alt="البحث عن اسم الكاتب.png" class="ipsImage ipsImage_thumbnailed" data-fileid="80275" data-unique="hd0fxboou" src="https://academy.hsoub.com/uploads/monthly_2021_10/6171224f87119_.thumb.png.4baab94b01b592d2ccbea9230d416f7e.png" style="width: 600px; height: auto;"></a>
</p>

<p>
	ونرى أن النتيجة عادت فقط بالمقالات التي كتبها عدنان، دون أي مجهود منا لجعل عملية البحث تستهدف حقل الكاتب من كائن المقال.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="80276" data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="https://academy.hsoub.com/uploads/monthly_2021_10/61712250f1826_.png.9a3f97c9601ebcd169473382abcba2a0.png" rel=""><img alt="نتائج البحث عن اسم الكاتب.png" class="ipsImage ipsImage_thumbnailed" data-fileid="80276" data-unique="jy9kpq3hl" src="https://academy.hsoub.com/uploads/monthly_2021_10/61712253b1377_.thumb.png.2cb9cc0b92fde2cbc9646663e72fe1b9.png" style="width: 600px; height: auto;"></a>
</p>

<h2 id="الخلاصة">
	الخلاصة
</h2>

<p>
	رأينا الفائدة من استخدام محرك بحث تجريبي، تخيل أن لديك موقع تجارة إلكترونية يعرض منتجاته باللغة العربية، أليس جميلًا أن تحصل على دقة كهذه في إرجاع النتائج، فضلًا عن ترتيبها حسب الأقرب للبحث؟ وهل تستطيع بناء دالة بحث بنفس القدرات باستخدام الأدوات التقليدية مثل <a href="https://wiki.hsoub.com/SQL#.D8.A8.D9.86.D9.8A.D8.A9_.D8.AA.D8.B9.D9.84.D9.8A.D9.85.D8.A7.D8.AA_SQL" rel="external">تعليمات SQL</a> أو مقارنة السلاسل النصية باستخدام لغة البرمجة التي بنيت موقعك بها؟ أجرينا في هذا المقال مقارنة بين البحث باستخدام دالة بدائية يدوية، والبحث باستخدام محرك بحث عربي، واخترنا لبلب لهذه المهمة، واستنتجنا أن محرك البحث يتجه أكثر نحو فهم الموضوع واستيعاب مختلف صيغ عبارات البحث التي يدخلها المستخدم بلغة طبيعية غير مصطنعة كما هو الحال في البحث باستخدام الوسوم، ونرجو أن يكون المقال مفيدًا ونرحب بأية تعليقات أو استفسارات لديكم.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		<a data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="https://academy.hsoub.com/programming/php/wordpress/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%B5%D9%81%D8%AD%D8%A9-%D8%A7%D9%84%D8%A8%D8%AD%D8%AB-%D9%81%D9%8A-%D8%A7%D9%84%D9%88%D9%88%D8%B1%D8%AF%D8%A8%D8%B1%D9%8A%D8%B3-r1221/" rel="">إنشاء صفحة البحث في الووردبريس</a>
	</li>
	<li>
		<a data-ss1634805651="1" data-ss1634814575="1" data-ss1634815237="1" data-ss1634815393="1" href="https://academy.hsoub.com/devops/servers/databases/%D9%86%D9%85%D8%B0%D8%AC%D8%A9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%88%D8%A3%D9%86%D9%88%D8%A7%D8%B9%D9%87%D8%A7-%D9%81%D9%8A-%D8%B9%D9%85%D9%84%D9%8A%D8%A9-%D8%AA%D8%B5%D9%85%D9%8A%D9%85-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-r521/" rel="">نمذجة البيانات وأنواعها في عملية تصميم قواعد البيانات</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1347</guid><pubDate>Thu, 21 Oct 2021 11:08:00 +0000</pubDate></item><item><title>&#x62A;&#x637;&#x648;&#x64A;&#x631; &#x62A;&#x637;&#x628;&#x64A;&#x642; &#x639;&#x645;&#x644;&#x64A; &#x64A;&#x632;&#x64A;&#x62F; &#x645;&#x646; &#x645;&#x639;&#x62F;&#x644; &#x627;&#x644;&#x627;&#x62D;&#x62A;&#x641;&#x627;&#x638; &#x628;&#x627;&#x644;&#x639;&#x645;&#x644;&#x627;&#x621; &#x639;&#x628;&#x631; &#x648;&#x627;&#x62C;&#x647;&#x629; &#x632;&#x62F; &#x627;&#x644;&#x628;&#x631;&#x645;&#x62C;&#x64A;&#x629;</title><link>https://academy.hsoub.com/programming/javascript/nodejs/%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%8A%D8%B2%D9%8A%D8%AF-%D9%85%D9%86-%D9%85%D8%B9%D8%AF%D9%84-%D8%A7%D9%84%D8%A7%D8%AD%D8%AA%D9%81%D8%A7%D8%B8-%D8%A8%D8%A7%D9%84%D8%B9%D9%85%D9%84%D8%A7%D8%A1-%D8%B9%D8%A8%D8%B1-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%B2%D8%AF-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r1320/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2021_09/61443021774d0_.png.3604d5306df64dee29dcfe3fb30d6002.png" /></p>

<p>
	بعد أن غطينا معظم ما يتعلق بالواجهات البرمجية وكيفية عملها، مرورًا بالتعرف على الواجهة البرمجية لمنصة زد، سنعمل على بناء تطبيق يتصل بالواجهة البرمجية لمنصة زد ويوسع من عمل المتاجر المبنية بتلك المنصة بإضافة ميزة لها وهي زيادة المبيعات بزيادة الاحتفاظ العملاء وكسب ولائهم.
</p>

<p>
	هذا المقال هو جزء من سلسلة مقالات حول الواجهة البرمجية <abbr title="Application Programming Interface | واجهة برمجية">API</abbr> وكيفية الاستفادة منها في بناء تطبيق ويب:
</p>

<ol>
<li>
		<a data-ss1631885755="1" data-ss1631886121="1" data-ss1631887126="1" href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-api-r1314/" rel="">مدخل إلى الواجهات البرمجية <abbr title="Application Programming Interface | واجهة برمجية">API</abbr></a>
	</li>
	<li>
		<a data-ss1631886121="1" data-ss1631887126="1" href="https://academy.hsoub.com/programming/general/%D8%A7%D9%84%D8%A7%D8%AA%D8%B5%D8%A7%D9%84-%D8%A8%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-api-%D9%88%D9%81%D9%87%D9%85-%D8%B9%D9%85%D9%84%D9%8A%D8%A9-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%8A%D8%AB%D8%A7%D9%82-%D9%88%D8%A7%D9%84%D8%AA%D8%B5%D8%B1%D9%8A%D8%AD-r1318/" rel="">الاتصال بواجهة زد البرمجية وفهم عملية الاستيثاق والتصريح</a>
	</li>
	<li>
		<a data-ss1631885755="1" data-ss1631886121="1" data-ss1631887126="1" href="https://academy.hsoub.com/programming/general/%D8%A3%D9%85%D8%AB%D9%84%D8%A9-%D8%B9%D9%85%D9%84%D9%8A%D8%A9-%D9%84%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AA%D8%A7%D8%AC%D8%B1-%D8%B2%D8%AF-zid-api-r1319/" rel="">أمثلة عملية لاستخدام واجهة برمجة متاجر زد zid <abbr title="Application Programming Interface | واجهة برمجية">API</abbr></a>
	</li>
	<li>
		تطوير تطبيق عملي يزيد من احتفاظ العملاء عبر واجهة زد البرمجية
	</li>
</ol>
<h2>
	فكرة التطبيق
</h2>

<p>
	فكرة التطبيق سهلة وبسيطة، بحيث سنستخدم نقطة الوصول الخاصة بسلات الشراء المتروكة لاستعراض العملاء الذين تركوا سلة ما دون إتمام عملية الشراء لمتجر ما ومن ثم سنولد عبر نقطة وصول القسائم قسيمة تخفيض Coupons ونرسلها عبر البريد اﻹلكتروني للعملاء لحضهم على استعمال قسيمة التخفيض وإتمام عملية الشراء. يُحدد المتجر بالمفتاح X-Manager-Token الذي سيزودنا به صاحب المتجر الذي يريد الاستفادة من هذا التطبيق وسنستعمل مفتاح متجر تجريبي أثناء عملية التطوير.
</p>

<p>
	سيكون بإمكانك استغلال هذا التطبيق وبناء منصة متكاملة لزيادة عملاء متاجر زد الإلكترونية، وبالتالي زيادة المداخيل السلبية Passive Income لأصحاب المتاجر، الأمر الذي يحقق الفائدة والنفع لهم.
</p>

<h2>
	المتطلبات المسبقة لبناء التطبيق
</h2>

<p>
	سنبني التطبيق باستخدام تقنية NodeJS كنظام خلفي Backend، مع واجهة مستخدم بسيطة تكفينا فقط ﻻستخدام سهل وسلس للتطبيق، وسنبني التطبيق باستخدام محرك القوالب Template Engine المعروف باسم Pug.
</p>

<p>
	يُفترض أن لديك معرفةً مسبقةً ولو قليلةً بالبرمجة باستخدام NodeJS وإطار العمل ExpressJS لهذا لن نتطرق إلى شرحهما. يمكنك استخدام مدير الحزم Package Manager الذي تفضله سواءً NPM أو YARN، سأستخدم YARN في هذا المشروع.
</p>

<h2>
	تهيئة المشروع
</h2>

<p>
	لبداية مشروع جديد، يكفي أن تفتح سطر أوامر وتكتب الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-ruby prettyprinted" id="ips_uid_235_12" style="">
<span class="pln">yarn init</span></pre>

<p>
	سيسألك مدير الحزم عن معلومات التطبيق الذي تريد إنشاؤه، يكفي أن تجيب بالقبول عن كل الأسئلة دون تعديل. بعدها، سينشئ مدير الحزم ملفًا باسم package.json بهذا المحتوى:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_14" style="">
<span class="pun">{</span><span class="pln">
  </span><span class="str">"name"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"zid"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"version"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"1.0.0"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"main"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"index.js"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"license"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"MIT"</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	الآن سنثبت عددًا من الإضافات اللازمة لعمل التطبيق بصورة طبيعية، وسنبدأ بتثبيت إطار العمل ExpressJS عن طريق تنفيذ الأمر التالي في سطر الأوامر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_16" style="">
<span class="pln">yarn add express</span></pre>

<p>
	بعد انتهاء مدير الحزم من تثبيت ExpressJS، سنثبت مكتبة Nodemon والتي تمكننا من تشغيل خادم دائم للتطبيق بحيث يعيد Nodemon تشغل الخادم آليًا في كل مرة نعدل على شفرة التطبيق.
</p>

<p>
	مكتبة Nodemon تستخدم فقط في مرحلة التطوير وليس في البيئة الإنتاجية، لهذا يجب علينا تثبيتها على هذا الأساس، ويكون ذلك باستخدام هذا الأمر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_18" style="">
<span class="pln">yarn add nodemon </span><span class="pun">--</span><span class="pln">dev</span></pre>

<p>
	الآن، الملف package.json سيظهر كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_20" style="">
<span class="pun">{</span><span class="pln">
  </span><span class="str">"name"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"zid"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"version"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"1.0.0"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"main"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"index.js"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"license"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"MIT"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"dependencies"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">"express"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^4.17.1"</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="str">"devDependencies"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">"nodemon"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^2.0.7"</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	الآن أنشئ ملفًا باسم nodemon.json، وهو ملف خاص بإعدادات Nodemon ويجب أن يحتوي على الشفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_22" style="">
<span class="pun">{</span><span class="pln">
    </span><span class="str">"ext"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"js"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"exec"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"node index.js"</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	السطر اﻷول، يعني أن على Nodemon أن يتتبع التغيرات الحاصلة على أي ملف بامتداد js. والسطر الثاني يمثل الأمر المراد تنفيذه عند حصول أي تغيير على أي ملف بالامتداد المحدد.
</p>

<h2>
	إنشاء أول نقطة وصول من الواجهة الخلفية
</h2>

<p>
	الآن، دعنا ننشئ أولى نقاط الوصول Endpoint الخاصة بنا.
</p>

<p>
	أولًا، نعدّل على ملف package.json ونضيف له سكريبت كالتالي
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_24" style="">
<span class="str">"scripts"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">"start"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"nodemon"</span><span class="pln">
  </span><span class="pun">}</span></pre>

<p>
	سيكون الملف النهائي كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_27" style="">
<span class="pun">{</span><span class="pln">
  </span><span class="str">"name"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"zid"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"version"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"1.0.0"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"main"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"index.js"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"license"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"MIT"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"dependencies"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">"express"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^4.17.1"</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="str">"devDependencies"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">"nodemon"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^2.0.7"</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
</span><span class="str">"scripts"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">"start"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"nodemon"</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	الآن كل شيء جاهز. ننشئ ملف جديد باسم <a data-ss1631886121="1" data-ss1631887126="1" href="https://github.com/HsoubAcademy/zid-retention-app/blob/master/index.js" rel="external nofollow">index.js</a> يحتوي على الشفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_29" style="">
<span class="kwd">import</span><span class="pln"> express from </span><span class="str">"express"</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">var</span><span class="pln"> app </span><span class="pun">=</span><span class="pln"> express</span><span class="pun">();</span><span class="pln">

app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">express</span><span class="pun">.</span><span class="pln">json</span><span class="pun">());</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">express</span><span class="pun">.</span><span class="pln">urlencoded</span><span class="pun">({</span><span class="pln"> extended</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">false</span><span class="pln"> </span><span class="pun">}));</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> port </span><span class="pun">=</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">PORT </span><span class="pun">||</span><span class="pln"> </span><span class="lit">3000</span><span class="pun">;</span><span class="pln">

app</span><span class="pun">.</span><span class="pln">listen</span><span class="pun">(</span><span class="pln">port</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(`</span><span class="typ">Server</span><span class="pln"> started on $</span><span class="pun">{</span><span class="pln">port</span><span class="pun">}`);</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	سنشرح هذه الشفرة المصدرية لأنها أساس كل ما سيأتي لاحقًا.
</p>

<p>
	السطرين الأول والثاني:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_31" style="">
<span class="kwd">import</span><span class="pln"> express from </span><span class="str">"express"</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">var</span><span class="pln"> app </span><span class="pun">=</span><span class="pln"> express</span><span class="pun">();</span></pre>

<p>
	استدعينا إطار العمل express، بعدها أنشأنا نسخة منه Instance منه باسم app، ومنها سيعمل التطبيق ككل.
</p>

<p>
	السطرين الثالث والرابع تخص إطار Express إذ أخبرنا ببساطة Express بأن كل الطلبات Requests ستكون على شكل JSON.
</p>

<p>
	السطر الخامس:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_33" style="">
<span class="kwd">const</span><span class="pln"> port </span><span class="pun">=</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">PORT </span><span class="pun">||</span><span class="pln"> </span><span class="lit">3000</span><span class="pun">;</span></pre>

<p>
	أنشأنا ثابتًا يحدد رقم المنفذ Port الذي سيعمل عليه الخادم الخاص بنا. بحيث إن كان معرفا كمتغير نظام أو سيؤخذ المنفذ 3000.
</p>

<p>
	السطر السادس هو إنشاء الخادم الخاص بالتطبيق والذي سيعمل على المنفذ Port الذي حددناه سابقًا:
</p>

<pre class="ipsCode prettyprint lang-ruby prettyprinted" id="ips_uid_235_36" style="">
<span class="pln">app</span><span class="pun">.</span><span class="pln">listen</span><span class="pun">(</span><span class="pln">port</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">`Server started on ${port}`</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	الآن أضف الشفرة المصدرية التالية مباشرةً بعد السطر الرابع:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_38" style="">
<span class="pln">app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(‘</span><span class="typ">Hello</span><span class="pln"> </span><span class="typ">Hsoub</span><span class="pun">’);</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	سيكون شكل الملف كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_40" style="">
<span class="kwd">import</span><span class="pln"> express from </span><span class="str">"express"</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">var</span><span class="pln"> app </span><span class="pun">=</span><span class="pln"> express</span><span class="pun">();</span><span class="pln">

app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">express</span><span class="pun">.</span><span class="pln">json</span><span class="pun">());</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">express</span><span class="pun">.</span><span class="pln">urlencoded</span><span class="pun">({</span><span class="pln"> extended</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">false</span><span class="pln"> </span><span class="pun">}));</span><span class="pln">

app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(`</span><span class="typ">Hello</span><span class="pln"> </span><span class="typ">Hsoub</span><span class="pun">`);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> port </span><span class="pun">=</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">PORT </span><span class="pun">||</span><span class="pln"> </span><span class="lit">3000</span><span class="pun">;</span><span class="pln">

app</span><span class="pun">.</span><span class="pln">listen</span><span class="pun">(</span><span class="pln">port</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(`</span><span class="typ">Server</span><span class="pln"> started on $</span><span class="pun">{</span><span class="pln">port</span><span class="pun">}`);</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	سنثبت بعض المكتبات التي سنستخدمها في بناء التطبيق الخاص بنا. نفِّذ الأمر التالي في سطر الأوامر في مجلد المشروع:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_43" style="">
<span class="pln">yarn add axios pug</span></pre>

<p>
	ثبتنا المكتبتين التاليتين:
</p>

<ul>
<li>
		Axios: هو مكتبة مهمتها القيام بإرسال طلبيات HTTP.
	</li>
	<li>
		Pug: مكتبة تخص قوالب لبناء واجهات المستخدم Frontend Templating Engine.
	</li>
</ul>
<p>
	هنالك عدة مكتبات تخص بناء واجهات المستخدم، وقع اﻹختيار على مكتبة Pug بسبب أن الشيفرة البرمجية الخاصة بها بسيطة وسهلة القراءة، باﻹضافة إلى أنها متوافقة بامتياز مع إطار العمل ExpressJS.
</p>

<p>
	الآن نفّذ الأمر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_45" style="">
<span class="pln">yarn start</span></pre>

<p>
	ستظهر لك رسالة خطأ تفيد بأنه ﻻ يمكنك استيراد إطار العمل express داخل المشروع عبر <code>import</code>، ويكفي لحل هذه المشكلة فقط أن تضيف السطر التالي إلى الملف package.json:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_47" style="">
<span class="pln">  </span><span class="str">"type"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"module"</span><span class="pun">,</span></pre>

<p>
	في حالة واجهت رسالة خطأ مثل الرسالة التالية:
</p>

<pre class="ipsCode prettyprint lang-ruby prettyprinted" id="ips_uid_235_49" style="">
<span class="typ">Error</span><span class="pun">:</span><span class="pln"> listen EADDRINUSE</span><span class="pun">:</span><span class="pln"> address already </span><span class="kwd">in</span><span class="pln"> use </span><span class="pun">:::</span><span class="lit">3000</span></pre>

<p>
	تأكد من إيقاف التطبيقات الأخرى التي تستخدم المنفذ 3000 أو غير المنفذ الخاص بالتطبيق ليكون مثلا 3001:
</p>

<p>
	افتح المتصفح، وادخل العنوان التالي:
</p>

<pre class="ipsCode prettyprint lang-ruby prettyprinted" id="ips_uid_235_55" style="">
<span class="pln">http</span><span class="pun">://</span><span class="pln">localhost</span><span class="pun">:</span><span class="lit">3000</span></pre>

<p>
	يفترض أن تحصل على الرسالة التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="77183" data-ss1631886121="1" data-ss1631887126="1" href="https://academy.hsoub.com/uploads/monthly_2021_09/First_Endpoint.png.80d0fe7f772cfac4502db60886a3c98a.png" rel=""><img alt="First_Endpoint.png" class="ipsImage ipsImage_thumbnailed" data-fileid="77183" data-unique="jzsyncj9p" src="https://academy.hsoub.com/uploads/monthly_2021_09/First_Endpoint.thumb.png.8001c513533000961cdfdb902cd73645.png"></a>
</p>

<p>
	تهانينا، أتممت تهئية التطبيق وإنشاء أولى نقاط الوصول Endpoint الخاصة بالواجهة الخلفية فيه باستخدام NodeJS وExpressJS.
</p>

<h2>
	بناء تطبيق الاحتفاظ بمستخدمي متاجر زد
</h2>

<p>
	الآن وقد شرحنا باختصار شديد طريقة بناء واجهة خلفية برمجية بسيطة باستخدام تقنية NodeJS وإطار عمل ExpressJS، سننتقل إلى بناء تطبيق بسيط لزيادة ولاء العملاء.
</p>

<p>
	قبل أن نبدأ، عليك أن تحاول الاستزادة قدر الإمكان في ما يتعلق بالبرمجة باستخدام NodeJS، وطريقة بناء الواجهات البرمجية، لأن هدفنا هنا هو التطبيق، لهذا لن تجدني أشرح باستفاضة الأمور البسيطة.
</p>

<h3>
	جلب سلات الشراء المتروكة
</h3>

<p>
	أنشئ ملفًا جديدًا باسم <a data-ss1631886121="1" data-ss1631887126="1" href="https://github.com/HsoubAcademy/zid-retention-app/blob/master/zid.js" rel="external nofollow">zid.js</a> يحوي على الشفرة المصدرية التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_59" style="">
<span class="com">//  axios استيراد مكتبة </span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> axios from </span><span class="str">"axios"</span><span class="pun">;</span><span class="pln">
</span><span class="com">// إنشاء متغير يحمل رابط الواجهة البرمجية</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">ZidAPI</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">"https://api.zid.dev/app/v1"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">AbandonedCarts</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> headers </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">"X-MANAGER-TOKEN"</span><span class="pun">:</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">MANAGER_TOKEN</span><span class="pun">,</span><span class="pln">
    </span><span class="typ">Authorization</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Bearer "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">auth</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">};</span><span class="pln">
  </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> getAbandondCarts </span><span class="pun">=</span><span class="pln"> await axios</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="pln">
      </span><span class="pun">`</span><span class="pln">$</span><span class="pun">{</span><span class="typ">ZidAPI</span><span class="pun">}/</span><span class="pln">managers</span><span class="pun">/</span><span class="pln">store</span><span class="pun">/</span><span class="pln">abandoned</span><span class="pun">-</span><span class="pln">carts</span><span class="pun">`,</span><span class="pln">
      </span><span class="pun">{</span><span class="pln">
        headers</span><span class="pun">:</span><span class="pln"> headers</span><span class="pun">,</span><span class="pln">
        params</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> page</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> page_size</span><span class="pun">:</span><span class="pln"> </span><span class="lit">10</span><span class="pun">,</span><span class="pln"> duration</span><span class="pun">:</span><span class="pln"> </span><span class="lit">4</span><span class="pln"> </span><span class="pun">},</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">getAbandondCarts</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">return</span><span class="pln"> getAbandondCarts</span><span class="pun">.</span><span class="pln">data</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="pln">error</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> error</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">};</span><span class="pln">

</span><span class="kwd">export</span><span class="pln"> </span><span class="kwd">default</span><span class="pln"> </span><span class="typ">AbandonedCarts</span><span class="pun">;</span></pre>

<p>
	والآن، لنشرح الشيفرة باختصار شديد. بدايةً، استدعينا المكتبة axios من أجل إرسال بطلبات للواجهة البرمجية الخاصة بمنصة زد، ثم أنشأنا ثابتًا باسم ZidAPI ويحوي أساس نقطة الوصول الخاصة بالواجهة البرمجية لمنصة زد.
</p>

<p>
	أنشأنا دالة Function <a data-ss1631886121="1" data-ss1631887126="1" href="https://academy.hsoub.com/programming/javascript/%D8%B4%D8%B1%D8%AD-%D8%A7%D9%84%D9%84%D8%A7%D8%AA%D8%B2%D8%A7%D9%85%D9%86-async-%D9%88%D8%B9%D8%AF%D9%85-%D8%A7%D9%84%D8%A7%D9%86%D8%AA%D8%B8%D8%A7%D8%B1-defer-%D8%A3%D8%AB%D9%86%D8%A7%D8%A1-%D8%AA%D8%AD%D9%85%D9%8A%D9%84-%D8%B3%D9%83%D8%B1%D8%A8%D8%AA%D8%A7%D8%AA-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-%D9%81%D9%8A-%D8%B5%D9%81%D8%AD%D8%A7%D8%AA-html-r1237/" rel="">غير متزامنة</a> باسم <code>AbandonedCarts</code> ومهمتها هي جلب سلات الشراء المتروكة على متجرنا.
</p>

<p>
	أنشأنا داخل تلك الدالة ثابتًا باسم <code>headers</code>، وهو كائن يحتوي على قيمتين مفتاح المتجر<code>X-MANAGER-TOKEN</code> و مفتاح الواجهة البرمجية <code>Authorization</code>. إن لم تعرف من أين حصلنا على هذين المتغيرين فارجع إلى المقالات السابقة.
</p>

<p>
	قد أجدك تتساءل ما معنى process.env؟ هي طريقة لحماية مفاتيح الوصول، وذلك عن طريق إضافة المفتاح كمتغير بيئة Environment Variable. فمثلا، في تطبيقنا هذا، حمينا مفتاح الوصول الخاص بالمتجر باسم X-MANAGER-TOKEN.
</p>

<p>
	تُنفّذ هذه العملية على نحو فتح سطر الأوامر وإدخال الأمر التالي:
</p>

<pre class="ipsCode">
export X-MANAGER-TOKEN=value
</pre>

<p>
	استبدل value بقيمة مفتاح المتجر بدون إضافة مسافة بعد علامة <code>=</code> ونفس الشيء بالنسبة للمتغير Authorization.
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		ملاحظة: هذه الطريقة تعمل على أنظمة التشغيل لينكس وماكنتوش، أما في نظام التشغيل ويندوز، فمن الأفضل تثبيت WSL 2 أو استخدام الطريقة المعروفة لانشاء متغيرات النظام.
	</p>
</blockquote>

<p>
	سنشرح في عجالة طريقة تثبيت WSL2 على نظام التشغيل ويندوز، ومن ثم طريقة إنشاء متغيرات النظام.
</p>

<p>
	بداية تأكد من تحديث نظام التشغيل عندك إلى آخر نسخة ممكنة حتى تستطيع الاستفادة من خاصية تثبيت نظام لينكس كنظام ثانوي داخل ويندوز.
</p>

<p>
	افتح PowerShell كمستخدم مدير، ونفذ الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-ruby prettyprinted" id="ips_uid_235_61" style="">
<span class="pln">dism</span><span class="pun">.</span><span class="pln">exe </span><span class="pun">/</span><span class="pln">online </span><span class="pun">/</span><span class="pln">enable</span><span class="pun">-</span><span class="pln">feature</span><span class="pun">/</span><span class="pln">featurename</span><span class="pun">:</span><span class="typ">Microsoft</span><span class="pun">-</span><span class="typ">Windows</span><span class="pun">-</span><span class="typ">Subsystem</span><span class="pun">-</span><span class="typ">Linux</span><span class="pln"> </span><span class="pun">/</span><span class="pln">all </span><span class="pun">/</span><span class="pln">norestart</span></pre>

<p>
	بعد انتهاء تنفيذ الأمر، نحتاج الآن إلى تفعيل خاصية الأنظمة الوهمية Virtual Machine الخاصة بنظام التشغيل ويندوز وذلك عن طريق تنفيذ الأمر التالي في نفس نافذة PowerShell السابقة:
</p>

<pre class="ipsCode prettyprint lang-ruby prettyprinted" id="ips_uid_235_63" style="">
<span class="pln">dism</span><span class="pun">.</span><span class="pln">exe </span><span class="pun">/</span><span class="pln">online </span><span class="pun">/</span><span class="pln">enable</span><span class="pun">-</span><span class="pln">feature </span><span class="pun">/</span><span class="pln">featurename</span><span class="pun">:</span><span class="typ">VirtualMachinePlatform</span><span class="pln"> </span><span class="pun">/</span><span class="pln">all </span><span class="pun">/</span><span class="pln">norestart</span></pre>

<p>
	بعد انتهاء تنفيذ الأمر، أعد تشغيل جهازك حتى يتسنى للنظام تفعيل جميع الخصائص المطلوبة.
</p>

<p>
	علينا الآن تحديث نواة نظام التشغيل لينكس، وذلك بتنزيل آخر <a data-ss1631886121="1" data-ss1631887126="1" href="https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi" rel="external nofollow">تحديث متوفر</a>، بعد انتهاء التنزيل، افتح الملف واتبع الخطوات لتثبيته كأي برنامج آخر.
</p>

<p>
	إفتح نافذة PowerShell، ونفذ الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_66" style="">
<span class="pln">wsl </span><span class="pun">--</span><span class="kwd">set</span><span class="pun">-</span><span class="kwd">default</span><span class="pun">-</span><span class="pln">version </span><span class="lit">2</span></pre>

<p>
	افتح <a data-ss1631886121="1" data-ss1631887126="1" href="https://aka.ms/wslstore" rel="external nofollow">متجر تطبيقات ويندوز</a> وابحث عن Ubuntu وثبِّته مثل أي تطبيق آخر، وبعد الانتهاء أعد تشغيل جهازك. بإمكانك الآن تنفيذ أوامر لينكس داخل نظام ويندوز دون مشاكل. لمزيد من التفاصيل، ارجع إلى مقال <a data-ss1631886121="1" data-ss1631887126="1" href="https://academy.hsoub.com/apps/windows/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-hyper-v-%D9%81%D9%8A-%D9%88%D9%8A%D9%86%D8%AF%D9%88%D8%B2-10-r432/" rel="">تطبيق Hyper-V في ويندوز 10</a>.
</p>

<p>
	على أي حال، هنالك طريقة أخرى ﻹضافة متغيرات النظام عن طريق مكتبة dotenv، وتكون كالتالي:
</p>

<p>
	في سطر اﻷوامر أدخل الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_68" style="">
<span class="pln">yarn add dotenv</span></pre>

<p>
	أنشئ ملفًا باسم env. داخل المجلد الرئيسي للتطبيق، وضع بداخله قيمتي مفتاح المتجر ومفتاح الواجهة البرمجية، النتيجة ستكون كالتالي:
</p>

<pre class="ipsCode prettyprint lang-ruby prettyprinted" id="ips_uid_235_71" style="">
<span class="pln">MANAGER_TOKEN</span><span class="pun">=</span><span class="pln">value
auth</span><span class="pun">=</span><span class="pln">value</span></pre>

<p>
	لا تنسى تبديل القيمة value ووضع المفتاح المناسب الخاص بك مكانها.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="77182" data-ss1631886121="1" data-ss1631887126="1" href="https://academy.hsoub.com/uploads/monthly_2021_09/DotEnv.png.4a6b62362de72a19f4ee46e096bf4c24.png" rel=""><img alt="DotEnv.png" class="ipsImage ipsImage_thumbnailed" data-fileid="77182" data-unique="8gjnn4doj" src="https://academy.hsoub.com/uploads/monthly_2021_09/DotEnv.thumb.png.6e54fed97f9beb6213a8bbf04cfafbfc.png"></a>
</p>

<p>
	في بداية الملف <a data-ss1631886121="1" data-ss1631887126="1" href="https://github.com/HsoubAcademy/zid-retention-app/blob/master/index.js" rel="external nofollow">index.js</a> نضيف الشيفرة البرمجية التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_74" style="">
<span class="kwd">import</span><span class="pln"> dotenv from </span><span class="str">"dotenv"</span><span class="pun">;</span></pre>

<p>
	بقي علينا اﻵن أن نضيف شيفرة تخبر إطار Express أننا سنستخدم مكتبة dotenv لجلب متغيرات البيئة من الملف ‎.env عوض استخدام المتغيرات المخزنة فعلا في نظام التشغيل.
</p>

<p>
	في ملف index.js وقبل السطر التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_76" style="">
<span class="kwd">var</span><span class="pln"> app </span><span class="pun">=</span><span class="pln"> express</span><span class="pun">();</span></pre>

<p>
	نضيف ما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_78" style="">
<span class="pln">dotenv</span><span class="pun">.</span><span class="pln">config</span><span class="pun">();</span></pre>

<p>
	بإمكانك اﻵن إضافة متغيرات النظام مباشرةً للملف env. دون إضافتها بصورة مباشرة إلى نظام التشغيل.
</p>

<p>
	بعد الانتهاء من ضبط متغيرات البيئة، أنشأنا كتلة برمجية باستخدام <a data-ss1631886121="1" data-ss1631887126="1" href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A3%D8%AE%D8%B7%D8%A7%D8%A1%D8%8C-%D8%AC%D8%B1%D8%A8-%D8%A7%D9%84%D8%AA%D9%82%D8%B7-trycatch-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r908/" rel="">Try-Catch</a> لتجريب الإتصال بالواجهة البرمجية لمنصة زد.
</p>

<p>
	أنشأنا متغيرًا باسم <code>getAbandondCarts</code> وأسندنا إليه القيمة الراجعة من إرسال الطلب إلى الواجهة البرمجية لمنصة زد. بحيث أرسلنا طلب Request من نوع GET، يحمل المعلومات المطلوبة في توثيق الواجهة البرمجية، وهي ترويسة الطلب headers وأيضا استعلامًا Query فيه عدد الصفحات وأيضا عدد العناصر في الصفحة.
</p>

<p>
	بعدها تحققنا إن استقبلنا إجابة خادم الواجهة البرمجية لمنصة زد، وفي حالة وجود الإجابة، فإننا نرجع المحتوى خاصتها؛ أما في حالة وجود خطأ، فسننتقل إلى الجزء <code>catch</code> والذي سيرجع قيمة الخطأ مثل رسالة.
</p>

<p>
	في آخر الملف، صدرنا الدالة لنستخدمها في مواضع أخرى.
</p>

<p>
	الآن ننتقل إلى الملف <a data-ss1631886121="1" data-ss1631887126="1" href="https://github.com/HsoubAcademy/zid-retention-app/blob/master/index.js" rel="external nofollow">index.js</a> ونستدعي الملف zid.js بالطريقة التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_80" style="">
<span class="kwd">import</span><span class="pln"> </span><span class="typ">AbandonedCarts</span><span class="pln"> from </span><span class="str">"./zid.js"</span><span class="pun">;</span></pre>

<p>
	ثم نضيف نقطة وصول جديدة بنفس الطريقة السابقة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_82" style="">
<span class="pln">app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/carts"</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> response </span><span class="pun">=</span><span class="pln"> await </span><span class="typ">AbandonedCarts</span><span class="pun">();</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> renderedData </span><span class="pun">=</span><span class="pln"> response</span><span class="pun">[</span><span class="str">"abandoned-carts"</span><span class="pun">];</span><span class="pln">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="pln">renderedData</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	دعنا نفهم الشيفرة السابقة:
</p>

<ul>
<li>
		أنشأنا نقطة وصول جديدة بحيث تستقبل طلبات من نوع GET
	</li>
	<li>
		أنشأنا ثابتًا جديدًا باسم <code>response</code> بحيث يستقبل إجابة الخادم من الدالة التي أنشأناها سابقًا باسم <code>AbandonedCarts</code>
	</li>
	<li>
		أنشأنا ثابتًا جديدًا باسم <code>renderedData</code> يحمل قيمة فرعية من الثابت <code>response</code>
	</li>
	<li>
		أعدنا محتوى الثابت <code>renderedData</code>
	</li>
</ul>
<p>
	لنجرب الآن نقطة الوصول الجديدة، افتح برنامج Insomnia، وأنشئ طلبًا جديدًا من نوع GET لنقطة الوصول التالية:
</p>

<pre class="ipsCode prettyprint lang-ruby prettyprinted" id="ips_uid_235_84" style="">
<span class="pln">http</span><span class="pun">:/</span><span class="str">/localhost:3000/</span><span class="pln">carts</span></pre>

<p>
	وأرسل الطلب، إن لم توجد سلات شراء متروكة، فإن الواجهة البرمجية لمنصة زد، ستعيد لنا مصفوفة فارغة، أما إن وجدت سلات شراء متروكة، ستحصل على مصفوفة مشابهة لهذه:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_86" style="">
<span class="pun">[</span><span class="pln">
  </span><span class="pun">{</span><span class="pln">
    </span><span class="str">"id"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"6718344e-8e2c-4667-b2cd-1178632de6e9"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"store_id"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"a739c51c-8103-4648-873b-cc3a8ea2dc8a"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"session_id"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"xQH4imSzunyafhd2QkD2llkjz8qjEKvW"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"cart_id"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"904142370"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"order_id"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">7231078</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"phase"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"completed"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"customer_id"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5835</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"customer_name"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"man"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"customer_email"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"m.a***at@zid.sa"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"customer_mobile"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"966550*****"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"city_id"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"products_count"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"reminders_count"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"cart_total"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">138</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"cart_total_string"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"138.00 SAR"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"created_at"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"2021-05-20 08:59:54"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"updated_at"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"2021-05-20 10:59:35"</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="pun">{</span><span class="pln">
    </span><span class="str">"id"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"33e932a1-0a9f-4468-a8de-c09540f9aa56"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"store_id"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"a739c51c-8103-4648-873b-cc3a8ea2dc8a"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"session_id"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"b6fRKKmcwVtLssqQNgvPSEaY2FvzAG7B"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"cart_id"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"914265844"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"order_id"</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"phase"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"shipping_address"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"customer_id"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3450024</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"customer_name"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Mohammad"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"customer_email"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"asha.k@fast.com"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"customer_mobile"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"966506766***"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"city_id"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"products_count"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">2</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"reminders_count"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"cart_total"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">504.85</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"cart_total_string"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"504.85 SAR"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"created_at"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"2021-05-19 09:09:45"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"updated_at"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"2021-05-19 12:23:16"</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">]</span></pre>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		ملاحظة: لقد أُخفيت أسماء العملاء حفاظا على الخصوصية.
	</p>
</blockquote>

<p>
	أصبحنا الآن قادرين على تتبع كل السلات المتروكة وحالتها والتفاصيل الخاصة بها وفعل أي شيء متاح معها عبر هذه التفاصيل مع كل سلة وهنا مربط الفرس.
</p>

<p>
	سننتقل الآن إلى إنشاء واجهة استخدام بسيطة باستخدام محرك القوالب Pug.
</p>

<h3>
	بناء شريط التنقل وواجهة السلات المتروكة
</h3>

<p>
	سنبني واجهات التطبيق عبر محركة القوالب Pug وسنبدأ بواجهة السلات المتروكة، ولكن دعنا أول أن نفهم ما هو محرك القوالب.
</p>

<p>
	<a data-ss1631886121="1" data-ss1631887126="1" href="https://academy.hsoub.com/programming/php/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-%D8%AA%D9%88%D9%8A%D8%BA-twig-r1227/" rel="">محرك القوالب Template Engine</a> هو نظام قوالب يسمح لنا بإنشاء واجهات الإستخدام على الخادم ومن ثم إخراجها Render للعميل. يمكنك الإطلاع على التوثيق الخاص بمحرك القوالب Pug من <a data-ss1631886121="1" data-ss1631887126="1" href="https://pugjs.org/api/getting-started.html" rel="external nofollow">الموقع الرسمي الخاص به</a>.
</p>

<p>
	أنشئ مجلدًا جديدًا باسم views داخل المجلد الرئيسي للمشروع، وأنشئ ملفًا جديدًا باسم <a data-ss1631886121="1" data-ss1631887126="1" href="https://github.com/HsoubAcademy/zid-retention-app/blob/master/views/page.pug" rel="external nofollow">page.pug</a>، ضع بداخله الشيفرة البرمجية التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_90" style="">
<span class="pln">doctype html
block head
    meta</span><span class="pun">(</span><span class="pln">charset</span><span class="pun">=</span><span class="str">'UTF-8'</span><span class="pun">)</span><span class="pln">
    meta</span><span class="pun">(</span><span class="pln">http</span><span class="pun">-</span><span class="pln">equiv</span><span class="pun">=</span><span class="str">'X-UA-Compatible'</span><span class="pln"> content</span><span class="pun">=</span><span class="str">'IE=edge'</span><span class="pun">)</span><span class="pln">
    meta</span><span class="pun">(</span><span class="pln">name</span><span class="pun">=</span><span class="str">'viewport'</span><span class="pln"> content</span><span class="pun">=</span><span class="str">'width=device-width, initial-scale=1.0'</span><span class="pun">)</span><span class="pln">
    link</span><span class="pun">(</span><span class="pln">href</span><span class="pun">=</span><span class="str">'https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css'</span><span class="pln"> rel</span><span class="pun">=</span><span class="str">'stylesheet'</span><span class="pun">)</span><span class="pln">
body
</span><span class="pun">.</span><span class="pln">w</span><span class="pun">-</span><span class="pln">full</span><span class="pun">.</span><span class="pln">max</span><span class="pun">-</span><span class="pln">w</span><span class="pun">-</span><span class="lit">8xl</span><span class="pun">.</span><span class="pln">min</span><span class="pun">-</span><span class="pln">w</span><span class="pun">-</span><span class="lit">0.mx</span><span class="pun">-</span><span class="kwd">auto</span><span class="pun">.</span><span class="pln">px</span><span class="pun">-</span><span class="lit">6</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">flex</span><span class="pun">.</span><span class="pln">mt</span><span class="pun">-</span><span class="lit">12.bg</span><span class="pun">-</span><span class="pln">white</span><span class="pun">.</span><span class="pln">rounded</span><span class="pun">-</span><span class="pln">md</span><span class="pun">.</span><span class="pln">shadow
        </span><span class="pun">.</span><span class="pln">w</span><span class="pun">-</span><span class="lit">64.bg</span><span class="pun">-</span><span class="pln">blue</span><span class="pun">-</span><span class="lit">100.rounded</span><span class="pun">-</span><span class="pln">l</span><span class="pun">-</span><span class="pln">md</span><span class="pun">.</span><span class="pln">border</span><span class="pun">-</span><span class="pln">r</span><span class="pun">.</span><span class="pln">border</span><span class="pun">-</span><span class="pln">dashed</span><span class="pun">.</span><span class="pln">border</span><span class="pun">-</span><span class="pln">blue</span><span class="pun">-</span><span class="lit">200</span><span class="pln">
            </span><span class="pun">.</span><span class="pln">flex</span><span class="pun">.</span><span class="pln">justify</span><span class="pun">-</span><span class="pln">center</span><span class="pun">.</span><span class="pln">items</span><span class="pun">-</span><span class="pln">center</span><span class="pun">.</span><span class="pln">h</span><span class="pun">-</span><span class="lit">32.text</span><span class="pun">-</span><span class="pln">blue</span><span class="pun">-</span><span class="lit">700.text</span><span class="pun">-</span><span class="pln">center</span><span class="pun">.</span><span class="pln">font</span><span class="pun">-</span><span class="pln">semibold</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="lit">3xl</span><span class="pun">.</span><span class="pln">italic
                </span><span class="pun">|</span><span class="pln"> </span><span class="typ">Loyalty</span><span class="pln"> </span><span class="typ">Program</span><span class="pln">
            </span><span class="pun">.</span><span class="pln">mt</span><span class="pun">-</span><span class="lit">8.border</span><span class="pun">-</span><span class="pln">t</span><span class="pun">.</span><span class="pln">border</span><span class="pun">-</span><span class="pln">blue</span><span class="pun">-</span><span class="lit">200</span><span class="pln">
                a</span><span class="pun">.</span><span class="pln">block</span><span class="pun">.</span><span class="pln">py</span><span class="pun">-</span><span class="lit">3.px</span><span class="pun">-</span><span class="lit">6.text</span><span class="pun">-</span><span class="pln">blue</span><span class="pun">-</span><span class="lit">700.font</span><span class="pun">-</span><span class="pln">semibold</span><span class="pun">.</span><span class="pln">border</span><span class="pun">-</span><span class="pln">b</span><span class="pun">.</span><span class="pln">border</span><span class="pun">-</span><span class="pln">blue</span><span class="pun">-</span><span class="lit">200</span><span class="pun">(</span><span class="pln">href</span><span class="pun">=</span><span class="str">'/'</span><span class="pun">)</span><span class="pln">
                    </span><span class="pun">|</span><span class="pln"> </span><span class="typ">Home</span><span class="pln">
                a</span><span class="pun">.</span><span class="pln">block</span><span class="pun">.</span><span class="pln">py</span><span class="pun">-</span><span class="lit">3.px</span><span class="pun">-</span><span class="lit">6.text</span><span class="pun">-</span><span class="pln">blue</span><span class="pun">-</span><span class="lit">700.font</span><span class="pun">-</span><span class="pln">semibold</span><span class="pun">.</span><span class="pln">border</span><span class="pun">-</span><span class="pln">b</span><span class="pun">.</span><span class="pln">border</span><span class="pun">-</span><span class="pln">blue</span><span class="pun">-</span><span class="lit">200</span><span class="pun">(</span><span class="pln">href</span><span class="pun">=</span><span class="str">'/carts'</span><span class="pun">)</span><span class="pln">
                    </span><span class="pun">|</span><span class="pln"> </span><span class="typ">Abandoned</span><span class="pln"> </span><span class="typ">Carts</span><span class="pln">
                a</span><span class="pun">.</span><span class="pln">block</span><span class="pun">.</span><span class="pln">py</span><span class="pun">-</span><span class="lit">3.px</span><span class="pun">-</span><span class="lit">6.text</span><span class="pun">-</span><span class="pln">blue</span><span class="pun">-</span><span class="lit">700.font</span><span class="pun">-</span><span class="pln">semibold</span><span class="pun">.</span><span class="pln">border</span><span class="pun">-</span><span class="pln">b</span><span class="pun">.</span><span class="pln">border</span><span class="pun">-</span><span class="pln">blue</span><span class="pun">-</span><span class="lit">200</span><span class="pun">(</span><span class="pln">href</span><span class="pun">=</span><span class="str">'/coupon'</span><span class="pun">)</span><span class="pln">
                    </span><span class="pun">|</span><span class="pln"> </span><span class="typ">Coupons</span><span class="pln">
                a</span><span class="pun">.</span><span class="pln">block</span><span class="pun">.</span><span class="pln">py</span><span class="pun">-</span><span class="lit">3.px</span><span class="pun">-</span><span class="lit">6.text</span><span class="pun">-</span><span class="pln">blue</span><span class="pun">-</span><span class="lit">700.font</span><span class="pun">-</span><span class="pln">semibold</span><span class="pun">.</span><span class="pln">border</span><span class="pun">-</span><span class="pln">b</span><span class="pun">.</span><span class="pln">border</span><span class="pun">-</span><span class="pln">blue</span><span class="pun">-</span><span class="lit">200</span><span class="pun">(</span><span class="pln">href</span><span class="pun">=</span><span class="str">'/loyalty'</span><span class="pun">)</span><span class="pln">
                    </span><span class="pun">|</span><span class="pln"> </span><span class="typ">Loyalty</span><span class="pln">


        block content
            </span><span class="pun">.</span><span class="pln">flex</span><span class="pun">-</span><span class="pln">grow
            </span><span class="pun">.</span><span class="pln">flex</span><span class="pun">.</span><span class="pln">flex</span><span class="pun">-</span><span class="pln">col</span><span class="pun">.</span><span class="pln">mx</span><span class="pun">-</span><span class="lit">2.mt</span><span class="pun">-</span><span class="lit">1</span><span class="pln">
                </span><span class="pun">.</span><span class="pln">px</span><span class="pun">-</span><span class="lit">3.py</span><span class="pun">-</span><span class="lit">4.flex</span><span class="pun">.</span><span class="pln">justify</span><span class="pun">-</span><span class="pln">center</span></pre>

<p>
	لن نشرح صيغة كتابة قوالب pug لأنها بسيطة جدًا، وتحفيزك أيضًا للإطلاع على التوثيق الرسمي الخاص بـ Pug.
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		ملاحظة: في هذا المشروع سنستخدم الإطار TailwindCSS ليكون المسؤول عن تنسيق صفحات المشروع CSS. لمن لا يعرف إطار TailwindCSS هو إطار عمل خاص بلغة CSS مشابه لإطار Bootstrap ولكنه يتميز بصغر حجمه موازنةً ببقية أطر العمل. سبب استخدام Tailwind كان فقط من أجل عدم الدخول في تفاصيل تنسيق الصفحات CSS ويمكنك استعمال أي إطار شئت أو الاعتماد على تنسيق CSS مباشرةً.
	</p>
</blockquote>

<p>
	يتيح محرك القوالب Pug إنشاء كتل برمجية blocks وإعادة استخدامها لاحقا في أي مكان تحتاجه، كمثال على ذلك، هو ما فعلناه في الملف السابق page.pug، بحيث أنشأنا كتلة برمجية سنستخدمها في بقية الصفحات.
</p>

<p>
	في الشيفرة البرمجية السابقة التي تخص الملف <a data-ss1631886121="1" data-ss1631887126="1" href="https://github.com/HsoubAcademy/zid-retention-app/blob/master/views/page.pug" rel="external nofollow">page.pug</a>، ﻻحظ أننا أنشأنا كتلة block باسم head وأضفنا إليها ما يتعلق بالبيانات الوصفية Metadata، مع إرفاق ملف خاص بإطار TailwindCSS، باستخدام شبكات الوصول CDN.
</p>

<p>
	بعدها أضفنا جسم الصفحة تحت ترويسة body، تحتوي على قائمة جانبية. وتحتها أضفنا كتلة جديدة تحت اسم content وهي ما سيحمل مختلف محتوى الصفحات الخاصة بالتطبيق.
</p>

<p>
	سنريكم صورةً عن الناتج النهائي المتوقع الحصول عليه:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="77184" data-ss1631886121="1" data-ss1631887126="1" href="https://academy.hsoub.com/uploads/monthly_2021_09/First_Page.png.dcd69dfb4894842915da762fac601747.png" rel=""><img alt="First_Page.png" class="ipsImage ipsImage_thumbnailed" data-fileid="77184" data-unique="rrrqvx2qv" src="https://academy.hsoub.com/uploads/monthly_2021_09/First_Page.thumb.png.0b0f6ffd46e5d5865d994ad5e1967931.png"></a>
</p>

<p>
	ننتقل اﻵن ﻹنشاء صفحة لعرض قائمة سلات الشراء المتروكة، باستخدام نقطة الوصول التي أنشأناها سابقًا، بحيث سنسترجع مصفوفة الكائنات التي تحصلنا عليها من الواجهة البرمجية والتي تخص متجر العميل، ونستخرج المعلومات التي نحتاجها باستخدام حلقة تكرارية بسيطة.
</p>

<p>
	أنشئ ملفًا جديدًا داخل المجلد views باسم <a data-ss1631886121="1" data-ss1631887126="1" href="https://github.com/HsoubAcademy/zid-retention-app/blob/master/views/abandoned.pug" rel="external nofollow">abandoned.pug</a> وضع بداخله الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_94" style="">
<span class="pln">extends page</span><span class="pun">.</span><span class="pln">pug 
block append head
    title </span><span class="typ">Abandoned</span><span class="pln"> </span><span class="typ">Carts</span><span class="pln">
block content 
  table</span><span class="pun">.</span><span class="pln">w</span><span class="pun">-</span><span class="pln">full</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">md</span><span class="pun">.</span><span class="pln">bg</span><span class="pun">-</span><span class="pln">white</span><span class="pun">.</span><span class="pln">shadow</span><span class="pun">-</span><span class="pln">md</span><span class="pun">.</span><span class="pln">rounded</span><span class="pun">.</span><span class="pln">mb</span><span class="pun">-</span><span class="lit">6.max</span><span class="pun">-</span><span class="pln">w</span><span class="pun">-</span><span class="lit">6xl</span><span class="pun">.</span><span class="pln">min</span><span class="pun">-</span><span class="pln">w</span><span class="pun">-</span><span class="lit">0.mx</span><span class="pun">-</span><span class="kwd">auto</span><span class="pun">.</span><span class="pln">px</span><span class="pun">-</span><span class="lit">6</span><span class="pln">
    thead
      tr</span><span class="pun">.</span><span class="pln">border</span><span class="pun">-</span><span class="pln">b
        th</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">left</span><span class="pun">.</span><span class="pln">p</span><span class="pun">-</span><span class="lit">1.px</span><span class="pun">-</span><span class="lit">4</span><span class="pln"> </span><span class="typ">Customer</span><span class="pln"> </span><span class="typ">Name</span><span class="pln">
        th</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">left</span><span class="pun">.</span><span class="pln">p</span><span class="pun">-</span><span class="lit">1.px</span><span class="pun">-</span><span class="lit">4</span><span class="pln"> </span><span class="typ">Customer</span><span class="pln"> </span><span class="typ">Email</span><span class="pln">
        th</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">left</span><span class="pun">.</span><span class="pln">p</span><span class="pun">-</span><span class="lit">1.px</span><span class="pun">-</span><span class="lit">4</span><span class="pln"> </span><span class="typ">Customer</span><span class="pln"> </span><span class="typ">Mobile</span><span class="pln">
        th</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">left</span><span class="pun">.</span><span class="pln">p</span><span class="pun">-</span><span class="lit">1.px</span><span class="pun">-</span><span class="lit">4</span><span class="pln"> </span><span class="typ">Products</span><span class="pln"> </span><span class="typ">In</span><span class="pln"> </span><span class="typ">Cart</span><span class="pln">
        th</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">left</span><span class="pun">.</span><span class="pln">p</span><span class="pun">-</span><span class="lit">1.px</span><span class="pun">-</span><span class="lit">4</span><span class="pln"> </span><span class="typ">Cart</span><span class="pln"> </span><span class="typ">Total</span><span class="pun">(</span><span class="pln">SAR</span><span class="pun">)</span><span class="pln">
        th</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">left</span><span class="pun">.</span><span class="pln">p</span><span class="pun">-</span><span class="lit">1.px</span><span class="pun">-</span><span class="lit">4</span><span class="pln"> </span><span class="typ">Phase</span><span class="pln">
    tbody 
    each element in renderedData 
      tr</span><span class="pun">.</span><span class="pln">border</span><span class="pun">-</span><span class="pln">b</span><span class="pun">.</span><span class="pln">bg</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">100</span><span class="pun">(</span><span class="kwd">class</span><span class="pun">=</span><span class="str">'hover:bg-orange-100'</span><span class="pun">)</span><span class="pln">
        td</span><span class="pun">.</span><span class="pln">p</span><span class="pun">-</span><span class="lit">3.px</span><span class="pun">-</span><span class="lit">5</span><span class="pun">=</span><span class="pln">element</span><span class="pun">.</span><span class="pln">customer_name
        td</span><span class="pun">.</span><span class="pln">p</span><span class="pun">-</span><span class="lit">3.px</span><span class="pun">-</span><span class="lit">5</span><span class="pun">=</span><span class="pln">element</span><span class="pun">.</span><span class="pln">customer_email
        td</span><span class="pun">.</span><span class="pln">p</span><span class="pun">-</span><span class="lit">3.px</span><span class="pun">-</span><span class="lit">5</span><span class="pun">=</span><span class="pln">element</span><span class="pun">.</span><span class="pln">customer_mobile
        td</span><span class="pun">.</span><span class="pln">p</span><span class="pun">-</span><span class="lit">3.px</span><span class="pun">-</span><span class="lit">5</span><span class="pun">=</span><span class="pln">element</span><span class="pun">.</span><span class="pln">products_count
        td</span><span class="pun">.</span><span class="pln">p</span><span class="pun">-</span><span class="lit">3.px</span><span class="pun">-</span><span class="lit">5</span><span class="pun">=</span><span class="pln">element</span><span class="pun">.</span><span class="pln">cart_total
        td</span><span class="pun">.</span><span class="pln">p</span><span class="pun">-</span><span class="lit">3.px</span><span class="pun">-</span><span class="lit">5</span><span class="pun">=</span><span class="pln">element</span><span class="pun">.</span><span class="pln">phase</span></pre>

<p>
	سأشرح هذا الكود باستفاضة.
</p>

<p>
	في السطر اﻷول، عملنًا تمديدًا للقالب اﻷب الذي أنشأناه سابقا، بحيث أن القالب الجديد (القالب ابن) سيأخذ كل محتوى القالب السابق ويضيف عليه ما نريد:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_96" style="">
<span class="pln">extends page</span><span class="pun">.</span><span class="pln">pug</span></pre>

<p>
	أما في السطرين التاليين، طلبنا من محرك القوالب Pug أن يعدل الكتلة block المسمى head في القالب الأب بما تحت هذا السطر، وهنا أعدنا تسمية عنوان الصفحة بـ Abandoned Carts:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_100" style="">
<span class="pln">block append head
    title </span><span class="typ">Abandoned</span><span class="pln"> </span><span class="typ">Carts</span></pre>

<p>
	بعدها أنشأنا جدولًا Table وقسمناه إلى 6 أعمدة تحمل عناوين المعلومات التي نريد عرضها.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_98" style="">
<span class="pln">    each element in renderedData </span></pre>

<p>
	في هذا السطر، أنشأنا حلقة تكرارية تستخرج المعلومات المطلوبة من مصفوفة الكائنات Array of Objects التي تحصلنا عليها سابقا، إذ وضعنا المعلومات المطلوبة في سطور حسب نوعيتها.
</p>

<p>
	بقيت لنا خطوة واحدة الآن، وهي ربط الصفحة التي أنشأناها مع نقطة الوصول التي سبق و اضفناها. توجه إلى الملف index.js وعدِّل نقطة الوصول السابقة لتكون كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_102" style="">
<span class="pln">app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/carts"</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> response </span><span class="pun">=</span><span class="pln"> await </span><span class="typ">AbandonedCarts</span><span class="pun">();</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> renderedData </span><span class="pun">=</span><span class="pln"> response</span><span class="pun">[</span><span class="str">"abandoned-carts"</span><span class="pun">];</span><span class="pln">
  res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">"abandoned"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> renderedData </span><span class="pun">});</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	لاحظ أننا غيرنا السطر الأخير واستبدلنا التابع <code>send</code> بتابع جديد هو <code>render</code>، والذي يأخذ معاملين، المعامل الأول هو القالب، والمعامل الثاني هي البيانات التي ستُعرَض في القالب.
</p>

<p>
	يجب التأكد من أن اسم القالب المرسل كمعامل للتابع <code>render</code> هو بنفس الاسم الذي يحمله القالب المعني. يجب التأكد أيضًا أن عنوان المعامل الثاني والمرسل كبيانات للعرض، هي بنفس الأسم الموجود في القالب الذي أُنشئ لهذا الشأن.
</p>

<p>
	شغِّل الآن الخادم الخاص بنا عن طريق تنفيذ الأمر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_104" style="">
<span class="pln">yarn start</span></pre>

<p>
	لم يعمل معك! بل وظهرت الكثير من رسائل الخطأ، صحيح؟
</p>

<p>
	عد إلى ملف <a data-ss1631886121="1" data-ss1631887126="1" href="https://github.com/HsoubAcademy/zid-retention-app/blob/master/index.js" rel="external nofollow">index.js</a> وأضف الأسطر التالية مباشرة قبل أول نقطة وصول Endpoint أنشأناها:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_106" style="">
<span class="pln">app</span><span class="pun">.</span><span class="kwd">set</span><span class="pun">(</span><span class="str">"views"</span><span class="pun">,</span><span class="pln"> path</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="pln">__dirname</span><span class="pun">,</span><span class="pln"> </span><span class="str">"views"</span><span class="pun">));</span><span class="pln">
app</span><span class="pun">.</span><span class="kwd">set</span><span class="pun">(</span><span class="str">"view engine"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"pug"</span><span class="pun">);</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">express</span><span class="pun">.</span><span class="kwd">static</span><span class="pun">(</span><span class="pln">path</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="pln">__dirname</span><span class="pun">,</span><span class="pln"> </span><span class="str">"public"</span><span class="pun">)));</span></pre>

<p>
	ما الذي تعنيه تلك الأسطر؟ ببساطة، طلبنا من إطار ExpressJS أن يستخدم Pug كنظام قوالب، وأيضًا أن يتعرف على مسار ملفات القوالب التي أنشأناها والموجودة داخل المجلد views.
</p>

<p>
	أما بالنسبة للسطر الأخير، فهي أننا أخبرنا ExpressJS بأن المجلد النهائي للقوالب بعد أن تحدث عملية التصدير/الاستخراج rendering أن يضع الملفات الناتجة في المجلد public.
</p>

<p>
	قبل أن تعيد تشغيل الخادم، استورد المكتبة path في بداية الملف، تمامًا بعد استيراد express مع إنشاء ثابت جديد باسم <code>dirname__</code> والذي سيحوي التابع <code>resolve</code> الخاص بالمكتبة path وبالتالي سيكون الملف index.js كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_108" style="">
<span class="kwd">import</span><span class="pln"> express from </span><span class="str">"express"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> path from </span><span class="str">"path"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="typ">AbandonedCarts</span><span class="pln"> from </span><span class="str">"./zid.js"</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> __dirname </span><span class="pun">=</span><span class="pln"> path</span><span class="pun">.</span><span class="pln">resolve</span><span class="pun">();</span><span class="pln">
</span><span class="kwd">var</span><span class="pln"> app </span><span class="pun">=</span><span class="pln"> express</span><span class="pun">();</span><span class="pln">

app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">express</span><span class="pun">.</span><span class="pln">json</span><span class="pun">());</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">express</span><span class="pun">.</span><span class="pln">urlencoded</span><span class="pun">({</span><span class="pln"> extended</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">false</span><span class="pln"> </span><span class="pun">}));</span><span class="pln">

app</span><span class="pun">.</span><span class="kwd">set</span><span class="pun">(</span><span class="str">"views"</span><span class="pun">,</span><span class="pln"> path</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="pln">__dirname</span><span class="pun">,</span><span class="pln"> </span><span class="str">"views"</span><span class="pun">));</span><span class="pln">
app</span><span class="pun">.</span><span class="kwd">set</span><span class="pun">(</span><span class="str">"view engine"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"pug"</span><span class="pun">);</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">express</span><span class="pun">.</span><span class="kwd">static</span><span class="pun">(</span><span class="pln">path</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="pln">__dirname</span><span class="pun">,</span><span class="pln"> </span><span class="str">"public"</span><span class="pun">)));</span><span class="pln">

app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/carts"</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> response </span><span class="pun">=</span><span class="pln"> await </span><span class="typ">AbandonedCarts</span><span class="pun">();</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> renderedData </span><span class="pun">=</span><span class="pln"> response</span><span class="pun">[</span><span class="str">"abandoned-carts"</span><span class="pun">];</span><span class="pln">
  res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">"abandoned"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> renderedData </span><span class="pun">});</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> port </span><span class="pun">=</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">PORT </span><span class="pun">||</span><span class="pln"> </span><span class="lit">3000</span><span class="pun">;</span><span class="pln">

app</span><span class="pun">.</span><span class="pln">listen</span><span class="pun">(</span><span class="pln">port</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(`</span><span class="typ">Server</span><span class="pln"> started on $</span><span class="pun">{</span><span class="pln">port</span><span class="pun">}`);</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	الآن، شغل الخادم الخاص بالتطبيق، وافتح المتصفح وادخل العنوان التالي:
</p>

<pre class="ipsCode prettyprint lang-ruby prettyprinted" id="ips_uid_235_110" style="">
<span class="pln">http</span><span class="pun">:/</span><span class="str">/localhost:3000/</span><span class="pln">carts</span></pre>

<p>
	ستظهر لنا النافذة التالي (اُخفيت معلومات العملاء حفاظا على الخصوصية):
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="77180" data-ss1631886121="1" data-ss1631887126="1" href="https://academy.hsoub.com/uploads/monthly_2021_09/Aband_Carts_First_Screen.png.9c8cffd5795a4f9e27ab43bfbe86b710.png" rel=""><img alt="Aband_Carts_First_Screen.png" class="ipsImage ipsImage_thumbnailed" data-fileid="77180" data-unique="llilxlx6s" src="https://academy.hsoub.com/uploads/monthly_2021_09/Aband_Carts_First_Screen.thumb.png.b04f11676f68a485270184a9c3f3d5c3.png"></a>
</p>

<p>
	تهانينا، لقد أكملت أول صفحات تطبيق.
</p>

<h3>
	إنشاء قسائم التخفيض Coupons
</h3>

<p>
	ننتقل الآن إلى بناء صفحة إنشاء قسيمة تخفيض باستخدام الواجهة البرمجية لمنصة زد، يرجى مراجعة <a data-ss1631886121="1" data-ss1631887126="1" href="https://portal.zid.dev/devportal/apis/" rel="external nofollow">التوثيق الخاص بها</a> من أجل معرفة المعلومات المطلوبة.
</p>

<p>
	أنشئ ملفًا جديدًا داخل المجلد views باسم <a data-ss1631886121="1" data-ss1631887126="1" href="https://github.com/HsoubAcademy/zid-retention-app/blob/master/views/coupon.pug" rel="external nofollow">coupon.pug</a> وضع الشيفرة البرمجية التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_112" style="">
<span class="pln">extends page</span><span class="pun">.</span><span class="pln">pug 
block append head
    title </span><span class="typ">Coupon</span><span class="pln">
block content 
form</span><span class="pun">#</span><span class="pln">form</span><span class="pun">(</span><span class="pln">action</span><span class="pun">=</span><span class="str">'/add-coupon'</span><span class="pln"> method</span><span class="pun">=</span><span class="str">'POST'</span><span class="pun">).</span><span class="pln">place</span><span class="pun">-</span><span class="pln">items</span><span class="pun">-</span><span class="pln">center</span><span class="pun">.</span><span class="pln">max</span><span class="pun">-</span><span class="pln">w</span><span class="pun">-</span><span class="pln">max</span><span class="pun">.</span><span class="pln">m</span><span class="pun">-</span><span class="lit">4.p</span><span class="pun">-</span><span class="lit">2.bg</span><span class="pun">-</span><span class="pln">white</span><span class="pun">.</span><span class="pln">rounded</span><span class="pun">.</span><span class="pln">shadow</span><span class="pun">-</span><span class="pln">xl
    p</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">800.font</span><span class="pun">-</span><span class="pln">medium </span><span class="typ">Adding</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> coupon code
    div
      label</span><span class="pun">.</span><span class="pln">block</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">sm</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">00</span><span class="pun">(</span><span class="kwd">for</span><span class="pun">=</span><span class="str">'name'</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Choose</span><span class="pln"> a name </span><span class="kwd">for</span><span class="pln"> the coupon
      input</span><span class="pun">.</span><span class="pln">w</span><span class="pun">-</span><span class="pln">full</span><span class="pun">.</span><span class="pln">px</span><span class="pun">-</span><span class="lit">5.py</span><span class="pun">-</span><span class="lit">1.text</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">700.bg</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">200.rounded</span><span class="pun">(</span><span class="pln">name</span><span class="pun">=</span><span class="str">'name'</span><span class="pln"> type</span><span class="pun">=</span><span class="str">'text'</span><span class="pln"> required</span><span class="pun">=</span><span class="str">''</span><span class="pln"> placeholder</span><span class="pun">=</span><span class="str">'Coupon Name'</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">mt</span><span class="pun">-</span><span class="lit">2</span><span class="pln">
      label</span><span class="pun">.</span><span class="pln">block</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">sm</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">600</span><span class="pun">(</span><span class="kwd">for</span><span class="pun">=</span><span class="str">'code'</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Coupon</span><span class="pln"> </span><span class="typ">Code</span><span class="pln">
      input</span><span class="pun">.</span><span class="pln">w</span><span class="pun">-</span><span class="pln">full</span><span class="pun">.</span><span class="pln">px</span><span class="pun">-</span><span class="lit">5.py</span><span class="pun">-</span><span class="lit">4.text</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">700.bg</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">200.rounded</span><span class="pun">(</span><span class="pln">name</span><span class="pun">=</span><span class="str">'code'</span><span class="pln"> type</span><span class="pun">=</span><span class="str">'text'</span><span class="pln"> required</span><span class="pun">=</span><span class="str">''</span><span class="pln"> placeholder</span><span class="pun">=</span><span class="str">'Hsoub10'</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">mt</span><span class="pun">-</span><span class="lit">2</span><span class="pln">
      label</span><span class="pun">.</span><span class="pln">block</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">sm</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">600</span><span class="pun">(</span><span class="kwd">for</span><span class="pun">=</span><span class="str">'discount_type'</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Discount</span><span class="pln"> </span><span class="typ">Type</span><span class="pln">
      input</span><span class="pun">.</span><span class="pln">w</span><span class="pun">-</span><span class="pln">full</span><span class="pun">.</span><span class="pln">px</span><span class="pun">-</span><span class="lit">2.py</span><span class="pun">-</span><span class="lit">2.text</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">700.bg</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">200.rounded</span><span class="pun">(</span><span class="pln">name</span><span class="pun">=</span><span class="str">'discount_type'</span><span class="pln"> type</span><span class="pun">=</span><span class="str">'text'</span><span class="pln"> required</span><span class="pun">=</span><span class="str">''</span><span class="pln"> placeholder</span><span class="pun">=</span><span class="str">'p for percentage and f for fixed'</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">mt</span><span class="pun">-</span><span class="lit">2</span><span class="pln">
      label</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">sm</span><span class="pun">.</span><span class="pln">block</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">600</span><span class="pun">(</span><span class="kwd">for</span><span class="pun">=</span><span class="str">'discount'</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Discount</span><span class="pln">
      input</span><span class="pun">.</span><span class="pln">w</span><span class="pun">-</span><span class="pln">full</span><span class="pun">.</span><span class="pln">px</span><span class="pun">-</span><span class="lit">2.py</span><span class="pun">-</span><span class="lit">2.text</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">700.bg</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">200.rounded</span><span class="pun">(</span><span class="pln">name</span><span class="pun">=</span><span class="str">'discount'</span><span class="pln"> type</span><span class="pun">=</span><span class="str">'text'</span><span class="pln"> required</span><span class="pun">=</span><span class="str">''</span><span class="pln"> placeholder</span><span class="pun">=</span><span class="str">'discount'</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="kwd">inline</span><span class="pun">-</span><span class="pln">block</span><span class="pun">.</span><span class="pln">mt</span><span class="pun">-</span><span class="lit">2.pr</span><span class="pun">-</span><span class="lit">1</span><span class="pun">(</span><span class="kwd">class</span><span class="pun">=</span><span class="str">'w-1/2'</span><span class="pun">)</span><span class="pln">
      label</span><span class="pun">.</span><span class="pln">block</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">sm</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">600</span><span class="pun">(</span><span class="kwd">for</span><span class="pun">=</span><span class="str">'free_shipping'</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Free</span><span class="pln"> </span><span class="typ">Shipping</span><span class="pln">
      input</span><span class="pun">.</span><span class="pln">w</span><span class="pun">-</span><span class="pln">full</span><span class="pun">.</span><span class="pln">px</span><span class="pun">-</span><span class="lit">2.py</span><span class="pun">-</span><span class="lit">2.text</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">700.bg</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">200.rounded</span><span class="pun">(</span><span class="pln">name</span><span class="pun">=</span><span class="str">'free_shipping'</span><span class="pln"> type</span><span class="pun">=</span><span class="str">'text'</span><span class="pln"> required</span><span class="pun">=</span><span class="str">''</span><span class="pln"> placeholder</span><span class="pun">=</span><span class="str">'1 to enable, 0 to disable'</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="kwd">inline</span><span class="pun">-</span><span class="pln">block</span><span class="pun">.</span><span class="pln">mt</span><span class="pun">-</span><span class="lit">2.</span><span class="pun">-</span><span class="pln">mx</span><span class="pun">-</span><span class="lit">1.pl</span><span class="pun">-</span><span class="lit">1</span><span class="pun">(</span><span class="kwd">class</span><span class="pun">=</span><span class="str">'w-1/2'</span><span class="pun">)</span><span class="pln">
      label</span><span class="pun">.</span><span class="pln">block</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">sm</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">600</span><span class="pun">(</span><span class="kwd">for</span><span class="pun">=</span><span class="str">'free_cod'</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Free</span><span class="pln"> </span><span class="typ">Cash</span><span class="pln"> on </span><span class="typ">Delivery</span><span class="pln">
      input</span><span class="pun">.</span><span class="pln">w</span><span class="pun">-</span><span class="pln">full</span><span class="pun">.</span><span class="pln">px</span><span class="pun">-</span><span class="lit">2.py</span><span class="pun">-</span><span class="lit">2.text</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">700.bg</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">200.rounded</span><span class="pun">(</span><span class="pln">name</span><span class="pun">=</span><span class="str">'free_cod'</span><span class="pln"> type</span><span class="pun">=</span><span class="str">'text'</span><span class="pln"> required</span><span class="pun">=</span><span class="str">''</span><span class="pln"> placeholder</span><span class="pun">=</span><span class="str">'1 to enable, 0 to disable'</span><span class="pun">)</span><span class="pln">
    div
      label</span><span class="pun">.</span><span class="pln">block</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">sm</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">600</span><span class="pun">(</span><span class="kwd">for</span><span class="pun">=</span><span class="str">'total'</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Total</span><span class="pln"> to apply coupon
      input</span><span class="pun">.</span><span class="pln">w</span><span class="pun">-</span><span class="pln">full</span><span class="pun">.</span><span class="pln">px</span><span class="pun">-</span><span class="lit">2.py</span><span class="pun">-</span><span class="lit">2.text</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">700.bg</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">200.rounded</span><span class="pun">(</span><span class="pln">name</span><span class="pun">=</span><span class="str">'total'</span><span class="pln"> type</span><span class="pun">=</span><span class="str">'text'</span><span class="pln"> required</span><span class="pun">=</span><span class="str">''</span><span class="pln"> placeholder</span><span class="pun">=</span><span class="str">'Total to apply coupon'</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="kwd">inline</span><span class="pun">-</span><span class="pln">block</span><span class="pun">.</span><span class="pln">mt</span><span class="pun">-</span><span class="lit">2.pr</span><span class="pun">-</span><span class="lit">1</span><span class="pun">(</span><span class="kwd">class</span><span class="pun">=</span><span class="str">'w-1/2'</span><span class="pun">)</span><span class="pln">
      label</span><span class="pun">.</span><span class="pln">block</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">sm</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">600</span><span class="pun">(</span><span class="kwd">for</span><span class="pun">=</span><span class="str">'date_start'</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Starting</span><span class="pln"> </span><span class="typ">Date</span><span class="pln">
      input</span><span class="pun">.</span><span class="pln">w</span><span class="pun">-</span><span class="pln">full</span><span class="pun">.</span><span class="pln">px</span><span class="pun">-</span><span class="lit">2.py</span><span class="pun">-</span><span class="lit">2.text</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">700.bg</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">200.rounded</span><span class="pun">(</span><span class="pln">name</span><span class="pun">=</span><span class="str">'date_start'</span><span class="pln"> type</span><span class="pun">=</span><span class="str">'text'</span><span class="pln"> required</span><span class="pun">=</span><span class="str">''</span><span class="pln"> placeholder</span><span class="pun">=</span><span class="str">'2021-06-01'</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="kwd">inline</span><span class="pun">-</span><span class="pln">block</span><span class="pun">.</span><span class="pln">mt</span><span class="pun">-</span><span class="lit">2.</span><span class="pun">-</span><span class="pln">mx</span><span class="pun">-</span><span class="lit">1.pl</span><span class="pun">-</span><span class="lit">1</span><span class="pun">(</span><span class="kwd">class</span><span class="pun">=</span><span class="str">'w-1/2'</span><span class="pun">)</span><span class="pln">
      label</span><span class="pun">.</span><span class="pln">block</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">sm</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">600</span><span class="pun">(</span><span class="kwd">for</span><span class="pun">=</span><span class="str">'date_end'</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Ending</span><span class="pln"> </span><span class="typ">Date</span><span class="pln">
      input</span><span class="pun">.</span><span class="pln">w</span><span class="pun">-</span><span class="pln">full</span><span class="pun">.</span><span class="pln">px</span><span class="pun">-</span><span class="lit">2.py</span><span class="pun">-</span><span class="lit">2.text</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">700.bg</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">200.rounded</span><span class="pun">(</span><span class="pln">name</span><span class="pun">=</span><span class="str">'date_end'</span><span class="pln"> type</span><span class="pun">=</span><span class="str">'text'</span><span class="pln"> required</span><span class="pun">=</span><span class="str">''</span><span class="pln"> placeholder</span><span class="pun">=</span><span class="str">'2021-12-31'</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">mt</span><span class="pun">-</span><span class="lit">2</span><span class="pln">
      label</span><span class="pun">.</span><span class="pln">block</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">sm</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">600</span><span class="pun">(</span><span class="kwd">for</span><span class="pun">=</span><span class="str">'uses_total'</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Total</span><span class="pln"> </span><span class="typ">Using</span><span class="pln">
      input</span><span class="pun">.</span><span class="pln">w</span><span class="pun">-</span><span class="pln">full</span><span class="pun">.</span><span class="pln">px</span><span class="pun">-</span><span class="lit">2.py</span><span class="pun">-</span><span class="lit">2.text</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">700.bg</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">200.rounded</span><span class="pun">(</span><span class="pln">name</span><span class="pun">=</span><span class="str">'uses_total'</span><span class="pln"> type</span><span class="pun">=</span><span class="str">'text'</span><span class="pln"> required</span><span class="pun">=</span><span class="str">''</span><span class="pln"> placeholder</span><span class="pun">=</span><span class="str">'1000'</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">mt</span><span class="pun">-</span><span class="lit">2</span><span class="pln">
      label</span><span class="pun">.</span><span class="pln">block</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">sm</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">600</span><span class="pun">(</span><span class="kwd">for</span><span class="pun">=</span><span class="str">'uses_customer'</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Total</span><span class="pln"> </span><span class="typ">Using</span><span class="pln"> </span><span class="typ">Per</span><span class="pln"> </span><span class="typ">Customer</span><span class="pln">
      input</span><span class="pun">.</span><span class="pln">w</span><span class="pun">-</span><span class="pln">full</span><span class="pun">.</span><span class="pln">px</span><span class="pun">-</span><span class="lit">2.py</span><span class="pun">-</span><span class="lit">2.text</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">700.bg</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">200.rounded</span><span class="pun">(</span><span class="pln">name</span><span class="pun">=</span><span class="str">'uses_customer'</span><span class="pln"> type</span><span class="pun">=</span><span class="str">'text'</span><span class="pln"> required</span><span class="pun">=</span><span class="str">''</span><span class="pln"> placeholder</span><span class="pun">=</span><span class="str">'5'</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">mt</span><span class="pun">-</span><span class="lit">2</span><span class="pln">
      label</span><span class="pun">.</span><span class="pln">block</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">sm</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">600</span><span class="pun">(</span><span class="kwd">for</span><span class="pun">=</span><span class="str">'apply_to'</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Apply</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> all </span><span class="typ">Products</span><span class="pun">?</span><span class="pln">
      input</span><span class="pun">.</span><span class="pln">w</span><span class="pun">-</span><span class="pln">full</span><span class="pun">.</span><span class="pln">px</span><span class="pun">-</span><span class="lit">2.py</span><span class="pun">-</span><span class="lit">2.text</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">700.bg</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">200.rounded</span><span class="pun">(</span><span class="pln">name</span><span class="pun">=</span><span class="str">'apply_to'</span><span class="pln"> type</span><span class="pun">=</span><span class="str">'text'</span><span class="pln"> required</span><span class="pun">=</span><span class="str">''</span><span class="pln"> placeholder</span><span class="pun">=</span><span class="str">'all or apply_to_array[]'</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">mt</span><span class="pun">-</span><span class="lit">2</span><span class="pln">
      label</span><span class="pun">.</span><span class="pln">block</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">sm</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">600</span><span class="pun">(</span><span class="kwd">for</span><span class="pun">=</span><span class="str">'status'</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Active</span><span class="pun">?</span><span class="pln">
      input</span><span class="pun">.</span><span class="pln">w</span><span class="pun">-</span><span class="pln">full</span><span class="pun">.</span><span class="pln">px</span><span class="pun">-</span><span class="lit">2.py</span><span class="pun">-</span><span class="lit">2.text</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">700.bg</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">200.rounded</span><span class="pun">(</span><span class="pln">name</span><span class="pun">=</span><span class="str">'status'</span><span class="pln"> type</span><span class="pun">=</span><span class="str">'text'</span><span class="pln"> required</span><span class="pun">=</span><span class="str">''</span><span class="pln"> placeholder</span><span class="pun">=</span><span class="str">'1 to enable, 0 to disable'</span><span class="pun">)</span><span class="pln">

    </span><span class="pun">.</span><span class="pln">mt</span><span class="pun">-</span><span class="lit">4</span><span class="pln">
      button</span><span class="pun">.</span><span class="pln">px</span><span class="pun">-</span><span class="lit">4.py</span><span class="pun">-</span><span class="lit">1.text</span><span class="pun">-</span><span class="pln">white</span><span class="pun">.</span><span class="pln">font</span><span class="pun">-</span><span class="pln">light</span><span class="pun">.</span><span class="pln">tracking</span><span class="pun">-</span><span class="pln">wider</span><span class="pun">.</span><span class="pln">bg</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">900.rounded</span><span class="pun">(</span><span class="pln">type</span><span class="pun">=</span><span class="str">'submit'</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Add</span></pre>

<p>
	في بداية الملف، استوردنا الشيفرة الموجودة في الملف page.pug والتي تحوي القائمة الجانبية واستيراد إطار TailwindCSS.
</p>

<p>
	بعدها أنشأنا نموذج Form لإرسال البيانات إلى نقطة الوصول التي سنبنيها الآن.
</p>

<p>
	أنشئ ملفًا جديدًا باسم <a data-ss1631886121="1" data-ss1631887126="1" href="https://github.com/HsoubAcademy/zid-retention-app/blob/master/addCoupon.js" rel="external nofollow">addCoupon.js</a> ونفّذ ما فعلناه في الملف zid.js ألا وهو استيراد مكتبة Axios من أجل إرسال طلبات إلى الواجهة البرمجية لمنصة زد ولكن هذه المرة بطريقة POST، وهذا حسب التوثيق الخاص بالمنصة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_114" style="">
<span class="kwd">import</span><span class="pln"> axios from </span><span class="str">"axios"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">ZidAPI</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">"https://api.zid.dev/app/v1"</span><span class="pun">;</span></pre>

<p>
	الآن سنحتاج إلى إنشاء دالة function ونسند إليها مهمة إرسال الطلب إلى الواجهة البرمجية لمنصة زد بالشكل التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_116" style="">
<span class="kwd">const</span><span class="pln"> </span><span class="typ">AddCoupon</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">couponInfo</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> headers </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">"X-MANAGER-TOKEN"</span><span class="pun">:</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">MANAGER_TOKEN</span><span class="pun">,</span><span class="pln">
    </span><span class="typ">Authorization</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Bearer "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">auth</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">};</span></pre>

<p>
	أنشأنا الدالة تحت اسم <code>addCoupon</code> والتي تأخذ معاملًا واحدًا ويجب أن يكون كائنًا. حددنا ترويسة الطلب الذي سنرسله داخل الثابت <code>headers</code>.
</p>

<p>
	الخطوة الثانية هي أن ننشئ ثابتًا <code>constant</code> يحوي جميع المعلومات المطلوبة من طرف الواجهة البرمجية لمنصة زد، الثابت يكون كائنا من أجل أن يحمل جميع المعلومات.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6723_12" style="">
<span class="pln">  </span><span class="kwd">const</span><span class="pln"> data </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    name</span><span class="pun">:</span><span class="pln"> couponInfo</span><span class="pun">.</span><span class="pln">name</span><span class="pun">,</span><span class="pln">
    code</span><span class="pun">:</span><span class="pln"> couponInfo</span><span class="pun">.</span><span class="pln">code</span><span class="pun">,</span><span class="pln">
    discount_type</span><span class="pun">:</span><span class="pln"> couponInfo</span><span class="pun">.</span><span class="pln">discount_type</span><span class="pun">,</span><span class="pln">
    discount</span><span class="pun">:</span><span class="pln"> couponInfo</span><span class="pun">.</span><span class="pln">discount</span><span class="pun">,</span><span class="pln">
    free_shipping</span><span class="pun">:</span><span class="pln"> couponInfo</span><span class="pun">.</span><span class="pln">free_shipping</span><span class="pun">,</span><span class="pln">
    free_cod</span><span class="pun">:</span><span class="pln"> couponInfo</span><span class="pun">.</span><span class="pln">free_cod</span><span class="pun">,</span><span class="pln">
    total</span><span class="pun">:</span><span class="pln"> couponInfo</span><span class="pun">.</span><span class="pln">total</span><span class="pun">,</span><span class="pln">
    date_start</span><span class="pun">:</span><span class="pln"> couponInfo</span><span class="pun">.</span><span class="pln">date_start</span><span class="pun">,</span><span class="pln">
    date_end</span><span class="pun">:</span><span class="pln"> couponInfo</span><span class="pun">.</span><span class="pln">date_end</span><span class="pun">,</span><span class="pln">
    uses_total</span><span class="pun">:</span><span class="pln"> couponInfo</span><span class="pun">.</span><span class="pln">uses_total</span><span class="pun">,</span><span class="pln">
    uses_customer</span><span class="pun">:</span><span class="pln"> couponInfo</span><span class="pun">.</span><span class="pln">uses_customer</span><span class="pun">,</span><span class="pln">
    apply_to</span><span class="pun">:</span><span class="pln"> couponInfo</span><span class="pun">.</span><span class="pln">apply_to</span><span class="pun">,</span><span class="pln">
    status</span><span class="pun">:</span><span class="pln"> couponInfo</span><span class="pun">.</span><span class="pln">status</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">};</span></pre>

<p>
	الآن ننشئ كتلة Try - Catch لإرسال الطلب للواجهة البرمجية بحيث أنشأنا ثابتًا باسم <code>addCoupon</code> يحوي الطلب المرسل إلى الواجهة البرمجية عن طريق مكتبة Axios كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_120" style="">
<span class="pln">  </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> addCoupon </span><span class="pun">=</span><span class="pln"> await axios</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="pln">
      </span><span class="pun">`</span><span class="pln">$</span><span class="pun">{</span><span class="typ">ZidAPI</span><span class="pun">}/</span><span class="pln">managers</span><span class="pun">/</span><span class="pln">store</span><span class="pun">/</span><span class="pln">coupons</span><span class="pun">/</span><span class="pln">add</span><span class="pun">`,</span><span class="pln">
      data</span><span class="pun">,</span><span class="pln">
      </span><span class="pun">{</span><span class="pln">
        headers</span><span class="pun">:</span><span class="pln"> headers</span><span class="pun">,</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">addCoupon</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">return</span><span class="pln"> addCoupon</span><span class="pun">.</span><span class="pln">data</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="pln">error</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> error</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span></pre>

<p>
	تحققنا من عدم وجود أي خطأ وفي حالة وجود إجابة من الخادم نرجع محتوى الإجابة عن طريق السطر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_122" style="">
<span class="kwd">return</span><span class="pln"> addCoupon</span><span class="pun">.</span><span class="pln">data</span><span class="pun">;</span></pre>

<p>
	أما في حالة وجود خطأ، فسيكون الانتقال مباشرةً إلى الجزء <code>catch</code> وهنا سيُسترجع محتوى الخطأ عن طريق السطر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_126" style="">
<span class="pln"> </span><span class="kwd">return</span><span class="pln"> error</span><span class="pun">;</span></pre>

<p>
	في أخر الملف، نصدِّر الدالة حتى نستطيع استخدامها في أماكن أخرى باستخدام السطر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_124" style="">
<span class="pun">};</span><span class="pln">

</span><span class="kwd">export</span><span class="pln"> </span><span class="kwd">default</span><span class="pln"> </span><span class="typ">AddCoupon</span><span class="pun">;</span></pre>

<p>
	اﻵن نضيف نقطة وصول جديدة Endpoint إلى الملف <a data-ss1631886121="1" data-ss1631887126="1" href="https://github.com/HsoubAcademy/zid-retention-app/blob/master/index.js" rel="external nofollow">index.js</a> بنفس الطريقة التي أضفنا بها نقاط الوصول السابقة.
</p>

<p>
	استدع في أعلى الملف الملف <code>addCoupon</code> كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_128" style="">
<span class="kwd">import</span><span class="pln"> </span><span class="typ">AddCoupon</span><span class="pln"> from </span><span class="pun">‘./</span><span class="pln">addCoupon</span><span class="pun">.</span><span class="pln">js</span><span class="pun">’</span></pre>

<p>
	لتكون بداية الملف تمامًا كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_130" style="">
<span class="kwd">import</span><span class="pln"> express from </span><span class="str">"express"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> dotenv from </span><span class="str">"dotenv"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> path from </span><span class="str">"path"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="typ">AbandonedCarts</span><span class="pln"> from </span><span class="str">"./zid.js"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="typ">AddCoupon</span><span class="pln"> from </span><span class="str">"./addCoupon.js"</span><span class="pun">;</span></pre>

<p>
	بعدها سنضيف نقطة الوصول بالشكل التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_132" style="">
<span class="pln">app</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">"/add-coupon"</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> data </span><span class="pun">=</span><span class="pln"> req</span><span class="pun">.</span><span class="pln">body</span><span class="pun">;</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> dataToSend </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Object</span><span class="pun">.</span><span class="pln">assign</span><span class="pun">({},</span><span class="pln"> data</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> response </span><span class="pun">=</span><span class="pln"> await </span><span class="typ">AddCoupon</span><span class="pun">(</span><span class="pln">dataToSend</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    res</span><span class="pun">.</span><span class="pln">redirect</span><span class="pun">(</span><span class="str">"/"</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    res</span><span class="pun">.</span><span class="pln">redirect</span><span class="pun">(</span><span class="str">"/error"</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	أعتقد أنك قد ﻻحظت أن نقطة الوصول هذه مختلفة بعض الشيء عن سابقاتها، ﻻ تقلق، سأشرح كل شيء بالتفصيل.
</p>

<p>
	في السطر اﻷول، أنشأنا نقطة الوصول الجديدة والتي تستعمل POST ﻹرسال البيانات، الرابط سيكون:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_134" style="">
<span class="pun">/</span><span class="pln">add</span><span class="pun">-</span><span class="pln">coupon</span></pre>

<p>
	ﻻحظت أننا أضفنا كلمة مفتاحية جديدة هي <code>async</code> وهي ببساطة تعني أن هنالك عملية ستأخذ وقتا غير معروف لهذا قبل الانتهاء من جميع العمليات المطلوبة، يجب إنهاء العمليات التي تأخذ وقتا، من الأفضل أن تتعرف أكثر على تقنية async/await خصوصا في NodeJS.
</p>

<p>
	في السطر الثاني، أنشأنا ثابتا جديدا باسم <code>data</code> والذي يحوي جسم الطلب المرسل Request إلى نقطة الوصول، في هذه النقطة ﻻ يزال جسم الطلب خاما، ويجب أن يُعدَّل ليكون كائنًا Object؛ أما في السطر الثالث، فقد عدّلنا أو تنقية الثابت <code>data</code> باستخدام التابع <code>assign</code> المدمج افتراضيًا في لغة Javascript، أُسنِدت النتيجة إلى الثابت <code>dataToSend</code>. بينما في السطر الرابع، أنشأنا ثابتًا جديدًا باسم response وأسندنا نتيجة الدالة <code>AddCoupon</code> إليه.
</p>

<p>
	لاحظ أننا استخدمنا كلمة مفتاحية جديدة هي <code>await</code> قبل الدالة <code>AddCoupon</code>، وهنا طلبنا أن ينتظر NodeJS العملية حتى انتهائها قبل القيام بتنفيذ السطر الموالي.
</p>

<p>
	كخطوة أخيرة، وضعنا شرطًا للتحقق إن كنا قد تحصلنا على إجابة من الخادم Response أم ﻻ. في حالة الحصول على إجابة، سيُنفَّذ السطر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_136" style="">
<span class="pln">res</span><span class="pun">.</span><span class="pln">redirect</span><span class="pun">(</span><span class="str">"/"</span><span class="pun">);</span></pre>

<p>
	وهنا، سيُعاد توجيه المستخدم إلى الصفحة الرئيسية. أما في حالة عدم الحصول على إجابة، فسيُنفَّذ السطر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_138" style="">
<span class="pln">res</span><span class="pun">.</span><span class="pln">redirect</span><span class="pun">(</span><span class="str">"/error"</span><span class="pun">);</span></pre>

<p>
	ويعني أنه سيُعاد توجيه المستخدم إلى صفحة الخطأ وهي في هذه الحالة نقطة وصول ستُنشأ لاحقًا لعرض رسائل الخطأ.
</p>

<p>
	اﻵن، بقيت لنا خطوة أخيرة، وهي إنشاء صفحة يستطيع المتصفح الوصول إليها. ببساطة، ننشئ نقطة وصول جديدة باسم coupon والتي تكون من نوع GET، تعرض فقط القالب coupon الذي سبق وأنشأناه.
</p>

<p>
	نقطة الوصول ستكون كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_140" style="">
<span class="pln">app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/coupon"</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">"coupon"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	شغل خادم التطبيق باستخدام الأمر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_142" style="">
<span class="pln">yarn start</span></pre>

<p>
	توجه إلى الرابط التالي:
</p>

<pre class="ipsCode prettyprint lang-ruby prettyprinted" id="ips_uid_235_144" style="">
<span class="pln">http</span><span class="pun">:/</span><span class="str">/localhost:3000/</span><span class="pln">coupon</span></pre>

<p>
	إن ظهرت لديك رسالة خطأ مشابهة لهذه الرسالة:
</p>

<pre class="ipsCode prettyprint lang-ruby prettyprinted" id="ips_uid_235_146" style="">
<span class="typ">Error</span><span class="pun">:</span><span class="str">/zid-local/</span><span class="pln">views</span><span class="pun">/</span><span class="pln">coupon</span><span class="pun">.</span><span class="pln">pug</span><span class="pun">:</span><span class="lit">5</span><span class="pun">:</span><span class="lit">1</span><span class="pln">

</span><span class="typ">Only</span><span class="pln"> named blocks </span><span class="kwd">and</span><span class="pln"> mixins can appear at the top level of an extending template
at makeError </span><span class="pun">(</span><span class="str">/zid-local/</span><span class="pln">node_modules</span><span class="pun">/</span><span class="pln">pug</span><span class="pun">-</span><span class="pln">error</span><span class="pun">/</span><span class="pln">index</span><span class="pun">.</span><span class="pln">js</span><span class="pun">:</span><span class="lit">34</span><span class="pun">:</span><span class="lit">13</span><span class="pun">)</span></pre>

<p>
	تأكد من إزاحة indentation السطور في الصفحة، ويفضل استعمال إضافة <a data-ss1631886121="1" data-ss1631887126="1" href="https://marketplace.visualstudio.com/items?itemName=ducfilan.pug-formatter" rel="external nofollow">pug (jade) formatter</a> والتي تساعدك في تنسيق الشفرات البرمجية الخاصة بمحرك القوالب Pug.
</p>

<p>
	بعد تثبيت اﻹضافة، اضغط على Alt+Shift+F لتنسيق الشيفرة البرمجية وحل المشكلة.
</p>

<p>
	سيظهر نموذج إنشاء قسيمة التخفيض التي أنشأناها سابقًا باستخدام الملف coupon.pug:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="77181" data-ss1631886121="1" data-ss1631887126="1" href="https://academy.hsoub.com/uploads/monthly_2021_09/Add_Coupon.png.f985a462d06f8dc2357b88c163e01cb1.png" rel=""><img alt="Add_Coupon.png" class="ipsImage ipsImage_thumbnailed" data-fileid="77181" data-unique="r52l8xqnu" src="https://academy.hsoub.com/uploads/monthly_2021_09/Add_Coupon.thumb.png.345e8cc10317343390f0b91ce61f5c08.png"></a>
</p>

<p>
	أدخل المعلومات الخاصة بقسيمة التخفيض واضغط على إضافة Add من أجل إرسال المعلومات إلى نقطة الوصول التي اضفناها سابقًا add-Coupon ومنها تُعالَج البيانات وتُرسَل إلى الواجهة البرمجية لمنصة زد.
</p>

<p>
	في حالة إنشاء قسيمة التخفيض، فسيوجَّه المستخدِم إلى الصفحة الرئيسية التي نحن بصدد إنشائها، أما في حالة وجود خطأ ما، أو عدم القدرة على إرسال الطلب بطريقة صحيحة، سيُعاد توجيه المستخدم إلى صفحة الخطأ التي أنشأناها سابقًا.
</p>

<h3>
	إنشاء صفحة الأخطاء
</h3>

<p>
	اﻵن، أنشئ ملفًا جديدًا داخل المجلد views باسم <a data-ss1631886121="1" data-ss1631887126="1" href="https://github.com/HsoubAcademy/zid-retention-app/blob/master/views/error.pug" rel="external nofollow">error.pug</a> وضع بداخله الشيفرة البرمجية التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_148" style="">
<span class="pln">doctype html
block head
    meta</span><span class="pun">(</span><span class="pln">charset</span><span class="pun">=</span><span class="str">'UTF-8'</span><span class="pun">)</span><span class="pln">
    meta</span><span class="pun">(</span><span class="pln">http</span><span class="pun">-</span><span class="pln">equiv</span><span class="pun">=</span><span class="str">'X-UA-Compatible'</span><span class="pln"> content</span><span class="pun">=</span><span class="str">'IE=edge'</span><span class="pun">)</span><span class="pln">
    meta</span><span class="pun">(</span><span class="pln">name</span><span class="pun">=</span><span class="str">'viewport'</span><span class="pln"> content</span><span class="pun">=</span><span class="str">'width=device-width, initial-scale=1.0'</span><span class="pun">)</span><span class="pln">
    link</span><span class="pun">(</span><span class="pln">href</span><span class="pun">=</span><span class="str">'https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css'</span><span class="pln"> rel</span><span class="pun">=</span><span class="str">'stylesheet'</span><span class="pun">)</span><span class="pln">
body
block content 
    style</span><span class="pun">.</span><span class="pln">
        </span><span class="pun">.</span><span class="pln">gradient </span><span class="pun">{</span><span class="pln">
        background</span><span class="pun">-</span><span class="pln">image</span><span class="pun">:</span><span class="pln"> linear</span><span class="pun">-</span><span class="pln">gradient</span><span class="pun">(</span><span class="lit">135deg</span><span class="pun">,</span><span class="pln"> </span><span class="pun">#</span><span class="lit">684ca0</span><span class="pln"> </span><span class="lit">35</span><span class="pun">%,</span><span class="pln"> </span><span class="pun">#</span><span class="lit">1c4ca0</span><span class="pln"> </span><span class="lit">100</span><span class="pun">%);</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">gradient</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">white</span><span class="pun">.</span><span class="pln">min</span><span class="pun">-</span><span class="pln">h</span><span class="pun">-</span><span class="pln">screen</span><span class="pun">.</span><span class="pln">flex</span><span class="pun">.</span><span class="pln">items</span><span class="pun">-</span><span class="pln">center
        </span><span class="pun">.</span><span class="pln">container</span><span class="pun">.</span><span class="pln">mx</span><span class="pun">-</span><span class="kwd">auto</span><span class="pun">.</span><span class="pln">p</span><span class="pun">-</span><span class="lit">4.flex</span><span class="pun">.</span><span class="pln">flex</span><span class="pun">-</span><span class="pln">wrap</span><span class="pun">.</span><span class="pln">items</span><span class="pun">-</span><span class="pln">center

        </span><span class="pun">.</span><span class="pln">w</span><span class="pun">-</span><span class="pln">full</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">center</span><span class="pun">.</span><span class="pln">p</span><span class="pun">-</span><span class="lit">4</span><span class="pun">(</span><span class="kwd">class</span><span class="pun">=</span><span class="str">'md:w-7/12 md:text-left'</span><span class="pun">)</span><span class="pln">
            </span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="lit">6xl</span><span class="pun">.</span><span class="pln">font</span><span class="pun">-</span><span class="pln">medium </span><span class="lit">404</span><span class="pln">
            </span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">xl</span><span class="pun">.</span><span class="pln">font</span><span class="pun">-</span><span class="pln">medium</span><span class="pun">.</span><span class="pln">mb</span><span class="pun">-</span><span class="lit">4</span><span class="pun">(</span><span class="kwd">class</span><span class="pun">=</span><span class="str">'md:text-3xl'</span><span class="pun">)</span><span class="pln">
                </span><span class="pun">|</span><span class="pln"> </span><span class="typ">Oops</span><span class="pun">.</span><span class="pln"> </span><span class="typ">This</span><span class="pln"> page has gone missing</span><span class="pun">.</span><span class="pln">
            </span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">lg</span><span class="pun">.</span><span class="pln">mb</span><span class="pun">-</span><span class="lit">8</span><span class="pln">
                </span><span class="pun">|</span><span class="pln"> </span><span class="typ">You</span><span class="pln"> may have mistyped the address or the page may have moved</span><span class="pun">.</span><span class="pln">
            a</span><span class="pun">.</span><span class="pln">border</span><span class="pun">.</span><span class="pln">border</span><span class="pun">-</span><span class="pln">white</span><span class="pun">.</span><span class="pln">rounded</span><span class="pun">.</span><span class="pln">p</span><span class="pun">-</span><span class="lit">4</span><span class="pun">(</span><span class="pln">href</span><span class="pun">=</span><span class="str">'/'</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Go</span><span class="pln"> </span><span class="typ">Home</span></pre>

<p>
	في الملف <a data-ss1631886121="1" data-ss1631887126="1" href="https://github.com/HsoubAcademy/zid-retention-app/blob/master/index.js" rel="external nofollow">index.js</a>، أنشئ نقطة وصول جديدة كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_150" style="">
<span class="pln">app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/error"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">"error"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	اﻷمر بسيط ونقطة الوصول هذه تعرض القالب error.pug الذي أنشأناه قبل قليل، حول أي أخطاء تظهر معك إلى الرابط <code>‎/error</code> من أجل عرض هذه الصفحة.
</p>

<h3>
	إنشاء الصفحة الرئيسية للتطبيق
</h3>

<p>
	لننشئ الصفحة الرئيسية للتطبيق، أنشئ ملفًا جديدًا داخل المجلد views باسم <a data-ss1631886121="1" data-ss1631887126="1" href="https://github.com/HsoubAcademy/zid-retention-app/blob/master/views/index.pug" rel="external nofollow">index.pug</a>، ثم ضع الشيفرة التالية بداخله:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_152" style="">
<span class="pln">extends page</span><span class="pun">.</span><span class="pln">pug 
block append head
  title </span><span class="typ">Welcome</span><span class="pln">
block content
  </span><span class="pun">.</span><span class="pln">flex</span><span class="pun">.</span><span class="pln">mx</span><span class="pun">-</span><span class="kwd">auto</span><span class="pun">.</span><span class="pln">items</span><span class="pun">-</span><span class="pln">center</span><span class="pun">.</span><span class="pln">justify</span><span class="pun">-</span><span class="pln">center</span><span class="pun">.</span><span class="pln">shadow</span><span class="pun">-</span><span class="pln">lg</span><span class="pun">.</span><span class="pln">mt</span><span class="pun">-</span><span class="lit">65.mx</span><span class="pun">-</span><span class="lit">8.mb</span><span class="pun">-</span><span class="lit">4.max</span><span class="pun">-</span><span class="pln">w</span><span class="pun">-</span><span class="pln">lg
    form</span><span class="pun">.</span><span class="pln">w</span><span class="pun">-</span><span class="pln">full</span><span class="pun">.</span><span class="pln">max</span><span class="pun">-</span><span class="pln">w</span><span class="pun">-</span><span class="pln">xl</span><span class="pun">.</span><span class="pln">bg</span><span class="pun">-</span><span class="pln">white</span><span class="pun">.</span><span class="pln">rounded</span><span class="pun">-</span><span class="pln">lg</span><span class="pun">.</span><span class="pln">px</span><span class="pun">-</span><span class="lit">4.pt</span><span class="pun">-</span><span class="lit">2</span><span class="pln">
      </span><span class="pun">.</span><span class="pln">flex</span><span class="pun">.</span><span class="pln">flex</span><span class="pun">-</span><span class="pln">wrap</span><span class="pun">.-</span><span class="pln">mx</span><span class="pun">-</span><span class="lit">3.mb</span><span class="pun">-</span><span class="lit">6</span><span class="pln">
        h2</span><span class="pun">.</span><span class="pln">px</span><span class="pun">-</span><span class="lit">4.pt</span><span class="pun">-</span><span class="lit">3.pb</span><span class="pun">-</span><span class="lit">2.text</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">800.text</span><span class="pun">-</span><span class="pln">lg </span><span class="pun">أضف</span><span class="pln"> </span><span class="pun">مفتاح</span><span class="pln"> </span><span class="pun">المتجر</span><span class="pln"> </span><span class="pun">الخاص</span><span class="pln"> </span><span class="pun">بك</span><span class="pln">
        </span><span class="pun">.</span><span class="pln">w</span><span class="pun">-</span><span class="pln">full</span><span class="pun">.</span><span class="pln">px</span><span class="pun">-</span><span class="lit">3.mb</span><span class="pun">-</span><span class="lit">2.mt</span><span class="pun">-</span><span class="lit">2</span><span class="pun">(</span><span class="kwd">class</span><span class="pun">=</span><span class="str">'md:w-full'</span><span class="pun">)</span><span class="pln">
          textarea</span><span class="pun">.</span><span class="pln">bg</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">100.rounded</span><span class="pun">.</span><span class="pln">border</span><span class="pun">.</span><span class="pln">border</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">400.leading</span><span class="pun">-</span><span class="pln">normal</span><span class="pun">.</span><span class="pln">resize</span><span class="pun">-</span><span class="pln">none</span><span class="pun">.</span><span class="pln">w</span><span class="pun">-</span><span class="pln">full</span><span class="pun">.</span><span class="pln">h</span><span class="pun">-</span><span class="lit">20.py</span><span class="pun">-</span><span class="lit">2.px</span><span class="pun">-</span><span class="lit">3.font</span><span class="pun">-</span><span class="pln">medium</span><span class="pun">.</span><span class="pln">placeholder</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">700</span><span class="pun">(</span><span class="kwd">class</span><span class="pun">=</span><span class="str">'focus:outline-none focus:bg-white'</span><span class="pln"> name</span><span class="pun">=</span><span class="str">'body'</span><span class="pln"> placeholder</span><span class="pun">=</span><span class="str">'Paste your store X-MANAGER-TOKEN HERE'</span><span class="pln"> required</span><span class="pun">=</span><span class="str">''</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">.</span><span class="pln">w</span><span class="pun">-</span><span class="pln">full</span><span class="pun">.</span><span class="pln">flex</span><span class="pun">.</span><span class="pln">items</span><span class="pun">-</span><span class="pln">start</span><span class="pun">.</span><span class="pln">px</span><span class="pun">-</span><span class="lit">3</span><span class="pun">(</span><span class="kwd">class</span><span class="pun">=</span><span class="str">'md:w-full md:w-full'</span><span class="pun">)</span><span class="pln">
          </span><span class="pun">.-</span><span class="pln">mr</span><span class="pun">-</span><span class="lit">1</span><span class="pln">
            input</span><span class="pun">.</span><span class="pln">bg</span><span class="pun">-</span><span class="pln">white</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">700.font</span><span class="pun">-</span><span class="pln">medium</span><span class="pun">.</span><span class="pln">py</span><span class="pun">-</span><span class="lit">1.px</span><span class="pun">-</span><span class="lit">4.border</span><span class="pun">.</span><span class="pln">border</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">400.rounded</span><span class="pun">-</span><span class="pln">lg</span><span class="pun">.</span><span class="pln">tracking</span><span class="pun">-</span><span class="pln">wide</span><span class="pun">.</span><span class="pln">mr</span><span class="pun">-</span><span class="lit">1</span><span class="pun">(</span><span class="pln">type</span><span class="pun">=</span><span class="str">'submit'</span><span class="pln"> </span><span class="kwd">class</span><span class="pun">=</span><span class="str">'hover:bg-gray-100'</span><span class="pln"> value</span><span class="pun">=</span><span class="str">'Add'</span><span class="pun">)</span></pre>

<p>
	الآن، علينا أن ننشئ نقطة الوصول الخاصة بالصفحة الرئيسية، في ملف <a data-ss1631886121="1" data-ss1631887126="1" href="https://github.com/HsoubAcademy/zid-retention-app/blob/master/index.js#L24" rel="external nofollow">index.js</a> ننشئ نقطة الوصول كما تعودنا، تستخدم طريقة GET لاستقبال البيانات من الخادم:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_154" style="">
<span class="pln">app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/"</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">"index"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	نقطة الوصول هذه بسيطة، بحيث أن رابطها هو الرابط الرئيسي للخادم، وتعرض فقط القالب الخاص بالصفحة الرئيسية، والنتيجة ستكون كالتالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="77185" data-ss1631886121="1" data-ss1631887126="1" href="https://academy.hsoub.com/uploads/monthly_2021_09/Home.png.1a6689c123be48c164254c9912515c65.png" rel=""><img alt="Home.png" class="ipsImage ipsImage_thumbnailed" data-fileid="77185" data-unique="wrydasvli" src="https://academy.hsoub.com/uploads/monthly_2021_09/Home.thumb.png.9a276ae2ec6f9f1b528bc8f76ddd571c.png"></a>
</p>

<p>
	أضفنا حقلًا لإدخال مفتاح مفتاح الوصول الخاص بالمتجر X-MANAGER-TOKEN ليُستخدم مستقبلًا، وسنشرح سبب إضافة هذا الحقل -الواضح- عند الانتهاء من بناء المنصة.
</p>

<h3>
	بناء خدمة إرسال رسائل بريد إلكتروني
</h3>

<p>
	بعد أن استرجعنا واستعراض سلات الشراء المتروكة، وأنشأنا قسيمة تخفيض، علينا الآن إرسال بريد إلكتروني يحمل قسيمة التخفيض التي أنشأناها لأصحاب سلات الشراء المتروكة، من أجل تحفيزهم على إنهاء طلباتهم وبالتالي زيادة مبيعات المتجر الخاص بنا.
</p>

<p>
	لإرسال رسائل البريد الإلكتروني بطريقة بسيطة، سنستخدم <a data-ss1631886121="1" data-ss1631887126="1" href="https://sendgrid.com/" rel="external nofollow">SendGrid</a> والذي يوفر خدمة إرسال مجاني لرسائل البريد الإلكتروني جيدًا للاستخدام البسيط (أو أي خدمة أخرى تختارها).
</p>

<p>
	أنشئ حسابًا على منصة SendGrid وفعل حسابك. بعدها، افتح نافذة سطر أوامر في نفس مجلد التطبيق ونفذ الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-ruby prettyprinted" id="ips_uid_235_156" style="">
<span class="pln">yarn add </span><span class="lit">@sendgrid</span><span class="pun">/</span><span class="pln">mail</span></pre>

<p>
	سيثبّت هذا الأمر المكتبة الخاصة بمنصة SendGrid.
</p>

<p>
	أنشئ ملفا جديدا باسم <a data-ss1631886121="1" data-ss1631887126="1" href="https://github.com/HsoubAcademy/zid-retention-app/blob/master/mail.js" rel="external nofollow">mail.js</a> وأضف الشيفرة البرمجية التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_158" style="">
<span class="kwd">import</span><span class="pln"> sgMail from </span><span class="str">"@sendgrid/mail"</span><span class="pun">;</span><span class="pln">

sgMail</span><span class="pun">.</span><span class="pln">setApiKey</span><span class="pun">(</span><span class="pln">process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">SENDGRID_API_KEY</span><span class="pun">);</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> sendMail </span><span class="pun">=</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">mailData</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> msg </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    to</span><span class="pun">:</span><span class="pln"> mailData</span><span class="pun">.</span><span class="pln">to</span><span class="pun">,</span><span class="pln">
    </span><span class="com">// SendGrid أضف البريد اﻹلكتروني الذي سجلت به في منصة </span><span class="pln">
    from</span><span class="pun">:</span><span class="pln"> </span><span class="str">"info@email.com"</span><span class="pun">,</span><span class="pln"> 
    subject</span><span class="pun">:</span><span class="pln"> mailData</span><span class="pun">.</span><span class="pln">subject</span><span class="pun">,</span><span class="pln">
    text</span><span class="pun">:</span><span class="pln"> mailData</span><span class="pun">.</span><span class="pln">text</span><span class="pun">,</span><span class="pln">
    html</span><span class="pun">:</span><span class="pln"> mailData</span><span class="pun">.</span><span class="pln">html</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">};</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> sendingMail </span><span class="pun">=</span><span class="pln"> await sgMail</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="pln">msg</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">sendingMail</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="str">"Mail has been sent"</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"SENDING MAIL ERROR ===&gt; "</span><span class="pun">,</span><span class="pln"> sendingMail</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="str">"There was a problem in sending the mail"</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">};</span><span class="pln">

</span><span class="kwd">export</span><span class="pln"> </span><span class="kwd">default</span><span class="pln"> sendMail</span><span class="pun">;</span></pre>

<p>
	بداية، استوردنا مكتبة منصة SendGrid التي ثبتناها قبل قليل، بعدها أضفنا المفتاح المساعد الخاص بحسابنا على المنصة، والذي تحصل عليه من لوحة التحكم الخاصة بحسابك.
</p>

<p>
	أنشأنا دالة باسم <code>sendMail</code> والتي تأخذ معاملًا واحدًا يجب أن يكون كائنًا Object والذي سيحوي المعلومات المطلوبة لإرسال البريد الإلكتروني.
</p>

<p>
	أنشأنا ثابتًا جديدًا باسم <code>msg</code> والذي يحوي كل من الحقول التالي:
</p>

<ul>
<li>
		<code>Subject</code>: يمثل عنوان البريد الإلكتروني الذي نريد إرساله
	</li>
	<li>
		<code>From</code>: وهو عنوان البريد الإلكتروني الذي سنرسل منه الرسائل، وهو نفس عنوان البريد الإلكتروني الذي أضفتَه إلى حسابك على منصة SendGrid
	</li>
	<li>
		`Subject: عنوان رسالة البريد الإلكتروني التي سنرسلها.
	</li>
	<li>
		Tex<code>t</code>: نص رسالة البريد الإلكتروني التي سترسله، يجب أن يكون نصا فقط
	</li>
	<li>
		<code>Html</code>: نفس نص رسالة البريد الإلكتروني، ولكن بإمكانك أن تضيف تنسيقات HTML.
	</li>
</ul>
<p>
	أنشأنا ثابتًا باسم <code>sendingMail</code> يحوي القيمة الراجعة من التابع <code>send</code> الخاص بمكتبة SendGrid والتي سترسل رسالة البريد اﻹلكتروني إن كانت جميع المدخلات صحيحة.
</p>

<p>
	أنشأنا شرطًا بعدها يتحقق إن كانت هنالك إجابة من الواجهة البرمجية لمنصة SendGrid، إن كانت هنالك إجابة ستُرجَع رسالة تفيد بأنه قد تمت عملية اﻹرسال بنجاح. في حالة عدم وجود إجابة من الواجهة البرمجية أو أرجعَت خطأ، نعرض الخطأ عن السطر الموجود بداخل الجزء الثاني من كتلة الشرط، وبعدها نرجع رسالة تفيد بأن هنالك مشكلة عند إرسال البريد اﻹلكتروني:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_160" style="">
<span class="pln">console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"SENDING MAIL ERROR ===&gt; "</span><span class="pun">,</span><span class="pln"> sendingMail</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">return</span><span class="pln"> </span><span class="str">"There was a problem in sending the mail"</span><span class="pun">;</span></pre>

<p>
	في النهاية، صدّرنا الدالة من أجل نستخدمها في مختلف أجزاء التطبيق.
</p>

<p>
	اﻵن سنضيف نقطة وصول جديدة لمشروعنا، في الملف <a data-ss1631886121="1" data-ss1631887126="1" href="https://github.com/HsoubAcademy/zid-retention-app/blob/master/index.js#L56" rel="external nofollow">index.js</a> أضف الشيفرة البرمجية التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_162" style="">
<span class="pln">app</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">"/mail"</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> data </span><span class="pun">=</span><span class="pln"> req</span><span class="pun">.</span><span class="pln">body</span><span class="pun">;</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> dataToSend </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Object</span><span class="pun">.</span><span class="pln">assign</span><span class="pun">({},</span><span class="pln"> data</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> send </span><span class="pun">=</span><span class="pln"> await sendMail</span><span class="pun">(</span><span class="pln">dataToSend</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">send</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"mail has been sent"</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"there was an error"</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	متأكد أنك قد تعودت على صيغة إضافة نقاط الوصول إلى المشروع، نقطة الوصول هذه مشابهة جدا لنقطة الوصول السابقة التي أضفناها سابقًا.
</p>

<h3>
	تركيب أجزاء التطبيق مع بعضها
</h3>

<p>
	انتهينا إلى الآن من بناء الخصائص اﻷساسية للتطبيق، بقي لنا أن نجمع هذه الخصائص كلها من أجل الوصول إلى هدفنا، وهو إرسال بريد إلكتروني لجميع أصحاب سلات الشراء المتروكة حتى نحفزهم على إكمال عملية الشراء بإرسال قسيمة تخفيض إليهم عن طريق البريد اﻹلكتروني.
</p>

<p>
	أنشئ ملفًا جديدًا داخل المجلد views باسم <a data-ss1631886121="1" data-ss1631887126="1" href="https://github.com/HsoubAcademy/zid-retention-app/blob/master/views/loyalty.pug" rel="external nofollow">loyalty.pug</a> وضع بداخله الشيفرة البرمجية التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_164" style="">
<span class="pln">extends page</span><span class="pun">.</span><span class="pln">pug 
block append head
  title </span><span class="typ">Loyalty</span><span class="pln">
block content 
  </span><span class="pun">.</span><span class="pln">wrapper</span><span class="pun">.</span><span class="pln">px</span><span class="pun">-</span><span class="lit">2.w</span><span class="pun">-</span><span class="pln">full
    p</span><span class="pun">.</span><span class="pln">text3xl</span><span class="pun">.</span><span class="pln">items</span><span class="pun">-</span><span class="pln">center</span><span class="pun">.</span><span class="pln">justify</span><span class="pun">-</span><span class="pln">center </span><span class="typ">Send</span><span class="pln"> a coupon code to all abandoned carts
    form</span><span class="pun">.</span><span class="pln">max</span><span class="pun">-</span><span class="pln">w</span><span class="pun">-</span><span class="pln">sm</span><span class="pun">.</span><span class="pln">bg</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">100.px</span><span class="pun">-</span><span class="lit">3.py</span><span class="pun">-</span><span class="lit">5.rounded</span><span class="pun">.</span><span class="pln">shadow</span><span class="pun">-</span><span class="pln">lg</span><span class="pun">.</span><span class="pln">my</span><span class="pun">-</span><span class="lit">10.m</span><span class="pun">-</span><span class="kwd">auto</span><span class="pun">(</span><span class="pln">action</span><span class="pun">=</span><span class="str">'/loyalty'</span><span class="pln"> method</span><span class="pun">=</span><span class="str">'POST'</span><span class="pun">)</span><span class="pln">
      </span><span class="pun">.</span><span class="pln">flex</span><span class="pun">.</span><span class="pln">flex</span><span class="pun">-</span><span class="pln">col</span><span class="pun">.</span><span class="pln">space</span><span class="pun">-</span><span class="pln">y</span><span class="pun">-</span><span class="lit">3</span><span class="pln">
        </span><span class="pun">.</span><span class="pln">flex</span><span class="pun">.</span><span class="pln">items</span><span class="pun">-</span><span class="pln">center</span><span class="pun">.</span><span class="pln">bg</span><span class="pun">-</span><span class="pln">white</span><span class="pun">.</span><span class="pln">border</span><span class="pun">.</span><span class="pln">border</span><span class="pun">-</span><span class="pln">gray</span><span class="pun">-</span><span class="lit">100.rounded</span><span class="pun">.</span><span class="pln">px</span><span class="pun">-</span><span class="lit">2</span><span class="pln">
          input</span><span class="pun">.</span><span class="pln">w</span><span class="pun">-</span><span class="pln">full</span><span class="pun">.</span><span class="pln">py</span><span class="pun">-</span><span class="lit">2.px</span><span class="pun">-</span><span class="lit">1.placeholder</span><span class="pun">-</span><span class="pln">indigo</span><span class="pun">-</span><span class="lit">400.outline</span><span class="pun">-</span><span class="pln">none</span><span class="pun">.</span><span class="pln">placeholder</span><span class="pun">-</span><span class="pln">opacity</span><span class="pun">-</span><span class="lit">50</span><span class="pun">(</span><span class="pln">type</span><span class="pun">=</span><span class="str">'text'</span><span class="pln"> placeholder</span><span class="pun">=</span><span class="str">'Enter Coupon Code'</span><span class="pln"> autocomplete</span><span class="pun">=</span><span class="str">'off'</span><span class="pun">)</span><span class="pln">
        button</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">white</span><span class="pun">.</span><span class="pln">bg</span><span class="pun">-</span><span class="pln">indigo</span><span class="pun">-</span><span class="lit">500.px</span><span class="pun">-</span><span class="lit">4.py</span><span class="pun">-</span><span class="lit">2.rounded</span><span class="pun">(</span><span class="pln">type</span><span class="pun">=</span><span class="str">'submit'</span><span class="pun">)</span><span class="pln">
          </span><span class="pun">|</span><span class="pln"> </span><span class="typ">Send</span><span class="pln"> </span><span class="typ">Coupon</span></pre>

<p>
	الصفحة بسيطة، فيها نافذة فقط ﻹضافة قسيمة التخفيض
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="77186" data-ss1631886121="1" data-ss1631887126="1" href="https://academy.hsoub.com/uploads/monthly_2021_09/Loyalty.png.0212f54e06b2632a23e7c8706aa5a79a.png" rel=""><img alt="Loyalty.png" class="ipsImage ipsImage_thumbnailed" data-fileid="77186" data-unique="t8jlpmqde" src="https://academy.hsoub.com/uploads/monthly_2021_09/Loyalty.thumb.png.be23abe65d78c929761240ae56cb530c.png"></a>
</p>

<p>
	أنشئ ملفًا جديدًا باسم <a data-ss1631886121="1" data-ss1631887126="1" href="https://github.com/HsoubAcademy/zid-retention-app/blob/master/loyality.js" rel="external nofollow">loyalty.js</a> يحمل الشفرة البرمجية التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_166" style="">
<span class="kwd">import</span><span class="pln"> </span><span class="typ">AbandonedCarts</span><span class="pln"> from </span><span class="str">"./zid.js"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> sendMail from </span><span class="str">"./mail.js"</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">Loyalty</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">couponCode</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> getAbandondCarts </span><span class="pun">=</span><span class="pln"> await </span><span class="typ">AbandonedCarts</span><span class="pun">();</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">getAbandondCarts</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> response </span><span class="pun">=</span><span class="pln"> getAbandondCarts</span><span class="pun">[</span><span class="str">"abandoned-carts"</span><span class="pun">];</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">const</span><span class="pln"> getEmails </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[];</span><span class="pln">
      </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let i </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> i </span><span class="pun">&lt;</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">length</span><span class="pun">;</span><span class="pln"> i</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        getEmails</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">response</span><span class="pun">[</span><span class="pln">i</span><span class="pun">].</span><span class="pln">customer_email</span><span class="pun">);</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">

      </span><span class="kwd">const</span><span class="pln"> mailData </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        to</span><span class="pun">:</span><span class="pln"> getEmails</span><span class="pun">,</span><span class="pln">
        </span><span class="com">// استخدم عنوان البريد اﻹلكتروني الذي سجلت به</span><span class="pln">
        from</span><span class="pun">:</span><span class="pln"> </span><span class="str">"info@geekcademy.com"</span><span class="pun">,</span><span class="pln"> 
        subject</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Special Offer for you"</span><span class="pun">,</span><span class="pln">
        text</span><span class="pun">:</span><span class="pln"> </span><span class="str">"يرجى استخدام قسيمة التخفيض التالية "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> couponCode </span><span class="pun">+</span><span class="pln"> </span><span class="str">" من أجل إنهاء طلبك"</span><span class="pun">,</span><span class="pln">
        html</span><span class="pun">:</span><span class="pln"> </span><span class="str">"يرجى استخدام قسيمة التخفيض التالية "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> couponCode </span><span class="pun">+</span><span class="pln"> </span><span class="str">" من أجل إنهاء طلبك"</span><span class="pun">,</span><span class="pln">
      </span><span class="pun">};</span><span class="pln">
      </span><span class="kwd">return</span><span class="pln"> await sendMail</span><span class="pun">(</span><span class="pln">mailData</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">return</span><span class="pln"> </span><span class="str">"There was an error getting abandoned carts"</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="str">"There is a server error"</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">};</span><span class="pln">

</span><span class="kwd">export</span><span class="pln"> </span><span class="kwd">default</span><span class="pln"> </span><span class="typ">Loyalty</span><span class="pun">;</span></pre>

<p>
	بداية استوردنا كل من دالة جلب سلات الشراء المتروكة <code>AbandonedCarts</code> ودالة إرسال رسائل البريد اﻹلكتروني.
</p>

<p>
	بعدها أنشأنا دالةً جديدةً تحت اسم <code>Loyalty</code> والتي ستدمج الدالتين السابقتين بحيث ستجلب سلات الشراء المتروكة أوﻻ، ومن ثم التحقق من وجود المعلومات المطلوبة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_168" style="">
<span class="kwd">const</span><span class="pln"> getEmails </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[];</span></pre>

<p>
	هنا أنشأنا ثابتًا جديدًا يمثل مصفوفة فارغة، ستوضع عناوين البريد المستخرجة من المعلومات التي حُصِّلت عليها من سلات الشراء المتروكة لعملاء المتجر، بعدها أنشأنا حلقة تكرارية، بحيث تمسح جميع المعلومات المتحصل عليها من الثابت <code>response</code> بعد التحقق من أنه يحوي المعلومات المطلوبة وهي مصفوفة كائنات.
</p>

<p>
	مع كل دورة للحلقة التكرارية، يُستخرَج البريد الإلكتروني للعميل الذي ترك سلته دون شراء وإضافته إلى الثابت <code>getEmails</code> ثم أنشأنا بعدها ثابتًا جديدًا تحت اسم <code>mailData</code> والذي سيحوي المعلومات المطلوبة في الدالة <code>sendMail</code> من أجل إرسال رسائل البريد اﻹلكتروني، بعدها أسندت قيمة الثابت <code>mailData</code> إلى الدالة <code>sendMail</code> من أجل إرسال رسالة إلى جميع أصحاب سلات الشراء المتروكة على عناوين بريدهم الإلكتروني المسجل.
</p>

<p>
	علينا اﻵن أن نضيف هذا الخاصية إلى ملف <a data-ss1631886121="1" data-ss1631887126="1" href="https://github.com/HsoubAcademy/zid-retention-app/blob/master/index.js#L28" rel="external nofollow">index.js</a>، مثل أي نقطة نهاية أضفناها سابقا.
</p>

<p>
	نقطة الوصول اﻷولى، خاصة بعرض الصفحة التي أنشأناها للتو:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_170" style="">
<span class="pln">app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/loyalty"</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">"loyalty"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	تعرض نقطة الوصول هذه القالب الموجود داخل المجلد views المسمى باسم loyalty.pug.
</p>

<p>
	نقطة الوصول الثانية، وهي التي ستحوي على الدالة التي أضفناها قبل قليل بالشكل التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_172" style="">
<span class="pln">app</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">"/loyalty"</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> response </span><span class="pun">=</span><span class="pln"> await </span><span class="typ">Loyalty</span><span class="pun">();</span><span class="pln">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="pln">response</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	يمكنك اﻵن التوجه إلى الرابط وجرب التطبيق:
</p>

<pre class="ipsCode prettyprint lang-ruby prettyprinted" id="ips_uid_235_174" style="">
<span class="pln">http</span><span class="pun">:/</span><span class="str">/localhost:3000/</span><span class="pln">loyalty</span></pre>

<p>
	اﻵن يمكنك إدخال قسيمة التخفيض في المكان المخصص وإرسالها إلى جميع أصحاب سلات الشراء المتروكة.
</p>

<p>
	عند اﻹنتهاء من جميع الخطوات التي ذُكرت في هذا المقال، ستكون الشيفرة البرمجية في الملف index.js كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_235_176" style="">
<span class="kwd">import</span><span class="pln"> express from </span><span class="str">"express"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> path from </span><span class="str">"path"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="typ">AbandonedCarts</span><span class="pln"> from </span><span class="str">"./zid.js"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="typ">AddCoupon</span><span class="pln"> from </span><span class="str">"./addCoupon.js"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> sendMail from </span><span class="str">"./mail.js"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Loyalty</span><span class="pln"> from </span><span class="str">"./loyality.js"</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> __dirname </span><span class="pun">=</span><span class="pln"> path</span><span class="pun">.</span><span class="pln">resolve</span><span class="pun">();</span><span class="pln">
</span><span class="kwd">var</span><span class="pln"> app </span><span class="pun">=</span><span class="pln"> express</span><span class="pun">();</span><span class="pln">

app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">express</span><span class="pun">.</span><span class="pln">json</span><span class="pun">());</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">express</span><span class="pun">.</span><span class="pln">urlencoded</span><span class="pun">({</span><span class="pln"> extended</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">false</span><span class="pln"> </span><span class="pun">}));</span><span class="pln">

app</span><span class="pun">.</span><span class="kwd">set</span><span class="pun">(</span><span class="str">"views"</span><span class="pun">,</span><span class="pln"> path</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="pln">__dirname</span><span class="pun">,</span><span class="pln"> </span><span class="str">"views"</span><span class="pun">));</span><span class="pln">
app</span><span class="pun">.</span><span class="kwd">set</span><span class="pun">(</span><span class="str">"view engine"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"pug"</span><span class="pun">);</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">express</span><span class="pun">.</span><span class="kwd">static</span><span class="pun">(</span><span class="pln">path</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="pln">__dirname</span><span class="pun">,</span><span class="pln"> </span><span class="str">"public"</span><span class="pun">)));</span><span class="pln">

app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/carts"</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> response </span><span class="pun">=</span><span class="pln"> await </span><span class="typ">AbandonedCarts</span><span class="pun">();</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> renderedData </span><span class="pun">=</span><span class="pln"> response</span><span class="pun">[</span><span class="str">"abandoned-carts"</span><span class="pun">];</span><span class="pln">
  res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">"abandoned"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> renderedData </span><span class="pun">});</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/"</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">"index"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/loyalty"</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">"loyalty"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/coupon"</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">"coupon"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/error"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">"error"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

app</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">"/loyalty"</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> response </span><span class="pun">=</span><span class="pln"> await </span><span class="typ">Loyalty</span><span class="pun">();</span><span class="pln">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="pln">response</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

app</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">"/add-coupon"</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> data </span><span class="pun">=</span><span class="pln"> req</span><span class="pun">.</span><span class="pln">body</span><span class="pun">;</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> dataToSend </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Object</span><span class="pun">.</span><span class="pln">assign</span><span class="pun">({},</span><span class="pln"> data</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> response </span><span class="pun">=</span><span class="pln"> await </span><span class="typ">AddCoupon</span><span class="pun">(</span><span class="pln">dataToSend</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    res</span><span class="pun">.</span><span class="pln">redirect</span><span class="pun">(</span><span class="str">"/"</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    res</span><span class="pun">.</span><span class="pln">redirect</span><span class="pun">(</span><span class="str">"/error"</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

app</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">"/mail"</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> data </span><span class="pun">=</span><span class="pln"> req</span><span class="pun">.</span><span class="pln">body</span><span class="pun">;</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> dataToSend </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Object</span><span class="pun">.</span><span class="pln">assign</span><span class="pun">({},</span><span class="pln"> data</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> send </span><span class="pun">=</span><span class="pln"> await sendMail</span><span class="pun">(</span><span class="pln">dataToSend</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">send</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"mail has been sent"</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"there was an error"</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> port </span><span class="pun">=</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">PORT </span><span class="pun">||</span><span class="pln"> </span><span class="lit">3000</span><span class="pun">;</span><span class="pln">

app</span><span class="pun">.</span><span class="pln">listen</span><span class="pun">(</span><span class="pln">port</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(`</span><span class="typ">Server</span><span class="pln"> started on $</span><span class="pun">{</span><span class="pln">port</span><span class="pun">}`);</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	أخيرًا، تجد شيفرة التطبيق كاملةً على GitHub بالمستودع <a data-ss1631886121="1" data-ss1631887126="1" href="https://github.com/HsoubAcademy/zid-retention-app" rel="external nofollow">zid-retention-app</a>.
</p>

<h2>
	خاتمة
</h2>

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

<p>
	لكي يكون التطبيق تجاريًا، عليك إضافة نظام لتسجيل الاعضاء ونظام اشتراكات وأيضا نظام تسجيل الدخول والخروج مع إمكانية إضافة المعلومات الشخصية لصاحب المتجر الراغب في التسجيل، وهنا سنستخدم الصفحة الرئيسية التي أضفناها إلى التطبيق index.pug، بحيث سيكون بإمكان العميل المسجل إضافة مفتاح المتجر الخاص به سواءً على منصة زد، أو إضافة المعلومات المطلوبة للربط مع التطبيق في حالة استخدامه لمنصة أخرى مثل Shopify.
</p>

<p>
	يمكنك أيضًا إضافة خصائص جديدة لتطبيق كما أشرنا مثل إرسال رسالة نصية للعملاء أصحاب سلات الشراء المتروكة بالتزامن مع إرسال بريد إلكتروني. يمكنك استخدام منصات إرسال الرسائل النصية المتوفرة في بلدك إن وجدت أو استخدام حلول مثل <a data-ss1631886121="1" data-ss1631887126="1" href="https://www.twilio.com/" rel="external nofollow">Twilio</a> أو <a data-ss1631886121="1" data-ss1631887126="1" href="https://www.messagebird.com/en/" rel="external nofollow">MessageBird</a>. وطبعًا يمكنك توسيع الخدمات التي تقدمها إلى خدمات أخرى يطلبها أصحاب المتاجر كما أشرنا وهنا نترك الأمر لك ولإبداعك.
</p>

<p>
	تهانينا، لقد أتممت بناء تطبيق مفيد بإمكانك استعماله بطريقة تجارية إن أحببت وﻹطلاق شركتك الناشئة العاملة في هذا المجال، ولم ﻻ توسعها لتشمل منصات أكثر غير منصة زد فقط! نرجو لك كل التوفيق في رحلتك القادمة.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		<a data-ss1631886121="1" data-ss1631887126="1" href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-api-r1314/" rel="">مدخل إلى الواجهات البرمجية <abbr title="Application Programming Interface | واجهة برمجية">API</abbr></a>
	</li>
	<li>
		<a data-ss1631886121="1" data-ss1631887126="1" href="https://academy.hsoub.com/programming/general/%D8%A3%D9%85%D8%AB%D9%84%D8%A9-%D8%B9%D9%85%D9%84%D9%8A%D8%A9-%D9%84%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AA%D8%A7%D8%AC%D8%B1-%D8%B2%D8%AF-zid-api-r1319/" rel="">أمثلة عملية لاستخدام واجهة برمجة متاجر زد zid <abbr title="Application Programming Interface | واجهة برمجية">API</abbr></a>
	</li>
	<li>
		<a data-ss1631886121="1" data-ss1631887126="1" href="https://academy.hsoub.com/apps/web/%D8%A7%D9%84%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%84%D9%88%D8%AD%D8%A9-%D8%AA%D8%AD%D9%83%D9%85-%D9%85%D8%AA%D8%AC%D8%B1-%D8%B2%D8%AF-%D8%A7%D9%84%D8%A5%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A-%D9%88%D8%B6%D8%A8%D8%B7-%D8%B9%D9%85%D9%84%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%AA%D8%AC%D8%B1-r441/" rel="">التعرف على لوحة تحكم متجر زد الإلكتروني وضبط عمليات المتجر</a>
	</li>
	<li>
		<a data-ss1631886121="1" data-ss1631887126="1" href="https://academy.hsoub.com/apps/web/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D8%AA%D8%AC%D8%B1-%D8%A5%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A-%D9%85%D8%AA%D9%83%D8%A7%D9%85%D9%84-%D8%A8%D8%A7%D8%B3%D8%AA%D8%B9%D9%85%D8%A7%D9%84-%D9%85%D9%86%D8%B5%D8%A9-%D8%B2%D8%AF-r440/" rel="">كيفية إنشاء متجر إلكتروني متكامل باستعمال منصة زد</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1320</guid><pubDate>Sat, 18 Sep 2021 15:00:00 +0000</pubDate></item><item><title>&#x627;&#x644;&#x627;&#x633;&#x62A;&#x64A;&#x62B;&#x627;&#x642; &#x639;&#x628;&#x631; &#x645;&#x641;&#x62A;&#x627;&#x62D; &#x627;&#x644;&#x645;&#x633;&#x62A;&#x62E;&#x62F;&#x645; &#x627;&#x644;&#x645;&#x634;&#x641;&#x631; (token authentication) &#x641;&#x64A; &#x62A;&#x637;&#x628;&#x64A;&#x642; Node.js &#x648; React</title><link>https://academy.hsoub.com/programming/javascript/nodejs/%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%8A%D8%AB%D8%A7%D9%82-%D8%B9%D8%A8%D8%B1-%D9%85%D9%81%D8%AA%D8%A7%D8%AD-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-%D8%A7%D9%84%D9%85%D8%B4%D9%81%D8%B1-token-authentication-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-nodejs-%D9%88-react-r1135/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2021_02/602be4b47da8e_-.png.9b746c1fca1296dd8a004770031d48e9.png" /></p>

<p>
	على المستخدمين تسجيل دخولهم إلى التطبيق، ومن المفترض عندما يحدث ذلك، أن ترتبط معلوماتهم تلقائيًا بالملاحظات التي سينشئونها. لذا سنضيف <a data-ss1613489326="1" data-ss1613491478="1" href="https://scotch.io/tutorials/the-ins-and-outs-of-token-based-authentication#toc-how-token-based-works" rel="external nofollow">آلية تحقق مبنية على الاستيثاق</a> إلى الواجهة الخلفية.
</p>

<p>
	يمكن تقديم أساسيات التحقق المبني على الاستيثاق من خلال المخطط التتابعي التالي:
</p>

<p style="text-align: center;">
	<img alt="token_auth_diagram_01.png" class="ipsImage ipsImage_thumbnailed" data-fileid="57793" data-unique="t8vm86pji" src="https://academy.hsoub.com/uploads/monthly_2021_02/token_auth_diagram_01.png.666b561c464cd593f856398cd0b2f96e.png"></p>

<ul>
<li>
		يسجل المستخدم دخوله أولًا باستخدام نافذة تسجيل الدخول التي أضيفت إلى تطبيق React (سنفعل ذلك في القسم 5).
		<ul>
<li>
				سيدفع تسجيل الدخول شيفرة React إلى إرسال اسم المستخدم وكلمة المرور إلى العنوان api/login/ على الخادم عن طريق طلب HTTP-POST.
			</li>
		</ul>
</li>
	<li>
		إن كان اسم المستخدم وكلمة المرور صحيحين، سيوّلد الخادم مفتاح مشفَّر (token) يكون بمثابة شهادة تعرّف بطريقة ما المستخدم الذي سجل دخوله.
		<ul>
<li>
				يتميز المتفاح بأنه معلَّم بعلامة رقمية (يقال عليه توقيع رقمي أحيانًا) مما يجعل تزويره مستحيلًا باستخدام طرق التزوير المعروفة.
			</li>
		</ul>
</li>
	<li>
		يستجيب الخادم برمز حالة يشير إلى نجاح العملية ويعيد نسخة مفتاح مشفر مع الاستجابة.
	</li>
	<li>
		يحفظ المتصفح بهذا المفتاح في حالة تطبيق React مثلًا.
	</li>
	<li>
		عندما ينشئ المستخدم ملاحظة جديدة (أو عندما يقوم بأي أمر يتطلب التوثيق)، سترسل React نسخة من المفتاح مع الطلب إلى الخادم.
	</li>
	<li>
		يستخدم عندها الخادم هذا المفتاح للتحقق من المستخدم (العملية مشابهة تقريبًا لفتح باب المنزل بالمفتاح الخاص بصاحب المنزل).
	</li>
</ul>
<p>
	ملاحظة: سنطلق على هذا المفتاح المشفر (token) اسم «شهادة» مجازًا لأنه يشهد للمستخدم الحامل له بأنه صاحب الحساب الحقيقي المخول للوصول إلى حسابه وبياناته على الموقع.
</p>

<p>
	لنضف أولًا وظيفة تسجيل الدخول. علينا تثبيت المكتبة <a data-ss1613489326="1" data-ss1613491478="1" href="https://github.com/auth0/node-jsonwebtoken" rel="external nofollow">jsonwebtoken</a> التي تسمح لنا بتوليد <a data-ss1613489326="1" data-ss1613491478="1" href="https://jwt.io/" rel="external nofollow">شهادات ويب JSON</a> (أي <strong>J</strong>SON <strong>w</strong>eb <strong>t</strong>okens وتختصر إلى JWT).
</p>

<pre class="ipsCode">
npm install jsonwebtoken --save
</pre>

<p>
	نضع شيفرة تسجيل الدخول التالية في الملف login.js ضمن المجلد controllers:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9792_7" style="">
<span class="kwd">const</span><span class="pln"> jwt </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'jsonwebtoken'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> bcrypt </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'bcrypt'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> loginRouter </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'express'</span><span class="pun">).</span><span class="typ">Router</span><span class="pun">()</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">User</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'../models/user'</span><span class="pun">)</span><span class="pln">

loginRouter</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> body </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">body

  </span><span class="kwd">const</span><span class="pln"> user </span><span class="pun">=</span><span class="pln"> await </span><span class="typ">User</span><span class="pun">.</span><span class="pln">findOne</span><span class="pun">({</span><span class="pln"> username</span><span class="pun">:</span><span class="pln"> body</span><span class="pun">.</span><span class="pln">username </span><span class="pun">})</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> passwordCorrect </span><span class="pun">=</span><span class="pln"> user </span><span class="pun">===</span><span class="pln"> </span><span class="kwd">null</span><span class="pln">
    </span><span class="pun">?</span><span class="pln"> </span><span class="kwd">false</span><span class="pln">
    </span><span class="pun">:</span><span class="pln"> await bcrypt</span><span class="pun">.</span><span class="pln">compare</span><span class="pun">(</span><span class="pln">body</span><span class="pun">.</span><span class="pln">password</span><span class="pun">,</span><span class="pln"> user</span><span class="pun">.</span><span class="pln">passwordHash</span><span class="pun">)</span><span class="pln">

  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!(</span><span class="pln">user </span><span class="pun">&amp;&amp;</span><span class="pln"> passwordCorrect</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">401</span><span class="pun">).</span><span class="pln">json</span><span class="pun">({</span><span class="pln">
      error</span><span class="pun">:</span><span class="pln"> </span><span class="str">'invalid username or password'</span><span class="pln">
    </span><span class="pun">})</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  </span><span class="kwd">const</span><span class="pln"> userForToken </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    username</span><span class="pun">:</span><span class="pln"> user</span><span class="pun">.</span><span class="pln">username</span><span class="pun">,</span><span class="pln">
    id</span><span class="pun">:</span><span class="pln"> user</span><span class="pun">.</span><span class="pln">_id</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  </span><span class="kwd">const</span><span class="pln"> token </span><span class="pun">=</span><span class="pln"> jwt</span><span class="pun">.</span><span class="pln">sign</span><span class="pun">(</span><span class="pln">userForToken</span><span class="pun">,</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">SECRET</span><span class="pun">)</span><span class="pln">

  response
    </span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">200</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">send</span><span class="pun">({</span><span class="pln"> token</span><span class="pun">,</span><span class="pln"> username</span><span class="pun">:</span><span class="pln"> user</span><span class="pun">.</span><span class="pln">username</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">:</span><span class="pln"> user</span><span class="pun">.</span><span class="pln">name </span><span class="pun">})</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

module</span><span class="pun">.</span><span class="pln">exports </span><span class="pun">=</span><span class="pln"> loginRouter</span></pre>

<p>
	يبدأ عمل الشيفرة بالبحث عن المستخدم ضمن قاعدة البيانات من خلال اسم المستخدم المرفق مع الطلب. بعد ذلك يتحقق من كلمة المرور التي ترفق أيضًا مع الطلب. وبما أن كلمات المرور لا تخزن كما هي في قواعد البيانات بل على شكل رموز محسوبة اعتمادًا عليها، سيتحقق التابع <code>bcrypt.compare</code> من صحة كلمة المرور:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9792_11" style="">
<span class="pln">await bcrypt</span><span class="pun">.</span><span class="pln">compare</span><span class="pun">(</span><span class="pln">body</span><span class="pun">.</span><span class="pln">password</span><span class="pun">,</span><span class="pln"> user</span><span class="pun">.</span><span class="pln">passwordHash</span><span class="pun">)</span></pre>

<p>
	إن لم يُعثَر على المستخدم، أو كانت كلمة المرور خاطئة، سيسيب الخادم للطلب <a data-ss1613489326="1" data-ss1613491478="1" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2" rel="external nofollow">برمز الحالة 401</a> (غير مخول بالعملية) وسيحدد سبب إخفاق الطلب في جسم الاستجابة.
</p>

<p>
	إن كانت كلمة المرور صحيحة، ستنشئ الشهادة باستخدام التابع <code>jwt.sign</code>. تحتوي الشهادة على اسم المستخدم ومعرف المستخدم بصيغة موقعة رقميًا.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9792_9" style="">
<span class="kwd">const</span><span class="pln"> userForToken </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  username</span><span class="pun">:</span><span class="pln"> user</span><span class="pun">.</span><span class="pln">username</span><span class="pun">,</span><span class="pln">
  id</span><span class="pun">:</span><span class="pln"> user</span><span class="pun">.</span><span class="pln">_id</span><span class="pun">,</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> token </span><span class="pun">=</span><span class="pln"> jwt</span><span class="pun">.</span><span class="pln">sign</span><span class="pun">(</span><span class="pln">userForToken</span><span class="pun">,</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">SECRET</span><span class="pun">)</span></pre>

<p>
	توّقع الشهادة رقميًا باستعمال قيمة نصية تمثل ""السر"" موجودة في متغير البيئة <code>SECRET</code>. يضمن هذا التوقيع عدم قدرة أي فريق لا يعرف كلمة "السر" من توليد شهادة صالحة. يجب أن توضع قيمة متغير البيئة في الملف ذو اللاحقة (env.).
</p>

<p>
	يستجيب الخادم لأي طلب صحيح بإرسال رمز الحالة 200 (مناسب). تعاد بعدها الشهادة الناتجة واسم المستخدم ضمن جسم الاستجابة.
</p>

<p>
	نضيف شيفرة تسجيل الدخول إلى التطبيق بإضافة متحكم المسار التالي إلى الملف app.js:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9792_13" style="">
<span class="kwd">const</span><span class="pln"> loginRouter </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'./controllers/login'</span><span class="pun">)</span><span class="pln">

</span><span class="com">//...</span><span class="pln">

app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="str">'/api/login'</span><span class="pun">,</span><span class="pln"> loginRouter</span><span class="pun">)</span></pre>

<p>
	لنحاول تسجيل الدخول باستخدام VS Code REST-client:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="57791" data-ss1613489326="1" data-ss1613491478="1" href="https://academy.hsoub.com/uploads/monthly_2021_02/vs_code_login_02.png.811e2293db53a43f1ba6a6b845f985d4.png" rel=""><img alt="vs_code_login_02.png" class="ipsImage ipsImage_thumbnailed" data-fileid="57791" data-unique="ksix9wwka" src="https://academy.hsoub.com/uploads/monthly_2021_02/vs_code_login_02.png.811e2293db53a43f1ba6a6b845f985d4.png"></a>
</p>

<p>
	لن تنجح العملية وستطبع الرسالة التالية على الطرفية:
</p>

<pre class="ipsCode">
(node:32911) UnhandledPromiseRejectionWarning: Error: secretOrPrivateKey must have a value
    at Object.module.exports [as sign] (/Users/mluukkai/opetus/_2019fullstack-koodit/osa3/notes-backend/node_modules/jsonwebtoken/sign.js:101:20)
    at loginRouter.post (/Users/mluukkai/opetus/_2019fullstack-koodit/osa3/notes-backend/controllers/login.js:26:21)
(node:32911) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)
</pre>

<p>
	أخفق الأمر <code>(jwt.sign(userForToken, process.env.SECRET</code> لأننا لم نضع قيمة "للسر" في متغير البيئة <code>SECRET</code>. سيعمل التطبيق بمجرد أن نقوم بذلك، وسيعيد تسجيل الدخول الناجح تفاصيل المستخدم والشهادة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="57787" data-ss1613489326="1" data-ss1613491478="1" href="https://academy.hsoub.com/uploads/monthly_2021_02/login_successful_03.png.d29526699753324ba688842c7988976c.png" rel=""><img alt="login_successful_03.png" class="ipsImage ipsImage_thumbnailed" data-fileid="57787" data-unique="h6fpg6k7r" src="https://academy.hsoub.com/uploads/monthly_2021_02/login_successful_03.png.d29526699753324ba688842c7988976c.png"></a>
</p>

<p>
	سيولد اسم المستخدم الخاطئ أو كلمة المرور الخاطئة رسالة خطأ وسيعيد الخادم رمز الحالة المناسب.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="57786" data-ss1613489326="1" data-ss1613491478="1" href="https://academy.hsoub.com/uploads/monthly_2021_02/login_failed_04.png.1dec1453ec7a827ffe6b4fa34ae2914f.png" rel=""><img alt="login_failed_04.png" class="ipsImage ipsImage_thumbnailed" data-fileid="57786" data-unique="s7t8nnhzp" src="https://academy.hsoub.com/uploads/monthly_2021_02/login_failed_04.png.1dec1453ec7a827ffe6b4fa34ae2914f.png"></a>
</p>

<h2>
	حصر تدوين الملاحظات بالمستخدمين المسجلين
</h2>

<p>
	لنمنع إنشاء ملاحظات جديدة ما لم تكن الشهادة المرفقة مع الطلب صحيحة. بعدها ستحفظ الملاحظة في قائمة ملاحظات المستخدم الذي تعرّفه الشهادة.
</p>

<p>
	هناك طرق عديدة لإرسال الشهادة من المتصفح إلى الخادم. سنستخدم منها ترويسة <a data-ss1613489326="1" data-ss1613491478="1" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization" rel="external nofollow">التصريح</a> (ِAuthorization). حيث توضح هذه الترويسة <a data-ss1613489326="1" data-ss1613491478="1" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#Authentication_schemes" rel="external nofollow">تخطيط الاستيثاق</a> المستخدم. سيكون ذلك ضروريًا إن سمح الخادم بطرق متعددة للتحقق. حيث يوضح التخطيط للخادم الطريقة التي يفسر بها الشهادة المرفقة بالطلب. سيكون التخطيط <code>Bearer</code> مناسبًا لنا. وعمليًا لو كانت قيمة الشهادة على سبيل المثال (eyJhbGciOiJIUzI1NiIsInR5c2VybmFtZSI6Im1sdXVra2FpIiwiaW) ستكون قيمة ترويسة التصريح:
</p>

<pre class="ipsCode">
Bearer eyJhbGciOiJIUzI1NiIsInR5c2VybmFtZSI6Im1sdXVra2FpIiwiaW
</pre>

<p>
	ستتغير شيفرة إنشاء ملاحظة جديدة على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9792_16" style="">
<span class="kwd">const</span><span class="pln"> jwt </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'jsonwebtoken'</span><span class="pun">)</span><span class="pln">
</span><span class="com">// ...</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> getTokenFrom </span><span class="pun">=</span><span class="pln"> request </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">  
    </span><span class="kwd">const</span><span class="pln"> authorization </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'authorization'</span><span class="pun">)</span><span class="pln">  
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">authorization </span><span class="pun">&amp;&amp;</span><span class="pln"> authorization</span><span class="pun">.</span><span class="pln">toLowerCase</span><span class="pun">().</span><span class="pln">startsWith</span><span class="pun">(</span><span class="str">'bearer '</span><span class="pun">))</span><span class="pln"> 
    </span><span class="pun">{</span><span class="pln">    
        </span><span class="kwd">return</span><span class="pln"> authorization</span><span class="pun">.</span><span class="pln">substring</span><span class="pun">(</span><span class="lit">7</span><span class="pun">)</span><span class="pln">  
    </span><span class="pun">}</span><span class="pln">  
    </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">}</span><span class="pln">
notesRouter</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> body </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">body
  </span><span class="kwd">const</span><span class="pln"> token </span><span class="pun">=</span><span class="pln"> getTokenFrom</span><span class="pun">(</span><span class="pln">request</span><span class="pun">)</span><span class="pln">  
  </span><span class="kwd">const</span><span class="pln"> decodedToken </span><span class="pun">=</span><span class="pln"> jwt</span><span class="pun">.</span><span class="pln">verify</span><span class="pun">(</span><span class="pln">token</span><span class="pun">,</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">SECRET</span><span class="pun">)</span><span class="pln">  
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">token </span><span class="pun">||</span><span class="pln"> </span><span class="pun">!</span><span class="pln">decodedToken</span><span class="pun">.</span><span class="pln">id</span><span class="pun">)</span><span class="pln"> 
  </span><span class="pun">{</span><span class="pln">    
      </span><span class="kwd">return</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">401</span><span class="pun">).</span><span class="pln">json</span><span class="pun">({</span><span class="pln"> error</span><span class="pun">:</span><span class="pln"> </span><span class="str">'token missing or invalid'</span><span class="pln"> </span><span class="pun">})</span><span class="pln">  
  </span><span class="pun">}</span><span class="pln">  
    </span><span class="kwd">const</span><span class="pln"> user </span><span class="pun">=</span><span class="pln"> await </span><span class="typ">User</span><span class="pun">.</span><span class="pln">findById</span><span class="pun">(</span><span class="pln">decodedToken</span><span class="pun">.</span><span class="pln">id</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> note </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Note</span><span class="pun">({</span><span class="pln">
    content</span><span class="pun">:</span><span class="pln"> body</span><span class="pun">.</span><span class="pln">content</span><span class="pun">,</span><span class="pln">
    important</span><span class="pun">:</span><span class="pln"> body</span><span class="pun">.</span><span class="pln">important </span><span class="pun">===</span><span class="pln"> </span><span class="kwd">undefined</span><span class="pln"> </span><span class="pun">?</span><span class="pln"> </span><span class="kwd">false</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> body</span><span class="pun">.</span><span class="pln">important</span><span class="pun">,</span><span class="pln">
    date</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">(),</span><span class="pln">
    user</span><span class="pun">:</span><span class="pln"> user</span><span class="pun">.</span><span class="pln">_id
  </span><span class="pun">})</span><span class="pln">

  </span><span class="kwd">const</span><span class="pln"> savedNote </span><span class="pun">=</span><span class="pln"> await note</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">
  user</span><span class="pun">.</span><span class="pln">notes </span><span class="pun">=</span><span class="pln"> user</span><span class="pun">.</span><span class="pln">notes</span><span class="pun">.</span><span class="pln">concat</span><span class="pun">(</span><span class="pln">savedNote</span><span class="pun">.</span><span class="pln">_id</span><span class="pun">)</span><span class="pln">
  await user</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">

  response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">savedNote</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	تعزل الدالة المساعدة <code>getTokenFrom</code> الشهادة عن ترويسة التصريح، ويتحقق التابع <code>jwt.verify</code> من صلاحيتها. يفك التابع تشفيرالشهادة أيضًا أو يعيد الكائن الذي بُنيت على أساسه الشهادة.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9792_21" style="">
<span class="kwd">const</span><span class="pln"> decodedToken </span><span class="pun">=</span><span class="pln"> jwt</span><span class="pun">.</span><span class="pln">verify</span><span class="pun">(</span><span class="pln">token</span><span class="pun">,</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">SECRET</span><span class="pun">)</span></pre>

<p>
	يحمل الكائن الناتج عن الشهادة بعد فك شيفرته حقلي اسم المستخدم والمعرف الخاص به، ليخبر الخادم من أرسل الطلب.
</p>

<p>
	إن لم تكن هناك شهادة، أو لم يحمل الكائن الناتج عن الشهادة معرف المستخدم، سيعيد الخادم <a data-ss1613489326="1" data-ss1613491478="1" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2" rel="external nofollow">رمز الحالة 401</a> (غير مصرّح له)، وسيحدد سبب الخطأ في جسم الاستجابة.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9792_18" style="">
<span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">token </span><span class="pun">||</span><span class="pln"> </span><span class="pun">!</span><span class="pln">decodedToken</span><span class="pun">.</span><span class="pln">id</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">401</span><span class="pun">).</span><span class="pln">json</span><span class="pun">({</span><span class="pln">
    error</span><span class="pun">:</span><span class="pln"> </span><span class="str">'token missing or invalid'</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	حالما يتم التحقق من هوية المستخدم، تتابع العملية التنفيذ بالشكل السابق.
</p>

<p>
	يمكن إنشاء ملاحظة جديدة باستخدام Postman إن أعطت ترويسة التصريح القيمة النصية الصحيحة وهي <em>bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ</em>، وستكون القيمة الأخرى هي الشهادة التي تعيدها عملية تسجيل الدخول.
</p>

<p>
	ستبدو العملية باستخدام Postman كالتالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="57788" data-ss1613489326="1" data-ss1613491478="1" href="https://academy.hsoub.com/uploads/monthly_2021_02/new_note_postman_05.png.d9a59c9f5853653ff964ffc539cc1eec.png" rel=""><img alt="new_note_postman_05.png" class="ipsImage ipsImage_thumbnailed" data-fileid="57788" data-unique="dpv4262pp" src="https://academy.hsoub.com/uploads/monthly_2021_02/new_note_postman_05.png.d9a59c9f5853653ff964ffc539cc1eec.png"></a>
</p>

<p>
	وباستعمال Visual Studio Code REST client كالتالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="57789" data-ss1613489326="1" data-ss1613491478="1" href="https://academy.hsoub.com/uploads/monthly_2021_02/new_note_VS_06.png.1e8f1856aaabdd97f84fc595dfb04243.png" rel=""><img alt="new_note_VS_06.png" class="ipsImage ipsImage_thumbnailed" data-fileid="57789" data-unique="q7sdosfwi" src="https://academy.hsoub.com/uploads/monthly_2021_02/new_note_VS_06.png.1e8f1856aaabdd97f84fc595dfb04243.png"></a>
</p>

<h2>
	معالجة الأخطاء
</h2>

<p>
	يمكن لعمليات التحقق أن تسبب أخطاء من النوع JsonWebTokenError. فلو أزلنا بعض المحارف من الشهادة وحاولنا كتابة ملاحظة جديدة، هذا ما سيحدث:
</p>

<pre class="ipsCode">
JsonWebTokenError: invalid signature
    at /Users/mluukkai/opetus/_2019fullstack-koodit/osa3/notes-backend/node_modules/jsonwebtoken/verify.js:126:19
    at getSecret (/Users/mluukkai/opetus/_2019fullstack-koodit/osa3/notes-backend/node_modules/jsonwebtoken/verify.js:80:14)
    at Object.module.exports [as verify] (/Users/mluukkai/opetus/_2019fullstack-koodit/osa3/notes-backend/node_modules/jsonwebtoken/verify.js:84:10)
    at notesRouter.post (/Users/mluukkai/opetus/_2019fullstack-koodit/osa3/notes-backend/controllers/notes.js:40:30)
</pre>

<p>
	هنالك أسباب عديدة لأخطاء فك التشفير. فقد تكون الشهادة خاطئة أو مزورة أو منتهية الصلاحية. إذًا سنوسع الأداة الوسطية لمعالجة الأخطاء لتأخذ بالحسبان أخطاء فك التشفير الممكنة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9792_23" style="">
<span class="kwd">const</span><span class="pln"> unknownEndpoint </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">404</span><span class="pun">).</span><span class="pln">send</span><span class="pun">({</span><span class="pln"> error</span><span class="pun">:</span><span class="pln"> </span><span class="str">'unknown endpoint'</span><span class="pln"> </span><span class="pun">})</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> errorHandler </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">error</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">error</span><span class="pun">.</span><span class="pln">name </span><span class="pun">===</span><span class="pln"> </span><span class="str">'CastError'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">400</span><span class="pun">).</span><span class="pln">send</span><span class="pun">({</span><span class="pln">
      error</span><span class="pun">:</span><span class="pln"> </span><span class="str">'malformatted id'</span><span class="pln">
    </span><span class="pun">})</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">error</span><span class="pun">.</span><span class="pln">name </span><span class="pun">===</span><span class="pln"> </span><span class="str">'ValidationError'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">400</span><span class="pun">).</span><span class="pln">json</span><span class="pun">({</span><span class="pln">
      error</span><span class="pun">:</span><span class="pln"> error</span><span class="pun">.</span><span class="pln">message 
    </span><span class="pun">})</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">error</span><span class="pun">.</span><span class="pln">name </span><span class="pun">===</span><span class="pln"> </span><span class="str">'JsonWebTokenError'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">    </span><span class="kwd">return</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">401</span><span class="pun">).</span><span class="pln">json</span><span class="pun">({</span><span class="pln">      
error</span><span class="pun">:</span><span class="pln"> </span><span class="str">'invalid token'</span><span class="pln">    
</span><span class="pun">})</span><span class="pln">  
</span><span class="pun">}</span><span class="pln">

  logger</span><span class="pun">.</span><span class="pln">error</span><span class="pun">(</span><span class="pln">error</span><span class="pun">.</span><span class="pln">message</span><span class="pun">)</span><span class="pln">

  next</span><span class="pun">(</span><span class="pln">error</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يمكنك إيجاد شيفرة التطبيق في الفرع part4-9 ضمن المستودع المخصص للتطبيق على <a data-ss1613489326="1" data-ss1613491478="1" href="https://github.com/fullstack-hy2020/part3-notes-backend/tree/part4-9" rel="external nofollow">GitHub</a>.
</p>

<p>
	إن احتوى التطبيق على عدة واجهات تتطلب التحقق، يجب أن نضع أدوات التحقق JWT ضمن أداة وسطية مستقلة. ستساعدنا بعض المكتبات الموجودة مثل <a data-ss1613489326="1" data-ss1613491478="1" href="https://www.npmjs.com/package/express-jwt" rel="external nofollow">express-jwt</a> في تحقيق ذلك.
</p>

<h2>
	ملاحظة ختامية
</h2>

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

<p>
	يجب استخدام كلمات السر وأسماء المستخدمين وشهادات التحقق مع البروتوكول HTTPS. يمكننا استخدام <a data-ss1613489326="1" data-ss1613491478="1" href="https://nodejs.org/api/https.html" rel="external nofollow">HTTPS</a> ضمن Node في تطبيقنا بدلًا من <a data-ss1613489326="1" data-ss1613491478="1" href="https://nodejs.org/docs/latest-v8.x/api/http.html" rel="external nofollow">HTTP</a>، لكن ذلك سيتطلب أوامر تهيئة أكثر. من ناحية أخرى، تتواجد نسخة الإنتاج من تطبيقنا على Heroku، وبذلك سيبقى التطبيق آمنًا. فكل المسارات بين المتصفح وخادم Heroku تستخدم HTTPS.
</p>

<p>
	سنضيف آلية تسجيل الدخول إلى الواجهة الأمامية في القسم التالي.
</p>

<h2>
	التمارين 4.15 - 4.22
</h2>

<p>
	سنضيف أساسيات إدارة المستخدمين إلى تطبيق قائمة المدونات في التمارين التالية. إن الطريقة الأكثر أمانًا في الحل، هي متابعة المعلومات التي عرضناها في هذا الفصل والفصل السابق من القسم 4. كما يمكنك بالطبع استعمال مخيلتك.
</p>

<p>
	<strong>تحذير من جديد</strong>: إن لاحظت أنك تستخدم await/async مع then فتأكد أنك ستقع في الأخطاء بنسبة %99. استخدم أحد الأسلوبين وليس كلاهما.
</p>

<h3>
	4.15 التوسع في قائمة المدونات: الخطوة 3
</h3>

<p>
	أضف طريقة لتسجيل مستخدم جديد باستخدام طلب HTTP إلى العنوان api/users/. للمستخدمين اسم حقيقي واسم للدخول وكلمة مرور. لا تخزّن كلمة السر كنص واضح، بل استخدم المكتبة bcrypt بالطريقة التي استخدمناها في القسم4 -فصل (إدارة المستخدمين).
</p>

<p>
	<strong>ملاحظة</strong>: عانى بعض مستخدمي Windows أثناء العمل مع bcrypt. إن واجهتك المشاكل معها، ألغ تثبيتها كالتالي:
</p>

<pre class="ipsCode">
npm uninstall bcrypt --save 
</pre>

<p>
	ثم ثبت المكتبة <a data-ss1613489326="1" data-ss1613491478="1" href="https://www.npmjs.com/package/bcryptjs" rel="external nofollow">bcryptjs</a> بدلًا عنها.
</p>

<p>
	أضف طريقة لعرض تفاصيل جميع المستخدمين من خلال طلب HTTP مناسب.
</p>

<p>
	يمكن أن تظهر قائمة المستخدمين بالشكل التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="57785" data-ss1613489326="1" data-ss1613491478="1" href="https://academy.hsoub.com/uploads/monthly_2021_02/blog_all_users_07.png.9125a549bbcbd94b515272acc33faad6.png" rel=""><img alt="blog_all_users_07.png" class="ipsImage ipsImage_thumbnailed" data-fileid="57785" data-unique="w1rhdqbmt" src="https://academy.hsoub.com/uploads/monthly_2021_02/blog_all_users_07.png.9125a549bbcbd94b515272acc33faad6.png"></a>
</p>

<h3>
	4.16 التوسع في قائمة المدونات: الخطوة 4 *
</h3>

<p>
	أضف ميزة للتطبيق تنفذ التقييدات التالية:
</p>

<ul>
<li>
		اسم المستخدم وكلمة المرور إلزاميتان
	</li>
	<li>
		يجب أن يكون طول كل من كلمة المرور واسم المستخدم ثلاثة محارف على الأقل.
	</li>
	<li>
		يجب أن يكون اسم المستخدم فريدًا (غير مكرر).
	</li>
</ul>
<p>
	يجب أن تعيد العملية رمز الحالة المناسب مع رسالة خطأ عند محاولة إنشاء مستخدم جديد مخالف للتقييدات.
</p>

<p>
	<strong>ملاحظة</strong>: لا تختبر تقييد كلمة المرور باستعمال مقيمات Mongoose. لأن كلمة المرور التي تتلقاها الواجهة الخلفية وكلمة السر المرمزة التي تحفظ في قاعدة البيانات أمران مختلفان. يجب أن يقيم طول كلمة المرور باستخدام المتحكم كما فعلنا في <a data-ss1613489326="1" data-ss1613491478="1" href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%AA%D9%82%D9%8A%D9%8A%D9%85-%D8%B5%D9%84%D8%A7%D8%AD%D9%8A%D8%A9-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%88%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%85%D8%AF%D9%82%D9%82-eslint-r1102/" rel="">القسم 3</a> قبل أن نستعمل مقيّمات Mongoose.
</p>

<p>
	أضف اختبارًا يتأكد من عدم إضافة مستخدم إذا كانت بياناته غير صحيحة. يجب أن تعيد العملية رمز حالة مناسب ورسالة خطأ.
</p>

<h3>
	4.17 التوسع في قائمة المدونات: الخطوة 5
</h3>

<p>
	وسٍّع المدونات بحيث تحتوي كل مدونة على معلومات عن المستخدم الذي أنشأها. عدل في أسلوب إضافة مدونة جديدة بحيث يُحدد أحد المستخدمين الموجودين في قاعدة البيانات كمنشئ لها (قد يكون أول شخص يظهر مثلًا). نفذ المهمة اعتمادًا على معلومات الفصل الثالث- القسم 4. لا يهم حاليًا إلى أي مستخدم قد نسبت المدونة، لأننا سنهتم بذلك في التمرين 4.19.
</p>

<p>
	عدّل طريقة عرض المدونات بحيث تظهر معلومات منشئ المدونة:
</p>

<p style="text-align: center;">
	<img alt="blog_show_creato08.png.png" class="ipsImage ipsImage_thumbnailed" data-fileid="57794" data-unique="xg1lgn3vk" src="https://academy.hsoub.com/uploads/monthly_2021_02/blog_show_creato08.png.png.0ee1bf66cae1439733366cc9dc36d719.png"></p>

<p>
	عدل طريقة عرض قائمة المستخدمين بحيث تظهر المدونات التي أنشأها المستخدم:
</p>

<p style="text-align: center;">
	<img alt="users_show_blog_09.png.png" class="ipsImage ipsImage_thumbnailed" data-fileid="57795" data-unique="ujmba651p" src="https://academy.hsoub.com/uploads/monthly_2021_02/users_show_blog_09.png.png.37dae5c9241a930bc4c1067d2256526a.png"></p>

<h3>
	4.18 التوسع في قائمة المدونات: الخطوة 6
</h3>

<p>
	أضف أسلوب توثيق يعتمد على الشهادات كما فعلنا في بداية هذا الفصل.
</p>

<h3>
	4.19 التوسع في قائمة المدونات: الخطوة 7
</h3>

<p>
	عدّل في طريقة إضافة مدونة جديدة لتُنفَّذ فقط في الحالة التي تُرسل فيها شهادة صحيحة عبر طلب HTTP-POST.
</p>

<p>
	ويعتبر عندها الشخص الذي تحدده الشهادة منشئ المدونة.
</p>

<h3>
	4.20 التوسع في قائمة المدونات: الخطوة 8 *
</h3>

<p>
	راجع المثال الذي أوردناه في هذا الفصل عن استخلاص الشهادة من ترويسة التصريح باستخدام الدالة المساعدة <code>getTokenFrom</code>.
</p>

<p>
	إن أردت استخدام الأسلوب ذاته، أعد كتابة الشيفرة التي تأخذ الشهادة إلى أداة وسطية. يجب أن تأخذ الأداة الوسطية الشهادة من ترويسة التصريح وتضعها في الحقل <code>token</code> من كائن الطلب <code>request</code>. يعني هذا: إن صرّحت عن الأداة الوسطية في بداية الملف app.js وقبل كل المسارات كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9792_40" style="">
<span class="pln">app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">middleware</span><span class="pun">.</span><span class="pln">tokenExtractor</span><span class="pun">)</span></pre>

<p>
	ستتمكن المسارات من الوصول إلى الشهادة باستخدام الأمر <code>request.token</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9792_38" style="">
<span class="pln">blogsRouter</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="com">// ..</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> decodedToken </span><span class="pun">=</span><span class="pln"> jwt</span><span class="pun">.</span><span class="pln">verify</span><span class="pun">(</span><span class="pln">request</span><span class="pun">.</span><span class="pln">token</span><span class="pun">,</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">SECRET</span><span class="pun">)</span><span class="pln">
  </span><span class="com">// ..</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	وتذكر أن الأداة الوسطية هي دالة من ثلاث معاملات، تستدعى في نهاية تنفيذها المعامل الأخير <code>next</code> ليمرر التنفيذ إلى الأداة الوسطية التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9792_36" style="">
<span class="kwd">const</span><span class="pln"> tokenExtractor </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="com">// code that extracts the token</span><span class="pln">

  next</span><span class="pun">()</span><span class="pln">
</span><span class="pun">}</span></pre>

<h3>
	4.21 التوسع في قائمة المدونات: الخطوة 9 *
</h3>

<p>
	عدّل في عملية حذف مدونة بحيث تحذف من قبل منشئها فقط. وبالتالي ستنفذ عملية الحذف إن كانت الشهادة المرسلة عبر الطلب مطابقة لشهادة المستخدم الذي أنشأ المدونة.
</p>

<p>
	إن جرت محاولة حذف المدونة دون شهادة صحيحة أو من قبل المستخدم الخاطئ، على العملية أن تعيد رمز الحالة المناسب.
</p>

<p>
	لاحظ أنك لو أحضرت المدونة من قاعدة البيانات كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9792_34" style="">
<span class="kwd">const</span><span class="pln"> blog </span><span class="pun">=</span><span class="pln"> await </span><span class="typ">Blog</span><span class="pun">.</span><span class="pln">findById</span><span class="pun">(...)</span></pre>

<p>
	وأردت أن توازن بين معرف الكائن الذي أحضرته من قاعدة البيانات والقيمة النصية للمعرف، فلن تنجح عملية الموازنة العادية. ذلك أن الحقل <code>blog.user</code> ليس قيمة نصية بل كائن. لذلك لابد من تحويل قيمة المعرف في الكائن الذي أحضر من قاعدة البيانات إلى قيمة نصية أولًا.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9792_32" style="">
<span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln"> blog</span><span class="pun">.</span><span class="pln">user</span><span class="pun">.</span><span class="pln">toString</span><span class="pun">()</span><span class="pln"> </span><span class="pun">===</span><span class="pln"> userid</span><span class="pun">.</span><span class="pln">toString</span><span class="pun">()</span><span class="pln"> </span><span class="pun">)</span><span class="pln"> </span><span class="pun">...</span></pre>

<h3>
	4.22 التوسع في قائمة المدونات: الخطوة 10 *
</h3>

<p>
	ستفشل اختبارات إضافة مدونة جديدة حالما تستخدم التوثيق المعتمد على الشهادات. أصلح الاختبارت، ثم اكتب اختبارًا جديدًا يتأكد من أن الخادم سيرد برسالة الخطأ 401 (غير مفوض) إن لم ترفق شهادة بالطلب.
</p>

<p>
	اطلع على <a data-ss1613489326="1" data-ss1613491478="1" href="https://github.com/visionmedia/supertest/issues/398" rel="external nofollow">بعض الأفكار المفيدة</a> التي قد تساعدك في إصلاح الاختبارات.
</p>

<p>
	هكذا نكون قد وصلنا إلى التمرين الأخير في القسم، وحان الوقت لتسليم الحلول إلى GitHub، والإشارة إلى أنك سلمت التمارين المنتهية ضمن <a data-ss1613489326="1" data-ss1613491478="1" href="https://studies.cs.helsinki.fi/stats/courses/fullstackopen" rel="external nofollow">منظومة تسليم التمارين</a>.
</p>

<p>
	ترجمة -وبتصرف- للفصل <a data-ss1613489326="1" data-ss1613491478="1" href="https://fullstackopen.com/en/part4/token_authentication" rel="external nofollow">Token authentication </a> من سلسلة <a data-ss1613489326="1" data-ss1613491478="1" href="https://fullstackopen.com/en/" rel="external nofollow">Deep Dive Into Modern Web Development</a>
</p>
]]></description><guid isPermaLink="false">1135</guid><pubDate>Sun, 21 Feb 2021 13:08:02 +0000</pubDate></item><item><title>&#x625;&#x62F;&#x627;&#x631;&#x629; &#x627;&#x644;&#x645;&#x633;&#x62A;&#x62E;&#x62F;&#x645;&#x64A;&#x646; &#x641;&#x64A; &#x62A;&#x637;&#x628;&#x64A;&#x642; Node.js &#x628;&#x627;&#x633;&#x62A;&#x639;&#x645;&#x627;&#x644; &#x642;&#x648;&#x627;&#x639;&#x62F; &#x628;&#x64A;&#x627;&#x646;&#x627;&#x62A; MongoDB &#x648;&#x645;&#x643;&#x62A;&#x628;&#x629; Mongoose</title><link>https://academy.hsoub.com/programming/javascript/nodejs/%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85%D9%8A%D9%86-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-nodejs-%D8%A8%D8%A7%D8%B3%D8%AA%D8%B9%D9%85%D8%A7%D9%84-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-mongodb-%D9%88%D9%85%D9%83%D8%AA%D8%A8%D8%A9-mongoose-r1134/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2021_02/602bc98e58fa2_-.png.cf90aacda065aa080509fe9046f4c0e8.png" /></p>

<p>
	سنحتاج إلى وسيلة للتحقق من المستخدم ومن الصلاحيات الممنوحة له في تطبيقنا. وينبغي أن تُحفظ بيانات المستخدمين في قاعدة البيانات وأن ترتبط بالمستخدم الذي أنشأها. فلن يُسمح بتعديل أو حذف الملاحظة إلا من قبل المستخدم الذي أنشأها.
</p>

<p>
	لنبدأ بإضافة معلومات عن المستخدمين إلى قاعدة البيانات. حيث ستظهر علاقة من الشكل "واحد إلى عدة" بين المستخدم <code>User</code> و الملاحظة <code>Note</code>:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-ss1613482287="1" href="https://academy.hsoub.com/uploads/monthly_2021_02/user_note_relation_01.png.cd5dd00de6bdc23615a64ef54c50aa55.png" data-fileid="57780" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="57780" data-unique="iqxdg6fr6" src="https://academy.hsoub.com/uploads/monthly_2021_02/user_note_relation_01.png.cd5dd00de6bdc23615a64ef54c50aa55.png" alt="user_note_relation_01.png"></a>
</p>

<p>
	لو كنا نعمل مع قاعدة بيانات علاقيّة لكانت إضافة البيانات السابقة عملية مباشرة. حيث سيمتلك كلا الموردين جداول مستقلة في قاعدة البيانات وسيخزن معرّف المستخدم الذي أنشأ الملاحظة في جدول الملاحظات كمفتاح خارجي (foreign key).
</p>

<p>
	سيختلف الأمر قليلًا عند العمل مع قاعدة بيانات المستندات لوجود طرق عدة للقيام بذلك.
</p>

<p>
	يقتضي الحل الذي طبقناه في حالة الملاحظات بتخزين كل ملاحظة في تجمع للملاحظات notes collection ضمن قاعدة البيانات. فإن لم نشأ أن نغير التجمع، فمن المنطقي إنشاء تجمع جديد للمستخدمين على سبيل المثال.
</p>

<p>
	كما هي العادة في جميع قواعد بيانات المستندات يمكن استخدام معرّف الكائن في Mongo كمرجع للمستندات في التجمعات المختلفة. وهذا أمر مشابه لاستخدام المفتاح الغريب في قواعد البيانات العلاقية.
</p>

<p>
	لا تدعم قواعد البيانات التقليدية مثل Mongo الاستقصاء المشترك (joint query) المتاح في القواعد العلاقية، والذي يستخدم في تجميع البيانات من جداول عدة. لكن اعتبارًا من الإصدار 3.2، دعمت Mongo فكرة <a data-ss1613482287="1" href="https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/" rel="external nofollow">استقصاءات البحث المجمعة</a>، والتي لن نتعامل معها في منهاجنا.
</p>

<p>
	إن احتجنا إلى وظيفة مشابهة للاستقصاء المجمع في تطبيقنا فسنضيفها إلى الشيفرة بتنفيذ استقصاءات متعددة. على الرغم من أن Mongoose تتكفل في بعض الحالات بموضوع ضم وتجميع البيانات بما يوحي بالاستقصاء المشترك، إلا أن ما تجريه في الواقع، هي استقصاءات متعددة في الخفاء.
</p>

<h2>
	المراجع ما بين التجمعات
</h2>

<p>
	لو استخدمنا قاعدة بيانات علاقية، فستحتوي الملاحظة على مفتاح مرجعي إلى المستخدم الذي أنشأها. وبالمثل يمكننا القيام بأمر مشابه في قواعد بيانات المستندات. لنفترض أن تجمع المستخدمين users سيضم مستخدمين اثنين:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1027_7" style="">
<span class="pun">[</span><span class="pln">
  </span><span class="pun">{</span><span class="pln">
    username</span><span class="pun">:</span><span class="pln"> </span><span class="str">'mluukkai'</span><span class="pun">,</span><span class="pln">
    _id</span><span class="pun">:</span><span class="pln"> </span><span class="lit">123456</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="pun">{</span><span class="pln">
    username</span><span class="pun">:</span><span class="pln"> </span><span class="str">'hellas'</span><span class="pun">,</span><span class="pln">
    _id</span><span class="pun">:</span><span class="pln"> </span><span class="lit">141414</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
</span><span class="pun">];</span></pre>

<p>
	يضم تجمع الملاحظات notes ثلاثة ملاحظات، لكل منها حقل خاص يرتبط مرجعيًا بمستخدم من تجمع المستخدمين:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1027_9" style="">
<span class="pun">[</span><span class="pln">
  </span><span class="pun">{</span><span class="pln">
    content</span><span class="pun">:</span><span class="pln"> </span><span class="str">'HTML is easy'</span><span class="pun">,</span><span class="pln">
    important</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">,</span><span class="pln">
    _id</span><span class="pun">:</span><span class="pln"> </span><span class="lit">221212</span><span class="pun">,</span><span class="pln">
    user</span><span class="pun">:</span><span class="pln"> </span><span class="lit">123456</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="pun">{</span><span class="pln">
    content</span><span class="pun">:</span><span class="pln"> </span><span class="str">'The most important operations of HTTP protocol are GET and POST'</span><span class="pun">,</span><span class="pln">
    important</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
    _id</span><span class="pun">:</span><span class="pln"> </span><span class="lit">221255</span><span class="pun">,</span><span class="pln">
    user</span><span class="pun">:</span><span class="pln"> </span><span class="lit">123456</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="pun">{</span><span class="pln">
    content</span><span class="pun">:</span><span class="pln"> </span><span class="str">'A proper dinosaur codes with Java'</span><span class="pun">,</span><span class="pln">
    important</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">,</span><span class="pln">
    _id</span><span class="pun">:</span><span class="pln"> </span><span class="lit">221244</span><span class="pun">,</span><span class="pln">
    user</span><span class="pun">:</span><span class="pln"> </span><span class="lit">141414</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
</span><span class="pun">]</span></pre>

<p>
	لا تتطلب قاعدة بيانات المستند تخزين مفتاح خارجي في حقول الملاحظة، بل يمكن أن يُخزّن في تجمع المستخدمين كما يمكن أن يخزن في كلا التجمعين:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1027_11" style="">
<span class="pun">[</span><span class="pln">
  </span><span class="pun">{</span><span class="pln">
    username</span><span class="pun">:</span><span class="pln"> </span><span class="str">'mluukkai'</span><span class="pun">,</span><span class="pln">
    _id</span><span class="pun">:</span><span class="pln"> </span><span class="lit">123456</span><span class="pun">,</span><span class="pln">
    notes</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="lit">221212</span><span class="pun">,</span><span class="pln"> </span><span class="lit">221255</span><span class="pun">],</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="pun">{</span><span class="pln">
    username</span><span class="pun">:</span><span class="pln"> </span><span class="str">'hellas'</span><span class="pun">,</span><span class="pln">
    _id</span><span class="pun">:</span><span class="pln"> </span><span class="lit">141414</span><span class="pun">,</span><span class="pln">
    notes</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="lit">221244</span><span class="pun">],</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
</span><span class="pun">]</span></pre>

<p>
	طالما أن المستخدم قد ينشئ عدة ملاحظات، سنخزن معرفات هذه الملاحظات في مصفوفة ضمن الحقل <code>notes</code> في مورد المستخدم.
</p>

<p>
	تقدم قواعد بيانات المستندات أيضًا طريقة مختلفة جذريًا في تنظيم البيانات: فمن المجدي في بعض الحالات، أن نشعِّب مصفوفة الملاحظات التي أنشأها المستخدم بأكملها كجزء من المستند في تجمع المستخدمين:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1027_13" style="">
<span class="pun">[</span><span class="pln">
  </span><span class="pun">{</span><span class="pln">
    username</span><span class="pun">:</span><span class="pln"> </span><span class="str">'mluukkai'</span><span class="pun">,</span><span class="pln">
    _id</span><span class="pun">:</span><span class="pln"> </span><span class="lit">123456</span><span class="pun">,</span><span class="pln">
    notes</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
      </span><span class="pun">{</span><span class="pln">
        content</span><span class="pun">:</span><span class="pln"> </span><span class="str">'HTML is easy'</span><span class="pun">,</span><span class="pln">
        important</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">,</span><span class="pln">
      </span><span class="pun">},</span><span class="pln">
      </span><span class="pun">{</span><span class="pln">
        content</span><span class="pun">:</span><span class="pln"> </span><span class="str">'The most important operations of HTTP protocol are GET and POST'</span><span class="pun">,</span><span class="pln">
        important</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
      </span><span class="pun">},</span><span class="pln">
    </span><span class="pun">],</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="pun">{</span><span class="pln">
    username</span><span class="pun">:</span><span class="pln"> </span><span class="str">'hellas'</span><span class="pun">,</span><span class="pln">
    _id</span><span class="pun">:</span><span class="pln"> </span><span class="lit">141414</span><span class="pun">,</span><span class="pln">
    notes</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
      </span><span class="pun">{</span><span class="pln">
        content</span><span class="pun">:</span><span class="pln">
          </span><span class="str">'A proper dinosaur codes with Java'</span><span class="pun">,</span><span class="pln">
        important</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">,</span><span class="pln">
      </span><span class="pun">},</span><span class="pln">
    </span><span class="pun">],</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
</span><span class="pun">]</span></pre>

<p>
	في هذا التخطيط لقاعدة البيانات، ستُشعّب الملاحظات تحت المستخدم، ولن تولّد قاعدة البيانات حينها أية معرفات لها.
</p>

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

<p>
	وللمفارقة، ستتطلب قواعد البيانات التي لا تمتلك تخطيطًا مثل Mongoose من المطور أن يتخذ قرارات جذرية جدًا حول تنظيم البيانات منذ البداية خلافًا للقواعد العلاقيّة. حيث تقدم القواعد العلاقية طرقًا أكثر أو أقل في تنظيم البيانات لعدة تطبيقات
</p>

<h2>
	تخطيط Mongoose للمستخدمين
</h2>

<p>
	لقد قررنا في حالتنا أن نخزن معرفات الملاحظات التي ينشئها مستخدم في مستند المستخدم. لذا سنعرف نموذجًا يمثل المستخدم ونضعه في الملف user.js ضمن المجلد models:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1027_15" style="">
<span class="kwd">const</span><span class="pln"> mongoose </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'mongoose'</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> userSchema </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> mongoose</span><span class="pun">.</span><span class="typ">Schema</span><span class="pun">({</span><span class="pln">
  username</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</span><span class="pun">,</span><span class="pln">
  name</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</span><span class="pun">,</span><span class="pln">
  passwordHash</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</span><span class="pun">,</span><span class="pln">
  notes</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
      type</span><span class="pun">:</span><span class="pln"> mongoose</span><span class="pun">.</span><span class="typ">Schema</span><span class="pun">.</span><span class="typ">Types</span><span class="pun">.</span><span class="typ">ObjectId</span><span class="pun">,</span><span class="pln">
      ref</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Note'</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">],</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

userSchema</span><span class="pun">.</span><span class="kwd">set</span><span class="pun">(</span><span class="str">'toJSON'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  transform</span><span class="pun">:</span><span class="pln"> </span><span class="pun">(</span><span class="pln">document</span><span class="pun">,</span><span class="pln"> returnedObject</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    returnedObject</span><span class="pun">.</span><span class="pln">id </span><span class="pun">=</span><span class="pln"> returnedObject</span><span class="pun">.</span><span class="pln">_id</span><span class="pun">.</span><span class="pln">toString</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">delete</span><span class="pln"> returnedObject</span><span class="pun">.</span><span class="pln">_id
    </span><span class="kwd">delete</span><span class="pln"> returnedObject</span><span class="pun">.</span><span class="pln">__v
    </span><span class="com">// the passwordHash should not be revealed</span><span class="pln">
    </span><span class="kwd">delete</span><span class="pln"> returnedObject</span><span class="pun">.</span><span class="pln">passwordHash
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">User</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> mongoose</span><span class="pun">.</span><span class="pln">model</span><span class="pun">(</span><span class="str">'User'</span><span class="pun">,</span><span class="pln"> userSchema</span><span class="pun">)</span><span class="pln">

module</span><span class="pun">.</span><span class="pln">exports </span><span class="pun">=</span><span class="pln"> </span><span class="typ">User</span></pre>

<p>
	خُزّنت معرفات الملاحظات ضمن مستند المستخدم كمصفوفة معرفات Mongoose. وللتعريف الشكل التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1027_17" style="">
<span class="pun">{</span><span class="pln">
  type</span><span class="pun">:</span><span class="pln"> mongoose</span><span class="pun">.</span><span class="typ">Schema</span><span class="pun">.</span><span class="typ">Types</span><span class="pun">.</span><span class="typ">ObjectId</span><span class="pun">,</span><span class="pln">
  ref</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Note'</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	إن نمط الحقل الذي يرتبط مرجعيًا بالملاحظات هو ObjectId. لن تكون Mongo قادرة على تميز هذا الحقل على أنه مرجع للملاحظات، لأن العبارة متعلقة ومعرّفة بالمكتبة Mongoose.
</p>

<p>
	لنوسع تخطيط الملاحظة المعرّف في الملف notre.js ضمن المجلد model لكي تضم معلومات على المستخدم الذي أنشأها:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1027_19" style="">
<span class="kwd">const</span><span class="pln"> noteSchema </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> mongoose</span><span class="pun">.</span><span class="typ">Schema</span><span class="pun">({</span><span class="pln">
  content</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    type</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</span><span class="pun">,</span><span class="pln">
    required</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
    minlength</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  date</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">,</span><span class="pln">
  important</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Boolean</span><span class="pun">,</span><span class="pln">
  user</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">    
      type</span><span class="pun">:</span><span class="pln"> mongoose</span><span class="pun">.</span><span class="typ">Schema</span><span class="pun">.</span><span class="typ">Types</span><span class="pun">.</span><span class="typ">ObjectId</span><span class="pun">,</span><span class="pln">    
      ref</span><span class="pun">:</span><span class="pln"> </span><span class="str">'User'</span><span class="pln">  
  </span><span class="pun">}})</span></pre>

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

<h2>
	تسجيل مستخدمين جدد
</h2>

<p>
	لنضف مسارًا لإنشاء مستخدمين جدد. يمتلك المستخدم اسمًا واسمًا آخر لتسجيل الدخول (username) ومعلومة أخرى تدعى كلمة السر المرمّزة passwordHash. هذه الكلمة المرمزة هي نتاج <a data-ss1613482287="1" href="https://en.wikipedia.org/wiki/Cryptographic_hash_function" rel="external nofollow">دالة الترميز وحيدة الاتجاه</a> عندما نطبقها على كلمة السر. فمن غير الملائم أن تخزن كلمة السر كنص عادي في قاعدة البيانات.
</p>

<p>
	لنثبت الحزمة <a data-ss1613482287="1" href="https://github.com/kelektiv/node.bcrypt.js" rel="external nofollow">bcrypt</a> التي ستتولى أمر توليد كلمات السر المرمزة:
</p>

<pre class="ipsCode">
npm install bcrypt --save
</pre>

<p>
	سننشئ مستخدمين جدد بما يتوافق مع توجيهات RESTful التي ناقشناها في القسم 3، وذلك بإرسال طلب HTTP POST إلى عنوان الموقع users.
</p>

<p>
	لنعرف متحكمًا بالمسار يتعامل مع المستخدمين ولنضعه في ملف جديد باسم users.js ضمن المجلد controllers. سنجعله قابلًا للاستخدام بإدراجه في الملف app.js، حيث سيعالج الطلبات المرسلة إلى العنوان api/users/:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1027_21" style="">
<span class="kwd">const</span><span class="pln"> usersRouter </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'./controllers/users'</span><span class="pun">)</span><span class="pln">

</span><span class="com">// ...</span><span class="pln">

app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="str">'/api/users'</span><span class="pun">,</span><span class="pln"> usersRouter</span><span class="pun">)</span></pre>

<p>
	يحتوي الملف الذي يعرّف المتحكم بالمسار الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1027_23" style="">
<span class="kwd">const</span><span class="pln"> bcrypt </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'bcrypt'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> usersRouter </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'express'</span><span class="pun">).</span><span class="typ">Router</span><span class="pun">()</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">User</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'../models/user'</span><span class="pun">)</span><span class="pln">

usersRouter</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> body </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">body

  </span><span class="kwd">const</span><span class="pln"> saltRounds </span><span class="pun">=</span><span class="pln"> </span><span class="lit">10</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> passwordHash </span><span class="pun">=</span><span class="pln"> await bcrypt</span><span class="pun">.</span><span class="pln">hash</span><span class="pun">(</span><span class="pln">body</span><span class="pun">.</span><span class="pln">password</span><span class="pun">,</span><span class="pln"> saltRounds</span><span class="pun">)</span><span class="pln">

  </span><span class="kwd">const</span><span class="pln"> user </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">User</span><span class="pun">({</span><span class="pln">
    username</span><span class="pun">:</span><span class="pln"> body</span><span class="pun">.</span><span class="pln">username</span><span class="pun">,</span><span class="pln">
    name</span><span class="pun">:</span><span class="pln"> body</span><span class="pun">.</span><span class="pln">name</span><span class="pun">,</span><span class="pln">
    passwordHash</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">

  </span><span class="kwd">const</span><span class="pln"> savedUser </span><span class="pun">=</span><span class="pln"> await user</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">

  response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">savedUser</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

module</span><span class="pun">.</span><span class="pln">exports </span><span class="pun">=</span><span class="pln"> usersRouter</span></pre>

<p>
	لا تُخزَّن كلمة السر التي تُرسل ضمن الطلب في قاعدة البيانات، بل النسخة المعمّاة التي تولّدها الدالة <code>bcrypt.hash</code>. إن أساسيات <a data-ss1613482287="1" href="https://codahale.com/how-to-safely-store-a-password/" rel="external nofollow">تخزين كلمات المرور</a> في قواعد البيانات موضوع خارج نطاق منهاجنا، ولن نناقش ما الذي يعنيه إسناد الرقم السحري 10 إلى المتغيّر <a data-ss1613482287="1" href="https://github.com/kelektiv/node.bcrypt.js/#a-note-on-rounds" rel="external nofollow">saltRounds</a>، لكن يمكن الاطلاع على معلومات أكثر عبر الإنترنت.
</p>

<p>
	لا تحتوي الشيفرة الحالية على معالجات أخطاء أو معالجات تقييم، لتتحقق من كون اسم المستخدم وكلمة المرور بالشكل الصحيح.
</p>

<p>
	يجب أن تُختبر الميزة الجديدة يدويًا بشكل مبدئي باستخدام أدوات مثل Postman. لكن سرعان ما سيغدو الأمر مربكًا وخاصة عندما نضيف الوظيفة التي تجبر المستخدم على إدخال اسم مستخدم فريد. لذلك فكتابة الاختبارات الآلية لن يحتاج جهدًا كبيرًا، وسيجعل تطوير التطبيق أكثر سهولة.
</p>

<p>
	قد يبدو اختبارنا المبدئي كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1027_25" style="">
<span class="kwd">const</span><span class="pln"> bcrypt </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'bcrypt'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">User</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'../models/user'</span><span class="pun">)</span><span class="pln">

</span><span class="com">//...</span><span class="pln">

describe</span><span class="pun">(</span><span class="str">'when there is initially one user in db'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  beforeEach</span><span class="pun">(</span><span class="pln">async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    await </span><span class="typ">User</span><span class="pun">.</span><span class="pln">deleteMany</span><span class="pun">({})</span><span class="pln">

    </span><span class="kwd">const</span><span class="pln"> passwordHash </span><span class="pun">=</span><span class="pln"> await bcrypt</span><span class="pun">.</span><span class="pln">hash</span><span class="pun">(</span><span class="str">'sekret'</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> user </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">User</span><span class="pun">({</span><span class="pln"> username</span><span class="pun">:</span><span class="pln"> </span><span class="str">'root'</span><span class="pun">,</span><span class="pln"> passwordHash </span><span class="pun">})</span><span class="pln">

    await user</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">

  test</span><span class="pun">(</span><span class="str">'creation succeeds with a fresh username'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> usersAtStart </span><span class="pun">=</span><span class="pln"> await helper</span><span class="pun">.</span><span class="pln">usersInDb</span><span class="pun">()</span><span class="pln">

    </span><span class="kwd">const</span><span class="pln"> newUser </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      username</span><span class="pun">:</span><span class="pln"> </span><span class="str">'mluukkai'</span><span class="pun">,</span><span class="pln">
      name</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Matti Luukkainen'</span><span class="pun">,</span><span class="pln">
      password</span><span class="pun">:</span><span class="pln"> </span><span class="str">'salainen'</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    await api
      </span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/api/users'</span><span class="pun">)</span><span class="pln">
      </span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="pln">newUser</span><span class="pun">)</span><span class="pln">
      </span><span class="pun">.</span><span class="pln">expect</span><span class="pun">(</span><span class="lit">200</span><span class="pun">)</span><span class="pln">
      </span><span class="pun">.</span><span class="pln">expect</span><span class="pun">(</span><span class="str">'Content-Type'</span><span class="pun">,</span><span class="pln"> </span><span class="str">/application\/json/</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">const</span><span class="pln"> usersAtEnd </span><span class="pun">=</span><span class="pln"> await helper</span><span class="pun">.</span><span class="pln">usersInDb</span><span class="pun">()</span><span class="pln">
    expect</span><span class="pun">(</span><span class="pln">usersAtEnd</span><span class="pun">).</span><span class="pln">toHaveLength</span><span class="pun">(</span><span class="pln">usersAtStart</span><span class="pun">.</span><span class="pln">length </span><span class="pun">+</span><span class="pln"> </span><span class="lit">1</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">const</span><span class="pln"> usernames </span><span class="pun">=</span><span class="pln"> usersAtEnd</span><span class="pun">.</span><span class="pln">map</span><span class="pun">(</span><span class="pln">u </span><span class="pun">=&gt;</span><span class="pln"> u</span><span class="pun">.</span><span class="pln">username</span><span class="pun">)</span><span class="pln">
    expect</span><span class="pun">(</span><span class="pln">usernames</span><span class="pun">).</span><span class="pln">toContain</span><span class="pun">(</span><span class="pln">newUser</span><span class="pun">.</span><span class="pln">username</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	تستخدم الاختبارات الدالة المساعدة <code>()usersInDb</code> التي أضفناها إلى الملف test_helper.js الموجود في المجلد tests. ستساعدنا الدالة على التحقق من حالة قاعدة البيانات عند إنشاء مستخدم جديد:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1027_27" style="">
<span class="kwd">const</span><span class="pln"> </span><span class="typ">User</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'../models/user'</span><span class="pun">)</span><span class="pln">

</span><span class="com">// ...</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> usersInDb </span><span class="pun">=</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> users </span><span class="pun">=</span><span class="pln"> await </span><span class="typ">User</span><span class="pun">.</span><span class="pln">find</span><span class="pun">({})</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> users</span><span class="pun">.</span><span class="pln">map</span><span class="pun">(</span><span class="pln">u </span><span class="pun">=&gt;</span><span class="pln"> u</span><span class="pun">.</span><span class="pln">toJSON</span><span class="pun">())</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

module</span><span class="pun">.</span><span class="pln">exports </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  initialNotes</span><span class="pun">,</span><span class="pln">
  nonExistingId</span><span class="pun">,</span><span class="pln">
  notesInDb</span><span class="pun">,</span><span class="pln">
  usersInDb</span><span class="pun">,</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تُضيف الكتلة <code>beforeEach</code> مستخدمًا له اسم المستخدم إلى قاعدة البيانات. يمكننا كتابة اختبار جديد يتحقق من عدم تسجيل اسم مستخدم جديد موجود أصلًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1027_29" style="">
<span class="pln">describe</span><span class="pun">(</span><span class="str">'when there is initially one user in db'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="com">// ...</span><span class="pln">

  test</span><span class="pun">(</span><span class="str">'creation fails with proper statuscode and message if username already taken'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> usersAtStart </span><span class="pun">=</span><span class="pln"> await helper</span><span class="pun">.</span><span class="pln">usersInDb</span><span class="pun">()</span><span class="pln">

    </span><span class="kwd">const</span><span class="pln"> newUser </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      username</span><span class="pun">:</span><span class="pln"> </span><span class="str">'root'</span><span class="pun">,</span><span class="pln">
      name</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Superuser'</span><span class="pun">,</span><span class="pln">
      password</span><span class="pun">:</span><span class="pln"> </span><span class="str">'salainen'</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="kwd">const</span><span class="pln"> result </span><span class="pun">=</span><span class="pln"> await api
      </span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/api/users'</span><span class="pun">)</span><span class="pln">
      </span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="pln">newUser</span><span class="pun">)</span><span class="pln">
      </span><span class="pun">.</span><span class="pln">expect</span><span class="pun">(</span><span class="lit">400</span><span class="pun">)</span><span class="pln">
      </span><span class="pun">.</span><span class="pln">expect</span><span class="pun">(</span><span class="str">'Content-Type'</span><span class="pun">,</span><span class="pln"> </span><span class="str">/application\/json/</span><span class="pun">)</span><span class="pln">

    expect</span><span class="pun">(</span><span class="pln">result</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">error</span><span class="pun">).</span><span class="pln">toContain</span><span class="pun">(</span><span class="str">'`username` to be unique'</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">const</span><span class="pln"> usersAtEnd </span><span class="pun">=</span><span class="pln"> await helper</span><span class="pun">.</span><span class="pln">usersInDb</span><span class="pun">()</span><span class="pln">
    expect</span><span class="pun">(</span><span class="pln">usersAtEnd</span><span class="pun">).</span><span class="pln">toHaveLength</span><span class="pun">(</span><span class="pln">usersAtStart</span><span class="pun">.</span><span class="pln">length</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	لن ينجح الاختبار في هذه الحالة حاليًا.
</p>

<p>
	نتدرب من خلال الأمثلة السابقة على ما يسمى التطوير المقاد بالاختبارات <a data-ss1613482287="1" href="https://en.wikipedia.org/wiki/Test-driven_development" rel="external nofollow">test-driven development (TDD)</a>، حيث تُكتب الاختبارات للوظائف الجديدة قبل إضافتها إلى التطبيق.
</p>

<p>
	لنقيّم تفرّد اسم المستخدم بمساعدة مقيمات المكتبة Mongoose. لا تمتلك المكتبة كما أشرنا في التمرين 3.19 على مقيمات مدمجة معها للتحقق من وحدانية القيمة لحقل محدد. لكن يمكننا أن نجد حلًا جاهزًا في حزمة npm <a data-ss1613482287="1" href="https://www.npmjs.com/package/mongoose-unique-validator" rel="external nofollow">mongoose-unique-validator</a>. لنثبت هذه الحزمة إذًا.
</p>

<pre class="ipsCode">
npm install --save mongoose-unique-validator
</pre>

<p>
	كما ينبغي أن نجري التعديلات التالية على الملف user.js الموجود ضمن المجلد models:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1027_31" style="">
<span class="kwd">const</span><span class="pln"> mongoose </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'mongoose'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> uniqueValidator </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'mongoose-unique-validator'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> userSchema </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> mongoose</span><span class="pun">.</span><span class="typ">Schema</span><span class="pun">({</span><span class="pln">
  username</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    type</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</span><span class="pun">,</span><span class="pln">
    unique</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln">  </span><span class="pun">},</span><span class="pln">
  name</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</span><span class="pun">,</span><span class="pln">
  passwordHash</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</span><span class="pun">,</span><span class="pln">
  notes</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
      type</span><span class="pun">:</span><span class="pln"> mongoose</span><span class="pun">.</span><span class="typ">Schema</span><span class="pun">.</span><span class="typ">Types</span><span class="pun">.</span><span class="typ">ObjectId</span><span class="pun">,</span><span class="pln">
      ref</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Note'</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">],</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

userSchema</span><span class="pun">.</span><span class="pln">plugin</span><span class="pun">(</span><span class="pln">uniqueValidator</span><span class="pun">)</span><span class="pln">
</span><span class="com">// ...</span></pre>

<p>
	يمكننا إضافة العديد من الاختبارت لتقييم حالة المستخدم الجديد، كالتحقق من طول اسم المستخدم إن كان كافيًا، أو أنه يحوي على محارف غير مسموحة، أو أن كلمة السر قوية بما يكفي. لقد تركنا تلك الوظائف كتمرينات اختيارية.
</p>

<p>
	قبل أن نكمل طريقنا، سنضيف معالج مسار يعيد كل المستخدمين المسجلين في قاعدة البيانات:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1027_33" style="">
<span class="pln">usersRouter</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> users </span><span class="pun">=</span><span class="pln"> await </span><span class="typ">User</span><span class="pun">.</span><span class="pln">find</span><span class="pun">({})</span><span class="pln">
  response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">users</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	ستبدو القائمة كالتالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-ss1613482287="1" href="https://academy.hsoub.com/uploads/monthly_2021_02/user_list_02.png.81403e4bf7b51b6d41c45116b2019714.png" data-fileid="57779" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="57779" data-unique="af4ydskjb" src="https://academy.hsoub.com/uploads/monthly_2021_02/user_list_02.png.81403e4bf7b51b6d41c45116b2019714.png" alt="user_list_02.png"></a>
</p>

<p>
	يمكنك إيجاد شيفرة التطبيق بأكملها في الفرع part4-7 ضمن المستودع الخاص بالتطبيق على <a data-ss1613482287="1" href="https://github.com/fullstack-hy2020/part3-notes-backend/tree/part4-7" rel="external nofollow">GitHub</a>.
</p>

<h2>
	إنشاء ملاحظة جديدة
</h2>

<p>
	ينبغي أن نعدل شيفرة إنشاء ملاحظة جديدة لكي نربطها بالمستخدم الذي أنشأها.
</p>

<p>
	لنوسع الشيفرة بحيث تُرسل معلومات عن المستخدم الذي أنشأ الشيفرة ضمن الحقل <code>userId</code> من جسم الطلب:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1027_35" style="">
<span class="kwd">const</span><span class="pln"> </span><span class="typ">User</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'../models/user'</span><span class="pun">)</span><span class="pln">

</span><span class="com">//...</span><span class="pln">

notesRouter</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> body </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">body

  </span><span class="kwd">const</span><span class="pln"> user </span><span class="pun">=</span><span class="pln"> await </span><span class="typ">User</span><span class="pun">.</span><span class="pln">findById</span><span class="pun">(</span><span class="pln">body</span><span class="pun">.</span><span class="pln">userId</span><span class="pun">)</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> note </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Note</span><span class="pun">({</span><span class="pln">
    content</span><span class="pun">:</span><span class="pln"> body</span><span class="pun">.</span><span class="pln">content</span><span class="pun">,</span><span class="pln">
    important</span><span class="pun">:</span><span class="pln"> body</span><span class="pun">.</span><span class="pln">important </span><span class="pun">===</span><span class="pln"> </span><span class="kwd">undefined</span><span class="pln"> </span><span class="pun">?</span><span class="pln"> </span><span class="kwd">false</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> body</span><span class="pun">.</span><span class="pln">important</span><span class="pun">,</span><span class="pln">
    date</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">(),</span><span class="pln">
    user</span><span class="pun">:</span><span class="pln"> user</span><span class="pun">.</span><span class="pln">_id  </span><span class="pun">})</span><span class="pln">

  </span><span class="kwd">const</span><span class="pln"> savedNote </span><span class="pun">=</span><span class="pln"> await note</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">
  user</span><span class="pun">.</span><span class="pln">notes </span><span class="pun">=</span><span class="pln"> user</span><span class="pun">.</span><span class="pln">notes</span><span class="pun">.</span><span class="pln">concat</span><span class="pun">(</span><span class="pln">savedNote</span><span class="pun">.</span><span class="pln">_id</span><span class="pun">)</span><span class="pln">  await user</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">  
  response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">savedNote</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	لن يؤثر التغيير الذي حصل على الكائن <code>user</code> في شيء. وسيُخزَّن معرف الملاحظة في الحقل <code>notes</code>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1027_37" style="">
<span class="kwd">const</span><span class="pln"> user </span><span class="pun">=</span><span class="pln"> await </span><span class="typ">User</span><span class="pun">.</span><span class="pln">findById</span><span class="pun">(</span><span class="pln">body</span><span class="pun">.</span><span class="pln">userId</span><span class="pun">)</span><span class="pln">

</span><span class="com">// ...</span><span class="pln">

user</span><span class="pun">.</span><span class="pln">notes </span><span class="pun">=</span><span class="pln"> user</span><span class="pun">.</span><span class="pln">notes</span><span class="pun">.</span><span class="pln">concat</span><span class="pun">(</span><span class="pln">savedNote</span><span class="pun">.</span><span class="pln">_id</span><span class="pun">)</span><span class="pln">
await user</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span></pre>

<p>
	لنحاول إنشاء ملاحظة جديدة:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-ss1613482287="1" href="https://academy.hsoub.com/uploads/monthly_2021_02/new_note_03.png.833f071d770c21ddf3d59f659b4c25e1.png" data-fileid="57775" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="57775" data-unique="s2reqsn5o" src="https://academy.hsoub.com/uploads/monthly_2021_02/new_note_03.png.833f071d770c21ddf3d59f659b4c25e1.png" alt="new_note_03.png"></a>
</p>

<p>
	تبدو العملية ناجحة. لنضف ملاحظة أخرى ثم نحضر بيانات كل المستخدمين باستخدام المسار المخصص لذلك:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-ss1613482287="1" href="https://academy.hsoub.com/uploads/monthly_2021_02/second_note_04.png.d780adf601362db4be17c15b487c1d25.png" data-fileid="57778" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="57778" data-unique="qlhiudb2h" src="https://academy.hsoub.com/uploads/monthly_2021_02/second_note_04.png.d780adf601362db4be17c15b487c1d25.png" alt="second_note_04.png"></a>
</p>

<p>
	يمكن أن نرى أن المستخدم قد أنشأ ملاحظتين. كذلك الأمر يمكن رؤية معرفات المستخدمين الذين أنشأوا الملاحظات إذا ما أحضرنا كل الملاحظات باستخدام المسار المخصص لذلك.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-ss1613482287="1" href="https://academy.hsoub.com/uploads/monthly_2021_02/all_notes_05.png.0e00e88c3f9b6f54f329181c730a7ee3.png" data-fileid="57774" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="57774" data-unique="zjd6wz0t4" src="https://academy.hsoub.com/uploads/monthly_2021_02/all_notes_05.png.0e00e88c3f9b6f54f329181c730a7ee3.png" alt="all_notes_05.png"></a>
</p>

<h2>
	استخدام التابع Populate
</h2>

<p>
	نريد لواجهتنا البرمجية أن تعمل بطريقة محددة بحيث تضع محتويات الملاحظات التي أنشأها المستخدم داخل الكائن <code>user</code> عند إرسال طلب HTTP GET، وليس فقط المعرفات الفريدة الخاصة بها. يمكن إنجاز ذلك في قواعد البيانات العلاقيّة باستخدام الاستقصاء المشترك.
</p>

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

<p>
	تستخدم Mongoose التابع <a data-ss1613482287="1" href="http://mongoosejs.com/docs/populate.html" rel="external nofollow">populate</a> في ضم التجمعات. لنعدل المسار الذي يعيد كل المستخدمين أولًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1027_39" style="">
<span class="pln">usersRouter</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> users </span><span class="pun">=</span><span class="pln"> await </span><span class="typ">User</span><span class="pun">.</span><span class="pln">find</span><span class="pun">({}).</span><span class="pln">populate</span><span class="pun">(</span><span class="str">'notes'</span><span class="pun">)</span><span class="pln">
  
  response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">users</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	يظهر التابع <a data-ss1613482287="1" href="http://mongoosejs.com/docs/populate.html" rel="external nofollow">populate</a> في السلسة بعد أن ينفذ التابع <code>find</code> الاستقصاء. يحدد الوسيط الذي يُمرر إلى التابع أن معرفات كائنات الملاحظة في الحقل <code>notes</code> من المستند <code>user</code> ستُستبدل بمستند <code>note</code> المرتبط معه مرجعيًا.
</p>

<p>
	ستبدو النتيجة الآن كما نريد تقريبًا:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-ss1613482287="1" href="https://academy.hsoub.com/uploads/monthly_2021_02/populate_mongoose_06.png.9fc361252d665bc6abcffbccc1e29853.png" data-fileid="57777" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="57777" data-unique="v01h6qcn5" src="https://academy.hsoub.com/uploads/monthly_2021_02/populate_mongoose_06.png.9fc361252d665bc6abcffbccc1e29853.png" alt="populate_mongoose_06.png"></a>
</p>

<p>
	يمكن استخدام معامل التابع populate لاختيار الحقول التي نريد إضافتها من المستندات المختلفة، ويتم ذلك باستخدام <a data-ss1613482287="1" href="https://docs.mongodb.com/manual/tutorial/project-fields-from-query-results/#return-the-specified-fields-and-the-id-field-only" rel="external nofollow">تعبير</a> Mong التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1027_41" style="">
<span class="pln">usersRouter</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> users </span><span class="pun">=</span><span class="pln"> await </span><span class="typ">User</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">find</span><span class="pun">({}).</span><span class="pln">populate</span><span class="pun">(</span><span class="str">'notes'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> content</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> date</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="pun">})</span><span class="pln">

  response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">users</span><span class="pun">)</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	ستكون النتيجة الآن كما نريد تمامًا:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-ss1613482287="1" href="https://academy.hsoub.com/uploads/monthly_2021_02/populate_exact_res_07.png.29d0d142efda86342cbf06413e81ce63.png" data-fileid="57776" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="57776" data-unique="620evwf0f" src="https://academy.hsoub.com/uploads/monthly_2021_02/populate_exact_res_07.png.29d0d142efda86342cbf06413e81ce63.png" alt="populate_exact_res_07.png"></a>
</p>

<p>
	لننشر معلومات المستخدم ضمن الملاحظات بشكل ملائم:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1027_43" style="">
<span class="pln">notesRouter</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> notes </span><span class="pun">=</span><span class="pln"> await </span><span class="typ">Note</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">find</span><span class="pun">({}).</span><span class="pln">populate</span><span class="pun">(</span><span class="str">'user'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> username</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="pun">})</span><span class="pln">

  response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">notes</span><span class="pun">)</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	وهكذا ستُضاف معلومات المستخدم في الحقل <code>user</code> من كائن الملاحظة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-ss1613482287="1" href="https://academy.hsoub.com/uploads/monthly_2021_02/add_user_info_08.png.eeb4d610cc6fbf1c19f47c65e28f18f3.png" data-fileid="57773" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="57773" data-unique="c6o3jmhrx" src="https://academy.hsoub.com/uploads/monthly_2021_02/add_user_info_08.png.eeb4d610cc6fbf1c19f47c65e28f18f3.png" alt="add_user_info_08.png"></a>
</p>

<p>
	من المهم معرفة أن قاعدة البيانات لاتعلم أن المعرفات المخزنة في الحقل <code>user</code> من الملاحظات مرتبطة مرجعيًا مع تجمع المستخدمين.
</p>

<p>
	تعتمد وظيفة التابع populate على حقيقة أننا عرّفنا أنماطًا مرجعية في تخطيط Mongoose مزودة بالخيار <code>ref</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1027_45" style="">
<span class="kwd">const</span><span class="pln"> noteSchema </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> mongoose</span><span class="pun">.</span><span class="typ">Schema</span><span class="pun">({</span><span class="pln">
  content</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    type</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</span><span class="pun">,</span><span class="pln">
    required</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
    minlength</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  date</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">,</span><span class="pln">
  important</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Boolean</span><span class="pun">,</span><span class="pln">
  user</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    type</span><span class="pun">:</span><span class="pln"> mongoose</span><span class="pun">.</span><span class="typ">Schema</span><span class="pun">.</span><span class="typ">Types</span><span class="pun">.</span><span class="typ">ObjectId</span><span class="pun">,</span><span class="pln">
    ref</span><span class="pun">:</span><span class="pln"> </span><span class="str">'User'</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	يمكنك إيجاد شيفرة التطبيق بأكملها في الفرع part4-8 ضمن المستودع الخاص بالتطبيق على <a data-ss1613482287="1" href="https://github.com/fullstack-hy2020/part3-notes-backend/tree/part4-8" rel="external nofollow">GitHub</a>.
</p>

<p>
	ترجمة -وبتصرف- للفصل <a data-ss1613482287="1" href="https://fullstackopen.com/en/part4/user_administration" rel="external nofollow">User Administration</a> من سلسلة <a data-ss1613482287="1" href="https://fullstackopen.com/en/" rel="external nofollow">Deep Dive Into Modern Web Development</a>
</p>
]]></description><guid isPermaLink="false">1134</guid><pubDate>Tue, 16 Feb 2021 13:40:46 +0000</pubDate></item><item><title>&#x627;&#x62E;&#x62A;&#x628;&#x627;&#x631; &#x627;&#x644;&#x648;&#x627;&#x62C;&#x647;&#x629; &#x627;&#x644;&#x62E;&#x644;&#x641;&#x64A;&#x629; &#x644;&#x62A;&#x637;&#x628;&#x64A;&#x642;&#x627;&#x62A; Node.js &#x639;&#x628;&#x631; &#x645;&#x643;&#x62A;&#x628;&#x629; Jest</title><link>https://academy.hsoub.com/programming/javascript/nodejs/%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D8%AE%D9%84%D9%81%D9%8A%D8%A9-%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-nodejs-%D8%B9%D8%A8%D8%B1-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-jest-r1133/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2021_02/602bc5e73e5f8_--.png.dd1122a78535bea087bd9f63bfca0078.png" /></p>

<p>
	سنبدأ الآن بكتابة الاختبارات الخاصة بالواجهة الخلفية. وطالما أن منطق شيفرة الواجهة الخلفية ليس بهذا التعقيد، فلن يكون هناك معنى لكتابة <a data-ss1613481443="1" href="https://en.wikipedia.org/wiki/Unit_testing" rel="external nofollow">اختبارت لأجزاء الشيفرة</a>. لكن الشيء الوحيد الذي يمكن أن نجري عليه اختبار الأجزاء، هو التابع <code>toJSON</code> الذي يستخدم لتنسيق الملاحظات.
</p>

<p>
	قد يكون مفيدًا في بعض الأحيان، أن نختبر الواجهة الخلفية بأن نحاكي قاعدة البيانات بدلًا من استخدام القاعدة الأصلية، ويمكن الاستعانة بالمكتبة <a data-ss1613481443="1" href="https://github.com/williamkapke/mongo-mock" rel="external nofollow">mongo-mock</a> في ذلك.
</p>

<p>
	وطالما أن تطبيقنا لا يزال سهلًا نوعًا ما، سنختبر التطبيق بالكامل عبر واجهته البرمجية REST، وبالتالي سنضمن اختباره مع قاعدة بياناته. يسمى هذا النوع من الاختبارات الذي نجريه على عدة مكونات كمجموعة متكاملة اسم <a data-ss1613481443="1" href="https://en.wikipedia.org/wiki/Integration_testing" rel="external nofollow">اختبار التكامل</a> (integration testing)
</p>

<h2>
	بيئة الاختبار
</h2>

<p>
	أشرنا في أحد الفصول السابقة إلى أن خادم الواجهة الخلفية سيكون في وضع الإنتاج production mode عندما يعمل على Heroku. وتقتضي تقاليد Node تعريف وضع التطبيق عند تنفيذه ضمن متحول البيئة <code>NODE_ENV</code>.
</p>

<p>
	نستعمل في تطبيقنا متحولات البيئة المعرفة ضمن الملف env. فقط إن لم يكن التطبيق في وضع الإنتاج. وقد جرت العادة على تعريف أوضاع التطبيق بشكل منفصل في حالتي الاختبار والإنتاج.
</p>

<p>
	عدلنا سكربت package.json لكي يأخذ متحول البيئة <code>NODE_ENV</code> القيمة test عندما نجري الاختبارات:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_7" style="">
<span class="pun">{</span><span class="pln">
  </span><span class="com">// ...</span><span class="pln">
  </span><span class="str">"scripts"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">"start"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"NODE_ENV=production node index.js"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"dev"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"NODE_ENV=development nodemon index.js"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"build:ui"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"rm -rf build &amp;&amp; cd ../../../2/luento/notes &amp;&amp; npm run build &amp;&amp; cp -r build ../../../3/luento/notes-backend"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"deploy"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"git push heroku master"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"deploy:full"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"npm run build:ui &amp;&amp; git add . &amp;&amp; git commit -m uibuild &amp;&amp; git push &amp;&amp; npm run deploy"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"logs:prod"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"heroku logs --tail"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"lint"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"eslint ."</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"test"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"NODE_ENV=test jest --verbose --runInBand"</span><span class="pln">  </span><span class="pun">},</span><span class="pln">
  </span><span class="com">// ...</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	كما أضفنا الخيار <a data-ss1613481443="1" href="https://jestjs.io/docs/en/cli.html#--runinband" rel="external nofollow">runInBand</a> إلى سكربت npm، حيث يمنع هذا الخيار مكتبة Jest من تنفيذ الاختبارات على التوازي. وسنناقش أهمية هذا الخيار عندما نبدأ الاختبارات مستخدمين قاعدة البيانات.
</p>

<p>
	حددنا وضع التطبيق في سكربت npm run dev ليكون في وضع التطوير development، بحيث يمكن استخدام المكتبة nodemon. بينما حددنا وضع التطبيق ليكون في وضع الإنتاج production في سكربت npm start والذي يحدد وضع التطبيق عند التشغيل الافتراضي.
</p>

<p>
	سنجد أن هناك مشكلة صغيرة في تعريفنا لوضع التطبيق بهذا الشكل، فلن يعمل التطبيق على Windows. ولحل هذه المشكلة علينا تثبيت حزمة <a data-ss1613481443="1" href="https://www.npmjs.com/package/cross-env" rel="external nofollow">cross-env</a> بتنفيذ الأمر التالي:
</p>

<pre class="ipsCode">
npm install cross-env
</pre>

<p>
	يمكننا إنجاز العمل بشكل متوافق مع منصات التشغيل المختلفة باستخدام المكتبة cross-env، حيث سنعدل في سكربت npm ضمن الملف package.json ليصبح على الشكل:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_9" style="">
<span class="pun">{</span><span class="pln">
  </span><span class="com">// ...</span><span class="pln">
  </span><span class="str">"scripts"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">"start"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"cross-env NODE_ENV=production node index.js"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"dev"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"cross-env NODE_ENV=development nodemon index.js"</span><span class="pun">,</span><span class="pln">
    </span><span class="com">// ...</span><span class="pln">
    </span><span class="str">"test"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"cross-env NODE_ENV=test jest --verbose --runInBand"</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="com">// ...</span><span class="pln">
</span><span class="pun">}</span></pre>

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

<p>
	على الرغم من إمكانية إنشاء قاعدة بيانات للاختبارات على MongoDB Atlas، إلا أن ذلك لن يكون حلًا جيدًا، لأن العديد من المطورين سيعملون على نفس التطبيق وبالتالي قد تظهر المشاكل. فمن المفترض ألا نجري عدة اختبارات في الوقت ذاته مستخدمين قاعدة البيانات ذاتها. ومن الأفضل إنجاز الاختبارات باستخدام قاعدة بيانات مثبتة على جهاز المطور وتعمل عليه. وبالتالي سيكون الخيار الأنسب استخدام قاعدة بيانات منفصلة لكل اختبار نجريه، وسيكون هذا الأمر بسيطًا نوعًا ما عندما نستخدم إحدى الحاويتين <a data-ss1613481443="1" href="https://docs.mongodb.com/manual/core/inmemory/" rel="external nofollow">running Mongo in-memory</a> أو <a data-ss1613481443="1" href="https://academy.hsoub.com/devops/cloud-computing/docker/" rel="">Docker</a>. لن نعقد الأمور أكثر بل سنستمر باستخدام قاعدة البيانات على MongoDB Atlas.
</p>

<p>
	لنجري بعض التعديلات على الوحدة التي تعرف إعدادات التهيئة لتطبيقنا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_14" style="">
<span class="pln">require</span><span class="pun">(</span><span class="str">'dotenv'</span><span class="pun">).</span><span class="pln">config</span><span class="pun">()</span><span class="pln">

let PORT </span><span class="pun">=</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">PORT
let MONGODB_URI </span><span class="pun">=</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">MONGODB_URI

</span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">NODE_ENV </span><span class="pun">===</span><span class="pln"> </span><span class="str">'test'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">  MONGODB_URI </span><span class="pun">=</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">TEST_MONGODB_URI</span><span class="pun">}</span><span class="pln">
module</span><span class="pun">.</span><span class="pln">exports </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  MONGODB_URI</span><span class="pun">,</span><span class="pln">
  PORT
</span><span class="pun">}</span></pre>

<p>
	يضم الملف env. متحولات منفصلة لتعريف عناوين قواعد البيانات لأغراض الاختبار والتطوير:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_16" style="">
<span class="pln">MONGODB_URI</span><span class="pun">=</span><span class="pln">mongodb</span><span class="pun">+</span><span class="pln">srv</span><span class="pun">:</span><span class="com">//fullstack:secred@cluster0-ostce.mongodb.net/note-app?retryWrites=true</span><span class="pln">
PORT</span><span class="pun">=</span><span class="lit">3001</span><span class="pln">

TEST_MONGODB_URI</span><span class="pun">=</span><span class="pln">mongodb</span><span class="pun">+</span><span class="pln">srv</span><span class="pun">:</span><span class="com">//fullstack:secret@cluster0-ostce.mongodb.net/note-app-test?retryWrites=true</span></pre>

<p>
	تشابه الوحدة config التي أضفناها إلى التطبيق الحزمة <a data-ss1613481443="1" href="https://github.com/lorenwest/node-config" rel="external nofollow">node-config</a> قليلًا. لكن إضافة إعداداتنا الخاصة إلى التطبيق له مبرراته طالما أن التطبيق بسيط وأنه مصمم خصيصًا لتعلم نقاط هامة.
</p>

<p>
	وهكذا نكون قد أجرينا كل التعديلات المطلوبة على تطبيقنا. يمكنك إيجاد شيفرة التطبيق الحالي بالكامل في الفرع part4-2 ضمن <a data-ss1613481443="1" href="https://github.com/fullstack-hy2020/part3-notes-backend/tree/part4-2" rel="external nofollow">المخزن المخصص على GitHub</a>.
</p>

<h2>
	استخدام المكتبة supertest
</h2>

<p>
	سنستخدم الحزمة <a data-ss1613481443="1" href="https://github.com/visionmedia/supertest" rel="external nofollow">supertest</a> التي ستساعدنا على إجراء الاختبارات على واجهة تطبيقنا البرمجية، حيث سنثبت الحزمة كاعتمادية تطوير كالتالي:
</p>

<pre class="ipsCode">
npm install --save-dev supertest
</pre>

<p>
	لنكتب أول اختباراتنا في الملف note_api.test.js الذي سننشئه في المجلد tests:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_18" style="">
<span class="kwd">const</span><span class="pln"> mongoose </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'mongoose'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> supertest </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'supertest'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> app </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'../app'</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> api </span><span class="pun">=</span><span class="pln"> supertest</span><span class="pun">(</span><span class="pln">app</span><span class="pun">)</span><span class="pln">

test</span><span class="pun">(</span><span class="str">'notes are returned as json'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  await api
    </span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">expect</span><span class="pun">(</span><span class="lit">200</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">expect</span><span class="pun">(</span><span class="str">'Content-Type'</span><span class="pun">,</span><span class="pln"> </span><span class="str">/application\/json/</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

afterAll</span><span class="pun">(()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  mongoose</span><span class="pun">.</span><span class="pln">connection</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	تُدرج شيفرة الاختبار تطبيق Express من الوحدة app.js وتغلفها داخل الدالة <code>supertest</code> لنحصل على كائن جديد يدعى <a data-ss1613481443="1" href="https://github.com/visionmedia/superagent" rel="external nofollow">superagent</a>. يُسند هذا الكائن إلى المتغير <code>api</code> حيث يستخدمه التطبيق لإرسال طلبات HTTP إلى الواجهة الخلفية.
</p>

<p>
	يرسل تطبيقنا طلب HTTP-GET إلى عنوان الموقع api/notes ويتحقق من استجابة الخادم عندما يعيد رمز الحالة (200). كما يتحقق الاختبار من أن قيمة ترويسة "نوع-المحتوى" هي application/json، وبالتالي ستكون البيانات بالتنسيق الصحيح. ويضم التطبيق أيضًا بعض التفاصيل الأخرى التي سنناقشها بعد قليل.
</p>

<p>
	تُسبق الدالة السهمية التي تعرف الاختبار بالتعليمة <code>async</code>، وكذلك يُسبق التابع الذي يستدعي الكائن api بالتعليمة <code>await</code>. سنعود إلى هذا الموضوع بعد أن ننفذ عدة اختبارات أولًا. لا تُعر الموضوع اهتمامًا حاليًا، وعليك فقط أن تتأكد بأن الاختبار يجري بنجاح. فالعبارتين awiat/async في واقع الأمر مرتبطتان بعدم تزامن طلبات HTTP إلى الواجهة البرمجية حيث تستخدم <a data-ss1613481443="1" href="https://facebook.github.io/jest/docs/en/asynchronous.html" rel="external nofollow">Async/await</a> لكتابة شيفرة غير متزامنة في التنفيذ، لكنها توحي بالتزامن.
</p>

<p>
	يجب إنهاء الاتصال الذي تستخدمه Mongoose بقاعدة البيانات بمجرد إنتهاء الاختبارات (هناك اختبار واحد حاليًا). تُنجز هذه المهمة باستخدام التابع <a data-ss1613481443="1" href="https://facebook.github.io/jest/docs/en/api.html#afterallfn-timeout" rel="external nofollow">afterAll</a>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_20" style="">
<span class="pln">afterAll</span><span class="pun">(()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  mongoose</span><span class="pun">.</span><span class="pln">connection</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	من الممكن أن تواجه التحذير التالي على الطرفية عندما تنفذ الاختبارات:
</p>

<p style="text-align: center;">
	<img class="ipsImage ipsImage_thumbnailed" data-fileid="57772" data-unique="a5t9e2gqv" src="https://academy.hsoub.com/uploads/monthly_2021_02/test_warn_01.png.c18c6b4fd32f17cf3a0a37ef24617d4d.png" alt="test_warn_01.png"></p>

<p>
	إن حدث هذا، علينا اتباع <a data-ss1613481443="1" href="https://mongoosejs.com/docs/jest.html" rel="external nofollow">التعليمات</a> وإضافة الملف jest.config.js عند جذر المشروع وبداخله الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_25" style="">
<span class="pln">module</span><span class="pun">.</span><span class="pln">exports </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  testEnvironment</span><span class="pun">:</span><span class="pln"> </span><span class="str">'node'</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يجدر بك الانتباه إلى تفصيل صغير ومهم، فلقد وضعنا تطبيق Express ضمن الملف app.js في <a data-ss1613481443="1" href="https://fullstackopen.com/en/part4/structure_of_backend_application_introduction_to_testing#project-structure" rel="external nofollow">بداية</a> هذا القسم، وبقي للملف index.js وظيفة تشغيل التطبيق على المنفذ المحدد باستخدام كائن http المدمج في Node:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_27" style="">
<span class="kwd">const</span><span class="pln"> app </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'./app'</span><span class="pun">)</span><span class="pln"> </span><span class="com">// the actual Express app</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> http </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'http'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> config </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'./utils/config'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> logger </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'./utils/logger'</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> server </span><span class="pun">=</span><span class="pln"> http</span><span class="pun">.</span><span class="pln">createServer</span><span class="pun">(</span><span class="pln">app</span><span class="pun">)</span><span class="pln">

server</span><span class="pun">.</span><span class="pln">listen</span><span class="pun">(</span><span class="pln">config</span><span class="pun">.</span><span class="pln">PORT</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  logger</span><span class="pun">.</span><span class="pln">info</span><span class="pun">(`</span><span class="typ">Server</span><span class="pln"> running on port $</span><span class="pun">{</span><span class="pln">config</span><span class="pun">.</span><span class="pln">PORT</span><span class="pun">}`)</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	تستخدم الاختبارات التي نجريها تطبيق express المعرّف في الملف app.js:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_29" style="">
<span class="kwd">const</span><span class="pln"> mongoose </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'mongoose'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> supertest </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'supertest'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> app </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'../app'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> api </span><span class="pun">=</span><span class="pln"> supertest</span><span class="pun">(</span><span class="pln">app</span><span class="pun">)</span><span class="pln">
</span><span class="com">// ...</span></pre>

<p>
	سنجد في توثيق supertest مايلي:
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		إن لم يكن الخادم منصتًا إلى الاتصالات، فسيكون مرتبطًا بمنفذ بك وبالتالي لا حاجة لتعقب المنافذ.
	</p>
</blockquote>

<p>
	بمعنًى آخر: ستهتم المكتبة بتشغيل التطبيق الذي تختبره على المنفذ الذي يستخدمه داخليًا.
</p>

<p>
	لنكتب عدة اختبارات جديدة:
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		*if the server is not already listening for connections then it is bound to an ephemeral port for you so there is no need to keep track of ports.
	</p>
</blockquote>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_31" style="">
<span class="pln">test</span><span class="pun">(</span><span class="str">'there are two notes'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> response </span><span class="pun">=</span><span class="pln"> await api</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">)</span><span class="pln">

  expect</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">body</span><span class="pun">).</span><span class="pln">toHaveLength</span><span class="pun">(</span><span class="lit">2</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

test</span><span class="pun">(</span><span class="str">'the first note is about HTTP methods'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> response </span><span class="pun">=</span><span class="pln"> await api</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">)</span><span class="pln">

  expect</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">body</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">content</span><span class="pun">).</span><span class="pln">toBe</span><span class="pun">(</span><span class="str">'HTML is easy'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	يخزن كلا الاختبارين الاستجابة ضمن المتغير <code>response</code>، وعلى خلاف الاختبار السابق الذي يستخدم توابع المكتبة supertest للتحقق من رمز الحالة الذي يعيده الخادم، سيتحقق الاختباران من البيانات المخزنة ضمن الخاصية <code>response.body</code>. حيث سيتحققا من تنسيق ومحتوى البيانات في الاستجابة باستخدام التابع <a data-ss1613481443="1" href="https://facebook.github.io/jest/docs/en/expect.html#content" rel="external nofollow">expect</a> العائد للمكتبة Jest.
</p>

<p>
	ستظهر لك الآن فائدة استخدام await/async. فعادة ما كنا نستخدم دوال الاستدعاء للوصول إلى البيانات التي تعيدها الوعود، لكن بهذه الطريقة ستغدو الأمور مريحةً أكثر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_33" style="">
<span class="kwd">const</span><span class="pln"> res </span><span class="pun">=</span><span class="pln"> await api</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">)</span><span class="pln">
</span><span class="com">//http سيكون تنفيذ الشيفرة هنا بعد اكتمال طلب </span><span class="pln">
</span><span class="com">//res تُحفظ نتيجة تنفيذ الطلب في المتغير </span><span class="pln">
expect</span><span class="pun">(</span><span class="pln">res</span><span class="pun">.</span><span class="pln">body</span><span class="pun">).</span><span class="pln">toHaveLength</span><span class="pun">(</span><span class="lit">2</span><span class="pun">)</span></pre>

<p>
	ستحجب الأداة الوسيطية المسؤولة عن إظهار معلومات طلب HTTP، المعلومات التي سيظهرها الاختبار، لذا سنعدل الأداة الوسطية logger بحيث لا تطبع أية معلومات على الطرفية أثناء الاختبار:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_35" style="">
<span class="kwd">const</span><span class="pln"> info </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(...</span><span class="pln">params</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">NODE_ENV </span><span class="pun">!==</span><span class="pln"> </span><span class="str">'test'</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">{</span><span class="pln">
      console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(...</span><span class="pln">params</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">}}</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> error </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(...</span><span class="pln">params</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">error</span><span class="pun">(...</span><span class="pln">params</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

module</span><span class="pun">.</span><span class="pln">exports </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  info</span><span class="pun">,</span><span class="pln"> error
</span><span class="pun">}</span></pre>

<h2>
	إعادة ضبط قاعدة البيانات قبل الاختبارات
</h2>

<p>
	تبدو الاختبارات حتى هذه اللحظة سهلة وناجحة. لكنها اختبارت سيئة كونها تعتمد على حالة قاعدة البيانات. ولكي نجعل اختباراتنا أكثر <strong>واقعية</strong>، علينا إعادة ضبط قاعدة البيانات وتوليد بيانات الاختبار المطلوب بطريقة مضبوطة قبل أن نبدأ الاختبار.
</p>

<p>
	تستخدم اختباراتنا الدالة <a data-ss1613481443="1" href="https://facebook.github.io/jest/docs/en/api.html#afterallfn-timeout" rel="external nofollow">afterAll</a> من المكتبة Jest لإنهاء الاتصال مع قاعدة البيانات بعد أن ينتهي تنفيذ الاختبار. لكن هناك العديد من <a data-ss1613481443="1" href="https://facebook.github.io/jest/docs/en/setup-teardown.html#content" rel="external nofollow">الدوال</a> التي تقدمها Jest والتي يمكنها تنفيذ العديد من المهام قبل أن يُنفذ أي اختبار أو قبل كل مرة يُنفذ فيها الاختبار. سنعيد ضبط قاعدة البيانات قبل كل اختبار مستخدمين الدالة <a data-ss1613481443="1" href="https://jestjs.io/docs/en/api.html#aftereachfn-timeout" rel="external nofollow">beforeEach</a>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_37" style="">
<span class="kwd">const</span><span class="pln"> mongoose </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'mongoose'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> supertest </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'supertest'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> app </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'../app'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> api </span><span class="pun">=</span><span class="pln"> supertest</span><span class="pun">(</span><span class="pln">app</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">Note</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'../models/note'</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> initialNotes </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
  </span><span class="pun">{</span><span class="pln">
    content</span><span class="pun">:</span><span class="pln"> </span><span class="str">'HTML is easy'</span><span class="pun">,</span><span class="pln">
    date</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">(),</span><span class="pln">
    important</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="pun">{</span><span class="pln">
    content</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Browser can execute only Javascript'</span><span class="pun">,</span><span class="pln">
    date</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">(),</span><span class="pln">
    important</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
</span><span class="pun">]</span><span class="pln">

beforeEach</span><span class="pun">(</span><span class="pln">async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  await </span><span class="typ">Note</span><span class="pun">.</span><span class="pln">deleteMany</span><span class="pun">({})</span><span class="pln">

  let noteObject </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Note</span><span class="pun">(</span><span class="pln">initialNotes</span><span class="pun">[</span><span class="lit">0</span><span class="pun">])</span><span class="pln">
  await noteObject</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">

  noteObject </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Note</span><span class="pun">(</span><span class="pln">initialNotes</span><span class="pun">[</span><span class="lit">1</span><span class="pun">])</span><span class="pln">
  await noteObject</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	تُمحى في البداية البيانات الموجودة في قاعدة البيانات، ثم تُحفظ الملاحظتين المُخزنتين في المصفوفة <code>initialNotes</code> فيها. نضمن عند قيامنا بذلك، بقاء حالة قاعدة البيانات كما هي قبل كل اختبار.
</p>

<p>
	سنجري بعض التعديلات على الاختبارين الأخيرين:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_40" style="">
<span class="pln">test</span><span class="pun">(</span><span class="str">'all notes are returned'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> response </span><span class="pun">=</span><span class="pln"> await api</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">)</span><span class="pln">

  expect</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">body</span><span class="pun">).</span><span class="pln">toHaveLength</span><span class="pun">(</span><span class="pln">initialNotes</span><span class="pun">.</span><span class="pln">length</span><span class="pun">)})</span><span class="pln">

test</span><span class="pun">(</span><span class="str">'a specific note is within the returned notes'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> response </span><span class="pun">=</span><span class="pln"> await api</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">)</span><span class="pln">

  </span><span class="kwd">const</span><span class="pln"> contents </span><span class="pun">=</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">map</span><span class="pun">(</span><span class="pln">r </span><span class="pun">=&gt;</span><span class="pln"> r</span><span class="pun">.</span><span class="pln">content</span><span class="pun">)</span><span class="pln">
  expect</span><span class="pun">(</span><span class="pln">contents</span><span class="pun">).</span><span class="pln">toContain</span><span class="pun">(</span><span class="pln">
    </span><span class="str">'Browser can execute only Javascript'</span><span class="pln">  </span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	انتبه جيدًا للتعليمة <code>expect</code> في الاختبار الأخير. ينشئ الأمر <code>(response.body.map(r =&gt; r.content</code> مصفوفة تضم محتوى كل ملاحظة تعيدها الواجهة البرمجية. بينما يتحقق التابع <a data-ss1613481443="1" href="https://facebook.github.io/jest/docs/en/expect.html#tocontainitem" rel="external nofollow">toContain</a> من وجود الملاحظة التي تُمرر إليه كوسيط، ضمن قائمة الملاحظات التي تعيدها الواجهة البرمجية.
</p>

<h2>
	إجراء الاختبارات واحدًا تلو الآخر
</h2>

<p>
	يُنفٍّذ الأمر <code>npm test</code> كل اختبارات التطبيق. لكن من الأفضل عند كتابة الاختبارات أن ننفذ كل اختبار أو اختبارين معًا. تقدم Jest طرقًا عدة لإنجاز الأمر. من هذه الطرق استخدام التابع <a data-ss1613481443="1" href="https://jestjs.io/docs/en/api#testonlyname-fn-timeout" rel="external nofollow">only</a>، لكنها ليست فعالة عندما يُكتب الاختبار ليُنفَّذ عبر ملفات عدة. أما الطريقة الأفضل فهي تحديد الاختبارات التي نريد تنفيذها كمعاملات للأمر <code>npm test</code>. فالأمر التالي على سبيل المثال ينفذ فقط الاختبارات الموجودة في الملف note_api.test.js ضمن المجلد tests:
</p>

<pre class="ipsCode">
npm test -- tests/note_api.test.js
</pre>

<p>
	نستخدم الخيار <code>t-</code> لتنفيذ اختبار يحمل اسمًا محددًا:
</p>

<pre class="ipsCode">
npm test -- -t 'a specific note is within the returned notes'
</pre>

<p>
	يمكن للمعامل الذي نمرره، أن يشير إلى اسم الاختبار أو إلى كتلة الوصف describe block. كما يمكن أن يحمل المعامل جزءًا من الاسم فقط. سينفذ الأمر التالي،على سبيل المثال، كل الاختبارات التي تحتوي أسماؤها على الكلمة notes:
</p>

<pre class="ipsCode">
npm test -- -t 'notes'
</pre>

<p>
	<strong>ملاحظة</strong>: يمكن أن يبقى اتصال Mongoose مفتوحًا عندما ننفذ اختبارًا واحدًا، إن لم تكن هناك اختبارات قيد التنفيذ تستعمل الاتصال. قد تكون المشكلة أن supertest ستجهز الاتصال، لكن Jest لا تنفذ شيفرة الدالة <code>afterAll</code>.
</p>

<h2>
	التعليمتين async/await
</h2>

<p>
	قبل أن نستأنف كتابة المزيد من الاختبارات، لنلق نظرة على التعليميتن await وasync.
</p>

<p>
	قُدٍّمت التعليمتان لأول مرة في الإصدار ES7، حيث مكّنتا من استخدام "الدوال غير المتزامنة التي تعيد وعدًا" بطريقة تبدو وكأنها متزامنة. وكمثال على ما ذكرناه، سيبدو إحضار الملاحظات من قاعدة البيانات باستخدام الوعود كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_42" style="">
<span class="typ">Note</span><span class="pun">.</span><span class="pln">find</span><span class="pun">({}).</span><span class="pln">then</span><span class="pun">(</span><span class="pln">notes </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'operation returned the following notes'</span><span class="pun">,</span><span class="pln"> notes</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	يعيد التابع <code>()Note.find</code> وعدًا يمكن الوصول إلى نتيجته بالتصريح عن دالة استدعاء كوسيط للتابع <code>then</code>. وسنجد كل الشيفرة التي يجب تنفيذها حالما تنتهي العملية ضمن دالة الاستدعاء. فلو أردنا أن نجري عدة استدعاءات غير متزامنة للدالة بشكل متسلسل، ستسوء الأمور. لأن الطلبات غير المتزامنة يجب أن تتم ضمن دوال استدعاء، وهذا ما سيعقد الشيفرة وقد يقود إلى ما يسمى <a data-ss1613481443="1" href="http://callbackhell.com" rel="external nofollow">جحيم الاستدعاءات</a> (callbak hell).
</p>

<p>
	يمكن أن نبقي الأمور تحت السيطرة نوعًا ما باستخدام <a data-ss1613481443="1" href="https://academy.hsoub.com/programming/javascript/%D8%B3%D9%84%D8%B3%D9%84%D8%A9-%D8%A7%D9%84%D9%88%D8%B9%D9%88%D8%AF-promises-chaining-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r916/" rel="">سلاسل الوعود</a>، وبالتالي نتحاشى "جحيم الاستدعاءات" بإنشاء سلسلة واضحة من التوابع <code>then</code>. ولقد صادفنا ذلك مراتٍ عدة في منهاجنا. لتوضيح ذلك، سنقدم مثالًا افتراضيًا عن دالة تحضر جميع الملاحظات وتحذف الأولى:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_44" style="">
<span class="typ">Note</span><span class="pun">.</span><span class="pln">find</span><span class="pun">({})</span><span class="pln">
  </span><span class="pun">.</span><span class="pln">then</span><span class="pun">(</span><span class="pln">notes </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> notes</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">remove</span><span class="pun">()</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">

  </span><span class="pun">.</span><span class="pln">then</span><span class="pun">(</span><span class="pln">response </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'the first note is removed'</span><span class="pun">)</span><span class="pln">
    </span><span class="com">// المزيد من الشيفرة هنا</span><span class="pln">
  </span><span class="pun">})</span></pre>

<p>
	لا بأس باستخدام سلسلة التابع <code>then</code>، لكن هناك طريقة أفضل. تؤمن <a data-ss1613481443="1" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator" rel="external nofollow">التوابع المولٍّدة</a> (genertaor functions) التي قُدّمت في الإصدار ES6، <a data-ss1613481443="1" href="https://github.com/getify/You-Dont-Know-JS/blob/1st-ed/async%20%26%20performance/ch4.md#iterating-generators-asynchronously" rel="external nofollow">طريقة ذكية</a> في كتابة الشيفرة غير المتزامنة بطريقة تجعلها "وكأنها متزامنة". لكنها طريقة غريبة ولم تستخدم بشكل واسع.
</p>

<p>
	تقدم التعليمتان await/async اللتان ظهرتا للوجود في الإصدار ES7، الوظائف نفسها التي تقدمها المولدات، لكن بطريقة واضحة ومنهجية ومفهومة بالنسبة لكل مستخدمي JavaScript. يمكن إحضار الملاحظات من قاعدة البيانات باستخدام <a data-ss1613481443="1" href="https://wiki.hsoub.com/JavaScript/await" rel="external">await</a> كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_46" style="">
<span class="kwd">const</span><span class="pln"> notes </span><span class="pun">=</span><span class="pln"> await </span><span class="typ">Note</span><span class="pun">.</span><span class="pln">find</span><span class="pun">({})</span><span class="pln">

console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'operation returned the following notes'</span><span class="pun">,</span><span class="pln"> notes</span><span class="pun">)</span></pre>

<p>
	تبدو الشيفرة وكأنها متزامنة. يتوقف التنفيذ مؤقتًا عند العبارة <code>({})const notes= await Note.find</code> حتى يتحقق الوعد، ثم ينتقل التنفيذ بعدها إلى السطر التالي. تُسند نتيجة العملية التي تعيد الوعد بعد متابعة التنفيذ إلى المتغير <code>notes</code>.
</p>

<p>
	يمكن أن ننفذ المثال الذي طرحناه سابقًا (والذي يحمل قليلًا من التعقيد) مستخدمين await كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_51" style="">
<span class="kwd">const</span><span class="pln"> notes </span><span class="pun">=</span><span class="pln"> await </span><span class="typ">Note</span><span class="pun">.</span><span class="pln">find</span><span class="pun">({})</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> response </span><span class="pun">=</span><span class="pln"> await notes</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">remove</span><span class="pun">()</span><span class="pln">

console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'the first note is removed'</span><span class="pun">)</span></pre>

<p>
	لقد غدا الأمر أسهل وأوضح من طريقة سلسلة <code>then</code> بفضل القاعدة الجديدة.
</p>

<p>
	لكن عليك الانتباه إلى بعض التفاصيل المهمة عند استخدام async/await. فيجب على await أن تعيد وعدًا إذا أردت تطبيقها على عملية غير متزامنة. وطبعًا لن يكون الأمر صعبًا، طالما أن الدوال النظامية غير المتزامنة التي تستخدم الاستدعاءات يمكنها تدبر أمر الوعود. كما لا يمكن استعمال await في أي مكان ضمن شيفرة JavaScript، بل فقط ضمن <a data-ss1613481443="1" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function" rel="external nofollow">دالة غير متزامنة</a>. أي حتى يعمل المثال السابق لا بد من استعمال دوال غير متزامنة.
</p>

<p>
	لنركز الآن على السطر الأول من الدالة السهمية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_53" style="">
<span class="kwd">const</span><span class="pln"> main </span><span class="pun">=</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">  </span><span class="kwd">const</span><span class="pln"> notes </span><span class="pun">=</span><span class="pln"> await </span><span class="typ">Note</span><span class="pun">.</span><span class="pln">find</span><span class="pun">({})</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'operation returned the following notes'</span><span class="pun">,</span><span class="pln"> notes</span><span class="pun">)</span><span class="pln">

  </span><span class="kwd">const</span><span class="pln"> response </span><span class="pun">=</span><span class="pln"> await notes</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">remove</span><span class="pun">()</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'the first note is removed'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
main</span><span class="pun">()</span></pre>

<p>
	تصرح الشيفرة بوضوح أن الدالة السهمية المُسندة إلى <code>main</code> غير متزامنة، ثم تُستدعى الدالة بعد ذلك باستخدام <code>()main</code>.
</p>

<h2>
	استخدام async/await في الواجهة الخلفية
</h2>

<p>
	سنغيّر شيفرة الواجهة الخلفية لتستخدم await وasync. يكفي أن نغير معالج المسار في أي دالة إلى دالة غير متزامنة حتى نستخدم التعليمة await، طالما أن كل العمليات غير المتزامنة ستجري داخل دالة.
</p>

<p>
	سنغير المسار الذي يستخدم لإحضار الملاحظات جميعها على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_55" style="">
<span class="pln">notesRouter</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> 
  </span><span class="kwd">const</span><span class="pln"> notes </span><span class="pun">=</span><span class="pln"> await </span><span class="typ">Note</span><span class="pun">.</span><span class="pln">find</span><span class="pun">({})</span><span class="pln">
  response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">notes</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	يمكنك التحقق من نجاح الأمر باختبار الشيفرة ضمن المتصفح وكذلك بتنفيذ الاختبارات التي كتبناها سابقًا.
</p>

<p>
	يمكنك إيجاد شيفرة التطبيق الحالي بأكملها في الفرع part4-3 ضمن المستودع الخاص بالتطبيق على <a data-ss1613481443="1" href="https://github.com/fullstack-hy2020/part3-notes-backend/tree/part4-3" rel="external nofollow">GitHub</a>.
</p>

<h2>
	اختبارت أكثر وإعادة كتابة الواجهة الخلفية
</h2>

<p>
	يبقى دائمًا خطر <a data-ss1613481443="1" href="https://en.wikipedia.org/wiki/Regression_testing" rel="external nofollow">التدهور</a> (regression) قائمًا عند إعادة كتابة الشيفرة. ونقصد بذلك احتمال توقف التطبيق عن تنفيذ الوظيفة المطلوبة. إذُا سنعيد كتابة ما تبقى من عمليات بكتابة اختبار لكل مسار من مسارات الواجهة البرمجية.
</p>

<p>
	سنبدأ بعملية إضافة ملاحظة جديدة، حيث سنكتب اختبارًا ينشئ ملاحظة جديدة ويتحقق أن عدد الملاحظات الذي ستعيده الواجهة البرمجية سيزداد، وأن الملاحظة الجديدة موجودة بالفعل ضمن قائمة الملاحظات.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_57" style="">
<span class="pln">test</span><span class="pun">(</span><span class="str">'a valid note can be added'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> newNote </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    content</span><span class="pun">:</span><span class="pln"> </span><span class="str">'async/await simplifies making async calls'</span><span class="pun">,</span><span class="pln">
    important</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  await api
    </span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="pln">newNote</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">expect</span><span class="pun">(</span><span class="lit">200</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">expect</span><span class="pun">(</span><span class="str">'Content-Type'</span><span class="pun">,</span><span class="pln"> </span><span class="str">/application\/json/</span><span class="pun">)</span><span class="pln">

  </span><span class="kwd">const</span><span class="pln"> response </span><span class="pun">=</span><span class="pln"> await api</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">)</span><span class="pln">

  </span><span class="kwd">const</span><span class="pln"> contents </span><span class="pun">=</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">map</span><span class="pun">(</span><span class="pln">r </span><span class="pun">=&gt;</span><span class="pln"> r</span><span class="pun">.</span><span class="pln">content</span><span class="pun">)</span><span class="pln">

  expect</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">body</span><span class="pun">).</span><span class="pln">toHaveLength</span><span class="pun">(</span><span class="pln">initialNotes</span><span class="pun">.</span><span class="pln">length </span><span class="pun">+</span><span class="pln"> </span><span class="lit">1</span><span class="pun">)</span><span class="pln">
  expect</span><span class="pun">(</span><span class="pln">contents</span><span class="pun">).</span><span class="pln">toContain</span><span class="pun">(</span><span class="pln">
    </span><span class="str">'async/await simplifies making async calls'</span><span class="pln">
  </span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	سيُنفًّذ الاختبار بنجاح كما خططنا وتوقعنا.
</p>

<p>
	لنتكب اختبارًا آخر يتحقق من عدم تخزين أية ملاحظة في قاعدة البيانات ما لم يكن فيها محتوًى.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_59" style="">
<span class="pln">test</span><span class="pun">(</span><span class="str">'note without content is not added'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> newNote </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    important</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  await api
    </span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="pln">newNote</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">expect</span><span class="pun">(</span><span class="lit">400</span><span class="pun">)</span><span class="pln">

  </span><span class="kwd">const</span><span class="pln"> response </span><span class="pun">=</span><span class="pln"> await api</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">)</span><span class="pln">

  expect</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">body</span><span class="pun">).</span><span class="pln">toHaveLength</span><span class="pun">(</span><span class="pln">initialNotes</span><span class="pun">.</span><span class="pln">length</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	يتحقق كلا الاختبارين من حالة الملاحظة المخزنة في قاعدة البيانات، وذلك بإحضار كل الملاحظات في التطبيق.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_61" style="">
<span class="kwd">const</span><span class="pln"> response </span><span class="pun">=</span><span class="pln"> await api</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">)</span></pre>

<p>
	سننفذ بنفس خطوات التحقق السابقة في الاختبارات القادمة، لذا يفضل نقل شيفرة الاختبارات إلى الدوال المساعدة. ولهذا سنضيف دالة تضم الشيفرة السابقة إلى ملف جديد يدعى test_helper.js ونضعه في المجلد tests:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_63" style="">
<span class="kwd">const</span><span class="pln"> </span><span class="typ">Note</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'../models/note'</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> initialNotes </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
  </span><span class="pun">{</span><span class="pln">
    content</span><span class="pun">:</span><span class="pln"> </span><span class="str">'HTML is easy'</span><span class="pun">,</span><span class="pln">
    date</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">(),</span><span class="pln">
    important</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">false</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="pun">{</span><span class="pln">
    content</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Browser can execute only Javascript'</span><span class="pun">,</span><span class="pln">
    date</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">(),</span><span class="pln">
    important</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">]</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> nonExistingId </span><span class="pun">=</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> note </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Note</span><span class="pun">({</span><span class="pln"> content</span><span class="pun">:</span><span class="pln"> </span><span class="str">'willremovethissoon'</span><span class="pun">,</span><span class="pln"> date</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">()</span><span class="pln"> </span><span class="pun">})</span><span class="pln">
  await note</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">
  await note</span><span class="pun">.</span><span class="pln">remove</span><span class="pun">()</span><span class="pln">

  </span><span class="kwd">return</span><span class="pln"> note</span><span class="pun">.</span><span class="pln">_id</span><span class="pun">.</span><span class="pln">toString</span><span class="pun">()</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> notesInDb </span><span class="pun">=</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> notes </span><span class="pun">=</span><span class="pln"> await </span><span class="typ">Note</span><span class="pun">.</span><span class="pln">find</span><span class="pun">({})</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> notes</span><span class="pun">.</span><span class="pln">map</span><span class="pun">(</span><span class="pln">note </span><span class="pun">=&gt;</span><span class="pln"> note</span><span class="pun">.</span><span class="pln">toJSON</span><span class="pun">())</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

module</span><span class="pun">.</span><span class="pln">exports </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  initialNotes</span><span class="pun">,</span><span class="pln"> nonExistingId</span><span class="pun">,</span><span class="pln"> notesInDb
</span><span class="pun">}</span></pre>

<p>
	تعرف الوحدة الدالة <code>notesIdDb</code> التي تتحقق من الملاحظات المخزنة في قاعدة البيانات. كما تضم الوحدة المصفوفة <code>initialNotes</code> التي تخزن الحالة الأساسية لقاعدة البيانات.كما تعرف الوحدة الدالة <code>nonExistingId</code> التي يمكن استخدامها لاحقًا في إنشاء كائن مُعرِّف قاعدة اليانات (ID Object)، وهو كائن لا يتبع لأي ملاحظة مخزنة في القاعدة.
</p>

<p>
	سنتمكن الآن من استخدام وحدة مساعدة في الاختبارات التي ستصبح على النحو:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_65" style="">
<span class="kwd">const</span><span class="pln"> supertest </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'supertest'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> mongoose </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'mongoose'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> helper </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'./test_helper'</span><span class="pun">)</span><span class="kwd">const</span><span class="pln"> app </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'../app'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> api </span><span class="pun">=</span><span class="pln"> supertest</span><span class="pun">(</span><span class="pln">app</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">Note</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'../models/note'</span><span class="pun">)</span><span class="pln">

beforeEach</span><span class="pun">(</span><span class="pln">async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  await </span><span class="typ">Note</span><span class="pun">.</span><span class="pln">deleteMany</span><span class="pun">({})</span><span class="pln">

  let noteObject </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Note</span><span class="pun">(</span><span class="pln">helper</span><span class="pun">.</span><span class="pln">initialNotes</span><span class="pun">[</span><span class="lit">0</span><span class="pun">])</span><span class="pln">  await noteObject</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">

  noteObject </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Note</span><span class="pun">(</span><span class="pln">helper</span><span class="pun">.</span><span class="pln">initialNotes</span><span class="pun">[</span><span class="lit">1</span><span class="pun">])</span><span class="pln">  await noteObject</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

test</span><span class="pun">(</span><span class="str">'notes are returned as json'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  await api
    </span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">expect</span><span class="pun">(</span><span class="lit">200</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">expect</span><span class="pun">(</span><span class="str">'Content-Type'</span><span class="pun">,</span><span class="pln"> </span><span class="str">/application\/json/</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

test</span><span class="pun">(</span><span class="str">'all notes are returned'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> response </span><span class="pun">=</span><span class="pln"> await api</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">)</span><span class="pln">

  expect</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">body</span><span class="pun">).</span><span class="pln">toHaveLength</span><span class="pun">(</span><span class="pln">helper</span><span class="pun">.</span><span class="pln">initialNotes</span><span class="pun">.</span><span class="pln">length</span><span class="pun">)})</span><span class="pln">

test</span><span class="pun">(</span><span class="str">'a specific note is within the returned notes'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> response </span><span class="pun">=</span><span class="pln"> await api</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">)</span><span class="pln">

  </span><span class="kwd">const</span><span class="pln"> contents </span><span class="pun">=</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">map</span><span class="pun">(</span><span class="pln">r </span><span class="pun">=&gt;</span><span class="pln"> r</span><span class="pun">.</span><span class="pln">content</span><span class="pun">)</span><span class="pln">
  expect</span><span class="pun">(</span><span class="pln">contents</span><span class="pun">).</span><span class="pln">toContain</span><span class="pun">(</span><span class="pln">
    </span><span class="str">'Browser can execute only Javascript'</span><span class="pln">
  </span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

test</span><span class="pun">(</span><span class="str">'a valid note can be added '</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> newNote </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    content</span><span class="pun">:</span><span class="pln"> </span><span class="str">'async/await simplifies making async calls'</span><span class="pun">,</span><span class="pln">
    important</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  await api
    </span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="pln">newNote</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">expect</span><span class="pun">(</span><span class="lit">200</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">expect</span><span class="pun">(</span><span class="str">'Content-Type'</span><span class="pun">,</span><span class="pln"> </span><span class="str">/application\/json/</span><span class="pun">)</span><span class="pln">

  </span><span class="kwd">const</span><span class="pln"> notesAtEnd </span><span class="pun">=</span><span class="pln"> await helper</span><span class="pun">.</span><span class="pln">notesInDb</span><span class="pun">()</span><span class="pln">  expect</span><span class="pun">(</span><span class="pln">notesAtEnd</span><span class="pun">).</span><span class="pln">toHaveLength</span><span class="pun">(</span><span class="pln">helper</span><span class="pun">.</span><span class="pln">initialNotes</span><span class="pun">.</span><span class="pln">length </span><span class="pun">+</span><span class="pln"> </span><span class="lit">1</span><span class="pun">)</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> contents </span><span class="pun">=</span><span class="pln"> notesAtEnd</span><span class="pun">.</span><span class="pln">map</span><span class="pun">(</span><span class="pln">n </span><span class="pun">=&gt;</span><span class="pln"> n</span><span class="pun">.</span><span class="pln">content</span><span class="pun">)</span><span class="pln">  expect</span><span class="pun">(</span><span class="pln">contents</span><span class="pun">).</span><span class="pln">toContain</span><span class="pun">(</span><span class="pln">
    </span><span class="str">'async/await simplifies making async calls'</span><span class="pln">
  </span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

test</span><span class="pun">(</span><span class="str">'note without content is not added'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> newNote </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    important</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  await api
    </span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="pln">newNote</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">expect</span><span class="pun">(</span><span class="lit">400</span><span class="pun">)</span><span class="pln">

  </span><span class="kwd">const</span><span class="pln"> notesAtEnd </span><span class="pun">=</span><span class="pln"> await helper</span><span class="pun">.</span><span class="pln">notesInDb</span><span class="pun">()</span><span class="pln">
  expect</span><span class="pun">(</span><span class="pln">notesAtEnd</span><span class="pun">).</span><span class="pln">toHaveLength</span><span class="pun">(</span><span class="pln">helper</span><span class="pun">.</span><span class="pln">initialNotes</span><span class="pun">.</span><span class="pln">length</span><span class="pun">)})</span><span class="pln">

afterAll</span><span class="pun">(()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  mongoose</span><span class="pun">.</span><span class="pln">connection</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
</span><span class="pun">})</span><span class="pln"> </span></pre>

<p>
	ستعمل الشيفرة التي تعتمد على الوعود وستنجح الاختبارات التي سننفذها، وبالتالي أصبحنا مستعدين لاستخدام العبارة async/await.
</p>

<p>
	سنغير في الشيفرة المسؤولة عن إنشاء ملاحظة جديدة، وانتبه إلى أن تعريف معالج المسار سيُسبق بالكلمة <code>async</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_67" style="">
<span class="pln">notesRouter</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> body </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">body

  </span><span class="kwd">const</span><span class="pln"> note </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Note</span><span class="pun">({</span><span class="pln">
    content</span><span class="pun">:</span><span class="pln"> body</span><span class="pun">.</span><span class="pln">content</span><span class="pun">,</span><span class="pln">
    important</span><span class="pun">:</span><span class="pln"> body</span><span class="pun">.</span><span class="pln">important </span><span class="pun">||</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">,</span><span class="pln">
    date</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">(),</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">

  </span><span class="kwd">const</span><span class="pln"> savedNote </span><span class="pun">=</span><span class="pln"> await note</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">
  response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">savedNote</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	لاتزال هناك هفوة صغيرة في شيفرتنا، فلم نكتب أي شيء للتعامل مع الأخطاء. كيف سنعالج الأمر إذًا؟
</p>

<h2>
	معالجة الأخطاء عند استعمال async/await
</h2>

<p>
	إن حدث خطأ في معالجة الطلب POST، سنواجهة حالة الخطأ المألوفة التالية:
</p>

<p style="text-align: center;">
	<img class="ipsImage ipsImage_thumbnailed" data-fileid="57770" data-unique="597plb2dc" src="https://academy.hsoub.com/uploads/monthly_2021_02/post_erorr_02.png.d73ee0992746c5d62e9106afaa4b0ec7.png" alt="post_erorr_02.png"></p>

<p>
	أي سينتهي بنا المطاف أمام حالة رفض للوعد لم يهيئ التطبيق لالتقاطه، ولن تكون هناك أية استجابة للطلب.
</p>

<p>
	إن الطريقة المفضلة للتعامل مع الاستثناءات عندما نستعمل async/await هي الطريقة القديمة المألوفة try/catch:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_69" style="">
<span class="pln">notesRouter</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> body </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">body

  </span><span class="kwd">const</span><span class="pln"> note </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Note</span><span class="pun">({</span><span class="pln">
    content</span><span class="pun">:</span><span class="pln"> body</span><span class="pun">.</span><span class="pln">content</span><span class="pun">,</span><span class="pln">
    important</span><span class="pun">:</span><span class="pln"> body</span><span class="pun">.</span><span class="pln">important </span><span class="pun">||</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">,</span><span class="pln">
    date</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">(),</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">
  </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">const</span><span class="pln"> savedNote </span><span class="pun">=</span><span class="pln"> await note</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">
      response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">savedNote</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  </span><span class="kwd">catch</span><span class="pun">(</span><span class="pln">exception</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">{</span><span class="pln">
          next</span><span class="pun">(</span><span class="pln">exception</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">}})</span></pre>

<p>
	تستدعي الكتلة <code>catch</code> ببساطة الدالة <code>next</code> التي تمرر معالجة الطلب إلى الوحدة الوسطية التي تعالج الأخطاء.
</p>

<p>
	ستنجح كل الاختبارات أيضًا عند إجراء التعديلات السابقة. سنكتب تاليًا اختبارات لإحضار وحذف ملاحظة واحدة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_71" style="">
<span class="pln">test</span><span class="pun">(</span><span class="str">'a specific note can be viewed'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> notesAtStart </span><span class="pun">=</span><span class="pln"> await helper</span><span class="pun">.</span><span class="pln">notesInDb</span><span class="pun">()</span><span class="pln">

  </span><span class="kwd">const</span><span class="pln"> noteToView </span><span class="pun">=</span><span class="pln"> notesAtStart</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln">

  </span><span class="kwd">const</span><span class="pln"> resultNote </span><span class="pun">=</span><span class="pln"> await api    
  </span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(`/</span><span class="pln">api</span><span class="pun">/</span><span class="pln">notes</span><span class="pun">/</span><span class="pln">$</span><span class="pun">{</span><span class="pln">noteToView</span><span class="pun">.</span><span class="pln">id</span><span class="pun">}`)</span><span class="pln">    
  </span><span class="pun">.</span><span class="pln">expect</span><span class="pun">(</span><span class="lit">200</span><span class="pun">)</span><span class="pln">    
  </span><span class="pun">.</span><span class="pln">expect</span><span class="pun">(</span><span class="str">'Content-Type'</span><span class="pun">,</span><span class="pln"> </span><span class="str">/application\/json/</span><span class="pun">)</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> processedNoteToView </span><span class="pun">=</span><span class="pln"> JSON</span><span class="pun">.</span><span class="pln">parse</span><span class="pun">(</span><span class="pln">JSON</span><span class="pun">.</span><span class="pln">stringify</span><span class="pun">(</span><span class="pln">noteToView</span><span class="pun">))</span><span class="pln">

  expect</span><span class="pun">(</span><span class="pln">resultNote</span><span class="pun">.</span><span class="pln">body</span><span class="pun">).</span><span class="pln">toEqual</span><span class="pun">(</span><span class="pln">processedNoteToView</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

test</span><span class="pun">(</span><span class="str">'a note can be deleted'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> notesAtStart </span><span class="pun">=</span><span class="pln"> await helper</span><span class="pun">.</span><span class="pln">notesInDb</span><span class="pun">()</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> noteToDelete </span><span class="pun">=</span><span class="pln"> notesAtStart</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln">

  await api
      </span><span class="pun">.</span><span class="kwd">delete</span><span class="pun">(`/</span><span class="pln">api</span><span class="pun">/</span><span class="pln">notes</span><span class="pun">/</span><span class="pln">$</span><span class="pun">{</span><span class="pln">noteToDelete</span><span class="pun">.</span><span class="pln">id</span><span class="pun">}`)</span><span class="pln">
      </span><span class="pun">.</span><span class="pln">expect</span><span class="pun">(</span><span class="lit">204</span><span class="pun">)</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> notesAtEnd </span><span class="pun">=</span><span class="pln"> await helper</span><span class="pun">.</span><span class="pln">notesInDb</span><span class="pun">()</span><span class="pln">

  expect</span><span class="pun">(</span><span class="pln">notesAtEnd</span><span class="pun">).</span><span class="pln">toHaveLength</span><span class="pun">(</span><span class="pln">
    helper</span><span class="pun">.</span><span class="pln">initialNotes</span><span class="pun">.</span><span class="pln">length </span><span class="pun">-</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
  </span><span class="pun">)</span><span class="pln">

  </span><span class="kwd">const</span><span class="pln"> contents </span><span class="pun">=</span><span class="pln"> notesAtEnd</span><span class="pun">.</span><span class="pln">map</span><span class="pun">(</span><span class="pln">r </span><span class="pun">=&gt;</span><span class="pln"> r</span><span class="pun">.</span><span class="pln">content</span><span class="pun">)</span><span class="pln">

  expect</span><span class="pun">(</span><span class="pln">contents</span><span class="pun">).</span><span class="pln">not</span><span class="pun">.</span><span class="pln">toContain</span><span class="pun">(</span><span class="pln">noteToDelete</span><span class="pun">.</span><span class="pln">content</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	نحصل في الاختبار الأول على كائن الملاحظة بعد أن تخضع الاستجابة إلى عملية تقسيم ومعالجة خاصة ببيانات JSON. ستحول عملية المعالجة قيمة الخاصية <code>date</code> لكائن الملاحظة من كائن <code>Date</code> إلى نص. ولهذا، لن نتمكن مباشرة من الموازنة بين <code>resultNote.body</code> و<code>noteToView</code>. بدلًا من ذلك علينا أن نُخضع <code>noteToView</code> إلى نفس عملية التقسيم والمعالجة كما يفعل الخادم مع كائن الملاحظة.
</p>

<p>
	لكلا الاختبارين الهيكل ذاته. ففي مرحلة إعادة الضبط (التصفير) سيحضر الاختبارين الملاحظة من قاعدة البيانات. يستدعي الاختباران بعد ذلك العملية الرئيسية التي يجري اختبارها. وأخيرًا يتحقق الاختباران من أن نتيجة العملية كما هو متوقع أو لا.
</p>

<p>
	سينجح الاختباران وبالتالي سنتمكن من إعادة كتابة شيفرة المسارات المختبرة بأسلوب:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_73" style="">
<span class="pln">notesRouter</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/:id'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">try</span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> note </span><span class="pun">=</span><span class="pln"> await </span><span class="typ">Note</span><span class="pun">.</span><span class="pln">findById</span><span class="pun">(</span><span class="pln">request</span><span class="pun">.</span><span class="pln">params</span><span class="pun">.</span><span class="pln">id</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">note</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">note</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">404</span><span class="pun">).</span><span class="pln">end</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">catch</span><span class="pun">(</span><span class="pln">exception</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    next</span><span class="pun">(</span><span class="pln">exception</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

notesRouter</span><span class="pun">.</span><span class="kwd">delete</span><span class="pun">(</span><span class="str">'/:id'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    await </span><span class="typ">Note</span><span class="pun">.</span><span class="pln">findByIdAndRemove</span><span class="pun">(</span><span class="pln">request</span><span class="pun">.</span><span class="pln">params</span><span class="pun">.</span><span class="pln">id</span><span class="pun">)</span><span class="pln">
    response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">204</span><span class="pun">).</span><span class="pln">end</span><span class="pun">()</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="pln">exception</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    next</span><span class="pun">(</span><span class="pln">exception</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	يمكنك إيجاد شيفرة التطبيق الحالي بأكملها في الفرع part4-4 ضمن المستودع الخاص بالتطبيق على <a data-ss1613481443="1" href="https://github.com/fullstack-hy2020/part3-notes-backend/tree/part4-4" rel="external nofollow">GitHub</a>.
</p>

<h2>
	الاستغناء عن الكتلة try/catch
</h2>

<p>
	سترتب العبارة async/await الشيفرة قليلًا، لكن الثمن هو الحاجة إلى استخدام الكتلة catch لالتقاط الاستثناءات. تشترك معالجات المسار بالبنية نفسها:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_75" style="">
<span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="com">// do the async operations here</span><span class="pln">
</span><span class="pun">}</span><span class="pln"> </span><span class="kwd">catch</span><span class="pun">(</span><span class="pln">exception</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  next</span><span class="pun">(</span><span class="pln">exception</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	قد يتسائل أحدنا عن إمكانية إعادة كتابة الشيفرة بالشكل الذي نستغني فيه عن كتلة catch. والحل بالطبع موجود في المكتبة <a data-ss1613481443="1" href="https://github.com/davidbanham/express-async-errors" rel="external nofollow">express-async-errors</a>. لثبتها إذًا.
</p>

<pre class="ipsCode">
npm install express-async-errors --save
</pre>

<p>
	إن استخدام المكتبة سهل للغاية. حيث ستدرج المكتبة ضمن الملف app.js في المجلد src كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_77" style="">
<span class="kwd">const</span><span class="pln"> config </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'./utils/config'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> express </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'express'</span><span class="pun">)</span><span class="pln">
                require</span><span class="pun">(</span><span class="str">'express-async-errors'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> app </span><span class="pun">=</span><span class="pln"> express</span><span class="pun">()</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> cors </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'cors'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> notesRouter </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'./controllers/notes'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> middleware </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'./utils/middleware'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> logger </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'./utils/logger'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> mongoose </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'mongoose'</span><span class="pun">)</span><span class="pln">

</span><span class="com">// ...</span><span class="pln">

module</span><span class="pun">.</span><span class="pln">exports </span><span class="pun">=</span><span class="pln"> app</span></pre>

<p>
	ستلغي هذه المكتبة كتل try/catch كليًا. فلو أخذنا على سبيل المثال المسار الذي يحذف ملاحظة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_79" style="">
<span class="pln">notesRouter</span><span class="pun">.</span><span class="kwd">delete</span><span class="pun">(</span><span class="str">'/:id'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    await </span><span class="typ">Note</span><span class="pun">.</span><span class="pln">findByIdAndRemove</span><span class="pun">(</span><span class="pln">request</span><span class="pun">.</span><span class="pln">params</span><span class="pun">.</span><span class="pln">id</span><span class="pun">)</span><span class="pln">
    response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">204</span><span class="pun">).</span><span class="pln">end</span><span class="pun">()</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> 
  </span><span class="kwd">catch</span><span class="pln"> </span><span class="pun">(</span><span class="pln">exception</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    next</span><span class="pun">(</span><span class="pln">exception</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	سيتحول إلى الشكل:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_81" style="">
<span class="pln">notesRouter</span><span class="pun">.</span><span class="kwd">delete</span><span class="pun">(</span><span class="str">'/:id'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  await </span><span class="typ">Note</span><span class="pun">.</span><span class="pln">findByIdAndRemove</span><span class="pun">(</span><span class="pln">request</span><span class="pun">.</span><span class="pln">params</span><span class="pun">.</span><span class="pln">id</span><span class="pun">)</span><span class="pln">
  response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">204</span><span class="pun">).</span><span class="pln">end</span><span class="pun">()</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	لم يعد علينا استدعاء <code>next(exception)‎</code> بعد الآن، حيث تعالج المكتبة كل ما هو مطلوب في الخفاء. وإن حدث استثناء في مسار <code>async</code> سيُمرر تلقائيًا إلى الأداة الوسطية لمعالجة الأخطاء.
</p>

<p>
	ستصبح المسارات الأخرى على النحو:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_83" style="">
<span class="pln">notesRouter</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> body </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">body

  </span><span class="kwd">const</span><span class="pln"> note </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Note</span><span class="pun">({</span><span class="pln">
    content</span><span class="pun">:</span><span class="pln"> body</span><span class="pun">.</span><span class="pln">content</span><span class="pun">,</span><span class="pln">
    important</span><span class="pun">:</span><span class="pln"> body</span><span class="pun">.</span><span class="pln">important </span><span class="pun">||</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">,</span><span class="pln">
    date</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">(),</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">

  </span><span class="kwd">const</span><span class="pln"> savedNote </span><span class="pun">=</span><span class="pln"> await note</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">
  response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">savedNote</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

notesRouter</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/:id'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> note </span><span class="pun">=</span><span class="pln"> await </span><span class="typ">Note</span><span class="pun">.</span><span class="pln">findById</span><span class="pun">(</span><span class="pln">request</span><span class="pun">.</span><span class="pln">params</span><span class="pun">.</span><span class="pln">id</span><span class="pun">)</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">note</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">note</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">404</span><span class="pun">).</span><span class="pln">end</span><span class="pun">()</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	ستجد شيفرة التطبيق في الفرع part4-5 ضمن المستودع المخصص على <a data-ss1613481443="1" href="https://github.com/fullstack-hy2020/part3-notes-backend/tree/part4-5" rel="external nofollow">GitHub</a>.
</p>

<h2>
	تحسين الدالة beforeEach
</h2>

<p>
	لنعد إلى كتابة الاختبارات ولنحاول أن نلقي نظرةً أقرب إلى الدالة <code>beforeEach</code> التي تضبط الاختبارات:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_85" style="">
<span class="pln">beforeEach</span><span class="pun">(</span><span class="pln">async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  await </span><span class="typ">Note</span><span class="pun">.</span><span class="pln">deleteMany</span><span class="pun">({})</span><span class="pln">

  let noteObject </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Note</span><span class="pun">(</span><span class="pln">helper</span><span class="pun">.</span><span class="pln">initialNotes</span><span class="pun">[</span><span class="lit">0</span><span class="pun">])</span><span class="pln">
  await noteObject</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">

  noteObject </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Note</span><span class="pun">(</span><span class="pln">helper</span><span class="pun">.</span><span class="pln">initialNotes</span><span class="pun">[</span><span class="lit">1</span><span class="pun">])</span><span class="pln">
  await noteObject</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	تحفظ الدالة أول ملاحظتين من المصفوفة <code>helper.initialNotes</code> ضمن قاعدة البيانات عبر عمليتين منفصلتين. لابأس بذلك، لكن هناك طريقة أفضل لتخزين عدة كائنات في قاعدة البيانات:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_87" style="">
<span class="pln">beforeEach</span><span class="pun">(</span><span class="pln">async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  await </span><span class="typ">Note</span><span class="pun">.</span><span class="pln">deleteMany</span><span class="pun">({})</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'cleared'</span><span class="pun">)</span><span class="pln">

  helper</span><span class="pun">.</span><span class="pln">initialNotes</span><span class="pun">.</span><span class="pln">forEach</span><span class="pun">(</span><span class="pln">async </span><span class="pun">(</span><span class="pln">note</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let noteObject </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Note</span><span class="pun">(</span><span class="pln">note</span><span class="pun">)</span><span class="pln">
    await noteObject</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">
    console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'saved'</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'done'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

test</span><span class="pun">(</span><span class="str">'notes are returned as json'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'entered test'</span><span class="pun">)</span><span class="pln">
  </span><span class="com">// ...</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	لقد خزَّنا الملاحظات الموجودة في المصفوفة ضمن قاعدة البيانات باستعمال حلقة <code>forEach</code>. لن يعمل التطبيق كما أردنا تمامًا، لذلك كان علينا أن نطبع بعض السجلات على الطرفية لتساعدنا على فهم المشكلة. فأظهرت الطرفية ما يلي:
</p>

<pre class="ipsCode">
cleared
done
entered test
saved
saved
</pre>

<p>
	على الرغم من استخدام async/await، لم يعمل الحل المقترح بالطريقة المتوقعة. بل بدأ الاختبار قبل أن يُعاد ضبط قاعدة البيانات.
</p>

<p>
	تكمن المشكلة في أن كل تكرار للحلقة <code>forEach</code> يولد عملية غير متزامنة خاصة به، ولن تنتظر الحلقة <code>forEach</code> كل تكرار لكي يُنهي تنفيذ عمليته. بمعنى آخر، لم توضع التعليمات <code>await</code> الموجودة داخل حلقة <code>forEach</code> في الدالة <code>beforeEach</code> بل في دوال منفصلة لن تنتظرها <code>beforeEach</code> حتى يكتمل تنفيذها.
</p>

<p>
	طالما ستُنفَّذ الاختبارات مباشرة بعد الانتهاء من تنفيذ <code>beforeEach</code>، إذًا ستُنفذ قبل إعادة ضبط قاعدة البيانات.
</p>

<p>
	إحدى الطرق المتبعة في معالجة الموضوع هي انتظار العمليات غير المتزامنة حتى تنتهي باستعمال التابع <a data-ss1613481443="1" href="https://wiki.hsoub.com/JavaScript/Promise/all" rel="external">Promise.all</a>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_89" style="">
<span class="pln">beforeEach</span><span class="pun">(</span><span class="pln">async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  await </span><span class="typ">Note</span><span class="pun">.</span><span class="pln">deleteMany</span><span class="pun">({})</span><span class="pln">

  </span><span class="kwd">const</span><span class="pln"> noteObjects </span><span class="pun">=</span><span class="pln"> helper</span><span class="pun">.</span><span class="pln">initialNotes
    </span><span class="pun">.</span><span class="pln">map</span><span class="pun">(</span><span class="pln">note </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Note</span><span class="pun">(</span><span class="pln">note</span><span class="pun">))</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> promiseArray </span><span class="pun">=</span><span class="pln"> noteObjects</span><span class="pun">.</span><span class="pln">map</span><span class="pun">(</span><span class="pln">note </span><span class="pun">=&gt;</span><span class="pln"> note</span><span class="pun">.</span><span class="pln">save</span><span class="pun">())</span><span class="pln">
  await </span><span class="typ">Promise</span><span class="pun">.</span><span class="pln">all</span><span class="pun">(</span><span class="pln">promiseArray</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	إن الأسلوب المتبع متقدم على الرغم من مظهره المختصر. نلاحظ كيف يُسند المتغير <code>noteObjects</code> إلى مصفوفة من كائنات Mongoose التي أنشئت باستخدام الدالة البانية <code>Note</code> من أجل كل ملاحظة موجودة في المصفوفة <code>helper.initialNotes</code>. يُنشئ السطر التالي من الشيفرة مصفوفة جديدة تتضمن الوعود التي أُنشئت بدورها باستدعاء التابع <code>save</code> من أجل كل عنصر من عناصر المصفوفة <code>noteOjects</code>. بمعنًى آخر، تمثل المصفوفة مصفوفةً لتخزين كل العناصر ضمن قاعدة البيانات.
</p>

<p>
	يستخدم التابع <a data-ss1613481443="1" href="https://wiki.hsoub.com/JavaScript/Promise/all" rel="external">Promise.all</a> لتحويل مصفوفة من الوعود إلى وعد وحيد يتحقق بمجرد تحقق كل الوعود في المصفوفة التي تمرر إلى التابع. ينتظر الأمر <code>(await Promise.all(promiseArray</code> في السطر الأخير من الشيفرة كل وعد سيخزن ملاحظة في قاعدة البيانات حتى يتحقق، ويعني هذا أن قاعدة البيانات قد أعيد ضبطها.
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		يمكن أن تصل إلى القيم التي يعيدها كل وعد في المصفوفة عندما تستخدم التابع Promise.all. فلو انتظرنا حتى تتحقق الوعود باستعمال الأمر (const results = await Promise.all(promiseArray، ستعيد العملية مصفوفة تحتوي على القيم التي يعيدها كل وعد، وستظهر بنفس الترتيب الذي تتخذه الوعود في المصفوفة.
	</p>
</blockquote>

<p>
	ينفذ التابع <code>Promise.all</code> الوعود التي تمرر إليه بالتوازي. وبالتالي إن أردنا تنفيذ الوعود بترتيب معين ستظهر المشاكل. يمكن أن تنفذ العمليات في حالات كهذه ضمن كتلة <a data-ss1613481443="1" href="https://wiki.hsoub.com/JavaScript/for...of" rel="external">for…of</a> التي تضمن ترتيبًا محددًا للتنفيذ:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_94" style="">
<span class="pln">beforeEach</span><span class="pun">(</span><span class="pln">async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  await </span><span class="typ">Note</span><span class="pun">.</span><span class="pln">deleteMany</span><span class="pun">({})</span><span class="pln">

  </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let note of helper</span><span class="pun">.</span><span class="pln">initialNotes</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let noteObject </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Note</span><span class="pun">(</span><span class="pln">note</span><span class="pun">)</span><span class="pln">
    await noteObject</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	قد تقود الطبيعة غير المتزامنة للغة JavaScript إلى سلوك مفاجئ، ولهذا يجب الانتباه والحرص عند استخدام async/await. وعلى الرغم من أن استعمالها يسهل التعامل مع الوعود، لكن يجدر بنا فهم الآلية التي تعمل بها الوعود بشكل جيد.
</p>

<h2>
	التمارين 4.8 - 4.12
</h2>

<p>
	<strong>ملاحظة</strong>: نستخدم في التمارين تابع المطابقة <a data-ss1613481443="1" href="https://facebook.github.io/jest/docs/en/expect.html#tocontainitem" rel="external nofollow">toContain</a> في أماكن عدة لنتأكد من وجود عنصر معين في مصفوفة. يستخدم التابع السابق العامل (===) في الموازنة والمطابقة بين العناصر، وهذا غير ملائم في العديد من الحالات بما فيها الكشف عن تطابق كائنين. بينما يعتبر تابع المطابقة <a data-ss1613481443="1" href="https://facebook.github.io/jest/docs/en/expect.html#tocontainequalitem" rel="external nofollow">toContainEqual</a> ملائمًا في معظم الحالات للمقارنة بين الكائنات ضمن المصفوفات. لا يتحقق نموذج الحل من الكائنات في المصفوفات باستخدام توابع المطابقة، لذا فاستخدام هذا الأسلوب ليس ملزمًا في حل التمارين.
</p>

<p>
	<strong>تحذير</strong>: إن وجدت نفسك قد استخدمت async/await مع then في نفس الشيفرة فهذا دليل على ارتكابك خطأً ما. استخدم أحد الأسلوبين وليس كلاهما.
</p>

<h3>
	4.8 اختبارات على قائمة المدونات: الخطوة 1
</h3>

<p>
	استخدم الحزمة supertest لكتابة اختبار يرسل طلب HTTP GET إلى العنوان api/blogs/. وتحقق أن تطبيق قائمة المدونات سيعيد العدد الصحيح من منشورات المدونات بصيغة JSON.
</p>

<p>
	حالما ينتهي الاختبار، أعد كتابة معالج المسار مستخدمًا async/await بدلًا من الوعود.
</p>

<p>
	لاحظ أنه عليك إجراء نفس التغيرات التي أجريناها سابقًا على شيفرتك كتحديد بيئة الاختبار، لتتمكن من كتابة اختبارات يتعامل كل منها مع قاعدة بيانات خاصة به.
</p>

<p>
	<strong>ملاحظة</strong>: قد تواجه التحذير التالي عندما تنفذ الاختبار:
</p>

<p style="text-align: center;">
	<img class="ipsImage ipsImage_thumbnailed" data-fileid="57769" data-unique="zobn081er" src="https://academy.hsoub.com/uploads/monthly_2021_02/blog_test_warn_03.png.8bdf71ad18465425bb7ec7a99a56f5ec.png" alt="blog_test_warn_03.png"></p>

<p>
	إن حدث ذلك، اتبع <a data-ss1613481443="1" href="https://mongoosejs.com/docs/jest.html" rel="external nofollow">التعليمات</a> وانشئ ملفًا جديدًا باسم jest.config.js عند جذر المشروع بحيث يحتوي الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_97" style="">
<span class="pln">module</span><span class="pun">.</span><span class="pln">exports </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  testEnvironment</span><span class="pun">:</span><span class="pln"> </span><span class="str">'node'</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	<strong>ملاحظة</strong>: يفضل عند كتابة الاختبارات أن لا تنفذها دفعة واحدة. <a data-ss1613481443="1" href="https://fullstackopen.com/en/part4/testing_the_backend#running-tests-one-by-one" rel="external nofollow">اختبر فقط ذلك الذي تعمل عليه</a>.
</p>

<h3>
	4.9 اختبارات على قائمة المدونات: الخطوة 2 *
</h3>

<p>
	اكتب اختبارًا يتحقق من أن اسم المعرِّف الفريد لكل منشور في المدونة هو id. ستسمي قاعدة البيانات هذه الخاصية بالاسم id_ بشكل افتراضي. يمكن تحقيق ذلك بسهولة عند استخدام تابع المطابقة <a data-ss1613481443="1" href="https://jestjs.io/docs/en/expect#tobedefined" rel="external nofollow">toBeDefined</a> العائد للمكتبة.
</p>

<p>
	أجري التعديلات المناسبة على الشيفرة حتي يُنفذ الاختبار بنجاح. قد يكون التابع <a data-ss1613481443="1" href="https://fullstackopen.com/en/part3/saving_data_to_mongo_db#backend-connected-to-a-database" rel="external nofollow">toJSON</a> الذي قدمناه في القسم3، المكان الأنسب لتعريف المعامل id.
</p>

<h3>
	4.10 اختبارات على قائمة المدونات: الخطوة 3
</h3>

<p>
	اكتب اختبارًا تتحقق فيه أن طلب HTTP POST إلى العنوان api/blogs/، سينشئ بنجاح منشورًا جديدًا. تحقق على الأقل أن العدد الكلي للمنشورات في المدونة قد ازداد بمقدار 1. كما يمكنك التحقق أيضًا، أن محتوى المنشور قد حُفظ بالشكل الصحيح في قاعدة البيانات.
</p>

<p>
	حالما ينجح الاختبار، أعد كتابة الشيفرة مستخدمًا async/await بدلًا من الوعود.
</p>

<h3>
	4.11 اختبارت على قائمة المدونات: الخطوة 4 *
</h3>

<p>
	اكتب اختبارًا تتحقق فيه من وجود الخاصية likes في الطلب، واجلعها 0 إن لم تكن موجودة. لا تختبر بقية خصائص المدونات التي أنشئت.
</p>

<p>
	عدل في الشيفرة حتى تنجز الاختبار بنجاح.
</p>

<h3>
	4.12 اختبارات على قائمة المدونات: الخطوة 5 *
</h3>

<p>
	اكتب اختبارًا متعلقًا بإنشاء مدونة جديدة من خلال الوجهة api/blogs/. حيث يتحقق من وجود الخاصيتين title وurl ضمن بيانات الطلب. فإن لم يعثر عليهما ستستجيب الواجهة الخلفية برمز الحالة 400 (طلب سيء).
</p>

<h2>
	إعادة كتابة الاختبارت
</h2>

<p>
	لم تكتمل تغطيتنا بعد لموضوع الاختبارات. فلم نختبر طلبات مثل GET /api/notes/:id وDELETE /api/notes/:id عندما ترسل بمعرف غير صالح. كما تحتاج عملية تنظيم وتجميع الاختبارات إلى بعض التحسينات، حيث كتبت جميعها في المستوى الأعلى نفسه ضمن ملف الاختبار.
</p>

<p>
	قد تتحسن قابلية قراءة الاختبارات عندما نجمع الاختبارات المترابطة باستخدام كتل الوصف (descripe blocks):
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2615_99" style="">
<span class="kwd">const</span><span class="pln"> supertest </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'supertest'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> mongoose </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'mongoose'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> helper </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'./test_helper'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> app </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'../app'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> api </span><span class="pun">=</span><span class="pln"> supertest</span><span class="pun">(</span><span class="pln">app</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">Note</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'../models/note'</span><span class="pun">)</span><span class="pln">

beforeEach</span><span class="pun">(</span><span class="pln">async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  await </span><span class="typ">Note</span><span class="pun">.</span><span class="pln">deleteMany</span><span class="pun">({})</span><span class="pln">

  </span><span class="kwd">const</span><span class="pln"> noteObjects </span><span class="pun">=</span><span class="pln"> helper</span><span class="pun">.</span><span class="pln">initialNotes
    </span><span class="pun">.</span><span class="pln">map</span><span class="pun">(</span><span class="pln">note </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Note</span><span class="pun">(</span><span class="pln">note</span><span class="pun">))</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> promiseArray </span><span class="pun">=</span><span class="pln"> noteObjects</span><span class="pun">.</span><span class="pln">map</span><span class="pun">(</span><span class="pln">note </span><span class="pun">=&gt;</span><span class="pln"> note</span><span class="pun">.</span><span class="pln">save</span><span class="pun">())</span><span class="pln">
  await </span><span class="typ">Promise</span><span class="pun">.</span><span class="pln">all</span><span class="pun">(</span><span class="pln">promiseArray</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

describe</span><span class="pun">(</span><span class="str">'when there is initially some notes saved'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  test</span><span class="pun">(</span><span class="str">'notes are returned as json'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    await api
      </span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">)</span><span class="pln">
      </span><span class="pun">.</span><span class="pln">expect</span><span class="pun">(</span><span class="lit">200</span><span class="pun">)</span><span class="pln">
      </span><span class="pun">.</span><span class="pln">expect</span><span class="pun">(</span><span class="str">'Content-Type'</span><span class="pun">,</span><span class="pln"> </span><span class="str">/application\/json/</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">

  test</span><span class="pun">(</span><span class="str">'all notes are returned'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> response </span><span class="pun">=</span><span class="pln"> await api</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">)</span><span class="pln">

    expect</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">body</span><span class="pun">).</span><span class="pln">toHaveLength</span><span class="pun">(</span><span class="pln">helper</span><span class="pun">.</span><span class="pln">initialNotes</span><span class="pun">.</span><span class="pln">length</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">

  test</span><span class="pun">(</span><span class="str">'a specific note is within the returned notes'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> response </span><span class="pun">=</span><span class="pln"> await api</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">const</span><span class="pln"> contents </span><span class="pun">=</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">map</span><span class="pun">(</span><span class="pln">r </span><span class="pun">=&gt;</span><span class="pln"> r</span><span class="pun">.</span><span class="pln">content</span><span class="pun">)</span><span class="pln">
    expect</span><span class="pun">(</span><span class="pln">contents</span><span class="pun">).</span><span class="pln">toContain</span><span class="pun">(</span><span class="pln">
      </span><span class="str">'Browser can execute only Javascript'</span><span class="pln">
    </span><span class="pun">)</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

describe</span><span class="pun">(</span><span class="str">'viewing a specific note'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  test</span><span class="pun">(</span><span class="str">'succeeds with a valid id'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> notesAtStart </span><span class="pun">=</span><span class="pln"> await helper</span><span class="pun">.</span><span class="pln">notesInDb</span><span class="pun">()</span><span class="pln">

    </span><span class="kwd">const</span><span class="pln"> noteToView </span><span class="pun">=</span><span class="pln"> notesAtStart</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln">

    </span><span class="kwd">const</span><span class="pln"> resultNote </span><span class="pun">=</span><span class="pln"> await api
      </span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(`/</span><span class="pln">api</span><span class="pun">/</span><span class="pln">notes</span><span class="pun">/</span><span class="pln">$</span><span class="pun">{</span><span class="pln">noteToView</span><span class="pun">.</span><span class="pln">id</span><span class="pun">}`)</span><span class="pln">
      </span><span class="pun">.</span><span class="pln">expect</span><span class="pun">(</span><span class="lit">200</span><span class="pun">)</span><span class="pln">
      </span><span class="pun">.</span><span class="pln">expect</span><span class="pun">(</span><span class="str">'Content-Type'</span><span class="pun">,</span><span class="pln"> </span><span class="str">/application\/json/</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">const</span><span class="pln"> processedNoteToView </span><span class="pun">=</span><span class="pln"> JSON</span><span class="pun">.</span><span class="pln">parse</span><span class="pun">(</span><span class="pln">JSON</span><span class="pun">.</span><span class="pln">stringify</span><span class="pun">(</span><span class="pln">noteToView</span><span class="pun">))</span><span class="pln">

    expect</span><span class="pun">(</span><span class="pln">resultNote</span><span class="pun">.</span><span class="pln">body</span><span class="pun">).</span><span class="pln">toEqual</span><span class="pun">(</span><span class="pln">processedNoteToView</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">

  test</span><span class="pun">(</span><span class="str">'fails with statuscode 404 if note does not exist'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> validNonexistingId </span><span class="pun">=</span><span class="pln"> await helper</span><span class="pun">.</span><span class="pln">nonExistingId</span><span class="pun">()</span><span class="pln">

    console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">validNonexistingId</span><span class="pun">)</span><span class="pln">

    await api
      </span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(`/</span><span class="pln">api</span><span class="pun">/</span><span class="pln">notes</span><span class="pun">/</span><span class="pln">$</span><span class="pun">{</span><span class="pln">validNonexistingId</span><span class="pun">}`)</span><span class="pln">
      </span><span class="pun">.</span><span class="pln">expect</span><span class="pun">(</span><span class="lit">404</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">

  test</span><span class="pun">(</span><span class="str">'fails with statuscode 400 id is invalid'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> invalidId </span><span class="pun">=</span><span class="pln"> </span><span class="str">'5a3d5da59070081a82a3445'</span><span class="pln">

    await api
      </span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(`/</span><span class="pln">api</span><span class="pun">/</span><span class="pln">notes</span><span class="pun">/</span><span class="pln">$</span><span class="pun">{</span><span class="pln">invalidId</span><span class="pun">}`)</span><span class="pln">
      </span><span class="pun">.</span><span class="pln">expect</span><span class="pun">(</span><span class="lit">400</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

describe</span><span class="pun">(</span><span class="str">'addition of a new note'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  test</span><span class="pun">(</span><span class="str">'succeeds with valid data'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> newNote </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      content</span><span class="pun">:</span><span class="pln"> </span><span class="str">'async/await simplifies making async calls'</span><span class="pun">,</span><span class="pln">
      important</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    await api
      </span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">)</span><span class="pln">
      </span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="pln">newNote</span><span class="pun">)</span><span class="pln">
      </span><span class="pun">.</span><span class="pln">expect</span><span class="pun">(</span><span class="lit">200</span><span class="pun">)</span><span class="pln">
      </span><span class="pun">.</span><span class="pln">expect</span><span class="pun">(</span><span class="str">'Content-Type'</span><span class="pun">,</span><span class="pln"> </span><span class="str">/application\/json/</span><span class="pun">)</span><span class="pln">


    </span><span class="kwd">const</span><span class="pln"> notesAtEnd </span><span class="pun">=</span><span class="pln"> await helper</span><span class="pun">.</span><span class="pln">notesInDb</span><span class="pun">()</span><span class="pln">
    expect</span><span class="pun">(</span><span class="pln">notesAtEnd</span><span class="pun">).</span><span class="pln">toHaveLength</span><span class="pun">(</span><span class="pln">helper</span><span class="pun">.</span><span class="pln">initialNotes</span><span class="pun">.</span><span class="pln">length </span><span class="pun">+</span><span class="pln"> </span><span class="lit">1</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">const</span><span class="pln"> contents </span><span class="pun">=</span><span class="pln"> notesAtEnd</span><span class="pun">.</span><span class="pln">map</span><span class="pun">(</span><span class="pln">n </span><span class="pun">=&gt;</span><span class="pln"> n</span><span class="pun">.</span><span class="pln">content</span><span class="pun">)</span><span class="pln">
    expect</span><span class="pun">(</span><span class="pln">contents</span><span class="pun">).</span><span class="pln">toContain</span><span class="pun">(</span><span class="pln">
      </span><span class="str">'async/await simplifies making async calls'</span><span class="pln">
    </span><span class="pun">)</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">

  test</span><span class="pun">(</span><span class="str">'fails with status code 400 if data invaild'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> newNote </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      important</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    await api
      </span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">)</span><span class="pln">
      </span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="pln">newNote</span><span class="pun">)</span><span class="pln">
      </span><span class="pun">.</span><span class="pln">expect</span><span class="pun">(</span><span class="lit">400</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">const</span><span class="pln"> notesAtEnd </span><span class="pun">=</span><span class="pln"> await helper</span><span class="pun">.</span><span class="pln">notesInDb</span><span class="pun">()</span><span class="pln">

    expect</span><span class="pun">(</span><span class="pln">notesAtEnd</span><span class="pun">).</span><span class="pln">toHaveLength</span><span class="pun">(</span><span class="pln">helper</span><span class="pun">.</span><span class="pln">initialNotes</span><span class="pun">.</span><span class="pln">length</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

describe</span><span class="pun">(</span><span class="str">'deletion of a note'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  test</span><span class="pun">(</span><span class="str">'succeeds with status code 204 if id is valid'</span><span class="pun">,</span><span class="pln"> async </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> notesAtStart </span><span class="pun">=</span><span class="pln"> await helper</span><span class="pun">.</span><span class="pln">notesInDb</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> noteToDelete </span><span class="pun">=</span><span class="pln"> notesAtStart</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln">

    await api
      </span><span class="pun">.</span><span class="kwd">delete</span><span class="pun">(`/</span><span class="pln">api</span><span class="pun">/</span><span class="pln">notes</span><span class="pun">/</span><span class="pln">$</span><span class="pun">{</span><span class="pln">noteToDelete</span><span class="pun">.</span><span class="pln">id</span><span class="pun">}`)</span><span class="pln">
      </span><span class="pun">.</span><span class="pln">expect</span><span class="pun">(</span><span class="lit">204</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">const</span><span class="pln"> notesAtEnd </span><span class="pun">=</span><span class="pln"> await helper</span><span class="pun">.</span><span class="pln">notesInDb</span><span class="pun">()</span><span class="pln">

    expect</span><span class="pun">(</span><span class="pln">notesAtEnd</span><span class="pun">).</span><span class="pln">toHaveLength</span><span class="pun">(</span><span class="pln">
      helper</span><span class="pun">.</span><span class="pln">initialNotes</span><span class="pun">.</span><span class="pln">length </span><span class="pun">-</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
    </span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">const</span><span class="pln"> contents </span><span class="pun">=</span><span class="pln"> notesAtEnd</span><span class="pun">.</span><span class="pln">map</span><span class="pun">(</span><span class="pln">r </span><span class="pun">=&gt;</span><span class="pln"> r</span><span class="pun">.</span><span class="pln">content</span><span class="pun">)</span><span class="pln">

    expect</span><span class="pun">(</span><span class="pln">contents</span><span class="pun">).</span><span class="pln">not</span><span class="pun">.</span><span class="pln">toContain</span><span class="pun">(</span><span class="pln">noteToDelete</span><span class="pun">.</span><span class="pln">content</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

afterAll</span><span class="pun">(()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  mongoose</span><span class="pun">.</span><span class="pln">connection</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	جُمّعت مخرجات الاختبارات وفقًا لكتل الوصف:
</p>

<p style="text-align: center;">
	<img class="ipsImage ipsImage_thumbnailed" data-fileid="57771" data-unique="pnwu60rna" src="https://academy.hsoub.com/uploads/monthly_2021_02/test_output_004.png.b670bbe5976e285327f29b77d978beec.png" alt="test_output_004.png"></p>

<p>
	هنالك متسع للتحسينات أيضًا، لكن لابد من المضي قُدمًا.
</p>

<p>
	إن هذه الطريقة في الاختبار التي تعتمد على إجراء طلبات HTTP وتحري قواعد البيانات باستخدام Mongoose، ليست الطريقة الوحيدة وليست الأفضل في إنجاز اختبارات التكامل على مستوى الواجهة البرمجية لتطبيقات الخوادم. في الواقع لا توجد طريقة مثلى معتمدة عالميًا لكتابة الاختبارات، لأنها تعتمد على طبيعة التطبيق المختبر والموارد المتاحة.
</p>

<p>
	يمكنك إيجاد شيفرة التطبيق الحالي بأكملها في الفرع part4-6 ضمن المستودع الخاص بالتطبيق على <a data-ss1613481443="1" href="https://github.com/fullstack-hy2020/part3-notes-backend/tree/part4-6" rel="external nofollow">GitHub</a>.
</p>

<h2>
	التمارين 4.13 - 4.14
</h2>

<h3>
	4.13 التوسع في قائمة المدونات: خطوة 1
</h3>

<p>
	أضف إلى التطبيق وظيفة حذف منشور واحد. استخدم العبارة async/await. إجعل عملك متوافقًا مع REST عندما تعرّف الواجهة البرمجية لطلبات HTTP.
</p>

<p>
	أضف، إن أردت، أية اختبارات للتأكد من عمل الوظيفة، أو استخدم Postman أو أية أداة أخرى.
</p>

<h3>
	4.14 التوسع في قائمة المدونات: خطوة 2
</h3>

<p>
	أضف إلى التطبيق وظيفة تعديل منشور واحد. استخدم العبارة async/await.
</p>

<p>
	أهم ما يتطلبه التطبيق هو تحديث عدد الإعجابات للمنشور. يمكنك إضافة هذه الوظيفة كما حدثنا الملاحظات في القسم 3.
</p>

<p>
	أضف، إن أردت، أية اختبارات للتأكد من عمل الوظيفة، أو استخدم Postman أو أية أداة أخرى.
</p>

<p>
	ترجمة -وبتصرف- للفصل <a data-ss1613481443="1" href="https://fullstackopen.com/en/part4/testing_the_backend" rel="external nofollow">testing the backend</a> من سلسلة <a data-ss1613481443="1" href="https://fullstackopen.com/en/" rel="external nofollow">Deep Dive Into Modern Web Development</a>
</p>
]]></description><guid isPermaLink="false">1133</guid><pubDate>Sun, 14 Feb 2021 13:00:00 +0000</pubDate></item><item><title>&#x647;&#x64A;&#x643;&#x644; &#x62A;&#x637;&#x628;&#x64A;&#x642;&#x627;&#x62A; &#x627;&#x644;&#x648;&#x627;&#x62C;&#x647;&#x629; &#x627;&#x644;&#x62E;&#x644;&#x641;&#x64A;&#x629;: &#x645;&#x62F;&#x62E;&#x644; &#x625;&#x644;&#x649; &#x627;&#x644;&#x627;&#x62E;&#x62A;&#x628;&#x627;&#x631;&#x627;&#x62A; (unit tests)</title><link>https://academy.hsoub.com/programming/javascript/nodejs/%D9%87%D9%8A%D9%83%D9%84-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D8%AE%D9%84%D9%81%D9%8A%D8%A9-%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1%D8%A7%D8%AA-unit-tests-r1132/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2021_02/602bc295101f7_------.png.4d188127ee125fb2fe8d3247c07fa023.png" /></p>

<p>
	سنعود للعمل على نسخة الواجهة الخلفية لتطبيق الملاحظات الذي بدأناه في <a data-ss1613480975="1" href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-nodejs-%D9%88express-r1099/" rel="">القسم 3</a> من سلسلة <a data-ss1613480975="1" href="https://academy.hsoub.com/tags/full_stack_101/" rel="">full_stack_101</a>.
</p>

<h2>
	الهيكل العام للمشروع
</h2>

<p>
	قبل أن ننتقل لموضوع الاختبارات، سنجري بعض التعديلات على هيكلية مشروعنا لنواكب أفضل المعايير في كتابة تطبيقات Node.js. سينتج عن هذه التعديلات الهيكل التالي لمجلد المشروع:
</p>

<pre class="ipsCode">
├── index.js
├── app.js
├── build
│   └── ...
├── controllers
│   └── notes.js
├── models
│   └── note.js
├── package-lock.json
├── package.json
├── utils
│   ├── config.js
│   ├── logger.js
│   └── middleware.js  
</pre>

<p>
	استخدمنا حتى هذه اللحظة الأمرين <code>console.log</code> و<code>console.error</code> في طباعة بيانات الشيفرة التي تهمنا على الطرفية. لكن من الأفضل فصل الشيفرات التي تتعلق بأمور الطباعة في وحدة مستقلة خاصة بها سنسميها logger.js، وسنضعها في المجلد utils:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1466_7" style="">
<span class="kwd">const</span><span class="pln"> info </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(...</span><span class="pln">params</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(...</span><span class="pln">params</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> error </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(...</span><span class="pln">params</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">error</span><span class="pun">(...</span><span class="pln">params</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

module</span><span class="pun">.</span><span class="pln">exports </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  info</span><span class="pun">,</span><span class="pln"> error
</span><span class="pun">}</span></pre>

<p>
	كما نلاحظ، تحتوي الوحدة على دالتين الأولى <code>info</code> تتولى أمر طباعة الرسائل العادية، والأخرى <code>error</code> تتولى طباعة رسائل الخطأ.
</p>

<p>
	لوضع شيفرة الطباعة في وحدة منفصلة مزايا عدة، فلو أردنا مثلًا طباعة السجلات أو الرسائل إلى ملف أو إلى خدمة خارجية لإدارة السجلات مثل <a data-ss1613480582="1" data-ss1613480975="1" href="https://www.graylog.org/" rel="external nofollow">graylog</a> أو <a data-ss1613480582="1" data-ss1613480975="1" href="https://papertrailapp.com/" rel="external nofollow">papertrail</a>، لن يكون علينا سوى تعديل الشيفرة في ملف واحد.
</p>

<p>
	سيصبح ملف تشغيل التطبيق index.js على الشكل التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1466_9" style="">
<span class="kwd">const</span><span class="pln"> app </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'./app'</span><span class="pun">)</span><span class="pln"> </span><span class="com">// the actual Express application</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> http </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'http'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> config </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'./utils/config'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> logger </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'./utils/logger'</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> server </span><span class="pun">=</span><span class="pln"> http</span><span class="pun">.</span><span class="pln">createServer</span><span class="pun">(</span><span class="pln">app</span><span class="pun">)</span><span class="pln">

server</span><span class="pun">.</span><span class="pln">listen</span><span class="pun">(</span><span class="pln">config</span><span class="pun">.</span><span class="pln">PORT</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  logger</span><span class="pun">.</span><span class="pln">info</span><span class="pun">(`</span><span class="typ">Server</span><span class="pln"> running on port $</span><span class="pun">{</span><span class="pln">config</span><span class="pun">.</span><span class="pln">PORT</span><span class="pun">}`)</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	تنحصر وظيفة الملف index.js الآن، بإدراج التطبيق الفعلي من الملف app.js وتشغيله. ستقوم الدالة <code>info</code> بطباعة عبارة على الطرفية تدل على أن التطبيق يعمل.
</p>

<p>
	كذلك سننقل الشيفرة التي تتعامل مع متغيرات البيئة إلى وحدة مستقلة اسمها config.js ونضعها في المجلد utils:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1466_11" style="">
<span class="pln">require</span><span class="pun">(</span><span class="str">'dotenv'</span><span class="pun">).</span><span class="pln">config</span><span class="pun">()</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> PORT </span><span class="pun">=</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">PORT
</span><span class="kwd">const</span><span class="pln"> MONGODB_URI </span><span class="pun">=</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">MONGODB_URI

module</span><span class="pun">.</span><span class="pln">exports </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  MONGODB_URI</span><span class="pun">,</span><span class="pln">
  PORT
</span><span class="pun">}</span></pre>

<p>
	يمكن لأي جزء من التطبيق الوصول إلى متغيرات البيئة بإدراج الوحدة السابقة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1466_13" style="">
<span class="kwd">const</span><span class="pln"> config </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'./utils/config'</span><span class="pun">)</span><span class="pln">

logger</span><span class="pun">.</span><span class="pln">info</span><span class="pun">(`</span><span class="typ">Server</span><span class="pln"> running on port $</span><span class="pun">{</span><span class="pln">config</span><span class="pun">.</span><span class="pln">PORT</span><span class="pun">}`)</span></pre>

<p>
	نقلنا كذلك الشيفرة التي تتعامل مع المسارات إلى وحدة خاصة. وطالما أن مصطلح "متحكمات" يستخدم للإشارة إلى هذه المسارات، سننشئ مجلدًا باسم controller وسنضع فيه الوحدة notes.js التي ستحتوي شيفرة المسارات التي تتعلق بالملاحظات. سيكون محتوى هذه الوحدة كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1466_15" style="">
<span class="kwd">const</span><span class="pln"> notesRouter </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'express'</span><span class="pun">).</span><span class="typ">Router</span><span class="pun">()</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">Note</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'../models/note'</span><span class="pun">)</span><span class="pln">

notesRouter</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="typ">Note</span><span class="pun">.</span><span class="pln">find</span><span class="pun">({}).</span><span class="pln">then</span><span class="pun">(</span><span class="pln">notes </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">notes</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

notesRouter</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/:id'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="typ">Note</span><span class="pun">.</span><span class="pln">findById</span><span class="pun">(</span><span class="pln">request</span><span class="pun">.</span><span class="pln">params</span><span class="pun">.</span><span class="pln">id</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">then</span><span class="pun">(</span><span class="pln">note </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">note</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">note</span><span class="pun">)</span><span class="pln">
      </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">404</span><span class="pun">).</span><span class="pln">end</span><span class="pun">()</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">})</span><span class="pln">
    </span><span class="pun">.</span><span class="kwd">catch</span><span class="pun">(</span><span class="pln">error </span><span class="pun">=&gt;</span><span class="pln"> next</span><span class="pun">(</span><span class="pln">error</span><span class="pun">))</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

notesRouter</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> body </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">body

  </span><span class="kwd">const</span><span class="pln"> note </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Note</span><span class="pun">({</span><span class="pln">
    content</span><span class="pun">:</span><span class="pln"> body</span><span class="pun">.</span><span class="pln">content</span><span class="pun">,</span><span class="pln">
    important</span><span class="pun">:</span><span class="pln"> body</span><span class="pun">.</span><span class="pln">important </span><span class="pun">||</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">,</span><span class="pln">
    date</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">()</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">

  note</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">then</span><span class="pun">(</span><span class="pln">savedNote </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">savedNote</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">})</span><span class="pln">
    </span><span class="pun">.</span><span class="kwd">catch</span><span class="pun">(</span><span class="pln">error </span><span class="pun">=&gt;</span><span class="pln"> next</span><span class="pun">(</span><span class="pln">error</span><span class="pun">))</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

notesRouter</span><span class="pun">.</span><span class="kwd">delete</span><span class="pun">(</span><span class="str">'/:id'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="typ">Note</span><span class="pun">.</span><span class="pln">findByIdAndRemove</span><span class="pun">(</span><span class="pln">request</span><span class="pun">.</span><span class="pln">params</span><span class="pun">.</span><span class="pln">id</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">then</span><span class="pun">(()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">204</span><span class="pun">).</span><span class="pln">end</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">})</span><span class="pln">
    </span><span class="pun">.</span><span class="kwd">catch</span><span class="pun">(</span><span class="pln">error </span><span class="pun">=&gt;</span><span class="pln"> next</span><span class="pun">(</span><span class="pln">error</span><span class="pun">))</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

notesRouter</span><span class="pun">.</span><span class="pln">put</span><span class="pun">(</span><span class="str">'/:id'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> body </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">body

  </span><span class="kwd">const</span><span class="pln"> note </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    content</span><span class="pun">:</span><span class="pln"> body</span><span class="pun">.</span><span class="pln">content</span><span class="pun">,</span><span class="pln">
    important</span><span class="pun">:</span><span class="pln"> body</span><span class="pun">.</span><span class="pln">important</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  </span><span class="typ">Note</span><span class="pun">.</span><span class="pln">findByIdAndUpdate</span><span class="pun">(</span><span class="pln">request</span><span class="pun">.</span><span class="pln">params</span><span class="pun">.</span><span class="pln">id</span><span class="pun">,</span><span class="pln"> note</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="kwd">new</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln"> </span><span class="pun">})</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">then</span><span class="pun">(</span><span class="pln">updatedNote </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">updatedNote</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">})</span><span class="pln">
    </span><span class="pun">.</span><span class="kwd">catch</span><span class="pun">(</span><span class="pln">error </span><span class="pun">=&gt;</span><span class="pln"> next</span><span class="pun">(</span><span class="pln">error</span><span class="pun">))</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

module</span><span class="pun">.</span><span class="pln">exports </span><span class="pun">=</span><span class="pln"> notesRouter</span></pre>

<p>
	هذه الشيفرة هي محتوى الملف index.js القديم إجمالًا ما عدا بعض التغييرات المهمة. حيث أنشأنا كائن <a data-ss1613480582="1" data-ss1613480975="1" href="http://expressjs.com/en/api.html#router" rel="external nofollow"> متحكم بالمسار</a> في بداية الملف:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1466_17" style="">
<span class="kwd">const</span><span class="pln"> notesRouter </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'express'</span><span class="pun">).</span><span class="typ">Router</span><span class="pun">()</span><span class="pln">

</span><span class="com">//...</span><span class="pln">

module</span><span class="pun">.</span><span class="pln">exports </span><span class="pun">=</span><span class="pln"> notesRouter</span></pre>

<p>
	لقد أدرجت الوحدة متحكمًا بالمسار ليكون متاحًا للاستخدام ضمنها. وهكذا تُستدعى المسارات التي نحتاجها كخصائص لكائن التحكم بالمسار بشكل مماثل لما يفعله الكائن الذي يتحكم بكامل التطبيق.
</p>

<p>
	ويجب الانتباه إلى الطريقة التي اختصرنا فيها عنوان المسار. فلقد عرفنا في النسخة السابقة مسارًا على الشكل:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1466_19" style="">
<span class="pln">app</span><span class="pun">.</span><span class="kwd">delete</span><span class="pun">(</span><span class="str">'/api/notes/:id'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span></pre>

<p>
	بينما أصبح التعريف في النسخة الجديدة كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1466_21" style="">
<span class="pln">notesRouter</span><span class="pun">.</span><span class="kwd">delete</span><span class="pun">(</span><span class="str">'/:id'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span></pre>

<p>
	ما هو إذًا بالتحديد كائن التحكم بالمسار؟ يزودنا توثيق المكتبة express بالتعريف التالي:
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		يعرّف كائن التحكم بالمسار على أنه حالة برمجية تضم أداة وسطية ومسارات. يمكن اعتباره "تطبيقًا مصغرًا" قادر على القيام بوظائف الأداة الوسطية ووظائف المسارات. يضم كل تطبيق Express متحكم مسار مدمج معه.
	</p>
</blockquote>

<p>
	يمثل المتحكم بالمسار في واقع الأمر أداة وسطية يمكن استخدامها لتعريف العمليات المتعلقة بالمسارات في مكان واحد، وهو عادة وحدة منفصلة خاصة.
</p>

<p>
	يستخدم الملف app.js -الذي ينشئ التطبيق الوظيفي المطلوب- هذا المتحكم بالطريقة التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1466_23" style="">
<span class="kwd">const</span><span class="pln"> notesRouter </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'./controllers/notes'</span><span class="pun">)</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">,</span><span class="pln"> notesRouter</span><span class="pun">)</span></pre>

<p>
	يُستخدم المتحكم بالمسار الذي عرفناه في الشيفرة السابقة، إذا كان الطلب موجهًا إلى الموقع الذي يبدأ عنوانه بالصيغة "/api/notes". ولهذا على المتحكم <code>notesRouter</code> أن يعرّف المسارت بعناوين نسبية فقط على شكل مسار فارغ "/" أو أن يذكر المعامل فقط "id:/".
</p>

<p>
	سيبدو الملف app.js بعد إجراء التعديلات على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1466_25" style="">
<span class="kwd">const</span><span class="pln"> config </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'./utils/config'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> express </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'express'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> app </span><span class="pun">=</span><span class="pln"> express</span><span class="pun">()</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> cors </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'cors'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> notesRouter </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'./controllers/notes'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> middleware </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'./utils/middleware'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> logger </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'./utils/logger'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> mongoose </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'mongoose'</span><span class="pun">)</span><span class="pln">

logger</span><span class="pun">.</span><span class="pln">info</span><span class="pun">(</span><span class="str">'connecting to'</span><span class="pun">,</span><span class="pln"> config</span><span class="pun">.</span><span class="pln">MONGODB_URI</span><span class="pun">)</span><span class="pln">

mongoose</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="pln">config</span><span class="pun">.</span><span class="pln">MONGODB_URI</span><span class="pun">,</span><span class="pln">
 </span><span class="pun">{</span><span class="pln"> useNewUrlParser</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln"> useUnifiedTopology</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln"> </span><span class="pun">})</span><span class="pln">
  </span><span class="pun">.</span><span class="pln">then</span><span class="pun">(()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    logger</span><span class="pun">.</span><span class="pln">info</span><span class="pun">(</span><span class="str">'connected to MongoDB'</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">
  </span><span class="pun">.</span><span class="kwd">catch</span><span class="pun">((</span><span class="pln">error</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    logger</span><span class="pun">.</span><span class="pln">error</span><span class="pun">(</span><span class="str">'error connection to MongoDB:'</span><span class="pun">,</span><span class="pln"> error</span><span class="pun">.</span><span class="pln">message</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">

app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">cors</span><span class="pun">())</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">express</span><span class="pun">.</span><span class="kwd">static</span><span class="pun">(</span><span class="str">'build'</span><span class="pun">))</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">express</span><span class="pun">.</span><span class="pln">json</span><span class="pun">())</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">middleware</span><span class="pun">.</span><span class="pln">requestLogger</span><span class="pun">)</span><span class="pln">

app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">,</span><span class="pln"> notesRouter</span><span class="pun">)</span><span class="pln">

app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">middleware</span><span class="pun">.</span><span class="pln">unknownEndpoint</span><span class="pun">)</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">middleware</span><span class="pun">.</span><span class="pln">errorHandler</span><span class="pun">)</span><span class="pln">

module</span><span class="pun">.</span><span class="pln">exports </span><span class="pun">=</span><span class="pln"> app</span></pre>

<p>
	ستجد أن التطبيق قد استخدم العديد من الأدوات الوسطية ومنها الأداة <code>notesRouter</code> التي ارتبطت بالمسار "api/notes/". نقلنا أيضًا الأداة الوسطية الخاصة التي أنشأناها في القسم السابق باسم middleware.js إلى المجلد utils بعد إجراء القليل من التعديلات:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1466_27" style="">
<span class="kwd">const</span><span class="pln"> logger </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'./logger'</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> requestLogger </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  logger</span><span class="pun">.</span><span class="pln">info</span><span class="pun">(</span><span class="str">'Method:'</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">method</span><span class="pun">)</span><span class="pln">
  logger</span><span class="pun">.</span><span class="pln">info</span><span class="pun">(</span><span class="str">'Path:  '</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">path</span><span class="pun">)</span><span class="pln">
  logger</span><span class="pun">.</span><span class="pln">info</span><span class="pun">(</span><span class="str">'Body:  '</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">body</span><span class="pun">)</span><span class="pln">
  logger</span><span class="pun">.</span><span class="pln">info</span><span class="pun">(</span><span class="str">'---'</span><span class="pun">)</span><span class="pln">
  next</span><span class="pun">()</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> unknownEndpoint </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">404</span><span class="pun">).</span><span class="pln">send</span><span class="pun">({</span><span class="pln"> error</span><span class="pun">:</span><span class="pln"> </span><span class="str">'unknown endpoint'</span><span class="pln"> </span><span class="pun">})</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> errorHandler </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">error</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  logger</span><span class="pun">.</span><span class="pln">error</span><span class="pun">(</span><span class="pln">error</span><span class="pun">.</span><span class="pln">message</span><span class="pun">)</span><span class="pln">

  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">error</span><span class="pun">.</span><span class="pln">name </span><span class="pun">===</span><span class="pln"> </span><span class="str">'CastError'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">400</span><span class="pun">).</span><span class="pln">send</span><span class="pun">({</span><span class="pln"> error</span><span class="pun">:</span><span class="pln"> </span><span class="str">'malformatted id'</span><span class="pln"> </span><span class="pun">})</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">error</span><span class="pun">.</span><span class="pln">name </span><span class="pun">===</span><span class="pln"> </span><span class="str">'ValidationError'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">400</span><span class="pun">).</span><span class="pln">json</span><span class="pun">({</span><span class="pln"> error</span><span class="pun">:</span><span class="pln"> error</span><span class="pun">.</span><span class="pln">message </span><span class="pun">})</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  next</span><span class="pun">(</span><span class="pln">error</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

module</span><span class="pun">.</span><span class="pln">exports </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  requestLogger</span><span class="pun">,</span><span class="pln">
  unknownEndpoint</span><span class="pun">,</span><span class="pln">
  errorHandler
</span><span class="pun">}</span></pre>

<p>
	أُوكلت مهمة الاتصال مع قاعدة البيانات إلى الوحدة الرئيسية app.js. بينما تتحمل الوحدة note.js الموجودة في المجلد models مسؤولية تعريف تخطيطات Mongoose للملاحظات.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1466_29" style="">
<span class="kwd">const</span><span class="pln"> mongoose </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'mongoose'</span><span class="pun">)</span><span class="pln">

mongoose</span><span class="pun">.</span><span class="kwd">set</span><span class="pun">(</span><span class="str">'useFindAndModify'</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> noteSchema </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> mongoose</span><span class="pun">.</span><span class="typ">Schema</span><span class="pun">({</span><span class="pln">
  content</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    type</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</span><span class="pun">,</span><span class="pln">
    required</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
    minlength</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  date</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    type</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">,</span><span class="pln">
    required</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  important</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Boolean</span><span class="pun">,</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

noteSchema</span><span class="pun">.</span><span class="kwd">set</span><span class="pun">(</span><span class="str">'toJSON'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  transform</span><span class="pun">:</span><span class="pln"> </span><span class="pun">(</span><span class="pln">document</span><span class="pun">,</span><span class="pln"> returnedObject</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    returnedObject</span><span class="pun">.</span><span class="pln">id </span><span class="pun">=</span><span class="pln"> returnedObject</span><span class="pun">.</span><span class="pln">_id</span><span class="pun">.</span><span class="pln">toString</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">delete</span><span class="pln"> returnedObject</span><span class="pun">.</span><span class="pln">_id
    </span><span class="kwd">delete</span><span class="pln"> returnedObject</span><span class="pun">.</span><span class="pln">__v
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

module</span><span class="pun">.</span><span class="pln">exports </span><span class="pun">=</span><span class="pln"> mongoose</span><span class="pun">.</span><span class="pln">model</span><span class="pun">(</span><span class="str">'Note'</span><span class="pun">,</span><span class="pln"> noteSchema</span><span class="pun">)</span></pre>

<p>
	باختصار، سيبدو هيكل المشروع بشكله الجديد كالتالي:
</p>

<pre class="ipsCode">
├── index.js
├── app.js
├── build
│   └── ...
├── controllers
│   └── notes.js
├── models
│   └── note.js
├── package-lock.json
├── package.json
├── utils
│   ├── config.js
│   ├── logger.js
│   └── middleware.js  
</pre>

<p>
	لا نهتم كثيرًا في التطبيقات الصغيرة بهيكلية مشروع التطبيق، لكن بمجرد أن يبدأ التطبيق بالنمو، لابد من تأسيس هيكل بشكل أو بآخر، وتوزيع العمل على وحدات منفصلة. بهذا يغدو تطوير التطبيق أسهل.
</p>

<p>
	لا توجد معايير أو تفاهمات خاصة بين المطورين على تسمية مجلدات المشروع عند بناء تطبيقات express. بالمقابل تحتاج بعض المكتبات مثل Ruby on Rails هيكلًا خاصًا.
</p>

<p>
	يقدم الهيكل الذي بنيناه لتطبيقنا السابق، واحدًا من أفضل الأمثلة لبناء التطبيقات التي قد تصادفها على الانترنت.
</p>

<p>
	ستجد التطبيق بشكله الكامل في الفرع part4-1 على <a data-ss1613480582="1" data-ss1613480975="1" href="https://github.com/fullstack-hy2020/part3-notes-backend/tree/part4-1" rel="external nofollow">GitHub</a>.
</p>

<p>
	إن نسخت المشروع لتحتفظ به لنفسك، نفذ الأمر <code>npm install</code>، قبل أن تشغل التطبيق بالأمر <code>npm run</code>.
</p>

<h2>
	التمارين 4.1 - 4.2
</h2>

<p>
	سنبني خلال تمارين هذا القسم تطبيقًا لإنشاء قائمة مدونات تسمح للمستخدم أن يحفظ معلومات عن مدونات وجدها مهمة خلال تصفحه للإنترنت. سيقوم التطبيق بحفظ اسم المؤلف وعنوان المدونة وعنوان موقعها وعدد التقييمات الإيجابية للمدونة من قبل مستخدمي التطبيق.
</p>

<h3>
	4.1 قائمة بالمدونات: الخطوة 1
</h3>

<p>
	لنتخيل أنك تلقيت بريدًا إلكترونيًا له جسم التطبيق التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1466_32" style="">
<span class="kwd">const</span><span class="pln"> http </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'http'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> express </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'express'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> app </span><span class="pun">=</span><span class="pln"> express</span><span class="pun">()</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> cors </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'cors'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> mongoose </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'mongoose'</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> blogSchema </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> mongoose</span><span class="pun">.</span><span class="typ">Schema</span><span class="pun">({</span><span class="pln">
  title</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</span><span class="pun">,</span><span class="pln">
  author</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</span><span class="pun">,</span><span class="pln">
  url</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</span><span class="pun">,</span><span class="pln">
  likes</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Number</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">Blog</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> mongoose</span><span class="pun">.</span><span class="pln">model</span><span class="pun">(</span><span class="str">'Blog'</span><span class="pun">,</span><span class="pln"> blogSchema</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> mongoUrl </span><span class="pun">=</span><span class="pln"> </span><span class="str">'mongodb://localhost/bloglist'</span><span class="pln">
mongoose</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="pln">mongoUrl</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> useNewUrlParser</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln"> useUnifiedTopology</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln"> </span><span class="pun">})</span><span class="pln">

app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">cors</span><span class="pun">())</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">express</span><span class="pun">.</span><span class="pln">json</span><span class="pun">())</span><span class="pln">

app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/api/blogs'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="typ">Blog</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">find</span><span class="pun">({})</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">then</span><span class="pun">(</span><span class="pln">blogs </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">blogs</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">})</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

app</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/api/blogs'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> blog </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Blog</span><span class="pun">(</span><span class="pln">request</span><span class="pun">.</span><span class="pln">body</span><span class="pun">)</span><span class="pln">

  blog
    </span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">then</span><span class="pun">(</span><span class="pln">result </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">201</span><span class="pun">).</span><span class="pln">json</span><span class="pun">(</span><span class="pln">result</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">})</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> PORT </span><span class="pun">=</span><span class="pln"> </span><span class="lit">3003</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">listen</span><span class="pun">(</span><span class="pln">PORT</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(`</span><span class="typ">Server</span><span class="pln"> running on port $</span><span class="pun">{</span><span class="pln">PORT</span><span class="pun">}`)</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	حول التطبيق إلى مشروع npm قابل للعمل. هيئ التطبيق ليُنفّذ بمساعدة nodemon. يمكنك أن تنشئ قاعدة بيانات جديدة على MongoDB Atlas، أو استخدم قاعدة البيانات التي أنشأناها في تمرينات القسم السابق. تأكد من إمكانية إضافة مدونات إلى القائمة باستخدام Postman أو VS Code REST client وتأكد كذلك أن التطبيق سيعيد المدونات التي أضيفت إلى الجهة التي طلبتها.
</p>

<h3>
	4.2 قائمة بالمدونات: الخطوة 2
</h3>

<p>
	قسم التطبيق إلى وحدات منفصلة كما فعلنا سابقًا في هذا القسم.
</p>

<p>
	<strong>ملاحظة</strong>: نفذ التقسيم بخطوات صغيرة خطوة تلو الأخرى، وتأكد أن التطبيق سيعمل بعد كل تغيير. إن حاولت أن تختصر الطريق وتقوم بعدة خطوات في آن واحد سيفشل شيء ما في تطبيقك فهذا السلوك خاضع <a data-ss1613480582="1" data-ss1613480975="1" href="https://en.wikipedia.org/wiki/Murphy's_law" rel="external nofollow">لقانون مورفي</a>. وستستغرق وقتًا في إيجاد حل لمشكلتك أكثر من الوقت الذي ستستغرقه في تنفيذ التقسيم على خطوات منهجية وصغيرة.
</p>

<p>
	حاول أن توضح ما فعلت من خلال التعليقات وخاصة عندما تنجح الخطوة التي نفذتها، سيساعدك هذا في التراجع إلى النقطة التي عَمل فيها التطبيق بشكل جيد.
</p>

<h2>
	اختبار تطبيقات Node
</h2>

<p>
	لقد أهملنا كليًا ناحية مهمة من نواحي تطوير البرمجيات وهي الاختبارات المؤتمتة. سنبدأ رحلتنا في مجال الاختبارات، باختبارات الأجزاء unit test. إن منطق التطبيق الذي نعمل عليه بسيط فلن يكون موضوع اختبارات الأجزاء مهمًا لهذه الدرجة. مع ذلك لننشئ ملفًا جديدًا باسم for_testing.js في المجلد utils ولنعرف فيه دالتين نستخدمهما لاختبار بعض نواحي كتابة الشيفرة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1466_34" style="">
<span class="kwd">const</span><span class="pln"> palindrome </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">string</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> string
    </span><span class="pun">.</span><span class="pln">split</span><span class="pun">(</span><span class="str">''</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">reverse</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="str">''</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> average </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">array</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
 </span><span class="kwd">const</span><span class="pln"> reducer </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">sum</span><span class="pun">,</span><span class="pln"> item</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> sum </span><span class="pun">+</span><span class="pln"> item
  </span><span class="pun">}</span><span class="pln">

  </span><span class="kwd">return</span><span class="pln"> array</span><span class="pun">.</span><span class="pln">reduce</span><span class="pun">(</span><span class="pln">reducer</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> array</span><span class="pun">.</span><span class="pln">length
</span><span class="pun">}</span><span class="pln">

module</span><span class="pun">.</span><span class="pln">exports </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  palindrome</span><span class="pun">,</span><span class="pln">
  average</span><span class="pun">,</span><span class="pln">
</span><span class="pun">}</span></pre>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		تستخدم الدالة average تابع المصفوفات <a data-ss1613480582="1" data-ss1613480975="1" href="https://wiki.hsoub.com/JavaScript/Array/reduce" rel="external">reduce</a>. إن لم تكن على دراية باستخدام هذا التابع، فمن المفيد أن تتابع مقاطع الفيديو الثلاثة الأولى من سلسلة <a data-ss1613480582="1" data-ss1613480975="1" href="https://www.youtube.com/watch?v=BMUiFMZr7vk&amp;list=PL0zVEGEvSaeEd9hlmCXrk5yUyqUag-n84" rel="external nofollow">Functional Javascript</a> على YouTyube.
	</p>
</blockquote>

<p>
	هناك الكثير من المكتبات أو أدوات الاختبار التي تُستخدم مع JavaScript. سنستخدم في منهاجنا المكتبة <a data-ss1613480582="1" data-ss1613480975="1" href="https://jestjs.io/" rel="external nofollow">Jest</a> التي طورت واستخدمت من قبل Facebook. وتشابه هذه المكتبة مكتبة <a data-ss1613480582="1" data-ss1613480975="1" href="https://mochajs.org/" rel="external nofollow">Mocha</a> الزعيمة السابقة لأدوات اختبار JavaScript. ومن البدائل المطروحة أيضًا مكتبة <a data-ss1613480582="1" data-ss1613480975="1" href="https://github.com/avajs/ava" rel="external nofollow">ava</a> التي اكتسبت شعبية كبيرة في بعض النطاقات.
</p>

<p>
	سيكون اختيار Jest أمرًا طبيعيًا في منهاجنا، فهي تعمل بشكل جيد عند إجراء الاختبارات على الواجهة الخلفية، وتقدم أداء رائعًا عند إجراء اختبارات على تطبيقات React.
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		لمستخدمي windows: يمكن أن لا تعمل Jest إن كان في مسار المشروع مجلدات تحتوي أسماءها على فراغات.
	</p>
</blockquote>

<p>
	طالما أن الاختبارات ستنفذ فقط في مرحلة التطوير، سنثبت Jest كاعتمادية للتطوير:
</p>

<pre class="ipsCode">
npm install --save-dev jest
</pre>

<p>
	لنعرف سكربت npm باسم test لتنفيذ الاختبارات باستخدام Jest وإنشاء تقرير عن التنفيذ باستخدام الأسلوب verbose:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1466_36" style="">
<span class="pun">{</span><span class="pln">
  </span><span class="com">//...</span><span class="pln">
  </span><span class="str">"scripts"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">"start"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"node index.js"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"dev"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"nodemon index.js"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"build:ui"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"rm -rf build &amp;&amp; cd ../../../2/luento/notes &amp;&amp; npm run build &amp;&amp; cp -r build ../../../3/luento/notes-backend"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"deploy"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"git push heroku master"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"deploy:full"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"npm run build:ui &amp;&amp; git add . &amp;&amp; git commit -m uibuild &amp;&amp; git push &amp;&amp; npm run deploy"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"logs:prod"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"heroku logs --tail"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"lint"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"eslint ."</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"test"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"jest --verbose"</span><span class="pln">  </span><span class="pun">},</span><span class="pln">
  </span><span class="com">//...</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تحتاج المكتبة Jest سكربت لنحدد فيه أن بيئة التنفيذ هي Node. ويتم ذلك بإضافة السطر التالي في نهاية الملف package.json:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1466_38" style="">
<span class="pun">{</span><span class="pln">
 </span><span class="com">//...</span><span class="pln">
 </span><span class="str">"jest"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="str">"testEnvironment"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"node"</span><span class="pln">
 </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	أو بدلًا عن ذلك، تبحث Jest عن ملف تهيئة باسم jest.config.js، حيث تُعرّف داخله بيئة التنفيذ كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1466_42" style="">
<span class="pln">module</span><span class="pun">.</span><span class="pln">exports </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  testEnvironment</span><span class="pun">:</span><span class="pln"> </span><span class="str">'node'</span><span class="pun">,</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	سننشئ مجلدًا مستقلًا للاختبارات يدعى tests ونضع فيه الملف palindrome.test.js الذي يحتوي الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1466_40" style="">
<span class="kwd">const</span><span class="pln"> palindrome </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'../utils/for_testing'</span><span class="pun">).</span><span class="pln">palindrome

test</span><span class="pun">(</span><span class="str">'palindrome of a'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> result </span><span class="pun">=</span><span class="pln"> palindrome</span><span class="pun">(</span><span class="str">'a'</span><span class="pun">)</span><span class="pln">

  expect</span><span class="pun">(</span><span class="pln">result</span><span class="pun">).</span><span class="pln">toBe</span><span class="pun">(</span><span class="str">'a'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

test</span><span class="pun">(</span><span class="str">'palindrome of react'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> result </span><span class="pun">=</span><span class="pln"> palindrome</span><span class="pun">(</span><span class="str">'react'</span><span class="pun">)</span><span class="pln">

  expect</span><span class="pun">(</span><span class="pln">result</span><span class="pun">).</span><span class="pln">toBe</span><span class="pun">(</span><span class="str">'tcaer'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

test</span><span class="pun">(</span><span class="str">'palindrome of releveler'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> result </span><span class="pun">=</span><span class="pln"> palindrome</span><span class="pun">(</span><span class="str">'releveler'</span><span class="pun">)</span><span class="pln">

  expect</span><span class="pun">(</span><span class="pln">result</span><span class="pun">).</span><span class="pln">toBe</span><span class="pun">(</span><span class="str">'releveler'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	ستعترض قواعد تهيئة المدقق ESLint الذي أضفناها إلى المشروع في الفصل السابق على الأمرين <code>test</code> و <code>expect</code> في ملف الاختبار، ذلك أن قواعد التهيئة لا تسمح بوجود متغيرات شاملة Globals. لنتخلص من ذلك بإضافة القيمة jest": true" إلى الخاصية <code>env</code> في الملف eslintrc.js:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1466_44" style="">
<span class="pln">module</span><span class="pun">.</span><span class="pln">exports </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="str">"env"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">"commonjs"</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln"> 
    </span><span class="str">"es6"</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"node"</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"jest"</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">  </span><span class="pun">},</span><span class="pln">
  </span><span class="str">"extends"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"eslint:recommended"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"rules"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// ...</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	يدرج الملف الاختبار في السطر الأول الدالة التي سنختبرها، ويسندها إلى متغير يدعى palindrome:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1466_46" style="">
<span class="kwd">const</span><span class="pln"> palindrome </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'../utils/for_testing'</span><span class="pun">).</span><span class="pln">palindrome</span></pre>

<p>
	تُعرّف الحالات المختبرة بشكل فردي باستخدام الدالة <code>test</code>. تقبل الدالة معاملين، الأول سلسلة نصية تصف الاختبار والثاني دالة تُعرِّف طريقة تنفيذ اختبار الحالة.
</p>

<p>
	تبدو طريقة اختبار الحالة الثانية في شيفرة ملف الاختبار palindrome.js كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1466_48" style="">
<span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> result </span><span class="pun">=</span><span class="pln"> palindrome</span><span class="pun">(</span><span class="str">'react'</span><span class="pun">)</span><span class="pln">

  expect</span><span class="pun">(</span><span class="pln">result</span><span class="pun">).</span><span class="pln">toBe</span><span class="pun">(</span><span class="str">'tcaer'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	ننفذ أولًا الشيفرة التي سنختبرها، حيث نولد سلسلة معكوسة للنص "react". ثم نتحقق من النتيجة باستخدام الدالة <a data-ss1613480582="1" data-ss1613480975="1" href="https://facebook.github.io/jest/docs/en/expect.html#content" rel="external nofollow">expect</a>. تغلف الدالة <code>expect</code> القيمة الناتجة على شكل كائن يقبل الكثير من توابع المطابقة التي يمكن استخدامها للتحقق من صحة النتيجة. يمكن استخدام تابع المطابقة <a data-ss1613480582="1" data-ss1613480975="1" href="https://facebook.github.io/jest/docs/en/expect.html#tobevalue" rel="external nofollow">toBe</a> كون عملية المقارنة في حالتنا بين سلسلتين نصيتين.
</p>

<p>
	كما هو متوقع ستنجح جميع الاختبارات:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="57768" data-ss1613480582="1" data-ss1613480975="1" href="https://academy.hsoub.com/uploads/monthly_2021_02/test_pass_001.png.7a6ecb25107026b16fcd579dd9663edc.png" rel=""><img alt="test_pass_001.png" class="ipsImage ipsImage_thumbnailed" data-fileid="57768" data-unique="k6o7vx7fu" src="https://academy.hsoub.com/uploads/monthly_2021_02/test_pass_001.png.7a6ecb25107026b16fcd579dd9663edc.png"></a>
</p>

<p>
	تتوقع المكتبة Jest أن تحتوي أسماء ملفات الاختبارات العبارة "test." وسنلتزم في منهاجنا بهذا التوجيه وسنجعل لاحقة كل ملفات الاختبار على الشكل "test.js."
</p>

<p>
	تتمتع Jest بإظهارها رسائل خطأ ممتازة، سنجعل الاختبار يفشل لعرض ذلك:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1466_50" style="">
<span class="pln">test</span><span class="pun">(</span><span class="str">'palindrom of react'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> result </span><span class="pun">=</span><span class="pln"> palindrome</span><span class="pun">(</span><span class="str">'react'</span><span class="pun">)</span><span class="pln">

  expect</span><span class="pun">(</span><span class="pln">result</span><span class="pun">).</span><span class="pln">toBe</span><span class="pun">(</span><span class="str">'tkaer'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	عند تنفيذ الاختبار السابق ستظهر رسالة الخطأ التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="57767" data-ss1613480582="1" data-ss1613480975="1" href="https://academy.hsoub.com/uploads/monthly_2021_02/jest_error_msg_002.png.41065f6612ee702d915a1eccde3edbeb.png" rel=""><img alt="jest_error_msg_002.png" class="ipsImage ipsImage_thumbnailed" data-fileid="57767" data-unique="f5vg7ys5s" src="https://academy.hsoub.com/uploads/monthly_2021_02/jest_error_msg_002.png.41065f6612ee702d915a1eccde3edbeb.png"></a>
</p>

<p>
	لنضف الآن عدة اختبارات إلى الدالة <code>average</code> في الملف average.test.js الموجود في المجلد tests.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1466_52" style="">
<span class="kwd">const</span><span class="pln"> average </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'../utils/for_testing'</span><span class="pun">).</span><span class="pln">average

describe</span><span class="pun">(</span><span class="str">'average'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  test</span><span class="pun">(</span><span class="str">'of one value is the value itself'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    expect</span><span class="pun">(</span><span class="pln">average</span><span class="pun">([</span><span class="lit">1</span><span class="pun">])).</span><span class="pln">toBe</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">

  test</span><span class="pun">(</span><span class="str">'of many is calculated right'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    expect</span><span class="pun">(</span><span class="pln">average</span><span class="pun">([</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">3</span><span class="pun">,</span><span class="pln"> </span><span class="lit">4</span><span class="pun">,</span><span class="pln"> </span><span class="lit">5</span><span class="pun">,</span><span class="pln"> </span><span class="lit">6</span><span class="pun">])).</span><span class="pln">toBe</span><span class="pun">(</span><span class="lit">3.5</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">

  test</span><span class="pun">(</span><span class="str">'of empty array is zero'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    expect</span><span class="pun">(</span><span class="pln">average</span><span class="pun">([])).</span><span class="pln">toBe</span><span class="pun">(</span><span class="lit">0</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	سنلاحظ أن الدالة لن تعمل بالشكل الصحيح مع المصفوفة الفارغة، لأن ناتج القسمة على 0 في JavaScript سيعطي القيمة NaN.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="57765" data-ss1613480582="1" data-ss1613480975="1" href="https://academy.hsoub.com/uploads/monthly_2021_02/empty_array_error_003.png.2afb4461c333d28d336d8ccfc2bdbbea.png" rel=""><img alt="empty_array_error_003.png" class="ipsImage ipsImage_thumbnailed" data-fileid="57765" data-unique="hgmiixypj" src="https://academy.hsoub.com/uploads/monthly_2021_02/empty_array_error_003.png.2afb4461c333d28d336d8ccfc2bdbbea.png"></a>
</p>

<p>
	تصحيح المشكلة السابقة أمر بسيط جدًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1466_54" style="">
<span class="kwd">const</span><span class="pln"> average </span><span class="pun">=</span><span class="pln"> array </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> reducer </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">sum</span><span class="pun">,</span><span class="pln"> item</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> sum </span><span class="pun">+</span><span class="pln"> item
  </span><span class="pun">}</span><span class="pln">

  </span><span class="kwd">return</span><span class="pln"> array</span><span class="pun">.</span><span class="pln">length </span><span class="pun">===</span><span class="pln"> </span><span class="lit">0</span><span class="pln">
    </span><span class="pun">?</span><span class="pln"> </span><span class="lit">0</span><span class="pln">
    </span><span class="pun">:</span><span class="pln"> array</span><span class="pun">.</span><span class="pln">reduce</span><span class="pun">(</span><span class="pln">reducer</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> array</span><span class="pun">.</span><span class="pln">length
</span><span class="pun">}</span></pre>

<p>
	ستعيد الدالة القيمة 0 إذا كان طول المصفوفة 0. ونستخدم في بقية الحالات التابع reduce لحساب المتوسط.
</p>

<p>
	يجب الانتباه إلى عدة أمور في الاختبارات التي أجريناها. فلقد أسمينا الكتلة النصية التي تصف الاختبار "average":
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1466_56" style="">
<span class="pln">describe</span><span class="pun">(</span><span class="str">'average'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="com">// tests</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	تستخدم الكتل التي تصف الاختبارات في تجميع الاختبارات ضمن مجموعات. كما تسمى نتيجة الاختبار في Jest باسم الكتلة أيضًا.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="57764" data-ss1613480582="1" data-ss1613480975="1" href="https://academy.hsoub.com/uploads/monthly_2021_02/describe_block_004.png.6c95b43232cbb3767941b70890e98335.png" rel=""><img alt="describe_block_004.png" class="ipsImage ipsImage_thumbnailed" data-fileid="57764" data-unique="6h1i88bro" src="https://academy.hsoub.com/uploads/monthly_2021_02/describe_block_004.png.6c95b43232cbb3767941b70890e98335.png"></a>
</p>

<p>
	وتبرز أهمية الكتل <code>describe</code> عندما نحاول تشغيل بعض الإعدادات المشتركة أو إنهاء العمليات المتعلقة بمجموعة من الاختبارات.
</p>

<p>
	وأخيرًا يجدر الانتباه إلى الطريقة المختصرة التي كتبت فيها شيفرة الاختبارات، بحيث لم نسند القيمة الناتجة عن تنفيذ الدالة إلى أي متغير:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1466_58" style="">
<span class="pln">test</span><span class="pun">(</span><span class="str">'of empty array is zero'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  expect</span><span class="pun">(</span><span class="pln">average</span><span class="pun">([])).</span><span class="pln">toBe</span><span class="pun">(</span><span class="lit">0</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span></pre>

<h2>
	التمارين 4.3 - 4.7
</h2>

<p>
	لننشئ مجموعة من الدوال المساعدة التي ستُخصص للتعامل مع قائمة المدونات. ضع هذه الدوال في ملف يدعى list_helper.js ضمن مجلد اسمه utils. اكتب الاختبارات في ملف يحمل اسمًا مناسبًا وضعه في المجلد tests.
</p>

<h3>
	4.3 الدوال المساعدة واختبارات الأجزاء: الخطوة 1
</h3>

<p>
	عرّف في البداية دالةً باسم <code>dummy</code> تستقبل مصفوفة من منشورات مدونة كمعامل وتعيد دائمًا القيمة 1. سيبدو محتوى الملف list_helper.js حتى هذه اللحظة كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1466_60" style="">
<span class="kwd">const</span><span class="pln"> dummy </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">blogs</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="com">// ...</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

module</span><span class="pun">.</span><span class="pln">exports </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  dummy
</span><span class="pun">}</span></pre>

<p>
	تحقق أن معلومات التهيئة لاختبارك ستعمل مع الاختبار التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1466_62" style="">
<span class="kwd">const</span><span class="pln"> listHelper </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'../utils/list_helper'</span><span class="pun">)</span><span class="pln">

test</span><span class="pun">(</span><span class="str">'dummy returns one'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> blogs </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">

  </span><span class="kwd">const</span><span class="pln"> result </span><span class="pun">=</span><span class="pln"> listHelper</span><span class="pun">.</span><span class="pln">dummy</span><span class="pun">(</span><span class="pln">blogs</span><span class="pun">)</span><span class="pln">
  expect</span><span class="pun">(</span><span class="pln">result</span><span class="pun">).</span><span class="pln">toBe</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span></pre>

<h3>
	4.4 الدوال المساعدة واختبارات الأجزاء: الخطوة 2
</h3>

<p>
	عرف دالةً جديدةً تدعى <code>totalLikes</code> تستقبل قائمة بمنشورات مدونة كمعامل وتعيد مجموع الإعجابات في كل منشورات هذه المدونة.
</p>

<p>
	اكتب اختبارًا مناسبًا للدالة. يفضل أن تضع الاختبار ضمن كتلة <code>describe</code>، كي تُجمّع التقارير المتولدة عن الاختبارات بشكل واضح.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="57766" data-ss1613480582="1" data-ss1613480975="1" href="https://academy.hsoub.com/uploads/monthly_2021_02/helpers_step2_005.png.f261c8047fa6d7c4a0cca67d0df74441.png" rel=""><img alt="helpers_step2_005.png" class="ipsImage ipsImage_thumbnailed" data-fileid="57766" data-unique="kykxcjn54" src="https://academy.hsoub.com/uploads/monthly_2021_02/helpers_step2_005.png.f261c8047fa6d7c4a0cca67d0df74441.png"></a>
</p>

<p>
	يمكن أن تُعرّف عناصر دخل الدالة على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1466_64" style="">
<span class="pln">describe</span><span class="pun">(</span><span class="str">'total likes'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> listWithOneBlog </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
      _id</span><span class="pun">:</span><span class="pln"> </span><span class="str">'5a422aa71b54a676234d17f8'</span><span class="pun">,</span><span class="pln">
      title</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Go To Statement Considered Harmful'</span><span class="pun">,</span><span class="pln">
      author</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Edsger W. Dijkstra'</span><span class="pun">,</span><span class="pln">
      url</span><span class="pun">:</span><span class="pln"> </span><span class="str">'http://www.u.arizona.edu/~rubinson/copyright_violations/Go_To_Considered_Harmful.html'</span><span class="pun">,</span><span class="pln">
      likes</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5</span><span class="pun">,</span><span class="pln">
      __v</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">]</span><span class="pln">

  test</span><span class="pun">(</span><span class="str">'when list has only one blog, equals the likes of that'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> result </span><span class="pun">=</span><span class="pln"> listHelper</span><span class="pun">.</span><span class="pln">totalLikes</span><span class="pun">(</span><span class="pln">listWithOneBlog</span><span class="pun">)</span><span class="pln">
    expect</span><span class="pun">(</span><span class="pln">result</span><span class="pun">).</span><span class="pln">toBe</span><span class="pun">(</span><span class="lit">5</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	إن وجدت أن إدخال قائمة المدونات يتطلب الكثير من الجهد، استخدم القائمة الجاهزة التي وضعناها على <a data-ss1613480582="1" data-ss1613480975="1" href="https://github.com/fullstack-hy2020/misc/blob/master/blogs_for_test.md" rel="external nofollow">GitHub</a>.
</p>

<p>
	ستواجهة المشاكل عندما ستكتب الاختبارات، لذلك تذكر استخدام وسائل <a data-ss1613480582="1" data-ss1613480975="1" href="https://fullstackopen.com/en/part3/saving_data_to_mongo_db#debugging-node-applications" rel="external nofollow">التنقيح</a> التي تعلمناها في القسم 3. ويمكنك دائمًا الطباعة على الطرفية مستخدمًا الأمر <code>console.log</code> حتى عند تنفيذ الاختبارات. كما يمكنك أيضًا استخدام المنقحات وستجد الكثير من <a data-ss1613480582="1" data-ss1613480975="1" href="https://jestjs.io/docs/en/troubleshooting" rel="external nofollow">مواقع الانترنت</a> التي تعطيك إرشادات لاستخدامها.
</p>

<p>
	<strong>ملاحظة</strong>: يفضل في حال أخفقت بعض الاختبارات، أن تشغلها فقط عندما تحاول إصلاح المشكلة. كما يمكنك أن تنفذ اختبارًا واحدًا مستخدمًا التابع <a data-ss1613480582="1" data-ss1613480975="1" href="https://facebook.github.io/jest/docs/en/api.html#testonlyname-fn-timeout" rel="external nofollow">only</a>.
</p>

<p>
	هناك أسلوب آخر لتنفيذ اختبار محدد بكتابة اسم الكتلة (اسم الاختبار) عند تنفيذ الاختبار مع الصفة <a data-ss1613480582="1" data-ss1613480975="1" href="https://jestjs.io/docs/en/cli.html" rel="external nofollow">t-</a>.
</p>

<pre class="ipsCode">
npm test -- -t 'when list has only one blog, equals the likes of that'
</pre>

<h3>
	4.5 الدوال المساعدة واختبارات الأجزاء: الخطوة 3 *
</h3>

<p>
	عرّف دالة جديدة باسم <code>favoriteBlog</code> تستقبل قائمة بالمدونات كمعامل. تكتشف الدالة المدونة التي تحمل أكبر عدد من الإعجابات. يكفي أن تعيد الدالة مدونة واحدة إن كان هناك أكثر من واحدة.
</p>

<p>
	يمكن أن تعيد الدالة قيمة لها الصيغة التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1466_67" style="">
<span class="pun">{</span><span class="pln">
  title</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Canonical string reduction"</span><span class="pun">,</span><span class="pln">
  author</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Edsger W. Dijkstra"</span><span class="pun">,</span><span class="pln">
  likes</span><span class="pun">:</span><span class="pln"> </span><span class="lit">12</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	<strong>ملاحظة</strong>: عندما توازن بين الكائنات، استخدم التابع <a data-ss1613480582="1" data-ss1613480975="1" href="https://jestjs.io/docs/en/expect#toequalvalue" rel="external nofollow">toEqual</a> فهو على الأغلب ما تحتاجه تمامًا، ذلك أن التابع <a data-ss1613480582="1" data-ss1613480975="1" href="https://jestjs.io/docs/en/expect#tobevalue" rel="external nofollow">toBe</a> سيحاول أن يتحقق من أن القيمتين متطابقتين بالإضافة إلى امتلاكهما نفس الخصائص.
</p>

<p>
	اكتب الاختبار ضمن كتلة <code>describe</code>. كرر ذلك في التمارين الباقية أيضًا.
</p>

<h3>
	4.6 الدوال المساعدة واختبارات الأجزاء: الخطوة 4 *
</h3>

<p>
	يحمل هذا التمرين والتمرين الذي يليه قدرًا من التحدي. وانتبه أن إكمال هذين التمرينين ليس ملزمًا لتتابع تقدمك في مفردات المنهاج. لذلك يفضل العودة إليهما بعد إنهائك مادة هذا القسم بالكامل.
</p>

<p>
	يُنجَز هذا التمرين بلا استخدام أية مكتبات إضافية. لكنه في المقابل فرصة مواتية لتعلم استخدام المكتبة <a data-ss1613480582="1" data-ss1613480975="1" href="https://lodash.com/" rel="external nofollow">Lodash</a>.
</p>

<p>
	عرّف دالة اسمها <code>mostBlogs</code> تستقبل مصفوفة من المدونات كمعامل. تعيد الدالة اسم المؤلف الذي لديه العدد الأكبر من المدونات، كما تعيد عدد هذه المدونات:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1466_69" style="">
<span class="pun">{</span><span class="pln">
  author</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Robert C. Martin"</span><span class="pun">,</span><span class="pln">
  blogs</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	إن وجد أكثر من مؤلف يحقق المطلوب، يكفي أن تعرض أحدهم.
</p>

<h3>
	4.7 الدوال المساعدة واختبارات الأجزاء: الخطوة 5 *
</h3>

<p>
	عرّف دالة باسم <code>mostLikes</code> تستقبل مصفوفة من المدونات كمعامل. تعيد الدالة المؤلف الذي حاز على أكبر عدد من الإعجابات، كما تعيد العدد الكلي لتلك الإعجابات:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1466_71" style="">
<span class="pun">{</span><span class="pln">
  author</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Edsger W. Dijkstra"</span><span class="pun">,</span><span class="pln">
  likes</span><span class="pun">:</span><span class="pln"> </span><span class="lit">17</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	إن وجد أكثر من مؤلف يحقق المطلوب، يكفي أن تعرض أحدهم.
</p>

<p>
	ترجمة -وبتصرف- للفصل <a data-ss1613480582="1" data-ss1613480975="1" href="https://fullstackopen.com/en/part4/structure_of_backend_application_introduction_to_testing" rel="external nofollow">Structure of backend application, introduction to testing</a> من سلسلة <a data-ss1613480582="1" data-ss1613480975="1" href="https://fullstackopen.com/en/" rel="external nofollow">Deep Dive Into Modern Web Development</a>
</p>
]]></description><guid isPermaLink="false">1132</guid><pubDate>Thu, 11 Feb 2021 13:00:00 +0000</pubDate></item><item><title>&#x62A;&#x642;&#x64A;&#x64A;&#x645; &#x635;&#x644;&#x627;&#x62D;&#x64A;&#x629; &#x628;&#x64A;&#x627;&#x646;&#x627;&#x62A; &#x627;&#x644;&#x62A;&#x637;&#x628;&#x64A;&#x642; &#x648;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x627;&#x644;&#x645;&#x62F;&#x642;&#x642; ESLint</title><link>https://academy.hsoub.com/programming/javascript/nodejs/%D8%AA%D9%82%D9%8A%D9%8A%D9%85-%D8%B5%D9%84%D8%A7%D8%AD%D9%8A%D8%A9-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%88%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%85%D8%AF%D9%82%D9%82-eslint-r1102/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2021_01/p16-2.jpg.8b3ae929303f9f3849eee00b05d9fef9.jpg" /></p>

<p>
	علينا في الواقع، أن نطبق مجموعة من القيود على البيانات التي ستخزن في قاعدة بيانات التطبيق. فلا ينبغي أن نسمح بوجود ملاحظات مفقودة المحتوى أو لا تملك الخاصية content لسبب ما. يجري تفقد صلاحية الملاحظة من خلال معالج مسار:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4054_6" style="">
<span class="pln">app</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> body </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">body
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">body</span><span class="pun">.</span><span class="pln">content </span><span class="pun">===</span><span class="pln"> </span><span class="kwd">undefined</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">return</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">400</span><span class="pun">).</span><span class="pln">json</span><span class="pun">({</span><span class="pln"> error</span><span class="pun">:</span><span class="pln"> </span><span class="str">'content missing'</span><span class="pln"> </span><span class="pun">})</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  </span><span class="com">// ...</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	إن لم تمتلك الملاحظة الخاصية content، سيستجيب الخادم برمز الحالة 400 (طلب خاطئ).
</p>

<p>
	يمكننا أن نستخدم طريقة أفضل في تقييم تنسيق البيانات قبل تخزينها في قاعدة البيانات وهي الوظيفة <a href="https://mongoosejs.com/docs/validation.html" rel="external nofollow">validation</a> التي تتيحها مكتبة Mongoose. حيث سنعرف معايير تقييم محددة لكل حقل من حقول مخطط قاعدة البيانات:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4054_8" style="">
<span class="kwd">const</span><span class="pln"> noteSchema </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> mongoose</span><span class="pun">.</span><span class="typ">Schema</span><span class="pun">({</span><span class="pln">
  content</span><span class="pun">:</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
        type</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</span><span class="pun">,</span><span class="pln">
        minlength</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5</span><span class="pun">,</span><span class="pln">
        required</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln">
    </span><span class="pun">},</span><span class="pln">
    date</span><span class="pun">:</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
        type</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">,</span><span class="pln">
        required</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln">
    </span><span class="pun">},</span><span class="pln">
    important</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Boolean</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	تحدد القواعد السابقة أن يكون طول المحتوى 5 محارف على الأقل، وأن المحتوى أمر إجباري <code>required:true</code> وبالتالي يجب أن لا يكون مفقودّا، وكذلك التاريخ. لم نضف تقييدات على حقل الأهمية، فلم يتغير تعريفه في المخطط.
</p>

<p>
	لاحظ على سبيل المثال أن المقيّم <code>minlength</code> يأتي <a href="https://mongoosejs.com/docs/validation.html#built-in-validators" rel="external nofollow">مدمجًا</a> وجاهزًا للاستخدام مع المكتبة mongoose، لكنها أيضًا تقدم وظيفة <a href="https://mongoosejs.com/docs/validation.html#custom-validators" rel="external nofollow">المُقيِّمات الخاصة</a> التي تمكننا من إنشاء مُقيِّمات بالطريقة التي نحتاجها إن لم تحقق المقّيمات المدمجة ما نريد. إن حاولنا أن نخزن كائنًا في قاعدة البيانات يخالف أحد التقييدات التي فرضناها عليه، سترمي العملية استثناءً.
</p>

<p>
	لنغيّر معالج إنشاء ملاحظة جديدة لكي يمرر أي استثناء محتمل إلى الأداة الوسطية لمعالجة الأخطاء:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4054_10" style="">
<span class="pln">app</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> body </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">body

  </span><span class="kwd">const</span><span class="pln"> note </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Note</span><span class="pun">({</span><span class="pln">
    content</span><span class="pun">:</span><span class="pln"> body</span><span class="pun">.</span><span class="pln">content</span><span class="pun">,</span><span class="pln">
    important</span><span class="pun">:</span><span class="pln"> body</span><span class="pun">.</span><span class="pln">important </span><span class="pun">||</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">,</span><span class="pln">
    date</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">(),</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">

  note</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">then</span><span class="pun">(</span><span class="pln">savedNote </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">savedNote</span><span class="pun">.</span><span class="pln">toJSON</span><span class="pun">())</span><span class="pln">
    </span><span class="pun">})</span><span class="pln">
    </span><span class="pun">.</span><span class="kwd">catch</span><span class="pun">(</span><span class="pln">error </span><span class="pun">=&gt;</span><span class="pln"> next</span><span class="pun">(</span><span class="pln">error</span><span class="pun">))})</span></pre>

<p>
	لنوسّع معالج الخطأ ليتعامل مع أخطاء التقييم.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4054_12" style="">
<span class="kwd">const</span><span class="pln"> errorHandler </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">error</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">error</span><span class="pun">(</span><span class="pln">error</span><span class="pun">.</span><span class="pln">message</span><span class="pun">)</span><span class="pln">

  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">error</span><span class="pun">.</span><span class="pln">name </span><span class="pun">===</span><span class="pln"> </span><span class="str">'CastError'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">400</span><span class="pun">).</span><span class="pln">send</span><span class="pun">({</span><span class="pln"> error</span><span class="pun">:</span><span class="pln"> </span><span class="str">'malformatted id'</span><span class="pln"> </span><span class="pun">})</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
 </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">error</span><span class="pun">.</span><span class="pln">name </span><span class="pun">===</span><span class="pln"> </span><span class="str">'ValidationError'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">400</span><span class="pun">).</span><span class="pln">json</span><span class="pun">({</span><span class="pln"> error</span><span class="pun">:</span><span class="pln"> error</span><span class="pun">.</span><span class="pln">message </span><span class="pun">})</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  next</span><span class="pun">(</span><span class="pln">error</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	عندما يُخفق تقييم الكائن، ستعيد Mongoose رسالة الخطأ التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="55556" href="https://academy.hsoub.com/uploads/monthly_2021_01/mongo_valid_error_001.png.be1656e66da07c815199fdabddce2cda.png" rel=""><img alt="mongo_valid_error_001.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55556" data-unique="oi8nk1cc6" src="https://academy.hsoub.com/uploads/monthly_2021_01/mongo_valid_error_001.png.be1656e66da07c815199fdabddce2cda.png"></a>
</p>

<h2>
	سلاسل الوعود
</h2>

<p>
	تحوّل معظم معالجات المسار البيانات القادمة مع الاستجابة إلى الصيغة الصحيحة باستخدام التابع <code>toJSON</code>. فعندما ننشئ ملاحظة جديدة، يُستدعى هذا التابع ليعالج الكائن الذي مرر إلى التابع <code>then</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4054_14" style="">
<span class="pln">app</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="com">// ...</span><span class="pln">

  note</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">then</span><span class="pun">(</span><span class="pln">savedNote </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">savedNote</span><span class="pun">.</span><span class="pln">toJSON</span><span class="pun">())</span><span class="pln">
    </span><span class="pun">})</span><span class="pln">
    </span><span class="pun">.</span><span class="kwd">catch</span><span class="pun">(</span><span class="pln">error </span><span class="pun">=&gt;</span><span class="pln"> next</span><span class="pun">(</span><span class="pln">error</span><span class="pun">))</span><span class="pln"> 
</span><span class="pun">})</span></pre>

<p>
	يمكن أن نحصل على نفس النتيجة وبطريقة أكثر وضوحًا باستخدام <a href="https://academy.hsoub.com/programming/javascript/%D8%B3%D9%84%D8%B3%D9%84%D8%A9-%D8%A7%D9%84%D9%88%D8%B9%D9%88%D8%AF-promises-chaining-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r916/" rel="">سلاسل الوعود</a>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4054_16" style="">
<span class="pln">app</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="com">// ...</span><span class="pln">

  note
    </span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">then</span><span class="pun">(</span><span class="pln">savedNote </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">return</span><span class="pln"> savedNote</span><span class="pun">.</span><span class="pln">toJSON</span><span class="pun">()</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">
      </span><span class="pun">.</span><span class="pln">then</span><span class="pun">(</span><span class="pln">savedAndFormattedNote </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">savedAndFormattedNote</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">})</span><span class="pln">
      </span><span class="pun">.</span><span class="kwd">catch</span><span class="pun">(</span><span class="pln">error </span><span class="pun">=&gt;</span><span class="pln"> next</span><span class="pun">(</span><span class="pln">error</span><span class="pun">))</span><span class="pln"> 
</span><span class="pun">})</span></pre>

<p>
	يستقبل أول تابع <code>then</code> الكائن <code>savedNote</code> الذي تعيده Mongoose ثم ينسقه، ويعيد نتيجة هذه العملية. وكما ناقشنا سابقًا يعيد تابع <code>then</code> الذي يليه وعدًا أيضًا بحيث يمكننا الوصول إلى الملاحظة المنسقة بالتصريح عن دالة استدعاء جديدة داخل التابع <code>then</code> الأخير. كما يمكننا توضيح الشيفرة أكثر باستخدام الصيغة المختصرة للدالة السهمية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4054_18" style="">
<span class="pln">app</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="com">// ...</span><span class="pln">

  note
    </span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">then</span><span class="pun">(</span><span class="pln">savedNote </span><span class="pun">=&gt;</span><span class="pln"> savedNote</span><span class="pun">.</span><span class="pln">toJSON</span><span class="pun">())</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">then</span><span class="pun">(</span><span class="pln">savedAndFormattedNote </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">savedAndFormattedNote</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">})</span><span class="pln"> 
    </span><span class="pun">.</span><span class="kwd">catch</span><span class="pun">(</span><span class="pln">error </span><span class="pun">=&gt;</span><span class="pln"> next</span><span class="pun">(</span><span class="pln">error</span><span class="pun">))</span><span class="pln"> 
</span><span class="pun">})</span></pre>

<p>
	لم تحقق سلسلة الوعود في هذا المثال الكثير من الفائدة. لكن الفائدة الحقيقة ستظهر عند تنفيذ عدة عمليات غير متزامنة على التسلسل. لن نخوض في هذا الموضوع كثيرًا، بل سنتركه إلى القسم التالي من المنهاج، حيث سنطلع على الصيغة <code>async/await</code> في JavaScript والتي تسهل كتابة العمليات المتسلسلة غير المتزامنة.
</p>

<h2>
	إنجاز نسخة الإنتاج من الواجهة الخلفية المرتبطة بقاعدة بيانات
</h2>

<p>
	ينبغي أن يعمل التطبيق كما هو على Heroku. ولا ينبغي أن ننشئ نسخة إنتاج جديدة للواجهة الأمامية نتيجة للتغيرات التي أجريناها عليها. ونشير أيضًا إلى أن استخدام متغيرات البيئة التي عرفناها باستخدام dotenv غير ممكن عندما تكون الواجهة الخلفية في وضع الإنتاج (على Heroku).
</p>

<p>
	لقد وضعنا متغيرات البيئة المستخدمة في مرحلة التطوير في الملف env.، لكن ينبغي ضبط متغيرات البيئة التي تعرف عنوان موقع قاعدة البيانات في وضع الإنتاج باستخدام الأمر <code>heroku config:set</code>
</p>

<pre class="ipsCode">
$ heroku config:set MONGODB_URI=mongodb+srv://fullstack:secretpasswordhere@cluster0-ostce.mongodb.net/note-app?retryWrites=true
</pre>

<p>
	<strong>ملاحظة</strong>: إن أعطى تنفيذ الأمر خطأً، ضع قيمة <code>MONGO_URI</code> ضمن إشارتي تنصيص مفردتين (' ').
</p>

<pre class="ipsCode">
$ heroku config:set MONGODB_URI='mongodb+srv://fullstack:secretpasswordhere@cluster0-ostce.mongodb.net/note-app?retryWrites=true'
</pre>

<p>
	ينبغي أن يعمل التطبيق الآن. لكن في بعض الأحيان لا تجري الأمور كما هو مخطط لها. لذلك استفد من سجلات heroku عند تنفيذ الشيفرة. توضح الصورة التالية ما أظهره Heroku عند فشل التطبيق بعد إجراء التغييرات:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="55554" href="https://academy.hsoub.com/uploads/monthly_2021_01/heroku_code_fail_002.png.7145a758ad576510a4de37de45943fec.png" rel=""><img alt="heroku_code_fail_002.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55554" data-unique="4pakosl2d" src="https://academy.hsoub.com/uploads/monthly_2021_01/heroku_code_fail_002.png.7145a758ad576510a4de37de45943fec.png"></a>
</p>

<p>
	لسبب ما، ظهر عنوان قاعدة البيانات ككائن غير معرّف. لكن سجلات Heroku قد كشفت أن الخطأ هو أننا أسندنا عنوان موقع قاعدة البيانات إلى متغير البيئة MONGO_URL بينما تتوقع الشيفرة أن يكون العنوان قد أسند إلى متغير البيئة <code>MONGODB_URI</code>.
</p>

<p>
	ستجد شيفرة التطبيق بوضعه الحالي في الفرع part3-5 ضمن المستودع الخاص بالقسم على <a href="https://github.com/fullstack-hy2019/part3-notes-backend/tree/part3-5" rel="external nofollow">GitHub</a>
</p>

<h2>
	التمارين 3.19 - 3.21
</h2>

<h3>
	3.19 دليل هاتف بقاعدة بيانات: الخطوة 7
</h3>

<p>
	ضع مقيّمات لتحديد صلاحية المُدخَلات إلى تطبيق دليل الهاتف، لتضمن أن المُدخَل الجديد يحمل اسمًا فريدًا غير مكرر. لن تسمح الواجهة الأمامية الآن أن يدخل المستخدم أسماء مكررة، لكن حاول أن تقوم بذلك مباشرة بوجود Postman أو VS Code REST client.
</p>

<p>
	لا تقدم Mongoose مُقيِّمات مدمجة لهذا الغرض، عليك تثبيت حزمة <a href="https://github.com/blakehaswell/mongoose-unique-validator#readme" rel="external nofollow">mongoose-unique-validator</a> باستخدام npm. إن حاول طلب HTTP-POST إضافة اسم موجود مسبقًا، على الخادم أن يجيب برمز الحالة المناسب، بالإضافة إلى رسالة خطأ.
</p>

<p>
	<strong>ملاحظة</strong>: سيسبب المعرف الفريد (unique-validator) تحذيرًا سيطبع على الطرفية.
</p>

<pre class="ipsCode">
(node:49251) DeprecationWarning: collection.ensureIndex is deprecated. Use createIndexes instead.
connected to MongoDB
</pre>

<p>
	اقرأ <a href="https://mongoosejs.com/docs/deprecations.html" rel="external nofollow">توثيق</a> Mongoose وجد طريقة للتخلص من هذا التحذير.
</p>

<h3>
	3.20 دليل هاتف بقاعدة بيانات: الخطوة 8 *
</h3>

<p>
	وسع التقييد بحيث لا يقل طول الاسم المخزّن في قاعدة البيانات عن ثلاثة محارف وأن لا يقل طول رقم الهاتف عن 8 أرقام. دع الواجهة الأمامية تظهر رسالة خطأ عندما يحدث خطأ في التقييم. يمكنك أن تعالج الخطأ بإضافة كتلة <code>catch</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4054_21" style="">
<span class="pln">personService
    </span><span class="pun">.</span><span class="pln">create</span><span class="pun">({</span><span class="pln"> </span><span class="pun">...</span><span class="pln"> </span><span class="pun">})</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">then</span><span class="pun">(</span><span class="pln">createdPerson </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="com">// ...</span><span class="pln">
    </span><span class="pun">})</span><span class="pln">
    </span><span class="pun">.</span><span class="kwd">catch</span><span class="pun">(</span><span class="pln">error </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="com">// هذه طريقة للوصول إلى رسالة الخطأ</span><span class="pln">
      console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">error</span><span class="pun">.</span><span class="pln">response</span><span class="pun">.</span><span class="pln">data</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">})</span></pre>

<p>
	يمكنك إظهار رسالة الخطأ الافتراضية التي تعيدها Mongoose علمًا أن قراءتها ليست يسيرة:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="55557" href="https://academy.hsoub.com/uploads/monthly_2021_01/mongoose_def_error_003.png.3bd348f01fad8535b86b4e4b80235a3d.png" rel=""><img alt="mongoose_def_error_003.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55557" data-unique="02l5ytqa6" src="https://academy.hsoub.com/uploads/monthly_2021_01/mongoose_def_error_003.png.3bd348f01fad8535b86b4e4b80235a3d.png"></a>
</p>

<p>
	<strong>ملاحظة</strong>: أثناء عمليات التحديث لن تعمل مقّيمات Mongoose بشكل افتراضي. اطلع على التوثيق <a href="https://mongoosejs.com/docs/validation.html" rel="external nofollow">لتتعلم كيفية تمكينها</a>.
</p>

<h3>
	3.21 إنجاز نسخة الإنتاج من الواجهة الخلفية المرتبطة بقاعدة بيانات
</h3>

<p>
	أنشئ نسخة كاملة (full stack) من التطبيق بإنجاز نسخة إنتاج عن الواجهة الأمامية ونسخها إلى مستودع الواجهة الخلفية. تحقق من أن كل شيء يعمل جيدًا باستخدام التطبيق بشكله الكامل على الخادم المحلي الذي عنوانه <a href="https://localhost:3001/" rel="external nofollow">https://localhost:3001</a>. انقل النسخة النهائية إلى خادم Heroku وتحقق أن كل شيء يعمل بشكل جيد.
</p>

<h2>
	المدققات (Lints)
</h2>

<p>
	قبل أن ننتقل إلى القسم التالي من المنهاج، سنلقي نظرة على أداة مهمة تدعى المدقق <a href="https://en.wikipedia.org/wiki/Lint_(software)" rel="external nofollow">lint</a>. وجاء في wikipedia عن المدقق ما يلي:
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		بشكل عام، المدقق هو أية أداة قادرة على التقاط الأخطاء والإشارة إليها في لغات البرمجة، بما فيها أخطاء التنسيق. ويشير المصطلح "سلوك مشابه للمدقق" أحيانًا إلى عملية الإشارة إلى الاستخدام المريب للغة البرمجة. تقدم الأدوات الشبيهة بالمدققات تحليلًا ساكنًا للشيفرة المصدرية (تحليل الشيفرة دون تنفيذها)
	</p>
</blockquote>

<p>
	يمكن للغات التي تترجم بشكل ساكن كلغة Java وبيئات التطوير مثل NetBeans أن تشير إلى الأخطاء في الشيفرة، حتى تلك الأخطاء التي تعتبر أكثر من أخطاء ترجمة. يمكن استعمال أدوات إضافية لتقوم <a href="https://en.wikipedia.org/wiki/Static_program_analysis" rel="external nofollow">بالتحليل الساكن</a> مثل <a href="https://checkstyle.sourceforge.io/" rel="external nofollow">checkstyle</a> لتوسيع إمكانيات بيئة التطوير بحيث تصبح قادرةً على الإشارة إلى مشاكل تتعلق حتى بالتنسيقات مثل إزاحة الكلمات ضمن الأسطر.
</p>

<p>
	في عالم JavaScript، تعتبر الأداة <a href="https://eslint.org/" rel="external nofollow">ESlint</a> هي الرائدة في مجال التدقيق والتحليل الساكن. لنثبت ESlint كملف ارتباط تطوير في مشروع الواجهة الخلفية بتنفيذ الأمر:
</p>

<pre class="ipsCode">
npm install eslint --save-dev
</pre>

<p>
	يمكننا بعد ذلك تهيئة المدقق بصيغته الافتراضية بتنفيذ الأمر:
</p>

<pre class="ipsCode">
node_modules/.bin/eslint --init
</pre>

<p>
	سنجيب طبعُا عن الأسئلة التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="55552" href="https://academy.hsoub.com/uploads/monthly_2021_01/eslint_config_004.png.352ce02ef2e840a70774e0daac12d712.png" rel=""><img alt="eslint_config_004.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55552" data-unique="4b0l04khf" src="https://academy.hsoub.com/uploads/monthly_2021_01/eslint_config_004.png.352ce02ef2e840a70774e0daac12d712.png"></a>
</p>

<p>
	ستخزّن إعدادات التهيئة في الملف eslinterc.js:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4054_23" style="">
<span class="pln">module</span><span class="pun">.</span><span class="pln">exports </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">'env'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="str">'commonjs'</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'es6'</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'node'</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln">
    </span><span class="pun">},</span><span class="pln">
    </span><span class="str">'extends'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'eslint:recommended'</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'globals'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="str">'Atomics'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'readonly'</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'SharedArrayBuffer'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'readonly'</span><span class="pln">
    </span><span class="pun">},</span><span class="pln">
    </span><span class="str">'parserOptions'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="str">'ecmaVersion'</span><span class="pun">:</span><span class="pln"> </span><span class="lit">2018</span><span class="pln">
    </span><span class="pun">},</span><span class="pln">
    </span><span class="str">'rules'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="str">'indent'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
            </span><span class="str">'error'</span><span class="pun">,</span><span class="pln">
            </span><span class="lit">4</span><span class="pln">
        </span><span class="pun">],</span><span class="pln">
        </span><span class="str">'linebreak-style'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
            </span><span class="str">'error'</span><span class="pun">,</span><span class="pln">
            </span><span class="str">'unix'</span><span class="pln">
        </span><span class="pun">],</span><span class="pln">
        </span><span class="str">'quotes'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
            </span><span class="str">'error'</span><span class="pun">,</span><span class="pln">
            </span><span class="str">'single'</span><span class="pln">
        </span><span class="pun">],</span><span class="pln">
        </span><span class="str">'semi'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
            </span><span class="str">'error'</span><span class="pun">,</span><span class="pln">
            </span><span class="str">'never'</span><span class="pln">
        </span><span class="pun">]</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	لنغيّر مباشرة القاعدة التي تنظم الإزاحة في السطر الواحد بحيث تكون بمقدار فراغين:
</p>

<pre class="ipsCode">
"indent": [
    "error",
    2
],
</pre>

<p>
	يمكن تفتيش الملفات مثل index.js والتحقق من صلاحيتها باستخدام الأمر:
</p>

<pre class="ipsCode">
node_modules/.bin/eslint index.js
</pre>

<p>
	يفضل أن تنشئ سكربت npm منفصل للتدقيق:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4054_25" style="">
<span class="pun">{</span><span class="pln">
  </span><span class="com">// ...</span><span class="pln">
  </span><span class="str">"scripts"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">"start"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"node index.js"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"dev"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"nodemon index.js"</span><span class="pun">,</span><span class="pln">
    </span><span class="com">// ...</span><span class="pln">
    </span><span class="str">"lint"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"eslint ."</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="com">// ...</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	سيتحقق الآن الأمر <code>npm run lint</code> من كل ملف في المشروع. كما سيتحقق من الملفات الموجودة في المجلد build، وهذا ما لا نريده. لذلك سنمنع ذلك بإنشاء ملف تجاهل لاحقته <a href="https://eslint.org/docs/user-guide/configuring#ignoring-files-and-directories" rel="external nofollow">eslintignorr.‎</a> في جذر المشروع و نزوده بالمحتوى التالي:
</p>

<pre class="ipsCode">
build
</pre>

<p>
	عندها لن يتحقق ESlint من المجلد build أو محتوياته.
</p>

<p>
	سيشير ESlint إلى الكثير من النقاط في شيفرتك:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="55553" href="https://academy.hsoub.com/uploads/monthly_2021_01/eslint_error_list_005.png.78dac8c72a42dcc0ecdc1e02176bb9f3.png" rel=""><img alt="eslint_error_list_005.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55553" data-unique="432b1ygm8" src="https://academy.hsoub.com/uploads/monthly_2021_01/eslint_error_list_005.png.78dac8c72a42dcc0ecdc1e02176bb9f3.png"></a>
</p>

<p>
	لن نصلح أي شيء الآن. يمكن أن تستخدم طريقة أفضل من سطر الأوامر في تنفيذ التدقيق، وهي تهيئة إضافة للتدقيق eslint-plugin على محررك بحيث تنفذ عملية التدقيق بشكل مستمر. وبالتالي سترى الأخطاء التي ترتكب مباشرة أثناء تحريرك للشيفرة.
</p>

<p>
	يمكنك الاطلاع أكثر من خلال الانترنت على الكثير من المعلومات <a href="https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint" rel="external nofollow">حول الإضافة</a> Visual Studio ESlint plugin. Editor.
</p>

<p>
	ستضع الإضافة السابقة خطًا أحمر تحت أخطاء التنسيق:
</p>

<p style="text-align: center;">
	<img class="ipsImage ipsImage_thumbnailed" data-fileid="55551" data-unique="6xy025kt6" src="https://academy.hsoub.com/uploads/monthly_2021_01/vs_eslint_style_error_006.png.8f00e2ce6bf1b9c42da0e4bd295a6e4a.png" alt="vs_eslint_style_error_006.png"></p>

<p style="text-align: center;">
	 
</p>

<p>
	وهكذا سنرصد الأخطاء بشكل أسهل.
</p>

<p>
	للمدقق ESlint الكثير من <a href="https://eslint.org/docs/rules/" rel="external nofollow">القواعد</a> سهلة الاستخدام والتي يمكن إضافتها في الملف eslintrc.js. لنضع الآن القاعدة <a href="https://eslint.org/docs/rules/eqeqeq" rel="external nofollow">eqeqeq</a> التي تحذرنا إن وجدت في الشيفرة عامل الموازنة الثلاثي (===). تضاف القاعدة ضمن الحقل rules في ملف التهيئة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4054_27" style="">
<span class="pun">{</span><span class="pln">
  </span><span class="com">// ...</span><span class="pln">
  </span><span class="str">'rules'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// ...</span><span class="pln">
   </span><span class="str">'eqeqeq'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'error'</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	وطالما أننا استخدمنا القواعد لنقم بتغييرات أخرى. لنمنع وجود المسافات الفارغة <a href="https://eslint.org/docs/rules/no-trailing-spaces" rel="external nofollow">trailing spaces</a> في آخر السطر البرمجي، ولنطلب أيضًا أن يكون هناك <a href="https://eslint.org/docs/rules/object-curly-spacing" rel="external nofollow">فراغ قبل وبعد الأقواس المعقوصة</a>، كذلك وجود فراغ بشكل دائم بين معاملات الدالة السهمية.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4054_29" style="">
<span class="pun">{</span><span class="pln">
  </span><span class="com">// ...</span><span class="pln">
  </span><span class="str">'rules'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// ...</span><span class="pln">
    </span><span class="str">'eqeqeq'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'error'</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'no-trailing-spaces'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'error'</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'object-curly-spacing'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
        </span><span class="str">'error'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'always'</span><span class="pln">
    </span><span class="pun">],</span><span class="pln">
    </span><span class="str">'arrow-spacing'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
        </span><span class="str">'error'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="str">'before'</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln"> </span><span class="str">'after'</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln"> </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">]</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	وتقدم لنا الإعدادات الافتراضية التي اعتمدناها في البداية، الكثير من القواعد التي ينصح بها المدقق، وتُطبق هذه القواعد بكتابة الأمر:
</p>

<pre class="ipsCode">
'extends': 'eslint:recommended',
</pre>

<p>
	تتضمن هذه القواعد تحذيرات تتعلق بأمر الطباعة على الطرفية <code>console.log</code>.
</p>

<p>
	يمكن <a href="https://eslint.org/docs/user-guide/configuring#configuring-rules" rel="external nofollow">تعطيل</a> أي قاعدة بجعل قيمتها تساوي 0 في ملف التهيئة. لنلغ القاعدة no-console على سبيل المثال:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4054_31" style="">
<span class="pun">{</span><span class="pln">
  </span><span class="com">// ...</span><span class="pln">
  </span><span class="str">'rules'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// ...</span><span class="pln">
    </span><span class="str">'eqeqeq'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'error'</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'no-trailing-spaces'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'error'</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'object-curly-spacing'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
        </span><span class="str">'error'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'always'</span><span class="pln">
    </span><span class="pun">],</span><span class="pln">
    </span><span class="str">'arrow-spacing'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
        </span><span class="str">'error'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="str">'before'</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln"> </span><span class="str">'after'</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln"> </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">],</span><span class="pln">
    </span><span class="str">'no-console'</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	<strong>ملاحظة</strong>: يفضل، إن أجريت أية تغييرات على الملف ‎.eslintrc.js، أن تشغل المدقق من سطر الأوامر لتتحقق من أن التعليمات في ملف التهيئة مكتوبة بالشكل الصحيح.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="55555" href="https://academy.hsoub.com/uploads/monthly_2021_01/lint_config_chk_007.png.b17496296dbda42e45b005d888703e43.png" rel=""><img alt="lint_config_chk_007.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55555" data-unique="9f0rnm5jg" src="https://academy.hsoub.com/uploads/monthly_2021_01/lint_config_chk_007.png.b17496296dbda42e45b005d888703e43.png"></a>
</p>

<p>
	فإن كانت هناك أية مشاكل في ملف التهيئة، ستتصرف إضافة المدقق بشكل غير مفهوم.
</p>

<p>
	تحدد الكثير من الشركات معايير لكتابة الشيفرة وتفرضها على <a href="https://www.w3.org/" rel="external nofollow">منظمة W3C</a> عبر ملفات تهيئة ESlint. إذًا ليس عليك إعادة اختراع العجلة في كل مرة، ومن الأفضل لك اعتماد ملف تهيئة جاهز أنجزته جهة ما.
</p>

<p>
	تعتمد الكثير من المشاريع حاليًا على <a href="https://academy.hsoub.com/programming/javascript/%D8%AF%D9%84%D9%8A%D9%84-airbnb-%D9%84%D9%86%D9%85%D8%B7-%D8%AC%D8%A7%D9%81%D8%A7-%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r632/" rel="">دليل تنسيق JavaScript</a> الذي قدمته Airbnb، وذلك باستخدامها ملف تهيئة <a href="https://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb" rel="external nofollow">ESlint</a> الذي تعتمده Airbnb.
</p>

<p>
	ستجد شيفرة التطبيق كاملًا في الفرع part3-6 ضمن المستودع الخاص بالقسم على <a href="https://github.com/fullstack-hy2019/part3-notes-backend/tree/part3-6" rel="external nofollow">GitHub</a>
</p>

<h2>
	التمرين 3.22
</h2>

<h3>
	3.22 تهيئة المدقق
</h3>

<p>
	أضف ESlint إلى تطبيقك وأصلح كل المشاكل.
</p>

<p>
	وهكذا نصل إلى آخر تمرينات هذا القسم، وحان الوقت لتسليم حلول التمارين إلى GitHub. لا تنس أن تشير إلى التمارين التي تسلمها أنها مكتملة ضمن <a href="https://studies.cs.helsinki.fi/stats" rel="external nofollow">منظومة تسليم التمارين</a>.
</p>

<p>
	ترجمة -وبتصرف- للفصل <a href="https://fullstackopen.com/en/part3/validation_and_eslint" rel="external nofollow">Validation and ESlint</a> من سلسلة <a href="https://fullstackopen.com/en/" rel="external nofollow">Deep Dive Into Modern Web Development</a>
</p>
]]></description><guid isPermaLink="false">1102</guid><pubDate>Fri, 15 Jan 2021 09:02:56 +0000</pubDate></item><item><title>&#x62D;&#x641;&#x638; &#x628;&#x64A;&#x627;&#x646;&#x627;&#x62A; &#x62A;&#x637;&#x628;&#x64A;&#x642;&#x627;&#x62A; Node.js &#x641;&#x64A; &#x642;&#x627;&#x639;&#x62F;&#x629; &#x628;&#x64A;&#x627;&#x646;&#x627;&#x62A; MongoDB</title><link>https://academy.hsoub.com/programming/javascript/nodejs/%D8%AD%D9%81%D8%B8-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-nodejs-%D9%81%D9%8A-%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-mongodb-r1101/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2021_01/p15-1.jpg.809d3a0ff1308473af70e5eb4c4eed8f.jpg" /></p>

<p>
	قبل أن ننتقل إلى الموضوع الرئيسي في هذا الفصل، سنلقي نظرة على بعض الطرق المتبعة في تنقيح تطبيقات Node.
</p>

<h2>
	تنقيح تطبيقات Node
</h2>

<p>
	إن تنقيح التطبيقات المبنية باستخدام Node أصعب قليلًا من تنقيح شيفرة JavaScript التي تنفذ على المتصفح. لكن مع ذلك تبقى فكرة الطباعة على الطرفية وأسلوب المحاولة والخطأ طريقة فعالة في حل المشاكل. قد تجد من المطورين من يعتقد أن استخدام أساليب أكثر تطورًا هو أمر ضروري، لكن تذكر أن نخبة مطوري البرمجيات مفتوحة المصدر في العالم <a href="https://tenderlovemaking.com/2016/02/05/i-am-a-puts-debuggerer.html" rel="external nofollow">يستخدمون</a> تلك <a href="https://swizec.com/blog/javascript-debugging-slightly-beyond-console-log/swizec/6633" rel="external nofollow">الطريقة</a>.
</p>

<h2>
	برنامج Visual Studio Code
</h2>

<p>
	ستجد أن المنقح المدمج ضمن هذا البرنامج ذو فائدة كبيرة في بعض الحالات. يمكنك أن تشغل البرنامج على وضع التنقيح كالتالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2021_01/vs_debug_mode_001.png.22c1dfabd78a0931a99706c4b06cef17.png" data-fileid="55542" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="55542" data-unique="r4r7m8czb" src="https://academy.hsoub.com/uploads/monthly_2021_01/vs_debug_mode_001.png.22c1dfabd78a0931a99706c4b06cef17.png" alt="vs_debug_mode_001.png"></a>
</p>

<p>
	<strong>ملاحظة</strong>: يمكن أن تجد التعليمة <code>Run</code> بدلًا من <code>Debug</code> في الإصدار الأحدث من Visual Studio Code. وقد يكون عليك أيضًا أن تهيئ ملف launch.json لتبدأ التنقيح. يمكن تنفيذ ذلك باختيار الأمر <code>...Add Configuration</code> من القائمة المنسدلة الموجودة بجانب زر التشغيل الأخضر وفوق قائمة <code>VARIABLES</code>، ثم اختيار الأمر <code>Run "npm start" in a debug terminal</code>. يمكنك إيجاد المزيد من الإرشادات بالاطلاع على <a href="https://code.visualstudio.com/docs/editor/debugging" rel="external nofollow">توثيق التنقيح </a> لبرنامج Visual Studio Code.
</p>

<p>
	تذكر أن لا تشغل البرنامج ضمن أكثر من طرفية لأن ذلك سيحجز المنفذ مسبقًا ولن تتمكن من العمل. تعرض لقطة الشاشة التالية الطرفية وقد أوقفنا التنفيذ مؤقتًا في منتصف عملية حفظ الملاحظة الجديدة:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2021_01/vs_paused_002.png.5ca16ed4f9b818b30d06a31af852a8d2.png" data-fileid="55543" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="55543" data-unique="alhpuiaeq" src="https://academy.hsoub.com/uploads/monthly_2021_01/vs_paused_002.png.5ca16ed4f9b818b30d06a31af852a8d2.png" alt="vs_paused_002.png"></a>
</p>

<p>
	توقَّف التنفيذ عندما وصلنا إلى نقطة التوقف التي وضعناها في السطر 63. يمكنك أن ترى في الطرفية قيمة المتغير <code>note</code>. وفي أعلى يسار النافذة ستجد بعض التفاصيل المتعلقة بحالة التطبيق. تُستخدم الأسهم في الأعلى للتحكم بترتيب عملية التنقيح.
</p>

<h2>
	أدوات تطوير Chrome
</h2>

<p>
	من الممكن أن نستخدم أدوات تطوير Chrome لتنقيح تطبيقات Node، وذلك بتشغيل التطبيق مستخدمين الأمر التالي:
</p>

<pre class="ipsCode">
node --inspect index.js
</pre>

<p>
	ستدخل إلى المنقح بالضغط على الأيقونة الخضراء (شعار Node) التي تظهر على طرفية تطوير Chrome:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2021_01/chrome_node_debug_003.png.fb7f8dea63968efc93054c16a53866ba.png" data-fileid="55525" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="55525" data-unique="zs7f2dqyy" src="https://academy.hsoub.com/uploads/monthly_2021_01/chrome_node_debug_003.png.fb7f8dea63968efc93054c16a53866ba.png" alt="chrome_node_debug_003.png"></a>
</p>

<p>
	يعمل المنقح بنفس الطريقة التي يعمل بها عند تنقيح تطبيقات React. يمكنك استخدام النافذة Sources لزرع نقاط توقف في الشيفرة لإيقاف التنفيذ بشكل مؤقت عندها.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2021_01/chrome_debug_sources_004.png.988f246a3a95799cc709998eb0aa8598.png" data-fileid="55524" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="55524" data-unique="fv5ar7uqx" src="https://academy.hsoub.com/uploads/monthly_2021_01/chrome_debug_sources_004.png.988f246a3a95799cc709998eb0aa8598.png" alt="chrome_debug_sources_004.png"></a>
</p>

<p>
	ستظهر جميع الرسائل التي يطبعها الأمر <code>console.log</code> في النافذة Console من المنقح. كما يمكنك التحري عن قيم المتغيرات وتنفيذ شيفرة JavaScript إن أردت.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2021_01/chrome_debug_console_005.png.2feab3e8ff75f49f7e92ac6b6b71188b.png" data-fileid="55523" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="55523" data-unique="rsggjh3jh" src="https://academy.hsoub.com/uploads/monthly_2021_01/chrome_debug_console_005.png.2feab3e8ff75f49f7e92ac6b6b71188b.png" alt="chrome_debug_console_005.png"></a>
</p>

<h2>
	تحقق من كل شيء
</h2>

<p>
	قد يبدو لك تنقيح تطبيقات التطوير الشامل (واجهة خلفية وأمامية) محيّرًا في البداية. وقريبًا سيتواصل التطبيق مع قاعدة بيانات. وبالتالي سيزداد احتمال ظهور الأخطاء في أجزاء عدة من الشيفرة.
</p>

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

<p>
	لا تتابع تطوير التطبيق إن لم تعثر على مصدر الخطأ، فهذه أسوأ استراتيجية. لأن ذلك سيسبب أخطاء أكثر وسيكون التنقيح أصعب. اتبع سياسة شركة Toyota لإنتاج الأنظمة (<a href="http://gettingtolean.com/toyota-principle-5-build-culture-stopping-fix/#.Wjv9axP1WCQ" rel="external nofollow">توقف وأصلح</a>) فهي بالفعل سياسة مجدية جدًا في حالتنا.
</p>

<h2>
	قاعدة البيانات MongoDB
</h2>

<p>
	سنحتاج قطعًا إلى قاعدة بيانات لحفظ الملاحظات بشكل دائم. تتعامل معظم مناهج جامعة هلسينكي مع قواعد البيانات العِلاقيّة (Relational Databases)، لكننا سنتعامل في منهاجنا مع قاعدة البيانات <a href="https://www.mongodb.com" rel="external nofollow">MongoDB</a> وهي من نمط قواعد <a href="https://en.wikipedia.org/wiki/Document-oriented_database" rel="external nofollow">البيانات المستقلة</a>.
</p>

<p>
	تختلف قواعد البيانات المستقلة (أو التي تأتي على شكل مستندات منفصلة) عن العِلاقيّة في كيفية تنظيم البيانات ولغة الاستعلام التي تدعمها. وعادة ما تصنف قواعدة البيانات المستقلة تحت مظلة <a href="https://en.wikipedia.org/wiki/NoSQL" rel="external nofollow">NoSQL</a> أي التي لا تستخدم لغة الاستعلام SQL.
</p>

<p>
	اطلع على الفصلين <a href="https://docs.mongodb.com/manual/core/databases-and-collections/" rel="external nofollow">collections</a> و <a href="https://docs.mongodb.com/manual/core/document/" rel="external nofollow">documents</a> من دليل استخدام MongoDB لتتعلم أساسيات تخزين البيانات في قواعد البيانات المستقلة.
</p>

<p>
	يمكنك أن تُثبّت وتُشغّل MongoDB على حاسوبك. كما يمكنك الاستفادة من المواقع التي تقدم خدمات MongoDB على الإنترنت. سنتعامل في منهاجنا مع مزود الخدمة <a href="https://www.mongodb.com/cloud/atlas" rel="external nofollow">MongoDB Atlas</a>.
</p>

<p>
	حالما تنشئ حسابًا على الموقع وتسجل الدخول، سينصحك الموقع بإنشاء عنقود:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2021_01/mongo_atlas_006.png.84cb266864496a1338c1c1d1dbe7f08a.png" data-fileid="55532" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="55532" data-unique="x17vmq06k" src="https://academy.hsoub.com/uploads/monthly_2021_01/mongo_atlas_006.png.84cb266864496a1338c1c1d1dbe7f08a.png" alt="mongo_atlas_006.png"></a>
</p>

<p>
	سنختار المزود AWS والمنطقة Frankfurt ثم ننشئ العنقود:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2021_01/mongo_atlas_provider_007.png.abf6be8df153617a5aed9c21f5f04e8d.png" data-fileid="55533" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="55533" data-unique="499zi9asi" src="https://academy.hsoub.com/uploads/monthly_2021_01/mongo_atlas_provider_007.png.abf6be8df153617a5aed9c21f5f04e8d.png" alt="mongo_atlas_provider_007.png"></a>
</p>

<p>
	انتظر حتى يكتمل العنقود ويصبح جاهزًا. قد يستغرق ذلك 10 دقائق.
</p>

<p>
	<strong>ملاحظة</strong>: لا تتابع قبل أن يصبح العنقود جاهزًا.
</p>

<p>
	سنستخدم نافذة database access لإنشاء معلومات التوثيق اللازمة لدخول قاعدة البيانات. وانتبه إلى أنها معلومات توثيق مختلفة عن تلك التي تستخدمها عند تسجيل الدخول، وسيستخدمها تطبيقك عندما يتصل بقاعدة البيانات.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2021_01/mongo_cred_008.png.382e0642fba63bc679c11136040d0829.png" data-fileid="55536" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="55536" data-unique="awjnn7ahz" src="https://academy.hsoub.com/uploads/monthly_2021_01/mongo_cred_008.png.382e0642fba63bc679c11136040d0829.png" alt="mongo_cred_008.png"></a>
</p>

<p>
	لنمنح المستخدم إمكانية القراءة والكتابة إلى قاعدة البيانات:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2021_01/mongo_permission_009.png.053b54ac0431c0a42a2483b07d0ba653.png" data-fileid="55538" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="55538" data-unique="6rz5oxedk" src="https://academy.hsoub.com/uploads/monthly_2021_01/mongo_permission_009.png.053b54ac0431c0a42a2483b07d0ba653.png" alt="mongo_permission_009.png"></a>
</p>

<p>
	<strong>ملاحظة</strong>: أبلغَ بعض المستخدمين عن عدم القدرة على الوصول إلى قاعدة البيانات بمعلومات التوثيق التي وضعوها بعد إنشاء القاعدة مباشرة. تريّث، فقد يستغرق الأمر دقائق حتى تفعّل هذه المعلومات.
</p>

<p>
	سنعرّف بعد ذلك عناوين IP التي يسمح لها بدخول قاعدة البيانات.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2021_01/mongo_ip_010.png.01a7c39c3f4720a282f412f08f855f4b.png" data-fileid="55537" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="55537" data-unique="6kg2b889k" src="https://academy.hsoub.com/uploads/monthly_2021_01/mongo_ip_010.png.01a7c39c3f4720a282f412f08f855f4b.png" alt="mongo_ip_010.png"></a>
</p>

<p>
	ولتسهيل الأمر، سنسمح بالوصول إلى القاعدة من أي عنوان IP:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2021_01/mongo_all_ip_011.png.3a54215cd6b37afb2562cfbdaf3b9a45.png" data-fileid="55531" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="55531" data-unique="qpdgcxbm8" src="https://academy.hsoub.com/uploads/monthly_2021_01/mongo_all_ip_011.png.3a54215cd6b37afb2562cfbdaf3b9a45.png" alt="mongo_all_ip_011.png"></a>
</p>

<p>
	أخيرًا أصبحنا جاهزين للاتصال بقاعدة البيانات، إبدأ بالنقر على connect:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2021_01/mongo_connect_012.png.5bf582d00fbaf405a1dce5ac55bef6ed.png" data-fileid="55534" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="55534" data-unique="e976veyx2" src="https://academy.hsoub.com/uploads/monthly_2021_01/mongo_connect_012.png.5bf582d00fbaf405a1dce5ac55bef6ed.png" alt="mongo_connect_012.png"></a>
</p>

<p>
	اختر Connect your application:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2021_01/mongo_connect_app_013.png.83276fa2c08eaf774a4a66c9dd8dd702.png" data-fileid="55535" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="55535" data-unique="3514wn92b" src="https://academy.hsoub.com/uploads/monthly_2021_01/mongo_connect_app_013.png.83276fa2c08eaf774a4a66c9dd8dd702.png" alt="mongo_connect_app_013.png"></a>
</p>

<p>
	سيظهر لك عنوان موقع MongoDB، وهو عنوان قاعدة البيانات التي أنشأناها والتي تزود مكتبة عميل MongoDB التي سنضيفها إلى تطبيقنا بالبيانات. يبدو العنوان كالتالي:
</p>

<pre class="ipsCode">
mongodb+srv://fullstack:&lt;PASSWORD&gt;@cluster0-ostce.mongodb.net/test?retryWrites=true
</pre>

<p>
	نحن الآن جاهزين لاستخدام قاعدة البيانات. يمكننا استخدام قاعدة البيانات مباشرة عبر شيفرة JavaScript بمساعدة المكتبة <a href="https://mongodb.github.io/node-mongodb-native/" rel="external nofollow">official MongoDb Node.js driver</a>، لكن استخدامها مربك قليلًا. لذلك سنستخدم بدلًا منها مكتبة <a href="http://mongoosejs.com/index.html" rel="external nofollow">Mongoose</a> التي تؤمن واجهة برمجية عالية المستوى. يمكن توصيف Mongoose بأنها رابط (Mapper) لكائنات من النوع document واختصارًا (ODM). وبالتالي سيكون حفظ كائنات JavaScript كمستندات Mongo مباشرًا باستخدام هذه المكتبة.
</p>

<p>
	لنثبت الآن Mongoose كالتالي:
</p>

<pre class="ipsCode">
npm install mongoose --save
</pre>

<p>
	لن نكتب أية شيفرات تتعامل مع Mongo في الواجهة الخلفية حاليًا، بل سننشئ تطبيقًا تدريبيًا ونضع في مجلده الجذري الملف mongo.js:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4385_7" style="">
<span class="kwd">const</span><span class="pln"> mongoose </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'mongoose'</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">process</span><span class="pun">.</span><span class="pln">argv</span><span class="pun">.</span><span class="pln">length </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">3</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'Please provide the password as an argument: node mongo.js &lt;password&gt;'</span><span class="pun">)</span><span class="pln">
  process</span><span class="pun">.</span><span class="pln">exit</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> password </span><span class="pun">=</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">argv</span><span class="pun">[</span><span class="lit">2</span><span class="pun">]</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> url </span><span class="pun">=</span><span class="pln">
  </span><span class="pun">`</span><span class="pln">mongodb</span><span class="pun">+</span><span class="pln">srv</span><span class="pun">:</span><span class="com">//fullstack:${password}@cluster0-ostce.mongodb.net/test?retryWrites=true`</span><span class="pln">

mongoose</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="pln">url</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> useNewUrlParser</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln"> useUnifiedTopology</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln"> </span><span class="pun">})</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> noteSchema </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> mongoose</span><span class="pun">.</span><span class="typ">Schema</span><span class="pun">({</span><span class="pln">
  content</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</span><span class="pun">,</span><span class="pln">
  date</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">,</span><span class="pln">
  important</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Boolean</span><span class="pun">,</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">Note</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> mongoose</span><span class="pun">.</span><span class="pln">model</span><span class="pun">(</span><span class="str">'Note'</span><span class="pun">,</span><span class="pln"> noteSchema</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> note </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Note</span><span class="pun">({</span><span class="pln">
  content</span><span class="pun">:</span><span class="pln"> </span><span class="str">'HTML is Easy'</span><span class="pun">,</span><span class="pln">
  date</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">(),</span><span class="pln">
  important</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

note</span><span class="pun">.</span><span class="pln">save</span><span class="pun">().</span><span class="pln">then</span><span class="pun">(</span><span class="pln">result </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'note saved!'</span><span class="pun">)</span><span class="pln">
  mongoose</span><span class="pun">.</span><span class="pln">connection</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	<strong>ملاحظة</strong>: قد يختلف عنوان موقع MongoDB عن العنوان الذي عرضناه سابقًا بناء على المنطقة التي اخترتها عند إنشائك العنقود. تأكد من استخدامك العنوان الصحيح الذي حصلت عليه من MongoDB Atlas.
</p>

<p>
	ستفترض الشيفرة بأنها ستمرر كلمة المرور الموجودة ضمن معلومات التوثيق كمعامل لسطر أوامر. حيث يمكننا الوصول إلى معامل سطر الأوامر كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4385_9" style="">
<span class="kwd">const</span><span class="pln"> password </span><span class="pun">=</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">argv</span><span class="pun">[</span><span class="lit">2</span><span class="pun">]</span></pre>

<p>
	عندما تنفذ الشيفرة باستخدام الأمر <code>node mongo.js password</code> ستضيف Mongo ملفًا جديدًا إلى قاعدة البيانات.
</p>

<p>
	<strong>ملاحظة</strong>: استخدم كلمة السر التي اخترتها عند إنشاء قاعدة البيانات وليست كلمة سر الدخول إلى MongoDB Atlas. وانتبه أيضًا إلى الرموز الخاصة التي قد تضعها في كلمة مرورك فستحتاج عندها إلى <a href="https://docs.atlas.mongodb.com/troubleshoot-connection/#special-characters-in-connection-string-password" rel="external nofollow">تشفير الرموز في كلمة المرور عند كتابة عنوان الموقع</a>.
</p>

<p>
	يمكنك الاطلاع على حالة قاعدة البيانات على MongoDB Atlas من Collections في النافذة Overview:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2021_01/atlas_overview_database_014.png.a33c512c6935eed82eeee977260487a2.png" data-fileid="55520" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="55520" data-unique="depygds7x" src="https://academy.hsoub.com/uploads/monthly_2021_01/atlas_overview_database_014.png.a33c512c6935eed82eeee977260487a2.png" alt="atlas_overview_database_014.png"></a>
</p>

<p>
	وكما هو واضح، أضيف مستند يطابق الملاحظة إلى المجموعة <code>notes</code> في قاعدة البيانات التجريبية.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2021_01/atlas_note_document_015.png.77793a50ad6ef2f27e1787d20102966b.png" data-fileid="55519" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="55519" data-unique="2xuz54svf" src="https://academy.hsoub.com/uploads/monthly_2021_01/atlas_note_document_015.png.77793a50ad6ef2f27e1787d20102966b.png" alt="atlas_note_document_015.png"></a>
</p>

<p>
	توصي توثيقات Mongo بإعطاء أسماء منطقية لقواعد البيانات. ويمكننا تغيير اسم قاعدة البيانات من عنوان الموقع للقاعدة:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2021_01/db_name_change_016.png.0f9a047c349aee7764d52be9b843c293.png" data-fileid="55526" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="55526" data-unique="ediqgezv8" src="https://academy.hsoub.com/uploads/monthly_2021_01/db_name_change_016.png.0f9a047c349aee7764d52be9b843c293.png" alt="db_name_change_016.png"></a>
</p>

<p>
	سندمّر الآن قاعدة البيانات التجريبية بتغيير اسم قاعدة البيانات التي يشير إليها مؤسس الاتصال (connection string) إلى note-app بمجرد تعديل عنوان موقع القاعدة:
</p>

<pre class="ipsCode">
mongodb+srv://fullstack:&lt;PASSWORD&gt;@cluster0-ostce.mongodb.net/note-app?retryWrites=true
</pre>

<p>
	سننفذ الشيفرة الآن:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2021_01/db_new_name_017.png.cba1a6fac4b818ffa708448f53beb91d.png" data-fileid="55527" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="55527" data-unique="8sbb3fcsj" src="https://academy.hsoub.com/uploads/monthly_2021_01/db_new_name_017.png.cba1a6fac4b818ffa708448f53beb91d.png" alt="db_new_name_017.png"></a>
</p>

<p>
	لقد خُزِّنت الآن البيانات في القاعدة الصحيحة. يمكن باستخدام create database أن ننشئ قواعد بيانات مباشرة على موقع الويب MongoDB Atlas. لكن لا حاجة لذلك طالما أن الموقع ينشئ تلقائيًا قاعدة بيانات جديدة عندما يحاول التطبيق أن يتصل مع قاعدة بيانات غير موجودة.
</p>

<h2>
	تخطيط قاعدة البيانات
</h2>

<p>
	بعد تأسيس الاتصال مع قاعدة البيانات، سنعرف <a href="http://mongoosejs.com/docs/guide.html" rel="external nofollow">تخطيطًا</a> (Schema) للملاحظة و<a href="http://mongoosejs.com/docs/models.html" rel="external nofollow">نموذجًا</a> (Model) مطابقًا للتخطيط:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4385_11" style="">
<span class="kwd">const</span><span class="pln"> noteSchema </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> mongoose</span><span class="pun">.</span><span class="typ">Schema</span><span class="pun">({</span><span class="pln">
  content</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</span><span class="pun">,</span><span class="pln">
  date</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">,</span><span class="pln">
  important</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Boolean</span><span class="pun">,</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">Note</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> mongoose</span><span class="pun">.</span><span class="pln">model</span><span class="pun">(</span><span class="str">'Note'</span><span class="pun">,</span><span class="pln"> noteSchema</span><span class="pun">)</span></pre>

<p>
	عرّفنا أولًا <a href="http://mongoosejs.com/docs/guide.html" rel="external nofollow">تخطيطًا</a> للملاحظة وأسندناه إلى المتغيّر <code>noteSchema</code>. يخبر التخطيط Mongosse كيف سيُخزّن الكائن الذي يمثل الملاحظة في قاعدة البيانات ثم نعرّف نموذجًا باسم <code>Note</code> يقبل معاملين، الأول هو اسم النموذج المفرد. حيث يختلف الاسم المفرد عن اسم المجموعة بأن الأخير يحمل صيغة الجمع ويكتب بأحرف صغيرة "notes" فهذا <a href="http://mongoosejs.com/docs/models.html" rel="external nofollow">عرف تتبعه Mongoose</a> وتقوم به تلقائيًا بينما يشير التخطيط إلى الملاحظات بالاسم المفرد. المعامل الثاني كما هو واضح هو تخطيط الملاحظة.
</p>

<p>
	لا تملك قواعد البيانات المستقلة مثل Mongo أية تخطيطات (schemaless). ويعني ذلك أن قاعدة البيانات لا تهتم ببنية البيانات المخزنة فيها، حيث يمكن تخزين ملفات بتخطيطات مختلفة تمامًا ضمن نفس المجموعة. لكن الفكرة وراء منح Mongoose البيانات المخزنة في قاعدة البيانات تخطيطًا على مستوى التطبيق، هي تحديد شكل المستندات المخزنة في مجموعة ما.
</p>

<h2>
	إنشاء وحفظ الكائنات
</h2>

<p>
	ينشئ التطبيق في الشيفرة التالية كائن ملاحظة جديد بمساعدة <a href="http://mongoosejs.com/docs/models.html" rel="external nofollow">النموذج</a> <code>Note</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4385_13" style="">
<span class="kwd">const</span><span class="pln"> note </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Note</span><span class="pun">({</span><span class="pln">
  content</span><span class="pun">:</span><span class="pln"> </span><span class="str">'HTML is Easy'</span><span class="pun">,</span><span class="pln">
  date</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">(),</span><span class="pln">
  important</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">,</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	تدعى النماذج "دوال البناء". فهي التي تنشئ الكائنات الجديدة في JavaScript بناء على المعاملات التي تملكها. وطالما أن الكائنات ستبنى باستخدام الدوال البانية للنماذج، ستمتلك كل خصائص النموذج بما فيها توابع حفظ الكائن في قاعدة البيانات. وتحفظ الكائنات في قواعد البيانات باستخدام التابع <code>save</code> الذي يُزوّد بمعالج حدث ضمن التابع <code>then</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4385_15" style="">
<span class="pln">note</span><span class="pun">.</span><span class="pln">save</span><span class="pun">().</span><span class="pln">then</span><span class="pun">(</span><span class="pln">result </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'note saved!'</span><span class="pun">)</span><span class="pln">
  mongoose</span><span class="pun">.</span><span class="pln">connection</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	بعدما يُحفظ الكائن في قاعدة البيانات، يُستدعَى معالج الحدث المعرّف ضمن التابع <code>then</code>. حيث يغلق معالج الحدث قناة الاتصال مع قاعدة البيانات باستخدام الأمر <code>()mongoose.connection.close</code>. إن لم يُغلق الاتصال، فلن ينتهي البرنامج من تنفيذ العملية.
</p>

<p>
	تُخزّن نتيجة عملية الحفظ في المعامل <code>result</code> لمعالج الحدث، لكنها غير مهمة كثيرًا، خاصة أننا خزّنا كائنًا مفردًا في قاعدة البيانات. يمكنك طباعة الكائن على الطرفية إن أردت التمعن فيه أثناء عمل التطبيق أو أثناء تنقيحه. لنخزّن عدة ملاحظات أخرى في قاعدة البيانات. لكن علينا أولًا تعديل الشيفرة ثم إعادة تنفيذها.
</p>

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

<h2>
	إحضار كائنات من قاعدة البيانات
</h2>

<p>
	حَوِّل الشيفرة السابقة التي استخدمناها لإنشاء ملاحظة جديدة إلى تعليقات واستخدم الشيفرة التالية بدلًا منها:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4385_17" style="">
<span class="typ">Note</span><span class="pun">.</span><span class="pln">find</span><span class="pun">({}).</span><span class="pln">then</span><span class="pun">(</span><span class="pln">result </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  result</span><span class="pun">.</span><span class="pln">forEach</span><span class="pun">(</span><span class="pln">note </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">note</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">
  mongoose</span><span class="pun">.</span><span class="pln">connection</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	عندما تُنفّذ الشيفرة سيطبع التطبيق كل الملاحظات الموجودة ضمن قاعدة البيانات:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2021_01/print_all_notes_018.png.02d1e3dca19dca2bc4801194298594cc.png" data-fileid="55539" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="55539" data-unique="yyjrfpq7c" src="https://academy.hsoub.com/uploads/monthly_2021_01/print_all_notes_018.png.02d1e3dca19dca2bc4801194298594cc.png" alt="print_all_notes_018.png"></a>
</p>

<p>
	نحصل على الكائنات من قاعدة البيانات مستعملين التابع <a href="https://mongoosejs.com/docs/api.html#model_Model.find" rel="external nofollow">find</a> العائد للنموذج <code>Note</code>. يقبل التابع السابق معاملًا على هيئة كائن يحتوي على معايير البحث. وطالما أن المعامل في الشيفرة السابقة كائن فارغ ({})، فسنحصل على جميع الملاحظات المخزنة في المجموعة notes. تخضع معايير البحث إلى <a href="https://docs.mongodb.com/manual/reference/operator" rel="external nofollow">قواعد الاستعلام</a> في Mongo. ويمكننا تحديد البحث ليشمل مثلًا الملاحظات الهامة فقط على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4385_19" style="">
<span class="typ">Note</span><span class="pun">.</span><span class="pln">find</span><span class="pun">({</span><span class="pln"> important</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln"> </span><span class="pun">}).</span><span class="pln">then</span><span class="pun">(</span><span class="pln">result </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="com">// ...</span><span class="pln">
</span><span class="pun">})</span></pre>

<h2>
	التمرين 3.12
</h2>

<h2>
	3.12 قاعدة بيانات بسطر أوامر
</h2>

<p>
	أنشئ قاعدة بيانات سحابية باستخدام MongoDB لتطبيق دليل الهاتف وذلك على موقع الويب MongoDb Atlas. أنشئ الملف mongo.js في مجلد المشروع، حيث تضيف الشيفرة في الملف مُدخلات إلى دليل الهاتف، وتشكيل قائمة بكل المُدخلات الموجودة في الدليل.
</p>

<p>
	<strong>ملاحظة</strong>: لا تضع كلمة المرور في الملف الذي سترفعه إلى GitHub.
</p>

<p>
	يجب أن يعمل التطبيق على النحو التالي: تمرير ثلاثة معاملات إلى سطر الأوامر عند تشغيل التطبيق، على أن تكون كلمة السر هي المعامل الأول كما في المثال التالي:
</p>

<pre class="ipsCode">
node mongo.js yourpassword Anna 040-1234556
</pre>

<p>
	سيطبع التطبيق النتيجة التالية:
</p>

<pre class="ipsCode">
added Anna number 040-1234556 to phonebook
</pre>

<p>
	يُخزَّن المُدخل الجديد ضمن قاعدة البيانات. وانتبه إلى وضع الاسم الذي يحتوي على فراغات ضمن قوسي تنصيص مزدوجين:
</p>

<pre class="ipsCode">
node mongo.js yourpassword "Arto Vihavainen" 045-1232456
</pre>

<p>
	إذا شغلت التطبيق بمعامل واحد فقط هو كلمة السر كما يلي:
</p>

<pre class="ipsCode">
node mongo.js yourpassword
</pre>

<p>
	على التطبيق عندها أن يعرض كل المُدخَلات في دليل الهاتف:
</p>

<pre class="ipsCode">
phonebook:
Anna 040-1234556
Arto Vihavainen 045-1232456
Ada Lovelace 040-1231236
</pre>

<p>
	يمكنك الحصول على معاملات سطر الأوامر من المتغيّر <a href="https://nodejs.org/docs/latest-v8.x/api/process.html#process_process_argv" rel="external nofollow">process.argv</a>.
</p>

<p>
	<strong>ملاحظة</strong>: لا تغلق الاتصال في المكان غير المناسب. فلن تعمل على سبيل المثال الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4385_22" style="">
<span class="typ">Person</span><span class="pln">
  </span><span class="pun">.</span><span class="pln">find</span><span class="pun">({})</span><span class="pln">
  </span><span class="pun">.</span><span class="pln">then</span><span class="pun">(</span><span class="pln">persons</span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// ...</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">

mongoose</span><span class="pun">.</span><span class="pln">connection</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span></pre>

<p>
	سيُنفَّذ الأمر <code>()mongoose.connection.close</code>مباشرة بعد أن تبدأ العملية <code>Person.find</code>. أي ستغلق قاعدة البيانات مباشرة قبل أن تنتهي العملية السابقة وتستدعى دالة معالج الحدث. لذلك فالمكان الصحيح لإغلاق قاعدة البيانات سيكون في نهاية معالج الحدث:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4385_24" style="">
<span class="typ">Person</span><span class="pln">
  </span><span class="pun">.</span><span class="pln">find</span><span class="pun">({})</span><span class="pln">
  </span><span class="pun">.</span><span class="pln">then</span><span class="pun">(</span><span class="pln">persons</span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// ...</span><span class="pln">
    mongoose</span><span class="pun">.</span><span class="pln">connection</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">)</span></pre>

<p>
	<strong>ملاحظة</strong>: إذا سميت النموذج <code>person</code>، ستسمي mongoose المجموعة المقابلة <code>people</code>.
</p>

<h2>
	ربط الواجهة الخلفية مع قاعدة البيانات
</h2>

<p>
	لقد تعلمنا ما يكفي لبدء استخدام Mongo في تطبيقنا. لننسخ ونلصق القيم التي عرفناها في التطبيق التجريبي، ضمن الملف index.js:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4385_26" style="">
<span class="kwd">const</span><span class="pln"> mongoose </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'mongoose'</span><span class="pun">)</span><span class="pln">

</span><span class="com">// DO NOT SAVE YOUR PASSWORD TO GITHUB!!</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> url </span><span class="pun">=</span><span class="pln">
  </span><span class="str">'mongodb+srv://fullstack:sekred@cluster0-ostce.mongodb.net/note-app?retryWrites=true'</span><span class="pln">

mongoose</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="pln">url</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> useNewUrlParser</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln"> useUnifiedTopology</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln"> </span><span class="pun">})</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> noteSchema </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> mongoose</span><span class="pun">.</span><span class="typ">Schema</span><span class="pun">({</span><span class="pln">
  content</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</span><span class="pun">,</span><span class="pln">
  date</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">,</span><span class="pln">
  important</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Boolean</span><span class="pun">,</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">Note</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> mongoose</span><span class="pun">.</span><span class="pln">model</span><span class="pun">(</span><span class="str">'Note'</span><span class="pun">,</span><span class="pln"> noteSchema</span><span class="pun">)</span></pre>

<p>
	لنغير معالج حدث إحضار كل الملاحظات إلى الشكل التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4385_28" style="">
<span class="pln">app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="typ">Note</span><span class="pun">.</span><span class="pln">find</span><span class="pun">({}).</span><span class="pln">then</span><span class="pun">(</span><span class="pln">notes </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">notes</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	يمكن أن نتحقق من عمل الواجهة الخلفية بعرض كل الملاحظات على المتصفح:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2021_01/verify_backend_work_019.png.a1bb979b0d071c8b021a39e08f908c7e.png" data-fileid="55541" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="55541" data-unique="j23byrdu6" src="https://academy.hsoub.com/uploads/monthly_2021_01/verify_backend_work_019.png.a1bb979b0d071c8b021a39e08f908c7e.png" alt="verify_backend_work_019.png"></a>
</p>

<p>
	يعمل التطبيق بشكل ممتاز الآن. تفترض الواجهة الأمامية أن لكل كائن معرفّا فريدًا id في الحقل <code>id</code>. وانتبه إلى أننا لا نريد إعادة حقل إصدار mongo إلى الواجهة الأمامية (v__). إحدى الطرق لنتحكم بصيغة الكائن الذي سنعيده، هي <a href="https://stackoverflow.com/questions/7034848/mongodb-output-id-instead-of-id" rel="external nofollow">تعديل</a> تخطيط الكائن باستخدام التابع <code>toJSON</code> والذي سنستعمله في كل النماذج التي تُنشأ اعتمادًا على هذا التخطيط. سيكون التعديل على النحو:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4385_30" style="">
<span class="pln">noteSchema</span><span class="pun">.</span><span class="kwd">set</span><span class="pun">(</span><span class="str">'toJSON'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  transform</span><span class="pun">:</span><span class="pln"> </span><span class="pun">(</span><span class="pln">document</span><span class="pun">,</span><span class="pln"> returnedObject</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    returnedObject</span><span class="pun">.</span><span class="pln">id </span><span class="pun">=</span><span class="pln"> returnedObject</span><span class="pun">.</span><span class="pln">_id</span><span class="pun">.</span><span class="pln">toString</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">delete</span><span class="pln"> returnedObject</span><span class="pun">.</span><span class="pln">_id
    </span><span class="kwd">delete</span><span class="pln"> returnedObject</span><span class="pun">.</span><span class="pln">__v
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	حتى لو بدت الخاصية (id__) لكائن Mongoose كسلسلة نصية فهي في الواقع كائن. لذلك يحولها التابع <code>toJSON</code> إلى سلسلة نصية حتى نتأكد أنها آمنة. إن لم نفعل ذلك ستظهر المشاكل أمامنا بمجرد أن نبدأ كتابة الاختبارات.
</p>

<p>
	ستجيب الواجهة الخلفية على طلب HTTP بقائمة من الكائنات التي أعيدت صياغتها باستخدام التابع <code>toJSON</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4385_32" style="">
<span class="pln">app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="typ">Note</span><span class="pun">.</span><span class="pln">find</span><span class="pun">({}).</span><span class="pln">then</span><span class="pun">(</span><span class="pln">notes </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">notes</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	أسندت مصفوفة الكائنات التي أعادتها Mongo إلى المتغير <code>notes</code>. وعندما ترسل الاستجابة بصيغة JSON، يستدعى التابع <code>toJSON</code> تلقائيًا من أجل كل كائن من المصفوفة باستخدام التابع <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify" rel="external nofollow">JSON.stringify</a>.
</p>

<h2>
	تهيئة قاعدة البيانات في وحدة خاصة بها
</h2>

<p>
	قبل أن نعيد كتابة بقية تطبيق الواجهة الخلفية، لنضع الشيفرة الخاصة بالتعامل مع Mongoose في وحدة مستقلة خاصة بها. لذلك سننشئ مجلدًا جديدًا للوحدة يدعى models ونضيف إليه ملفًا باسم note.js:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4385_34" style="">
<span class="kwd">const</span><span class="pln"> mongoose </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'mongoose'</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> url </span><span class="pun">=</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">MONGODB_URI
console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'connecting to'</span><span class="pun">,</span><span class="pln"> url</span><span class="pun">)</span><span class="pln">
mongoose</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="pln">url</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> useNewUrlParser</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln"> useUnifiedTopology</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln"> </span><span class="pun">})</span><span class="pln">
  </span><span class="pun">.</span><span class="pln">then</span><span class="pun">(</span><span class="pln">result </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'connected to MongoDB'</span><span class="pun">)</span><span class="pln">  </span><span class="pun">})</span><span class="pln">
  </span><span class="pun">.</span><span class="kwd">catch</span><span class="pun">((</span><span class="pln">error</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'error connecting to MongoDB:'</span><span class="pun">,</span><span class="pln"> error</span><span class="pun">.</span><span class="pln">message</span><span class="pun">)</span><span class="pln">  </span><span class="pun">})</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> noteSchema </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> mongoose</span><span class="pun">.</span><span class="typ">Schema</span><span class="pun">({</span><span class="pln">
  content</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</span><span class="pun">,</span><span class="pln">
  date</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">,</span><span class="pln">
  important</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Boolean</span><span class="pun">,</span><span class="pln">
</span><span class="pun">})</span><span class="pln">
noteSchema</span><span class="pun">.</span><span class="kwd">set</span><span class="pun">(</span><span class="str">'toJSON'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  transform</span><span class="pun">:</span><span class="pln"> </span><span class="pun">(</span><span class="pln">document</span><span class="pun">,</span><span class="pln"> returnedObject</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    returnedObject</span><span class="pun">.</span><span class="pln">id </span><span class="pun">=</span><span class="pln"> returnedObject</span><span class="pun">.</span><span class="pln">_id</span><span class="pun">.</span><span class="pln">toString</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">delete</span><span class="pln"> returnedObject</span><span class="pun">.</span><span class="pln">_id
    </span><span class="kwd">delete</span><span class="pln"> returnedObject</span><span class="pun">.</span><span class="pln">__v
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

module</span><span class="pun">.</span><span class="pln">exports </span><span class="pun">=</span><span class="pln"> mongoose</span><span class="pun">.</span><span class="pln">model</span><span class="pun">(</span><span class="str">'Note'</span><span class="pun">,</span><span class="pln"> noteSchema</span><span class="pun">)</span></pre>

<p>
	يختلف تعريف <a href="https://nodejs.org/docs/latest-v8.x/api/modules.html" rel="external nofollow">الوحدات</a> في Node قليلًا عن الطريقة التي عرفنا فيها <a href="https://academy.hsoub.com/programming/javascript/react/%D8%AA%D8%B5%D9%8A%D9%8A%D8%B1-%D9%85%D8%AC%D9%85%D9%88%D8%B9%D8%A9-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%88%D9%85%D9%81%D9%87%D9%88%D9%85-%D8%A7%D9%84%D9%88%D8%AD%D8%AF%D8%A7%D8%AA-%D9%81%D9%8A-react-r1094/" rel="">وحدات ES6</a> في القسم 2. فتُعرّف الواجهة العامة للوحدة بإسناد قيمة للمتغيّر <code>module.exports</code>. سنسند له إذًا النموذج Note. لن يتمكن مستخدم الوحدة من الوصول أو رؤية الأشياء المعرفة داخلها، كالمتغيرات وmonogose وعنوان موقع القاعدة url.
</p>

<p>
	يجري إدراج الوحدة بإضافة السطر التالي إلى الملف index.js:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4385_36" style="">
<span class="kwd">const</span><span class="pln"> </span><span class="typ">Note</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'./models/note'</span><span class="pun">)</span></pre>

<p>
	وهكذا سيُسند المتغيّر <code>Note</code> إلى نفس الكائن الذي تعرّفه الوحدة.
</p>

<p>
	تغيّرت قليلًا الطريقة التي نجري بها الاتصال مع قاعدة البيانات:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4385_38" style="">
<span class="kwd">const</span><span class="pln"> url </span><span class="pun">=</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">MONGODB_URI

console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'connecting to'</span><span class="pun">,</span><span class="pln"> url</span><span class="pun">)</span><span class="pln">

mongoose</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="pln">url</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> useNewUrlParser</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln"> useUnifiedTopology</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln"> </span><span class="pun">})</span><span class="pln">
  </span><span class="pun">.</span><span class="pln">then</span><span class="pun">(</span><span class="pln">result </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'connected to MongoDB'</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">
  </span><span class="pun">.</span><span class="kwd">catch</span><span class="pun">((</span><span class="pln">error</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'error connecting to MongoDB:'</span><span class="pun">,</span><span class="pln"> error</span><span class="pun">.</span><span class="pln">message</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> noteSchema </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> mongoose</span><span class="pun">.</span><span class="typ">Schema</span><span class="pun">({</span><span class="pln">
  content</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</span><span class="pun">,</span><span class="pln">
  date</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">,</span><span class="pln">
  important</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Boolean</span><span class="pun">,</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

noteSchema</span><span class="pun">.</span><span class="kwd">set</span><span class="pun">(</span><span class="str">'toJSON'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  transform</span><span class="pun">:</span><span class="pln"> </span><span class="pun">(</span><span class="pln">document</span><span class="pun">,</span><span class="pln"> returnedObject</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    returnedObject</span><span class="pun">.</span><span class="pln">id </span><span class="pun">=</span><span class="pln"> returnedObject</span><span class="pun">.</span><span class="pln">_id</span><span class="pun">.</span><span class="pln">toString</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">delete</span><span class="pln"> returnedObject</span><span class="pun">.</span><span class="pln">_id
    </span><span class="kwd">delete</span><span class="pln"> returnedObject</span><span class="pun">.</span><span class="pln">__v
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

module</span><span class="pun">.</span><span class="pln">exports </span><span class="pun">=</span><span class="pln"> mongoose</span><span class="pun">.</span><span class="pln">model</span><span class="pun">(</span><span class="str">'Note'</span><span class="pun">,</span><span class="pln"> noteSchema</span><span class="pun">)</span></pre>

<p>
	لا تكتب عنوان الموقع لقاعدة البيانات بشكل مسبق في الشيفرة فهذه فكرة سيئة. بل مرر العنوان إلى التطبيق عبر متغيّر البيئة <code>MONGODB_URI</code>.
</p>

<p>
	تقدم الطريقة التي اتبعناها في تأسيس الاتصال دالتين للتعامل مع حالتي نجاح الاتصال وفشله. حيث تطبع كلتا الدالتين رسائل إلى الطرفية لوصف حالة الاتصال.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2021_01/modules_connect_state_020.png.c3ce32256a4c82c0a6d0712de3df80c3.png" data-fileid="55530" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="55530" data-unique="gka53eewd" src="https://academy.hsoub.com/uploads/monthly_2021_01/modules_connect_state_020.png.c3ce32256a4c82c0a6d0712de3df80c3.png" alt="modules_connect_state_020.png"></a>
</p>

<p>
	ستجد طرقًا عديدة لتعريف قيمة متغيّر البيئة، إحداها أن تعرّفه عندما تشغل التطبيق:
</p>

<pre class="ipsCode">
MONGODB_URI=address_here npm run dev
</pre>

<p>
	الطريقة الأخرى الأكثر تعقيدًا هي استخدام المكتبة <a href="https://github.com/motdotla/dotenv#readme" rel="external nofollow">dotenv</a> التي يمكنك تثبيتها بتنفيذ الأمر:
</p>

<pre class="ipsCode">
npm install dotenv --save
</pre>

<p>
	عليك إنشاء ملف لاحقته env. عند جذر المشروع، ومن ثم تعرف متغيرات البيئة داخله. سيبدو الملف بالشكل التالي:
</p>

<pre class="ipsCode">
MONGODB_URI='mongodb+srv://fullstack:sekred@cluster0-ostce.mongodb.net/note-app?retryWrites=true'
PORT=3001
</pre>

<p>
	لاحظ أننا حددنا رقم المنفذ بشكل مسبق ضمن متغير البيئة <code>PORT</code>.
</p>

<p>
	<strong>ملاحظة</strong> تَجاهل الملف ذو اللاحقة "env."، لأننا لا نريد أن ننشر معلومات التوثيق الخاصة بنا في العلن.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2021_01/env_variable_021.png.87cc00b209bbc10a5757585e1bc639b9.png" data-fileid="55528" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="55528" data-unique="e39aqnwtk" src="https://academy.hsoub.com/uploads/monthly_2021_01/env_variable_021.png.87cc00b209bbc10a5757585e1bc639b9.png" alt="env_variable_021.png"></a>
</p>

<p>
	نستخدم متغيرات البيئة التي عرّفناها في الملف env. بكتابة العبارة <code>()require('dotenv').config</code>، ثم يمكنك بعدها الإشارة إليهم في الشيفرة بالطريقة المعهودة <code>process.env.MONGODB_URI</code>.
</p>

<p>
	لنغيّر الملف index.js على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4385_41" style="">
<span class="pln">require</span><span class="pun">(</span><span class="str">'dotenv'</span><span class="pun">).</span><span class="pln">config</span><span class="pun">()</span><span class="kwd">const</span><span class="pln"> express </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'express'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> app </span><span class="pun">=</span><span class="pln"> express</span><span class="pun">()</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">Note</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'./models/note'</span><span class="pun">)</span><span class="pln">
</span><span class="com">// ..</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> PORT </span><span class="pun">=</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="typ">PORTapp</span><span class="pun">.</span><span class="pln">listen</span><span class="pun">(</span><span class="pln">PORT</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(`</span><span class="typ">Server</span><span class="pln"> running on port $</span><span class="pun">{</span><span class="pln">PORT</span><span class="pun">}`)</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	من المهم إدراج المكتبة dotenv قبل إدراج الوحدة note، لكي نضمن أن متحولات البيئة التي عرّفت داخلها ستكون متاحة للاستخدام ضمن كامل الشيفرة، وذلك قبل إدراج بقية الوحدات.
</p>

<h2>
	استخدام قاعدة البيانات مع معالجات المسار
</h2>

<p>
	لنغيّر بقية الوظائف في تطبيق الواجهة الخلفية ليتعامل مع قواعد البيانات. تضاف ملاحظة جديدة كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4385_43" style="">
<span class="pln">app</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> body </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">body

  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">body</span><span class="pun">.</span><span class="pln">content </span><span class="pun">===</span><span class="pln"> </span><span class="kwd">undefined</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">400</span><span class="pun">).</span><span class="pln">json</span><span class="pun">({</span><span class="pln"> error</span><span class="pun">:</span><span class="pln"> </span><span class="str">'content missing'</span><span class="pln"> </span><span class="pun">})</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  </span><span class="kwd">const</span><span class="pln"> note </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Note</span><span class="pun">({</span><span class="pln">
    content</span><span class="pun">:</span><span class="pln"> body</span><span class="pun">.</span><span class="pln">content</span><span class="pun">,</span><span class="pln">
    important</span><span class="pun">:</span><span class="pln"> body</span><span class="pun">.</span><span class="pln">important </span><span class="pun">||</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">,</span><span class="pln">
    date</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">(),</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">

  note</span><span class="pun">.</span><span class="pln">save</span><span class="pun">().</span><span class="pln">then</span><span class="pun">(</span><span class="pln">savedNote </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">savedNote</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	تنشئ الدالة البانية للنموذج <code>Note</code> الكائنات التي تمثل الملاحظات. ثم ترسل الاستجابة داخل دالة استدعاء التابع <code>save</code>. يضمن هذا أن الاستجابة لن تُعاد إلى المرسل إن لم تنجح العملية. وسنناقش بعد قليل آلية التعامل مع الأخطاء.
</p>

<p>
	يحمل معامل دالة الاستدعاء <code>savedNote</code> الملاحظة الجديدة التي أنشئت. وتذكر أن البيانات التي أعيدت في الاستجابة قد أعيد تنسيقها باستعمال التابع <code>toJSON</code>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4385_45" style="">
<span class="pln">response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">savedNote</span><span class="pun">)</span></pre>

<p>
	تغيرت طريقة إحضار الملاحظات المفردة لتصبح على النحو:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4385_47" style="">
<span class="pln">app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/api/notes/:id'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="typ">Note</span><span class="pun">.</span><span class="pln">findById</span><span class="pun">(</span><span class="pln">request</span><span class="pun">.</span><span class="pln">params</span><span class="pun">.</span><span class="pln">id</span><span class="pun">).</span><span class="pln">then</span><span class="pun">(</span><span class="pln">note </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">note</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">
</span><span class="pun">})</span></pre>

<h2>
	التحقق من تكامل أداء الواجهتين الخلفية والأمامية
</h2>

<p>
	من الجيد اختبار التطبيق الذي يعمل على الواجهة الخلفية عندما نضيف إليه وظائف جديدة، يمكن أن نستعمل في الاختبار برنامج Postman أو VS Code REST client أو المتصفح الذي تستخدمه.
</p>

<p>
	لننشئ ملاحظة جديدة ونخزّنها في قاعدة البيانات:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2021_01/backend_db_note_022.png.de5a4bbb0bce8dcd06f8c4218a91cebd.png" data-fileid="55521" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="55521" data-unique="a4y93qqj5" src="https://academy.hsoub.com/uploads/monthly_2021_01/backend_db_note_022.png.de5a4bbb0bce8dcd06f8c4218a91cebd.png" alt="backend_db_note_022.png"></a>
</p>

<p>
	بعد أن نتأكد من أن كل شيء يعمل على ما يرام في الواجهة الخلفية، نختبر تكامل الواجهة الأمامية مع الخلفية. فمن غير الكافي إطلاقًا اختبار الأشياء ضمن الواجهة الأمامية فقط.
</p>

<p>
	ربما عليك أن تدرس تكامل الوظائف بين الواجهتين وظيفة تلو الأخرى. فيمكننا أولًا إضافة الشيفرة التي تحضر كل البيانات من قاعدة البيانات ونختبرها على طرفية الواجهة الخلفية ضمن المتصفح. بعدها نتأكد أن الواجهة الأمامية تعمل جيدًا مع الشيفرة الجديدة للواجهة الخلفية. عندما يجري كل شيء بشكل جيد ننتقل إلى الوظيفة التالية.
</p>

<p>
	علينا تفقد الحالة الراهنة لقاعدة البيانات بمجرد بدأنا العمل معها. يمكننا القيام بذلك على سبيل المثال، عبر لوحة التحكم في MongoDB Atlas. كما ستفيدك أثناء التطوير بعض برامج Node الصغيرة، كالبرنامج mongo.js الذي كتبناه في هذا الفصل.
</p>

<p>
	ستجد شيفرة التطبيق بشكله الحالي في المستودع part3-4 على موقع <a href="https://github.com/fullstack-hy2020/part3-notes-backend/tree/part3-4" rel="external nofollow">GitHub</a>.
</p>

<h2>
	التمارين 3.13 - 3.14
</h2>

<p>
	يمثل التمرينين التاليين تطبيقًا مباشرًا وسهلًا لما تعلمناه. لكن إن لم تتكامل الواجهتين معًا أثناء العمل، فستكمن الأهمية في كيفية إيجاد ومعالجة الأخطاء التي سببت ذلك.
</p>

<h3>
	3.13 دليل هاتف بقاعدة بيانات: الخطوة 1
</h3>

<p>
	غيِّر طريقة إحضار جميع المُدخَلات لكي يتم ذلك من قاعدة بيانات. تأكد أن الواجهة الأمامية ستعمل بعد إجراء هذه التغييرات. سنتكتب شيفرة التعامل مع قاعدة البيانات MongoDB في وحدة خاصة بها خلال التمارين القادمة، كما فعلنا سابقًا في هذا الفصل (تهيئة قاعدة البيانات في وحدة خاصة بها).
</p>

<h3>
	3.14 دليل هاتف بقاعدة بيانات: الخطوة 2
</h3>

<p>
	غيّر في شيفرة الواجهة الخلفية بحيث تحفظ الأرقام في قاعدة البيانات، وتحقق أن الواجهة الأمامية ستعمل بشكل جيد بعد التغييرات في الواجهة الخلفية.
</p>

<p>
	يمكنك في هذه المرحلة أن تجعل المستخدمين يدخلون كل ما يشاؤون في دليل الهاتف. ولاحظ أن دليل الهاتف قد يحوي الاسم نفسه مكررًا مرات عدة.
</p>

<h2>
	معالجة الأخطاء
</h2>

<p>
	لو حاولنا الوصول إلى موقع ملاحظة بمعرّف id غير موجود. فستكون الإجابة Null (لا شيء). لنغير ذلك بحيث يستجيب الخادم على هذا الطلب برمز الحالة 404 (غير موجود). سنضيف أيضًا كتلة <code>catch</code> لتتعامل مع الحالات التي يُرفض فيها الوعد الذي يعيده التابع <code>findById</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4385_49" style="">
<span class="pln">app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/api/notes/:id'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="typ">Note</span><span class="pun">.</span><span class="pln">findById</span><span class="pun">(</span><span class="pln">request</span><span class="pun">.</span><span class="pln">params</span><span class="pun">.</span><span class="pln">id</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">then</span><span class="pun">(</span><span class="pln">note </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">note</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
          response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">note</span><span class="pun">)</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
      </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
          response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">404</span><span class="pun">).</span><span class="pln">end</span><span class="pun">()</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">
    </span><span class="pun">.</span><span class="kwd">catch</span><span class="pun">(</span><span class="pln">error </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">error</span><span class="pun">)</span><span class="pln">
      response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">500</span><span class="pun">).</span><span class="pln">end</span><span class="pun">()</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	إن لم يُعثر على تطابق في قاعدة البيانات، ستكون قيمة الكائن <code>note</code> هي null، وبالتالي ستنفذ كتلة <code>else</code>. سينتج عن ذلك استجابة برمز الحالة 404 (غير موجود). وأخيرًا، إن رفض الوعد الذي يعيده التابع <code>findById</code>، ستكون الاستجابة برمز الحالة 500 (خطأ داخلي في الخادم). ستعرض لك طرفية التطوير معلومات مفصلة أكثر عن الخطأ.
</p>

<p>
	بالإضافة إلى الخطأ الناتج عن عدم وجود ملاحظة، ستواجه حالة أخرى يتوجب عليك معالجتها. تتلخص هذه الحالة بمحاولة إحضار ملاحظة بمعرّف id من نوع خاطئ لا يطابق تنسيق Mongo للمعرفات IDs. فلو ارتكبنا خطأً كهذا، سنرى الرسالة التالية:
</p>

<pre class="ipsCode">
Method: GET
Path:   /api/notes/someInvalidId
Body:   {}
---
{ CastError: Cast to ObjectId failed for value "someInvalidId" at path "_id"
    at CastError (/Users/mluukkai/opetus/_fullstack/osa3-muisiinpanot/node_modules/mongoose/lib/error/cast.js:27:11)
    at ObjectId.cast (/Users/mluukkai/opetus/_fullstack/osa3-muisiinpanot/node_modules/mongoose/lib/schema/objectid.js:158:13)
    ...
</pre>

<p>
	إن إعطاء معرّف id بصيغة خاطئة، سيدفع التابع <code>findById</code> لإشهار خطأ يسبب رفضًا للوعد. كنتيجة لذلك ستُستدعى الدالة الموجودة في الكتلة <code>catch</code>. لنغيّر طريقة الاستجابة قليلًا في تلك الكتلة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4385_52" style="">
<span class="pln">app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/api/notes/:id'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="typ">Note</span><span class="pun">.</span><span class="pln">findById</span><span class="pun">(</span><span class="pln">request</span><span class="pun">.</span><span class="pln">params</span><span class="pun">.</span><span class="pln">id</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">then</span><span class="pun">(</span><span class="pln">note </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">note</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">note</span><span class="pun">)</span><span class="pln">
      </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">404</span><span class="pun">).</span><span class="pln">end</span><span class="pun">()</span><span class="pln"> 
      </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">})</span><span class="pln">
    </span><span class="pun">.</span><span class="kwd">catch</span><span class="pun">(</span><span class="pln">error </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">error</span><span class="pun">)</span><span class="pln">
      response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">400</span><span class="pun">).</span><span class="pln">send</span><span class="pun">({</span><span class="pln"> error</span><span class="pun">:</span><span class="pln"> </span><span class="str">'malformatted id'</span><span class="pln"> </span><span class="pun">})</span><span class="pln">    </span><span class="pun">})</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	إن لم يكن تنسيق المعرف id صحيحًا، ستنتهي العملية بتنفيذ معالج الخطأ الموجود في <code>catch</code>. إن الاستجابة الملائمة لهذا الخطأ هو رمز الحالة 400 (طلب خاطئ) <a href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1" rel="external nofollow">400 Bad Request</a>، لأن هذه الحالة تطابق تمامًا الوصف التالي:
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		لم يتمكن الخادم من فهم الطلب نظرًا لخطأ في صياغته. لا ينبغي على العميل إعادة هذا الطلب قبل تعديله.
	</p>
</blockquote>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4385_54" style="">
<span class="pun">.</span><span class="kwd">catch</span><span class="pun">(</span><span class="pln">error </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">error</span><span class="pun">)</span><span class="pln">
  response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">400</span><span class="pun">).</span><span class="pln">send</span><span class="pun">({</span><span class="pln"> error</span><span class="pun">:</span><span class="pln"> </span><span class="str">'malformatted id'</span><span class="pln"> </span><span class="pun">})</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	قد يُستدعى معالج الخطأ التي زرعته، نتيجة لخطأ مختلف تمامًا عن الخطأ الذي تريد اعتراضه. فإن طبعت الخطأ على الطرفية ستوفر على نفسك عناء جلسات التنقيح المحبطة. وعلاوة على ذلك تزودك معظم الخدمات الحديثة بوسيلة ما لطباعة عمليات النظام، بحيث يمكنك الاطلاع عليها متى أردت والخادم Heroku مثال مهم عليها.
</p>

<p>
	وطالما أنك تعمل على الواجهة الخلفية فابق نظرك على الطرفية التي تظهر لك خرج العملية حتى لو عملت على شاشة صغيرة، سيلفت وقوع أي خطأ انتباهك.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2021_01/error_screen_023.png.79adb19c01c9f7072f843e647e51ccdc.png" data-fileid="55529" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="55529" data-unique="ek7uhk7ja" src="https://academy.hsoub.com/uploads/monthly_2021_01/error_screen_023.png.79adb19c01c9f7072f843e647e51ccdc.png" alt="error_screen_023.png"></a>
</p>

<h2>
	تحويل معالجات الأخطاء إلى أداة وسطية
</h2>

<p>
	لقد كتبنا شيفرة ملاحقة الأخطاء ضمن بقية أجزاء الشيفرة، ويبدو الأمر معقولًا أحيانًا، لكن من الأفضل إضافة معالجات الأخطاء في مكان واحد. سيكون هذا الأمر مفيدًا إذا أردنا لاحقًا أن نقدم تقريرًا عن الأخطاء إلى منظومة تتبع أخطاء خارجية مثل <a href="https://sentry.io/welcome/" rel="external nofollow">Sentry</a>.
</p>

<p>
	لنغيّر معالج المسار api/notes/:id/، بحيث يمرر الخطأ إلى الدالة <code>next</code> كمعامل، وتمثل هذه الدالة بدورها المعامل الثالث لمعالج المسار:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4385_56" style="">
<span class="pln">app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/api/notes/:id'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">  </span><span class="typ">Note</span><span class="pun">.</span><span class="pln">findById</span><span class="pun">(</span><span class="pln">request</span><span class="pun">.</span><span class="pln">params</span><span class="pun">.</span><span class="pln">id</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">then</span><span class="pun">(</span><span class="pln">note </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">note</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">note</span><span class="pun">)</span><span class="pln">
      </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">404</span><span class="pun">).</span><span class="pln">end</span><span class="pun">()</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">})</span><span class="pln">
    </span><span class="pun">.</span><span class="kwd">catch</span><span class="pun">(</span><span class="pln">error </span><span class="pun">=&gt;</span><span class="pln"> next</span><span class="pun">(</span><span class="pln">error</span><span class="pun">))})</span></pre>

<p>
	عندما تستدعى <code>next</code> دون معامل، سينتقل التنفيذ بكل بساطة إل المسار أو الأداة الوسطية التالية. بينما لو امتلكت هذه الدالة معاملًا فسيتابع التنفيذ إلى الأداة الوسطية لمعالجة الخطأ.
</p>

<p>
	<a href="https://expressjs.com/en/guide/error-handling.html" rel="external nofollow">معالجات أخطاء المكتبة Express</a> هي أدوات وسطية تعرّف على شكل دالة تقبل أربع معاملات. سيبدو معالج الخطأ بالشكل التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4385_58" style="">
<span class="kwd">const</span><span class="pln"> errorHandler </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">error</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">error</span><span class="pun">(</span><span class="pln">error</span><span class="pun">.</span><span class="pln">message</span><span class="pun">)</span><span class="pln">

  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">error</span><span class="pun">.</span><span class="pln">name </span><span class="pun">===</span><span class="pln"> </span><span class="str">'CastError'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">400</span><span class="pun">).</span><span class="pln">send</span><span class="pun">({</span><span class="pln"> error</span><span class="pun">:</span><span class="pln"> </span><span class="str">'malformatted id'</span><span class="pln"> </span><span class="pun">})</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> 

  next</span><span class="pun">(</span><span class="pln">error</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">errorHandler</span><span class="pun">)</span></pre>

<p>
	يتحقق المعالج من طبيعة الخطأ. فإن كان الاستثناء هو خطأ تحويل نوع (CastError)، سنتأكد أن مصدر الخطأ هو كائن id بتنسيق مخالف لقواعد Mongo. وعندها سيرسل معالج الخطأ استجابته إلى المتصفح عبر كائن الاستجابة <code>response</code> الذي يمرر كمعامل للمعالج. أما في بقية الاستثناءات، فسيمرر المعالج الخطأ إلى معالج الخطأ الافتراضي في express.
</p>

<h2>
	تسلسل استخدام الأدوات الوسطية
</h2>

<p>
	تنفذ الأدوات الوسطية بنفس تسلسل استخدامها في express، أي بنفس تسلسل ظهور الأمر <code>app.use</code>. لذلك ينبغي الانتباه أثناء تعريفها. سيكون التسلسل الصحيح للاستخدام كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4385_60" style="">
<span class="pln">app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">express</span><span class="pun">.</span><span class="kwd">static</span><span class="pun">(</span><span class="str">'build'</span><span class="pun">))</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">express</span><span class="pun">.</span><span class="pln">json</span><span class="pun">())</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">logger</span><span class="pun">)</span><span class="pln">

app</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> body </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">body
  </span><span class="com">// ...</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> unknownEndpoint </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">404</span><span class="pun">).</span><span class="pln">send</span><span class="pun">({</span><span class="pln"> error</span><span class="pun">:</span><span class="pln"> </span><span class="str">'unknown endpoint'</span><span class="pln"> </span><span class="pun">})</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="com">// handler of requests with unknown endpoint</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">unknownEndpoint</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> errorHandler </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">error</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="com">// ...</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="com">// handler of requests with result to errors</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">errorHandler</span><span class="pun">)</span></pre>

<p>
	يجب أن تضع الأداة الوسطية json-parser بين الأدوات الوسطية التي تعرّف أولًا. فلو كان الترتيب كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4385_62" style="">
<span class="pln">app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">logger</span><span class="pun">)</span><span class="pln"> </span><span class="com">// request.body is undefined!</span><span class="pln">

app</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="com">// request.body is undefined!</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> body </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">body
  </span><span class="com">// ...</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">express</span><span class="pun">.</span><span class="pln">json</span><span class="pun">())</span></pre>

<p>
	لن تكون البيانات المرسلة عبر طلب HTTP بصيغة JSON متاحة للاستخدام عبر الأداة الوسطية للولوج أو عبر معالج المسار POST، لأن الخاصية <code>request.body</code> ستكون غير محددة undefined في هذه المرحلة. ومن المهم جدًا أن تُعرّف الأداة الوسطية التي تعالج مشكلة المسارات غير المدعومة ضمن التعريفات الأخيرة، تمامًا قبل معالج الأخطاء. سيسبب الترتيب التالي على سبيل المثال مشكلة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4385_64" style="">
<span class="kwd">const</span><span class="pln"> unknownEndpoint </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">404</span><span class="pun">).</span><span class="pln">send</span><span class="pun">({</span><span class="pln"> error</span><span class="pun">:</span><span class="pln"> </span><span class="str">'unknown endpoint'</span><span class="pln"> </span><span class="pun">})</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="com">// handler of requests with unknown endpoint</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">unknownEndpoint</span><span class="pun">)</span><span class="pln">

app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="com">// ...</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	لقد ظهر معالج النهاية غير المحددة (unknown endpoint) قبل معالج طلبات HTTP. وطالما أن معالج النهايات غير المحددة سيستجيب إلى كافة الطلبات بالرمز 404 unknown endpoint، فلن يُستدعى بعدها أي مسار أو أداة وسطية. ويبقى الاستثناء الوحيد هو معالج الخطأ الذي يجب أن يأتي أخيرًا بعد معالج النهايات غير المحددة.
</p>

<h2>
	خيارات أخرى
</h2>

<p>
	سنضيف الآن بعض الوظائف التي لم ندرجها بعد، بما فيها الحذف وتحديث ملاحظة مفردة. وأسهل الطرق لحذف ملاحظة من قاعدة البيانات هي استخدام التابع <a href="https://mongoosejs.com/docs/api.html#model_Model.findByIdAndRemove" rel="external nofollow">findByIdAndRemove</a>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4385_66" style="">
<span class="pln">app</span><span class="pun">.</span><span class="kwd">delete</span><span class="pun">(</span><span class="str">'/api/notes/:id'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="typ">Note</span><span class="pun">.</span><span class="pln">findByIdAndRemove</span><span class="pun">(</span><span class="pln">request</span><span class="pun">.</span><span class="pln">params</span><span class="pun">.</span><span class="pln">id</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">then</span><span class="pun">(</span><span class="pln">result </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">204</span><span class="pun">).</span><span class="pln">end</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">})</span><span class="pln">
    </span><span class="pun">.</span><span class="kwd">catch</span><span class="pun">(</span><span class="pln">error </span><span class="pun">=&gt;</span><span class="pln"> next</span><span class="pun">(</span><span class="pln">error</span><span class="pun">))</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	سيستجيب الخادم في كلتا حالتي نجاح عملية الحذف برمز الحالة 202 (لا يوجد محتوى). ونقصد بالحالتين، حذف ملاحظة موجودة فعلًا، أو حذف ملاحظة غير موجودة. يمكن أن نستخدم المعامل <code>result</code> للتحقق من حذف المورد أم لا، وبالتالي سنتمكن من تحديد أي من حالتي النجاح قد أعيدت إن كنا بحاجة ماسة لذلك. ستمرر كل الاستثناءات إلى معالج الأخطاء.
</p>

<p>
	يمكن بسهولة تغيير أهمية الملاحظة باستخدام التابع <a href="https://mongoosejs.com/docs/api.html#model_Model.findByIdAndUpdate" rel="external nofollow">findByIdAndUpdate</a>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4385_68" style="">
<span class="pln">app</span><span class="pun">.</span><span class="pln">put</span><span class="pun">(</span><span class="str">'/api/notes/:id'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> body </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">body

  </span><span class="kwd">const</span><span class="pln"> note </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    content</span><span class="pun">:</span><span class="pln"> body</span><span class="pun">.</span><span class="pln">content</span><span class="pun">,</span><span class="pln">
    important</span><span class="pun">:</span><span class="pln"> body</span><span class="pun">.</span><span class="pln">important</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  </span><span class="typ">Note</span><span class="pun">.</span><span class="pln">findByIdAndUpdate</span><span class="pun">(</span><span class="pln">request</span><span class="pun">.</span><span class="pln">params</span><span class="pun">.</span><span class="pln">id</span><span class="pun">,</span><span class="pln"> note</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="kwd">new</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln"> </span><span class="pun">})</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">then</span><span class="pun">(</span><span class="pln">updatedNote </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">updatedNote</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">})</span><span class="pln">
    </span><span class="pun">.</span><span class="kwd">catch</span><span class="pun">(</span><span class="pln">error </span><span class="pun">=&gt;</span><span class="pln"> next</span><span class="pun">(</span><span class="pln">error</span><span class="pun">))</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	تسمح الشيفرة السابقة أيضًا بتعديل محتوى الملاحظة لكنها لا تدعم تغيير تاريخ الإنشاء. ولاحظ كيف يتلقى التابع <code>findByIdAndUpdate</code> معاملًا على هيئة كائن JavaScript نظامي، وليس كائن ملاحظة أنشئ بواسطة الدالة البانية للنموذج <code>Note</code>. يبقى لدينا تفصيل مهم يتعلق بالتابع <code>findByIdAndUpdate</code>. فمعامل معالج الحدث <code>upDateNote</code> سيستقبل الملف الأصلي للملاحظة <a href="https://mongoosejs.com/docs/api.html#model_Model.findByIdAndUpdate" rel="external nofollow">بلا تعديلات</a>. لذلك وضعنا المعامل الاختياري <code>{new: true}</code>، الذي يسبب استدعاء معالج الحدث بالنسخة المعدَّلة من الملاحظة بدلًا من الأصلية.
</p>

<p>
	بعد اختبار الواجهة الخلفية مباشرة مستخدمين Postman أو VS Code REST client، يمكننا التحقق من أنها تعمل بشكل صحيح. وكذلك التحقق من أن الواجهة الأمامية تتكامل مع الخلفية التي تستخدم قاعدة البيانات.
</p>

<p>
	لكن عندما نحاول تغيير أهمية ملاحظة، تستظهر على الطرفية رسالة الخطأ التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2021_01/toggle_imporant_error_24.png.fb833fa026aa6b4ebb1b1a44130cdf7e.png" data-fileid="55540" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="55540" data-unique="kyw4bbmlg" src="https://academy.hsoub.com/uploads/monthly_2021_01/toggle_imporant_error_24.png.fb833fa026aa6b4ebb1b1a44130cdf7e.png" alt="toggle_imporant_error_24.png"></a>
</p>

<p>
	استخدم google للبحث عن سبب الخطأ وسيقودك إلى <a href="https://stackoverflow.com/questions/52572852/deprecationwarning-collection-findandmodify-is-deprecated-use-findoneandupdate" rel="external nofollow">إرشادات</a> لتصحيحه. و<a href="https://mongoosejs.com/docs/deprecations.html" rel="external nofollow">اتباعًا للاقتراح الموجود في توثيق Mongoose</a>، أضفنا السطر التالي للملف note.js:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4385_70" style="">
<span class="kwd">const</span><span class="pln"> mongoose </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'mongoose'</span><span class="pun">)</span><span class="pln">

mongoose</span><span class="pun">.</span><span class="kwd">set</span><span class="pun">(</span><span class="str">'useFindAndModify'</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">)</span><span class="pln">
</span><span class="com">// ...</span><span class="pln">

module</span><span class="pun">.</span><span class="pln">exports </span><span class="pun">=</span><span class="pln"> mongoose</span><span class="pun">.</span><span class="pln">model</span><span class="pun">(</span><span class="str">'Note'</span><span class="pun">,</span><span class="pln"> noteSchema</span><span class="pun">)</span><span class="pln"> </span></pre>

<p>
	ستجد شيفرة التطبيق بشكله الحالي في المستودع part3-5 على موقع <a href="https://github.com/fullstack-hy2020/part3-notes-backend/tree/part3-5" rel="external nofollow">GitHub</a>.
</p>

<h2>
	التمارين 3.15 - 3.18
</h2>

<h3>
	3.15 دليل هاتف بقاعدة بيانات: الخطوة 3
</h3>

<p>
	عدّل شيفرة الواجهة الخلفية لتحذف المُدخَلات مباشرة من قاعدة البيانات. تحقق أن الواجهة الأمامية تعمل بشكل صحيح بعد التعديلات.
</p>

<h3>
	3.16 دليل هاتف بقاعدة بيانات: الخطوة 4
</h3>

<p>
	حوّل معالج الخطأ في التطبيق إلى أداة وسطية جديدة لمعالجة الأخطاء.
</p>

<h3>
	3.17 دليل هاتف بقاعدة بيانات: الخطوة 5 *
</h3>

<p>
	إذا حاول المستخدم إنشاء مُدخَل جديد إلى الدليل، وكان اسم الشخص موجودًا مسبقًا، ستحاول الواجهة الأمامية تحديث رقم الهاتف للمُدخَل الموجود بإرسال طلب HTTP-PUT إلى عنوان المُدخَل. عدّل الواجهة الخلفية لتدعم هذا الفعل، ثم تأكد أن الواجهة الأمامية ستعمل بعد التعديل.
</p>

<h3>
	3.18 دليل هاتف بقاعدة بيانات: الخطوة 6 *
</h3>

<p>
	عدّل معالج المسار api/persons/:id ومعالج المسار api/persons/info ليستخدما قاعدة البيانات، ثم تحقق من أنهما يعملان جيدًا مستخدمًا المتصفح وPostman وVS Code REST client. سيبدو لك الأمر عند التحقق من مُدخَل فردي إلى دليل الهاتف كما في الشكل التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2021_01/browser_chk_025.png.644abb697e72fbcc25ea1b8f3356507b.png" data-fileid="55522" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="55522" data-unique="j5xre9qay" src="https://academy.hsoub.com/uploads/monthly_2021_01/browser_chk_025.png.644abb697e72fbcc25ea1b8f3356507b.png" alt="browser_chk_025.png"></a>
</p>

<p>
	ترجمة -وبتصرف- للفصل <a href="https://fullstackopen.com/en/part3/saving_data_to_mongo_db" rel="external nofollow">saving data to MongoDB</a> من سلسلة <a href="https://fullstackopen.com/en/" rel="external nofollow">Deep Dive Into Modern Web Development</a>
</p>
]]></description><guid isPermaLink="false">1101</guid><pubDate>Sun, 17 Jan 2021 13:07:04 +0000</pubDate></item><item><title>&#x646;&#x634;&#x631; &#x62A;&#x637;&#x628;&#x64A;&#x642; Node.js &#x639;&#x644;&#x649; &#x627;&#x644;&#x648;&#x64A;&#x628;: &#x62E;&#x62F;&#x645;&#x629; &#x647;&#x64A;&#x631;&#x648;&#x643;&#x648; (Heroku) &#x645;&#x62B;&#x627;&#x644;&#x64B;&#x627;</title><link>https://academy.hsoub.com/programming/javascript/nodejs/%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-nodejs-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D8%AE%D8%AF%D9%85%D8%A9-%D9%87%D9%8A%D8%B1%D9%88%D9%83%D9%88-heroku-%D9%85%D8%AB%D8%A7%D9%84%D9%8B%D8%A7-r1100/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2021_01/p14-2.jpg.803aac7773adb16f0b296c1b0f060460.jpg" /></p>
<p>
	سنربط في الفقرات التالية تطبيق الواجهة الأمامية الذي أنشأناه في القسم السابق من <a href="https://academy.hsoub.com/tags/full_stack_101/" rel="">هذه السلسلة</a> مع تطبيق <a href="https://academy.hsoub.com/programming/general/%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D8%AE%D9%84%D9%81%D9%8A%D8%A9-backend-web-development/" rel="">الواجهة الخلفية</a>.
</p>

<p>
	رأينا في القسم السابق، أن الواجهة الأمامية قادرة على الوصول إلى قائمة بكل الملاحظات من خادم JSON الذي لعب دور الواجهة الخلفية وذلك بطلب العنوان <a href="http://localhost:3001/notes." ipsnoembed="false" rel="external nofollow">http://localhost:3001/notes.</a> لكن عنوان الموقع قد تغير قليلًا الآن، وستجد الملاحظات على العنوان <a href="http://localhost:3001/api/notes." ipsnoembed="false" rel="external nofollow">http://localhost:3001/api/notes.</a> إذًا، سنغيّر الصفة <code>baseUrl</code> في الملف src/services/notes.js كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5642_7" style=""><span class="kwd">import</span><span class="pln"> axios from </span><span class="str">'axios'</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> baseUrl </span><span class="pun">=</span><span class="pln"> </span><span class="str">'http://localhost:3001/api/notes'</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> getAll </span><span class="pun">=</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> request </span><span class="pun">=</span><span class="pln"> axios</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="pln">baseUrl</span><span class="pun">)</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">then</span><span class="pun">(</span><span class="pln">response </span><span class="pun">=&gt;</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">data</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="com">// ...</span><span class="pln">

</span><span class="kwd">export</span><span class="pln"> </span><span class="kwd">default</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> getAll</span><span class="pun">,</span><span class="pln"> create</span><span class="pun">,</span><span class="pln"> update
 </span><span class="pun">}</span></pre>

<p>
	لم يعمل الطلب GET إلى العنوان <a href="http://localhost:3001/api/notes" ipsnoembed="false" rel="external nofollow">http://localhost:3001/api/notes</a> لسبب ما.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="55515" href="https://academy.hsoub.com/uploads/monthly_2021_01/get_request_error_001.png.1af9a6d99651b3519244d62e78fd35dc.png" rel="" data-fileext="png"><img alt="get_request_error_001.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55515" data-unique="u82o4t979" src="https://academy.hsoub.com/uploads/monthly_2021_01/get_request_error_001.png.1af9a6d99651b3519244d62e78fd35dc.png"></a>
</p>

<p>
	مالذي يحدث؟ يمكننا التحقق بالولوج إلى الواجهة الخلفية عبر المتصفح باستخدام Postman بلا أدنى مشكلة.
</p>

<h2>
	سياسة الجذر المشترك ومفهوم CORS
</h2>

<p>
	تأتي التسمية CORS من العبارة Cross-Origin Resource Sharing وتعني هذه العبارة "مشاركة الموارد ذات الجذور المختلطة". ووفقًا لموقع Wikipedia:
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p data-gramm="false">
		مشاركة الموارد ذات الجذور المختلطة هي آلية تسمح بطلب مصادر محددة (مثل تعريف خطوط الكتابة) في صفحة ويب معينة من قبل موقع آخر مختلف عن الموقع الذي نشر هذا المورد للمرة الأولى. حيث يمكن لصفحة ويب أن تضيف الصور ذات الأصول المختلطة وأوراق التسيق المتتالي وسكربتات البرمجة وأطر دمج الموارد في الصفحات iframes ومقاطع الفيديو. وقد تُمنع طلبات بعض أنواع المصادر ذات الأصول المختلطة افتراضيًا مثل طلبات AJAX وفقًا لسياسة خصوصية الجذر المشترك.
	</p>
</blockquote>

<p>
	تظهر هذه المشكلة في حالتنا بالصورة التالية: لا يمكن لشيفرة JavaScript التي نكتبها لتطبيق الواجهة الأمامية أن يعمل افتراضيًا مع خادم حتى يشتركا بالجذر نفسه. حيث يعمل تطبيق الخادم الذي أنجزناه سابقًا على المنفذ 3001، ويعمل تطبيق الواجهة الأمامية على المنفذ 3000. فلا يملكان جذرًا مشتركًا.
</p>

<p>
	وتذكر أن هذه السياسة لاتنطبق على React و Node فقط، بل هي سياسة عالمية لتشغيل تطبيقات الويب.
</p>

<p>
	يمكننا تخطي ذلك في تطبيقاتنا باستعمال الأداة الوسطية <a href="https://github.com/expressjs/cors" rel="external nofollow">cors</a> التي تقدمها Node. ثَبّت <a href="https://github.com/expressjs/cors" rel="external nofollow">cors</a> باستخدام الأمر التالي:
</p>

<pre class="ipsCode">npm install cors --save
</pre>

<p>
	أدرج الأداة واستعملها على النحو:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5642_9" style=""><span class="kwd">const</span><span class="pln"> cors </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'cors'</span><span class="pun">)</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">cors</span><span class="pun">())</span></pre>

<p>
	وهكذا سترى أن تطبيق الواجهة الأمامية سيعمل، لكننا لم نضف حتى الآن وظيفة تغيير أهمية الملاحظة إلى الواجهة الخلفية.
</p>

<p>
	يمكنك الاطلاع على معلومات أكثر عن الأداة CORS من خلال <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" rel="external nofollow">Mozilla's page</a>.
</p>

<h2>
	تطبيقات للإنترنت
</h2>

<p>
	طالما تأكدنا أن التطبيق بشقيه أصبح جاهزًا للعمل، سننقله إلى الإنترنت. سنستعين بالخادم <a href="https://www.heroku.com/" rel="external nofollow">Heroku</a> لتنفيذ ذلك.
</p>

<p>
	إن لم تستخدم Heroku من قبل، ستجد تعليمات الاستخدام في <a href="https://devcenter.heroku.com/articles/getting-started-with-nodejs" rel="external nofollow">توثيق Heroku</a> أو بالبحث عبر الإنترنت.
</p>

<p>
	أضف الملف Procfile إلى جذر المشروع لتخبر Heroku كيف سيُشغّل التطبيق.
</p>

<pre class="ipsCode">web: npm start
</pre>

<p>
	غيّر تعريف المنفذ الذي نستخدمه في تطبيقنا أسفل الملف index.js ليصبح كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5642_12" style=""><span class="kwd">const</span><span class="pln"> PORT </span><span class="pun">=</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">PORT </span><span class="pun">||</span><span class="pln"> </span><span class="lit">3001</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">listen</span><span class="pun">(</span><span class="pln">PORT</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(`</span><span class="typ">Server</span><span class="pln"> running on port $</span><span class="pun">{</span><span class="pln">PORT</span><span class="pun">}`)</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	وهكذا فإما أن سنستخدم المنفذ الذي عرّفناه <a href="https://en.wikipedia.org/wiki/Environment_variable" rel="external nofollow">كمتغيير بيئة</a> أو المنفذ 3001 إن لم يُعرّف منفذ من خلال متغير البيئة. يُهيِّئ Heroku منفذ التطبيق بناء على قيمة منفذ متغير البيئة.
</p>

<p>
	أنشئ مستودع git في جذر المشروع وأضف الملف ذو اللاحقة gitignore. وفيه المعلومات التالية:
</p>

<pre class="ipsCode">node_modules
</pre>

<p>
	أنشئ تطبيق Heroku بتنفيذ الأمر <code>heroku create</code>، حمل شيفرتك إلى المستودع ثم انقله إلى Heroku بتنفيذ الأمر <code>git push heroku master</code>. إن جرى كل شيء على مايرام، سيعمل التطبيق:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="55511" href="https://academy.hsoub.com/uploads/monthly_2021_01/backend_heroku_works_002.png.728736c03b164539d9dc40558c509aa2.png" rel="" data-fileext="png"><img alt="backend_heroku_works_002.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55511" data-unique="lpe45ctvs" src="https://academy.hsoub.com/uploads/monthly_2021_01/backend_heroku_works_002.png.728736c03b164539d9dc40558c509aa2.png"></a>
</p>

<p>
	إن لم يعمل التطبيق، تحقق من المشكلة بقراءة سجلات Heroku، وذلك بتنفيذ الأمر <code>heroku logs</code>.
</p>

<p>
	من المفيد في البداية أن تراقب باستمرار ما يظهره Heroku من سجلات. وأفضل وسيلة للمراقبة تنفيذ الأمر <code>heroku log -t</code> الذي يطبع سجلاته على الطرفية، عندما تحصل مشكلة ما على الخادم.
</p>

<p>
	إن كنت ستنشر تطبيقك من مستودع git ولم تكن الشيفرة موجودة في الفرع الرئيسي (إي إن كنت ستعدل <a href="https://github.com/fullstack-hy2020/part3-notes-backend/tree/part3-2" rel="external nofollow">مستوع الملاحظات</a> من الدرس السابق)، عليك استخدام الأمر <code>git push heroku HEAD:master</code>. وإن فعلت ذلك مسبقًا، ربما ستحتاج إلى تنفيذ هذا الأمر <code>git push heroku HEAD:master --force</code>.
</p>

<p>
	تتكامل الواجهة الخلفية مع الأمامية على Heroku أيضًا. يمكنك التحقق من ذلك بتغيير عنوان الواجهة الخلفية الموجود ضمن شيفرة الواجهة الأمامية، ليصبح نفس عنوان الواجهة الخلفية على Heroku بدلًا من <a href="http://localhost:3001" ipsnoembed="false" rel="external nofollow">http://localhost:3001</a>
</p>

<p>
	السؤال التالي هو: كيف سننشر تطبيق الواجهة الأمامية على الإنترنت؟ لدينا العديد من الخيارات.
</p>

<h2>
	بناء نسخة الإنتاج من الواجهة الأمامية
</h2>

<p>
	لقد أنشأنا حتى هذه اللحظة تطبيقات React في وضعية التطوير (development mode)، حيث يهيأ التطبيق لإعطاء رسائل خطأ واضحة، وتُصيّر الشيفرة مباشرة عند حدوث أية تغييرات وهكذا.
</p>

<p>
	لكن عندما يغدو التطبيق جاهزًا للنشر لابد من إنشاء <a href="https://reactjs.org/docs/optimizing-performance.html#use-the-production-build" rel="external nofollow">نسخة إنتاج</a> (production build) أو نسخة التطبيق المتمثلة للإنتاج.
</p>

<p>
	ننشئ نسخة الإنتاج من التطبيقات التي بنيت باستخدام create-react-app <a href="https://github.com/facebookincubator/create-react-app#npm-run-build-or-yarn-build" rel="external nofollow">بتنفيذ الأمر</a> <code>npm run build</code>. لننفذ الأمر السابق عند جذر مشروع الواجهة الأمامية. ينشئ تنفيذ الأمر السابق مجلدًا يدعى build (يحوي ملف HTML الوحيد للتطبيق ويدعى index.html) وفي داخله مجلد آخر يدعى static ستوَلَّد فيه نسخة مصغرة عن شيفرة JavaScript للتطبيق. وعلى الرغم من وجود عدة ملفات JavaScript في تطبيقنا، إلا أنها ستُجمّع ضمن ملف مصغّر واحد. وفي واقع الأمر، سيَضم هذا الملف كل محتويات ملفات الارتباط الخاصة بالتطبيق أيضًا.
</p>

<p>
	لن يكون فهم أو قراءة محتويات هذا الملف يسيرًا كما ترى:
</p>

<pre class="ipsCode">!function(e){function r(r){for(var n,f,i=r[0],l=r[1],a=r[2],c=0,s=[];c&lt;i.length;c++)f=i[c],o[f]&amp;&amp;s.push(o[f][0]),o[f]=0;for(n in l)Object.prototype.hasOwnProperty.call(l,n)&amp;&amp;(e[n]=l[n]);for(p&amp;&amp;p(r);s.length;)s.shift()();return u.push.apply(u,a||[]),t()}function t(){for(var e,r=0;r&lt;u.length;r++){for(var t=u[r],n=!0,i=1;i&lt;t.length;i++){var l=t[i];0!==o[l]&amp;&amp;(n=!1)}n&amp;&amp;(u.splice(r--,1),e=f(f.s=t[0]))}return e}var n={},o={2:0},u=[];function f(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.m=e,f.c=n,f.d=function(e,r,t){f.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},f.r=function(e){"undefined"!==typeof Symbol&amp;&amp;Symbol.toStringTag&amp;&amp;Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})
</pre>

<h2>
	تقديم الملفات ذات المحتوى الثابت من الواجهة الخلفية
</h2>

<p>
	إن أحد الطرق المتبعة في نشر الواجهة الأمامية هو بناء نسخة إنتاج ووضعها في جذر المستودع الذي يحتوي الواجهة الخلفية. ومن ثم نهيئ الواجهة الخلفية لتعرض الصفحة الرئيسية للواجهة الأمامية (build/index.html).
</p>

<p>
	نبدأ العملية بنقل نسخة الإنتاج إلى جذر الواجهة الخلفية. استخدم الأمر التالي في إجراء عملية النسخ إن كنت تستخدم أحد نظامي التشغيل Mac أو Linux
</p>

<pre class="ipsCode">cp -r build ../../../osa3/notes-backend
</pre>

<p>
	واستخدم في النظام windows إحدى التعليميتن <a href="https://www.windows-commandline.com/windows-copy-command-syntax-examples/" rel="external nofollow">copy</a> أو <a href="https://www.windows-commandline.com/xcopy-command-syntax-examples/" rel="external nofollow">xcopy</a> أو استخدم ببساطة النسخ و اللصق. سيبدو مجلد الواجهة الخلفية كالتالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="55512" href="https://academy.hsoub.com/uploads/monthly_2021_01/backend_root_003.png.2ef90bfcef2261606b7253763e17cd0c.png" rel="" data-fileext="png"><img alt="backend_root_003.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55512" data-unique="s88bc8s45" src="https://academy.hsoub.com/uploads/monthly_2021_01/backend_root_003.png.2ef90bfcef2261606b7253763e17cd0c.png"></a>
</p>

<p>
	سنستخدم أداة وسطية مدمجة مع المكتبة express تعرض الملفات ذات المحتوى الثابت التي تحضرها من الخادم مثل الملف index.html وملفات JavaScript وغيرها، تدعى هذه الأداة <a href="http://expressjs.com/en/starter/static-files.html" rel="external nofollow">static</a>. فعندما نضيف العبارة التالية إلى شيفرة الواجهة الخلفية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5642_14" style=""><span class="pln">app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">express</span><span class="pun">.</span><span class="kwd">static</span><span class="pun">(</span><span class="str">'build'</span><span class="pun">))</span></pre>

<p>
	ستتحقق Express من محتويات المجلد build عندما تتلقى أية طلبات HTTP-GET. فإن وجدت ملفًا مطابقًا للملف المطلوب ستعيده. وهكذا ستظهر الواجهة الأمامية المبنية باستخدام React، عندما يستجيب الخادم إلى طلبات GET إلى العنوان www.serversaddress.com/index.html أو إلى العنوان www.serversaddress.com. بينما تتعامل الواجهة الخلفية مع الطلب GET إلى العنوان www.serversaddress.com/api/notes.
</p>

<p>
	في حالتنا هذه سنجد أن للواجهتين العنوان نفسه، لذلك نستطيع أن نعطي للمتغير <code>baseUrl</code> عنوان موقع <a href="https://www.w3.org/TR/WD-html40-970917/htmlweb.html#h-5.1.2" rel="external nofollow">نسبي</a>، وذلك كي لانذكر القسم الأول من العنوان والمتعلق بالخادم.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5642_16" style=""><span class="kwd">import</span><span class="pln"> axios from </span><span class="str">'axios'</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> baseUrl </span><span class="pun">=</span><span class="pln"> </span><span class="str">'/api/notes'</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> getAll </span><span class="pun">=</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> request </span><span class="pun">=</span><span class="pln"> axios</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="pln">baseUrl</span><span class="pun">)</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">then</span><span class="pun">(</span><span class="pln">response </span><span class="pun">=&gt;</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">data</span><span class="pun">)</span><span class="pln">
</span><span class="com">// ...</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	بعد إجراء تلك التغييرات، علينا أن ننشئ نسخة إنتاج ونضعها في جذر مستودع الواجهة الخلفية. وبالتالي سنصبح قادرين على استخدام الواجهة الأمامية بطلب عنوان الواجهة الخلفية <a href="http://localhost:3001/" rel="external nofollow">http://localhost:3001</a>.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="55518" href="https://academy.hsoub.com/uploads/monthly_2021_01/localhost_call_004.png.f0b8ab1f819ae9ee0247f675c57dc267.png" rel="" data-fileext="png"><img alt="localhost_call_004.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55518" data-unique="0dyh4rrxc" src="https://academy.hsoub.com/uploads/monthly_2021_01/localhost_call_004.png.f0b8ab1f819ae9ee0247f675c57dc267.png"></a>
</p>

<p>
	وهكذا سيعمل تطبيقنا تمامًا <a href="https://academy.hsoub.com/programming/javascript/react/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%A8%D9%86%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r1071/" rel="">كتطبيق الصفحة الواحدة</a> النموذجي الذي درسناه في القسم 0. فعندما نطلب عنوان الواجهة الخلفية <a href="http://localhost:3001/" rel="external nofollow">http://localhost:3001</a> سيعيد الخادم الملف index.html من المجلد build. ستجد محتوى الملف (بشكل مختصر) كالتالي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_5642_18" style=""><span class="tag">&lt;head&gt;</span><span class="pln">
  </span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">charset</span><span class="pun">=</span><span class="atv">"utf-8"</span><span class="tag">/&gt;</span><span class="pln">
  </span><span class="tag">&lt;title&gt;</span><span class="pln">React App</span><span class="tag">&lt;/title&gt;</span><span class="pln">
  </span><span class="tag">&lt;link</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"/static/css/main.f9a47af2.chunk.css"</span><span class="pln"> </span><span class="atn">rel</span><span class="pun">=</span><span class="atv">"stylesheet"</span><span class="tag">&gt;</span><span class="pln">
</span><span class="tag">&lt;/head&gt;</span><span class="pln">
</span><span class="tag">&lt;body&gt;</span><span class="pln">
  </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">id</span><span class="pun">=</span><span class="atv">"root"</span><span class="tag">&gt;&lt;/div&gt;</span><span class="pln">
  </span><span class="tag">&lt;script</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"/static/js/1.578f4ea1.chunk.js"</span><span class="tag">&gt;&lt;/script&gt;</span><span class="pln">
  </span><span class="tag">&lt;script</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"/static/js/main.104ca08d.chunk.js"</span><span class="tag">&gt;&lt;/script&gt;</span><span class="pln">
</span><span class="tag">&lt;/body&gt;</span><span class="pln">
</span><span class="tag">&lt;/html&gt;</span></pre>

<p>
	يتضمن الملف تعليمات لإحضار ملف CSS الذي يعرف تنسيقات عناصر التطبيق ومعرّفي شيفرة (script tag) يوجهان المتصفح إلى إحضار شيفرة JavaScript التي يحتاجها التطبيق، وهي الشيفرة الفعلية لتطبيق React.
</p>

<p>
	تحضر شيفرة React الملاحظات من العنوان <a href="http://localhost:3001/api/notes%D8%8C" ipsnoembed="false" rel="external nofollow">http://localhost:3001/api/notes،</a> وتصيّرها على الشاشة. يمكنك أن تتابع تفاصيل الاتصال بين المتصفح والخادم من نافذة Network ضمن طرفية التطوير:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="55513" href="https://academy.hsoub.com/uploads/monthly_2021_01/browser_server_comm_005.png.d921764cda3b4ad89ca2ad59e9d5271c.png" rel="" data-fileext="png"><img alt="browser_server_comm_005.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55513" data-unique="qob6p7rh8" src="https://academy.hsoub.com/uploads/monthly_2021_01/browser_server_comm_005.png.d921764cda3b4ad89ca2ad59e9d5271c.png"></a>
</p>

<p>
	بعد أن نتأكد من عمل نسخة الإنتاج على الخادم المحلي، انقل المجلد build الذي يحوي الواجهة الأمامية إلى مستودع الواجهة الخلفية، ثم انقل الشيفرة كلها إلى خادم Heroku مجددًا.
</p>

<p>
	سيعمل <a href="https://vast-oasis-81447.herokuapp.com/" rel="external nofollow">التطبيق</a> بشكل جيد، ماعدا الجزئية التي تتعلق بتغيير أهمية الملاحظات.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="55510" href="https://academy.hsoub.com/uploads/monthly_2021_01/app_works_on_heoku_006.png.adc3d1cd4c9b0f43af474d29d58015bf.png" rel="" data-fileext="png"><img alt="app_works_on_heoku_006.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55510" data-unique="8h1mjsuu7" src="https://academy.hsoub.com/uploads/monthly_2021_01/app_works_on_heoku_006.png.adc3d1cd4c9b0f43af474d29d58015bf.png"></a>
</p>

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

<h2>
	نشر الواجهة الخلفية بطريقة انسيابية
</h2>

<p>
	سنكتب سكربت npm بسيط لإنشاء نسخة إنتاج من الواجهة الأمامية بأقل جهد ممكن. ضع الشيفرة التالية في ملف package.json الموجود في مستودع الواجهة الخلفية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5642_20" style=""><span class="pun">{</span><span class="pln">
  </span><span class="str">"scripts"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">//...</span><span class="pln">
    </span><span class="str">"build:ui"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"rm -rf build &amp;&amp; cd ../../osa2/materiaali/notes-new &amp;&amp; npm run build --prod &amp;&amp; cp -r build ../../../osa3/notes-backend/"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"deploy"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"git push heroku master"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"deploy:full"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"npm run build:ui &amp;&amp; git add . &amp;&amp; git commit -m uibuild &amp;&amp; npm run deploy"</span><span class="pun">,</span><span class="pln">    
    </span><span class="str">"logs:prod"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"heroku logs --tail"</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	عندما ينفذ npm الجزء (build:ui) من السكريبت السابقة بالأمر <code>npm run build:ui</code>، سيبني نسخة إنتاج من الواجهة الأمامية ضمن مستودع الواجهة الخلفية. بينما سينشر الأمر <code>npm run deploy</code> النسخة الحالية للواجهة الخلفية على الخادم Heroku. وأخيرًا يدمج الأمر <code>npm run deploy:full</code> التطبيقين معًا ويزودهما بأوامر git تساعد في تحديث مستودع الواجهة الخلفية.
</p>

<p>
	يبقى هناك جزء من سكريبت npm يُنفَّذ بالأمر <code>npm run logs:prod</code>، ويُستخدَم لطباعة سجلات Heroku أثناء التنفيذ.
</p>

<p>
	<strong>انتبه</strong>: تعتمد المسارات المكتوبة في الجزء build:ui من السكربت على موقع المستودعات في منظومة الملفات.
</p>

<p>
	تُنفَّذ npm سكريبت في نظام windows باستخدام cmd.exe لأن واجهة النظام (shell) الافتراضية لاتدعم أوامر أو واجهة bash. ولتنفيذ الأوامر السابقة استبدل واجهة النظام بالواجهة bash باستخدام محرر Git الافتراضي في windows كالتالي:
</p>

<pre class="ipsCode">npm config set script-shell "C:\\Program Files\\git\\bin\\bash.exe"
</pre>

<h2>
	الخادم الوكيل
</h2>

<p>
	لن تعمل الواجهة الأمامية بعد التغييرات التي أجريناها في وضعية التطوير (عندما تُشغَّل باستخدام<code>npm start</code>)، لأن الاتصال مع الواجهة الخلفية لن يعمل.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="55514" href="https://academy.hsoub.com/uploads/monthly_2021_01/dev_mode_change_007.png.8a61771b96a0ab74f09301a9b57b6ccb.png" rel="" data-fileext="png"><img alt="dev_mode_change_007.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55514" data-unique="847vejx9w" src="https://academy.hsoub.com/uploads/monthly_2021_01/dev_mode_change_007.png.8a61771b96a0ab74f09301a9b57b6ccb.png"></a>
</p>

<p>
	وذلك لتغيّر عنوان الواجهة الخلفية إلى عنوان نسبي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5642_22" style=""><span class="kwd">const</span><span class="pln"> baseUrl </span><span class="pun">=</span><span class="pln"> </span><span class="str">'/api/notes'</span></pre>

<p>
	لقد كان عنوان الواجهة الأمامية في مرحلة التطوير localhost:3000، وبالتالي ستُرسل الطلبات الآن إلى الواجهة الخلفية على العنوان الخاطئ localhost:3000/api/notes. وطبعًا الواجهة الخلفية موجودة فعلًا على العنوان localhost:3001.
</p>

<p>
	سيكون الحل بسيطًا عندما ننشئ المشروع باستخدام create-react-app. أضف التصريحات التالية إلى الملف package.json الموجود في مستودع الواجهة الأمامية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5642_26" style=""><span class="pun">{</span><span class="pln">
  </span><span class="str">"dependencies"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// ...</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="str">"scripts"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// ...</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="str">"proxy"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"http://localhost:3001"</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	بعد إعادة تشغيل التطبيق، ستعمل بيئة تطوير React كخادم وكيل. فلو أرسلت شيفرة React طلبًا إلى خادم على العنوان http://localhost:3000، ولم يكن تطبيق React هو من يدير هذا الخادم (أي في الحالة التي لا تحضر فيها الطلبات ملفات CSS أو JavaScript الخاصة بالتطبيق)، سيعاد توجيه الطلب إلى الخادم الذي عنوانه http://localhost:3001. وهكذا ستعمل الواجهة الأمامية بشكل جيد في وضعي التطوير والإنتاج.
</p>

<p>
	إن سلبية هذه المقاربة هي التعقيد الذي تظهره في نشر الواجهة الأمامية على الإنترنت. فنشر نسخة جديدة سيتطلب إنشاء نسخة إنتاج جديدة ونقلها إلى مستودع الواجهة الخلفية. وسيصعّب ذلك إنشاء <a href="https://martinfowler.com/bliki/DeploymentPipeline.html" rel="external nofollow">خطوط نشر</a> آلية. وتعرّف خطوط النشر بأنها طريقة مؤتمتة وقابلة للتحكم لنقل الشيفرة من حاسوب المطور إلى بيئة الإنتاج مرورًا باختبارات مختلفة بالإضافة إلى التحقق من الجودة.
</p>

<p>
	يمكن إنجاز ذلك بطرق عدة، منها وضع الواجهتين الخلفية والأمامية <a href="https://github.com/mars/heroku-cra-node" rel="external nofollow">في نفس المستودع</a>. لكننا لن نناقش ذلك الآن. في بعض الحالات من المعقول أن ننشر الواجهة الأمامية على شكل تطبيق مستقل وهذا أمر بسيط <a href="https://github.com/mars/create-react-app-buildpack" rel="external nofollow">ويطبق مباشرة</a> إن كتب التطبيق باستخدام create-react-app.
</p>

<p>
	ستجد الشيفرة الحالية للواجهة الخلفية في الفرع part3-3 على <a href="https://github.com/fullstack-hy2020/part3-notes-backend/tree/part3-3" rel="external nofollow">Github</a>. بينما ستجد التعديلات على الواجهة الأمامية في الفرع part3-1 في <a href="https://github.com/fullstack-hy2020/part2-notes/tree/part3-1" rel="external nofollow">مستوودع الواجهة الأمامية</a>.
</p>

<h2>
	التمارين 3.9 - 3.11
</h2>

<p>
	لايتطلب إنجاز التمارين التالية العديد من الأسطر البرمجية. لكنها ستحمل شيئًا من التحدي، لأنها تتطلب فهمًا دقيقًا لما يحدث وأين يحدث، بالإضافة إلى تهيئة التطبيق كما يجب.
</p>

<h3>
	3.11 دليل هاتف للواجهة الخلفية: الخطوة 9
</h3>

<p>
	اجعل الواجهة الخلفية تتكامل مع الأمامية في نفس التمرين من القسم السابق. لا تضف وظيغة تغيير الأرقام حاليًا، بل سنقوم بذلك لاحقًا. ربما ستغيّر قليلًا في الواجهة الأمامية، على الأقل عناوين مواقع الواجهة الخلفية. وتذكر أن تبقي طرفية التطوير مفتوحة في متصفحك. تحقق مما يحدث إن فشل أي طلب HTTP من خلال النافذة Network، وابق نظرك دائما على طرفية <a href="https://academy.hsoub.com/programming/general/%D8%AA%D8%B9%D9%84%D9%85-%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%A7%D9%84%D9%88%D9%8A%D8%A8/#%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D8%AE%D9%84%D9%81%D9%8A%D8%A9-Back-End" rel="">تطوير الواجهة الخلفية</a> في نفس الوقت. إن لم تتمكن من إنجاز التمرين السابق، ستفيدك طباعة بيانات الطلب أوجسم الطلب على الطرفية بزرع تعليمة الطباعة في دالة معالج الحدث المسؤولة عن طلبات POST.
</p>

<h3>
	3.10 دليل هاتف للواجهة الخلفية: الخطوة 10
</h3>

<p>
	انشر الواجهة الخلفية على الإنترنت (على Heroku مثلًا).
</p>

<p>
	<strong>ملاحظة</strong>: إن لم تستطع لسبب ما تثبيت Heroku على حاسوبك، استخدم الأمر <a href="https://www.npmjs.com/package/heroku-cli" rel="external nofollow">npx heroku-cli</a>.
</p>

<p>
	اختبر الواجهة الخلفية التي نشرتها من خلال المتصفح بمساعدة Postman أو VS Code REST client للتأكد من أنها تعمل.
</p>

<p>
	<strong>نصيحة للاحتراف</strong>: عندما تنشر تطبيقك على Heroku، يفضل -على الأقل في البداية- أن تبقي نظرك على ما يطبعه تطبيق Heroku دائمًا، وذلك بتنفيذ الأمر <code>heroku logs -t</code>.
</p>

<p>
	تمثل الصورة التالية مايطبعه heroku عندما لا يستطيع إيجاد ملف الارتباط express.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="55517" href="https://academy.hsoub.com/uploads/monthly_2021_01/600152af12bb5_heroku_error_no_express_008.png.aa5b45d1c94b2a3528bcc24bf1e061c6.png" rel="" data-fileext="png"><img alt="heroku_error_no _express_008.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55517" data-unique="jufostmtq" src="https://academy.hsoub.com/uploads/monthly_2021_01/600152af12bb5_heroku_error_no_express_008.png.aa5b45d1c94b2a3528bcc24bf1e061c6.png"></a>
</p>

<p>
	والسبب أن الخيار save-- لم يحدد عندما ثُبتت المكتبة express. وبالتالي لم تحفظ معلومات ملف الارتباط ضمن الملف package.json.
</p>

<p>
	أحد الأخطاء الأخرى، هو أن التطبيق لم يهيأ لاستعمال المنفذ الذي عُرِّف في متغير البيئة PORT:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="55516" href="https://academy.hsoub.com/uploads/monthly_2021_01/heroku_erro_port_009.png.573982b0dd48859c0982e1f3d6f586ab.png" rel="" data-fileext="png"><img alt="heroku_erro_port_009.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55516" data-unique="5trpdpt27" src="https://academy.hsoub.com/uploads/monthly_2021_01/heroku_erro_port_009.png.573982b0dd48859c0982e1f3d6f586ab.png"></a>
</p>

<p>
	أنشئ ملفًا باسم README.md في جذر مستودعك، وأضف إليه رابطًا لتطبيقك على الإنترنت.
</p>

<h3>
	3.11 دليل هاتف للواجهة الخلفية: الخطوة 11
</h3>

<p>
	أنشئ نسخة إنتاج من الواجهة الأمامية، وانشرها على الإنترنت بالأسلوب الذي ناقشناه في هذا الفصل.
</p>

<p>
	<strong>ملاحظة</strong>: تأكد من أن المجلد build ليس ضمن قائمة الملفات التي يهملها git، وهو الملف الذي لاحقته gitignore. تأكد أيضًا أن الواجهة الأمامية لازالت تعمل.
</p>

<p>
	ترجمة -وبتصرف- للفصل <a href="https://fullstackopen.com/en/part3/deploying_app_to_internet" rel="external nofollow">Deploying App to Internet</a> من سلسلة <a href="https://fullstackopen.com/en/" rel="external nofollow">Deep Dive Into Modern Web Development</a>
</p>
]]></description><guid isPermaLink="false">1100</guid><pubDate>Thu, 14 Jan 2021 13:00:00 +0000</pubDate></item><item><title>&#x62A;&#x623;&#x645;&#x64A;&#x646; &#x62A;&#x637;&#x628;&#x64A;&#x642; Node.js  &#x64A;&#x639;&#x645;&#x644; &#x639;&#x644;&#x649; &#x627;&#x644;&#x62D;&#x627;&#x648;&#x64A;&#x627;&#x62A; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; Nginx &#x648; Let&#x2019;s Encrypt &#x648; Compose Docker</title><link>https://academy.hsoub.com/programming/javascript/nodejs/%D8%AA%D8%A3%D9%85%D9%8A%D9%86-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-nodejs-%D9%8A%D8%B9%D9%85%D9%84-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-nginx-%D9%88-let%E2%80%99s-encrypt-%D9%88-compose-docker-r814/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2020_02/6.jpg.c8c8821c3bce665f2450a2d6d6dc3504.jpg" /></p>

<p>
	هناك دائمًا طرق متعددة لتعزيز مرونة وأمان تطبيقك Node.js. ويتيح لك استخدام وكيل عكسي مثل Nginx إمكانية تحميل طلبات الرصيد وتخزين محتوى ثابت في ذاكرة التخزين المؤقت وتنفيذ بروتوكول أمان طبقة النقل (<abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">TLS</abbr>) وهي اختصار لـTransport Layer Security. ويضمن تفعيل HTTPS المشفر على خادمك أن الاتصالات من وإلى تطبيقك تبقى آمنة.
</p>

<p>
	يتضمن تطبيق وكيل عكسي باستخدام <abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">TLS</abbr>/SSLعلى الحاويات مجموعة من الإجراءات مختلفة عن العمل مباشرة على نظام تشغيل مضيف. على سبيل المثال، إذا كنت تحصل على شهادات من Let’s Encrypt لتطبيق يعمل على الخادم، فستثبّت البرنامج المطلوب مباشرة على مضيفك. أما الحاويات فتسمح لك أن تسلك نهجا مختلفا. باستخدام Docker Compose، يمكنك إنشاء حاويات لتطبيقك، وخادم الويب الخاص بك، وعميل Certbot الذي سيتيح لك الحصول على شهاداتك. ويمكّنك اتباع هذه الخطوات من الاستفادة من مزايا النمطية وقابلية التنقل في سير عملٍ يعتمد على الحاويات.
</p>

<p>
	في هذا الدرس، سوف تنشر تطبيق Node.js بوكيل عكسي Nginx باستخدام Docker Compose. وستحصل على شهادات <abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">TLS</abbr>/<abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">SSL</abbr> للمجال المرتبط بتطبيقك كما ستتأكد من حصوله على تصنيف أمان عالي من <a href="https://www.ssllabs.com/" rel="external nofollow"><abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">SSL</abbr> Labs</a>. وفي الأخير، سوف تنشئ وظيفة cron لتجديد شهاداتك بحيث يظل نطاقك آمنًا.
</p>

<h2>
	المتطلبات الأساسية
</h2>

<p>
	لمتابعة هذا البرنامج التعليمي، ستحتاج إلى ما يلي:
</p>

<ul>
<li>
		خادم أوبونتو 18.04 ومستخدم غير جذري ذي صلاحيات sudo وجدار حماية نشط. للحصول على إرشادات حول كيفية إعدادها، يرجى الاطلاع على <a href="https://academy.hsoub.com/devops/linux/%D8%A7%D9%84%D8%AA%D9%87%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%A3%D9%88%D9%84%D9%8A%D8%A9-%D9%84%D8%AE%D8%A7%D8%AF%D9%85-%D8%A3%D9%88%D8%A8%D9%88%D9%86%D8%AA%D9%88-1804-r431/" rel="">دليل إعداد الخادم الأولي</a>.
	</li>
	<li>
		Docker مثبت على خادمك. للحصول على إرشادات حول تثبيت Docker، اتبع الخطوتين 1 و 2 للدليل <a href="https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-18-04" rel="external nofollow">كيفية تثبيت واستخدام Docker على Ubuntu 18.04</a>. للحصول على إرشادات حول تثبيت Compose، اتبع الخطوة الأولى من <a href="https://www.digitalocean.com/community/tutorials/how-to-install-docker-compose-on-ubuntu-18-04" rel="external nofollow">كيفية تثبيت Docker Compose على أوبونتو 18.04</a>.
	</li>
	<li>
		اسم نطاق مسجل. سيستخدم هذا البرنامج التعليمي example.com طوال الوقت. يمكنك الحصول على نطاق مجاني من <a href="http://www.freenom.com/en/index.html" rel="external nofollow">Freenom</a> ، أو استخدام مسجل النطاقات الذي تختاره. سجلّا DNS التاليين المعدّين لخادمك. يمكنك متابعة هذه <a href="https://www.digitalocean.com/community/tutorials/an-introduction-to-digitalocean-dns" rel="external nofollow">المقدمة</a> إلى DNS في DigitalOcean للحصول على تفاصيل حول كيفية إضافتها إلى حساب DigitalOcean، إذا كنت تستخدمه:
	</li>
	<li>
		سجل A يتضمن example.com يشير إلى عنوان IP العام لخادمك.
	</li>
	<li>
		سجل A يتضمن www.example.com يشير إلى عنوان IP العام لخادمك.
	</li>
</ul>
<h2>
	الخطوة الأولى: استنساخ واختبار تطبيق Node
</h2>

<p>
	سوف نبدأ أولًا باستنساخ المستودع الذي يحتوي على شيفرة التطبيق Node، والذي يتضمن الملف <code>Dockerfile</code> الذي سنستخدمه لإنشاء صورة تطبيقنا اعتمادًا على Compose. ويمكننا البدء باختبار التطبيق عبر بنائه وتنفيذه باستخدام الأمر <code>docker run</code> دون وكيل عكسي أو شهادة <abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">SSL</abbr>.
</p>

<p>
	في المجلّد الرئيسي للمستخدم غير الجذري، استنسخ مستودع <code>nodejs-image-demo</code> من حساب DigitalOcean Community GitHub. يتضمن
</p>

<p>
	مستودع التخزين هذا شيفرة الإعداد الموضح في كيفية إنشاء تطبيق Node.js باستخدام Docker.
</p>

<p>
	انسخ المستودع في مجلّد يسمى node_project:
</p>

<pre class="ipsCode">
git clone https://github.com/do-community/nodejs-image-demo.git node_project
</pre>

<p>
	انتقل إلى المجلّد <code>node_project</code>:
</p>

<pre class="ipsCode">
cd  node_project
</pre>

<p>
	يوجد في هذا المجلّد ملفّ Dockerfile يحتوي على إرشادات حول بناء تطبيق Node باستخدام صورة Docker node:10 أضافة إلى محتويات مجلّد مشروعك الحالي. يمكنك إلقاء نظرة على محتويات Dockerfile بكتابة مايلي:
</p>

<pre class="ipsCode">
cat Dockerfile
</pre>

<p>
	المخرجات:
</p>

<pre class="ipsCode">
FROM node:10-alpine

RUN mkdir -p /home/node/app/node_modules &amp;&amp; chown -R node:node /home/node/app

WORKDIR /home/node/app

COPY package*.json ./

USER node

RUN npm install

COPY --chown=node:node . .

EXPOSE 8080

CMD [ "node", "app.js" ]
</pre>

<p>
	تبني هذه التعليمات صورة Node عبر نسخ شيفرة المشروع من المجلّد الحالي إلى الحاوية وتثبيت الاعتماديات باستخدام <code>npm install</code>. كما أنها تستخدم أيضًا التخزين المؤقت وطبقات الصور في Docker عبر فصل نسخة من <code>package.json</code> و <code>package-lock.json</code>، التي تحتوي على اعتماديات المشروع المدرجة، عن نسخة باقي شيفرة التطبيق. وتحدد هذه التعليمات كذلك أن تشغيل الحاوية سيكون عبر مستخدم Node غير الجذري بالأذونات المناسبة المعينة على شيفرة التطبيق والمجلّدات <code>node_modules</code>.
</p>

<p>
	لمزيد من المعلومات حول أفضل ممارسات Dockerfile و Node image ، يرجى الاطلاع على التوضيحات في الخطوة 3 حول <a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%A8%D9%86%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-nodejs-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-docker-r809/" rel="">كيفية بناء تطبيق Node.js باستخدام Docker</a>.
</p>

<p>
	لاختبار التطبيق بدون <abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">SSL</abbr>، يمكنك بناء الصورة وتعليمها باستخدام Docker والراية <code>t-</code>. سوف نسمّي الصورة <code>node-demo</code>، ولكن تبقى لك الحرية في إعطائها اسمًا آخر:
</p>

<pre class="ipsCode">
docker build -t node-demo .
</pre>

<p>
	بمجرد اكتمال عملية البناء، يمكنك عرض قائمة صورك باستخدام <code>docker images</code>:
</p>

<pre class="ipsCode">
docker images
</pre>

<p>
	سيظهر لك الإخراج التالي مؤكّدًا بناء صورة التطبيق:
</p>

<pre class="ipsCode">
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
node-demo           latest              23961524051d        7 seconds ago       73MB
node                10-alpine           8a752d5af4ce        3 weeks ago         70.7MB
</pre>

<p>
	بعد ذلك، أنشئ الحاوية باستخدام <code>docker run</code>. سوف نستخدم ثلاث رايات مع هذا الأمر:
</p>

<ul>
<li>
		<code>p-</code>: ينشر هذا المنفذ على الحاوية ويوجّهه إلى منفذٍ على مضيفنا. سنستخدم المنفذ 80 على المضيف، ولكن يمكنك تعديل هذا عند الضرورة إذا كان لديك عملية أخرى تشغَل هذا المنفذ. لمزيد من المعلومات، راجع هذه التوضيحات في توثيق Docker حول <a href="https://docs.docker.com/v17.09/engine/userguide/networking/default_network/binding/" rel="external nofollow">ربط المنافذ</a>.
	</li>
	<li>
		<code>d-</code>: يشغّل هذا الحاوية في الخلفية.
	</li>
	<li>
		<code>name-</code> : يتيح لنا هذا إعطاء الحاوية اسمًا سهل التذكّر.
	</li>
</ul>
<p>
	نفّذ الأمر التالي لإنشاء الحاوية:
</p>

<pre class="ipsCode">
docker run --name node-demo -p 80:8080 -d node-demo
</pre>

<p>
	تفقّد الحاويات قيد التشغيل باستخدام <code>docker ps</code>:
</p>

<pre class="ipsCode">
docker ps
</pre>

<p>
	سترى الإخراج التالي الذي يؤكد أن حاوية تطبيقك قيد التشغيل:
</p>

<pre class="ipsCode">
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                  NAMES
4133b72391da        node-demo           "node app.js"       17 seconds ago      Up 16 seconds  
</pre>

<p>
	يمكنك الآن زيارة عنوان نطاقك لاختبار إعداداتك: <a href="http://example.com." ipsnoembed="true" rel="external nofollow">http://example.com.</a> لا تنس تعويض example.com باسم نطاقك الخاص. سيعرض تطبيقك صفحة الهبوط التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="33649" href="https://academy.hsoub.com/uploads/monthly_2020_02/landing_page.png.427e3c68e01f1c4ff26eb3c82023af42.png" rel=""><img alt="landing_page.png" class="ipsImage ipsImage_thumbnailed" data-fileid="33649" data-unique="00syst8zt" src="https://academy.hsoub.com/uploads/monthly_2020_02/landing_page.thumb.png.25a1ae58d9fdc21bf7a1f7bf5319c9a6.png"></a>
</p>

<p>
	الآن وبعد اختبار التطبيق، يمكنك إيقاف الحاوية وحذف الصور. استخدم <code>docker ps</code> مرة أخرى للحصول على معرّف الحاويات CONTAINER ID:
</p>

<pre class="ipsCode">
docker ps
</pre>

<p>
	المخرجات:
</p>

<pre class="ipsCode">
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                  NAMES
4133b72391da        node-demo           "node app.js"       17 seconds ago      Up 16 seconds
</pre>

<p>
	أوقف الحاوية باستخدام <code>docker stop</code>. تأكد من تعويض المعرِّف CONTAINER ID المدرج هنا بمعرّف تطبيقك CONTAINER ID:
</p>

<pre class="ipsCode">
docker stop 4133b72391da
</pre>

<p>
	يمكنك الآن حذف الحاوية المتوقفة وجميع الصور، بما في ذلك الصور غير المستخدمة والمعلّقة، باستخدام <code>docker system prune</code> والراية <code>a-</code>:
</p>

<pre class="ipsCode">
docker system prune -a
</pre>

<p>
	اكتب y في الإخراج عند ما تطلب منك الكتابة لتأكيد رغبتك في حذف الحاوية والصور المتوقّفة. يرجى العلم أن هذا سيؤدي أيضًا إلى حذف ذاكرة التخزين المؤقت للبناء.
</p>

<p>
	بعد اختبار صورة تطبيقك، يمكنك الانتقال إلى بناء بقية الإعداد باستخدام <code>Docker Compose</code>.
</p>

<h2>
	الخطوة الثانية: تحديد تكوين خادم الويب
</h2>

<p>
	يمكننا بعد إنشاء Dockerfile لتطبيقنا، إنشاء ملف تكوين لتشغيل حاوية Nginx الخاصة بنا. سنبدأ بالتكوين ذي الحد الأدنى الذي سيتضمن اسم نطاقنا، و الملف الجذري، ومعلومات الوكيل، وكتلة الموقع (Location block) لتوجيه طلبات Certbot إلى المجلّد <code>well-known</code>. إذ سيضع ملفًا مؤقتًا للتحقق من أن معلومات DNS لنطاقنا توجّه نحو خادمنا.
</p>

<p>
	أنشئ أولاً مجلّدًا في مجلّد المشروع الحالي لملف التكوين:
</p>

<pre class="ipsCode">
mkdir nginx-conf
</pre>

<p>
	افتح الملف باستخدام nano أو المحرر المفضل لديك:
</p>

<pre class="ipsCode">
nano nginx-conf/nginx.conf
</pre>

<p>
	أضف كتلة الخادم التالية لطلبات المستخدم الوكيل إلى حاوية تطبيقك Node ولتوجيه طلبات Certbot إلى المجلّد <code>well-known</code>. لا تنس تعويض example.com باسم نطاقك الخاص:
</p>

<pre class="ipsCode">
server {
        listen 80;
        listen [::]:80;

        root /var/www/html;
        index index.html index.htm index.nginx-debian.html;

        server_name example.com www.example.com;

        location / {
                proxy_pass http://nodejs:8080;
        }

        location ~ /.well-known/acme-challenge {
                allow all;
                root /var/www/html;
        }
}
</pre>

<p>
	تسمح لنا كتلة الخادم هذه بتشغيل حاوية Nginx كبديل عكسي، والذي سيمرر الطلبات إلى حاوية تطبيقنا Node. سيسمح لنا أيضًا باستخدام المكوّن الإضافي webroot الخاص بـ Certbot للحصول على شهادات لنطاقنا. يعتمد هذا المكوّن الإضافي على طريقة التحقق من HTTP-01، والتي تستخدم طلب HTTP لإثبات أن Certbot يمكنه الوصول إلى الموارد من خادمٍ يوافق اسمَ نطاق معين.
</p>

<p>
	احفظ الملف وأغلقه بمجرد الانتهاء من التحرير.
</p>

<p>
	لمعرفة المزيد حول خوارزميات Nginx لخادم المواقع وكتلها، يرجى الرجوع إلى هذه المقالة حول فهم خوارزميات Nginx للخادم وحظر المواقع.
</p>

<p>
	بعد تحديد تفاصيل تكوين خادم الويب، يمكننا الانتقال إلى إنشاء ملف docker-compose.yml، والذي سيتيح لنا إنشاء خدمات التطبيقات الخاصة بنا وحاوية Certbot التي سنستخدمها للحصول على شهاداتنا.
</p>

<h2>
	الخطوة الثالثة: إنشاء ملف Docker Compose
</h2>

<p>
	سيحدد ملف <code>docker-compose.yml</code> خدماتنا، بما في ذلك تطبيق Node وخادم الويب. سوف يحدد تفاصيل مثل وحدات التخزين المسماة، والتي ستكون ضرورية لمشاركة بيانات اعتماد <abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">SSL</abbr> بين الحاويات، وكذلك معلومات الشبكة والمنفذ. سوف يسمح لنا أيضًا بتحديد أوامر محددة لتنفيذها عند إنشاء حاوياتنا. هذا الملف هو المورد المركزي الذي سيحدّد كيف ستعمل خدماتنا معًا.
</p>

<p>
	افتح الملف في مجلّدك الحالي:
</p>

<pre class="ipsCode">
nano docker-compose.yml
</pre>

<p>
	حدد خدمة التطبيق أولًا:
</p>

<pre class="ipsCode">
version: '3'

services:
  nodejs:
    build:
      context: .
      dockerfile: Dockerfile
    image: nodejs
    container_name: nodejs
    restart: unless-stopped
</pre>

<p>
	يتضمن تعريف خدمة nodejs ما يلي:
</p>

<ul>
<li>
		Build: يحدّد هذا خيارات التكوين، بما في ذلك context وdockerfile، والتي ستطبّق عندما ينشئ Docker صورة التطبيق. إذا كنت ترغب في استخدام صورة موجودة من سجلٍّ مثل Docker Hub، فيمكنك استخدام التعليمة image عوض ذلك، مع معلومات حول اسم المستخدم والمستودع وعلامة الصورة.
	</li>
	<li>
		Context: يعرّف هذا سياق البناء لصورة التطبيق. وهو في هذه الحالة مجلّد المشروع الحالي.
	</li>
	<li>
		Dockerfile: يحدّد هذا ملف Dockerfile الذي سيستخدمه Compose للبناء وهو الملف Dockerfile الذي رأيناه في الخطوة الأولى.
	</li>
	<li>
		Image و container_name: تعطي هذه أسماء للصورة والحاوية.
	</li>
	<li>
		restart : هذا يحدّد سياسة إعادة التشغيل. تكون قيمته الافتراضية هي "لا"، لكننا عيّننا الحاوية لإعادة تشغيل ما لم يتم إيقافها.
	</li>
</ul>
<p>
	لاحظ أننا لا نُضمّن وصلات الربط (bind mounts) مع هذه الخدمة، وذلك لأن إعدادنا يركز على النشر بدلاً من التطوير. لمزيد من المعلومات، يرجى الاطلاع على توثيق Docker على وصلات الربط والحجوم.
</p>

<p>
	لتمكين الاتصال بين التطبيق وحاويات خادم الويب، سنضيف أيضًا شبكة جسرية (bridge network) تسمى <code>app-network</code> أسفل تعريف إعادة التشغيل:
</p>

<pre class="ipsCode">
services:
  nodejs:
...
    networks:
      - app-network
</pre>

<p>
	تتيح الشبكة الجسرية التي يعرّفها المستخدم الاتصالَ بين الحاويات على مضيف Docker نفسه. ويعمل ذلك على تبسيط حركة المرور والاتصالات داخل التطبيق، لأنه يفتح جميع المنافذ بين الحاويات على نفس الشبكة الجسرية، مع عدم تعريض أي منافذ للعالم الخارجي. وبالتالي، يمكنك الاختيار في فتح المنافذ التي تحتاجها فقط لعرض خدمات واجهتك الأمامية.
</p>

<p>
	بعد ذلك، حدّد خدمة خادم الويب:
</p>

<pre class="ipsCode">
...
 webserver:
    image: nginx:mainline-alpine
    container_name: webserver
    restart: unless-stopped
    ports:
      - "80:80" 
    volumes:
      - web-root:/var/www/html
      - ./nginx-conf:/etc/nginx/conf.d
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
    depends_on:
      - nodejs
    networks:
      - app-network
</pre>

<p>
	تظل بعض الإعدادات التي حددناها لخدمة nodejs كما هي، لكننا أجرينا أيضًا التغييرات التالية:
</p>

<ul>
<li>
		image: يطلب هذا من Compose سحب أحدث صورة ألبية Nginx ‏(Alpine-based Nginx image) من Docker Hub. لمزيد من المعلومات حول الصور الألبية، يرجى الاطلاع على الخطوة الثالثة من كيفية إنشاء تطبيق Node.js باستخدام Docker.
	</li>
	<li>
		ports: هذا يعرض المنفذ 80 لتفعيل خيارات التكوين التي حددناها في تكوين Nginx.
	</li>
</ul>
<p>
	لقد حددنا أيضًا وحدات التخزين المسماة ووصلات الربط التالية:
</p>

<ul>
<li>
		web-root:/var/www/html: سيؤدي ذلك إلى إضافة الأصول الثابتة لموقعنا ، المنسوخة إلى وحدة تخزين تسمى web-root ، إلى مجلّد /var/www/html على الحاوية.
	</li>
	<li>
		./nginx-conf:/etc/nginx/conf.d: سيؤدي هذا إلى ربط دليل تكوين Nginx على المضيف بالمجلّد ذي الصلة على الحاوية، مع التأكد من أن أي تغييرات نجريها على الملفات الموجودة على المضيف ستنعكس في الحاوية.
	</li>
	<li>
		certbot-etc:/etc/letsencrypt : سيؤدي ذلك إلى ربط شهادات ومفاتيح Let’s Encrypt لنطاقنا على المجلّد المناسب على الحاوية.
	</li>
	<li>
		certbot-var:/var/lib/letsencrypt: يؤدي هذا إلى تحديث مجلّد العمل الافتراضي الخاص بـ Let's Encrypt إلى المجلّد المناسب على الحاوية.
	</li>
</ul>
<p>
	بعد ذلك، أضف خيارات التكوين لحاوية certbot. تأكد من تعويض النطاق ومعلومات البريد الإلكتروني باسم نطاقك وبريدك الإلكتروني للاتصال:
</p>

<pre class="ipsCode">
...
  certbot:
    image: certbot/certbot
    container_name: certbot
    volumes:
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
      - web-root:/var/www/html
    depends_on:
      - webserver
    command: certonly --webroot --webroot-path=/var/www/html --email sammy@example.com --agree-tos
</pre>

<p>
	يطلب هذا التعريف من Compose سحب صورة <code>certbot/certbot</code> من Docker Hub. كما أنّه يستخدم وحدات التخزين المسمّاة لمشاركة الموارد مع حاوية Nginx، بما في ذلك شهادات النّطاق والمفتاح في certbot-etc، ومجلّد عمل Let's Encrypt في <code>certbot-var</code>، وشيفرة التطبيق في web-root.
</p>

<p>
	مرة أخرى، لقد استخدمنا <code>depends_on</code> لتحديد أن حاوية certbot يجب أن تشتغل بمجرد تشغيل الخدمة webserver.
</p>

<p>
	لقد أضفنا أيضًا خيار <code>command</code> يحدد الأمر الذي سيتم تنفيذه عند بدء تشغيل الحاوية. وهو يتضمن الأمر الفرعي certonly مع الخيارات التالية:
</p>

<ul>
<li>
		webroot--: هذا يطلب من Certbot استخدام البرنامج المساعد webroot لوضع الملفات في مجلد webroot للتصديق.
	</li>
	<li>
		webroot-path--: يحدد مسار المجلّد webroot.
	</li>
	<li>
		email--: بريدك الإلكتروني المفضل للتسجيل والاسترداد.
	</li>
	<li>
		agree-tos--: هذا يحدد أنك توافق على اتفاقية المشتركين في ACME.
	</li>
	<li>
		no-eff-email--: هذا يخبر Certbot أنك لا ترغب في مشاركة بريدك الإلكتروني مع مؤسسة الحدود الإلكترونية Electronic Frontier Foundation (EFF). لا تتردد في حذف هذا إذا كنت تفضل ذلك.
	</li>
	<li>
		staging--: هذا يخبر Certbot أنك ترغب في استخدام بيئة تشغيل Let's Encrypt للحصول على شهادات الاختبار. يتيح لك استخدام هذا الخيار اختبار خيارات التكوين وتفادي حدود طلبات النطاق المحتملة. لمزيد من المعلومات حول هذه الحدود، يرجى الاطلاع على التوثيق الخاص بمعدّل الحدود في Let's Encrypt.
	</li>
	<li>
		d-: يتيح لك ذلك تحديد أسماء النطاق التي ترغب في تطبيقها على طلبك. في هذه الحالة، ضمّننا example.com و www.example.com. لا تنس تعويضها بتفضيلات النطاق الخاصة بك.
	</li>
</ul>
<p>
	كخطوة أخيرة، أضف تعريفات وحدة التخزين والشبكة. وتأكد من تعويض اسم المستخدم هنا بمستخدمك غير الجذري:
</p>

<pre class="ipsCode">
...
volumes:
  certbot-etc:
  certbot-var:
  web-root:
    driver: local
    driver_opts:
      type: none
      device: /home/sammy/node_project/views/
      o: bind

networks:
  app-network:
    driver: bridge
</pre>

<p>
	تتضمن وحدات التخزين المسماة لدينا شهادات Certbot ووحدات تخزين مجلّد العمل، ووحدة تخزين الأصول الثابتة لموقعنا، web-root. في معظم الحالات، يكون برنامج التشغيل الافتراضي لوحدات تخزين Docker هو برنامج التشغيل المحلي، والذي يقبل على Linux خيارات مماثلة للأمر mount. ونستطيع بفضل هذا تحديد قائمة خيارات برنامج التشغيل باستخدام driver_opts الذي يربط مجلّد views على المضيف، والذي يحتوي على أصول ثابتة للتطبيق، مع وحدة التخزين في وقت التشغيل. يمكن بعد ذلك مشاركة محتويات المجلّد بين الحاويات. لمزيد من المعلومات حول محتويات المجلّد views، يرجى الاطلاع على الخطوة الثانية من كيفية إنشاء تطبيق Node.js باستخدام Docker.
</p>

<p>
	سيبدو ملف docker-compose.yml بهذا الشكل عند الانتهاء:
</p>

<pre class="ipsCode">
version: '3'

services:
  nodejs:
    build:
      context: .
      dockerfile: Dockerfile
    image: nodejs
    container_name: nodejs
    restart: unless-stopped
    networks:
      - app-network

  webserver:
    image: nginx:mainline-alpine
    container_name: webserver
    restart: unless-stopped
    ports:
      - "80:80"
    volumes:
      - web-root:/var/www/html
      - ./nginx-conf:/etc/nginx/conf.d
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
    depends_on:
      - nodejs
    networks:
      - app-network

  certbot:
    image: certbot/certbot
    container_name: certbot
    volumes:
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
      - web-root:/var/www/html
    depends_on:
      - webserver
    command: certonly --webroot --webroot-path=/var/www/html --email sammy@example.com --agree-tos --no-eff-email --staging -d example.com  -d www.example.com 

volumes:
  certbot-etc:
  certbot-var:
  web-root:
    driver: local
    driver_opts:
      type: none
      device: /home/sammy/node_project/views/
      o: bind

networks:
  app-network:
    driver: bridge
</pre>

<p>
	بوضعك لتعريفات الخدمة في مكانها الصحيح، ستكون مستعدًّا لبدء تشغيل الحاويات واختبار طلبات الشهادة الخاصة بك.
</p>

<h2>
	الخطوة الرابعة: الحصول على شهادات <abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">SSL</abbr> وبيانات الاعتماد
</h2>

<p>
	يمكننا أن نبدأ تشغيل حاوياتنا باستخدام <code>docker-compose up</code>، وهو الأمر الذي سيؤدي إلى إنشاء وتشغيل حاوياتنا وخدماتنا بالترتيب الذي حددناه. إذا كانت طلبات النطاق ناجحة، فستظهر حالة الخروج الصحيحة في الإخراج والشهادات الملائمة موصولة بالمجلد <code>etc/letsencrypt/live/</code> على حاوية webserver.
</p>

<p>
	أنشئ الخدمات باستخدام <code>docker-compose up</code> والراية <code>d-</code> التي ستشغّل حاويات nodejs و webserver في الخلفية:
</p>

<pre class="ipsCode">
docker-compose up -d
</pre>

<p>
	سيظهر لك الإخراج التالي الذي يؤكد أن خدماتك أُنشِئت:
</p>

<pre class="ipsCode">
Creating nodejs ... done
Creating webserver ... done
Creating certbot   ... done
</pre>

<p>
	تحقق من حالة خدماتك باستخدام docker-compose ps:
</p>

<pre class="ipsCode">
docker-compose ps
</pre>

<p>
	إذا كان كل شيء على ما يرام، فيجب أن تكون خدماتك nodejs و webserver في الحالة "Up" وستكون حاوية certbot في الحالة "0":
</p>

<pre class="ipsCode">
  Name                 Command               State          Ports
------------------------------------------------------------------------
certbot     certbot certonly --webroot ...   Exit 0
nodejs      node app.js                      Up       8080/tcp
webserver   nginx -g daemon off;             Up       0.0.0.0:80-&gt;80/tcp
</pre>

<p>
	إذا رأيت شيءًا آخر غير Up في عمود الحالة لخدمات node وwebserver، أو قيمة أخرى غير الصفر 0 في حالة الخروج للحاوية certbot، فاحرص على التحقق من سجلات الخدمة باستخدام الأمر docker-compose logs:
</p>

<pre class="ipsCode">
docker-compose logs service_name    
</pre>

<p>
	يمكنك الآن التحقق من تركيب بيانات الاعتماد على حاوية webserver باستخدام docker-compose exec:
</p>

<pre class="ipsCode">
docker-compose exec webserver ls -la /etc/letsencrypt/live
</pre>

<p>
	إذا كان الطلب ناجحًا، فسترى في الإخراج ما يلي:
</p>

<pre class="ipsCode">
total 16
drwx------ 3 root root 4096 Dec 23 16:48 .
drwxr-xr-x 9 root root 4096 Dec 23 16:48 ..
-rw-r--r-- 1 root root  740 Dec 23 16:48 README
drwxr-xr-x 2 root root 4096 Dec 23 16:48 example.com
</pre>

<p>
	الآن بعد تأكّدك من نجاح طلبك، يمكنك تحرير تعريف خدمة certbot من أجل حذف الراية <code>staging--</code>.
</p>

<p>
	افتح <code>docker-compose.yml</code>:
</p>

<pre class="ipsCode">
nano docker-compose.yml
</pre>

<p>
	ابحث في الملف عن الجزء الذي يتضمن تعريف خدمة certbot، وعوّض الراية <code>staging--</code> في الخيار command بالراية <code>force-renewal--</code>، والتي ستخبر Certbot أنك تريد طلب شهادة جديدة بنفس النطاقات التي في الشهادة الحالية. يجب أن يبدو تعريف خدمة certbot الآن كما يلي:
</p>

<pre class="ipsCode">
...
  certbot:
    image: certbot/certbot
    container_name: certbot
    volumes:
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
      - web-root:/var/www/html
    depends_on:
      - webserver
    command: certonly --webroot --webroot-path=/var/www/html --email sammy@example.com --agree-tos
</pre>

<p>
	يمكنك الآن تنفيذ docker-compose up لإعادة إنشاء حاوية certbot ووحدات التخزين ذات الصلة. سنضمّن هنا أيضًا الخيار <code>no-deps--</code> لنطلب من Compose تخطي تشغيل خدمة webserver، لأنها قيد التشغيل بالفعل:
</p>

<pre class="ipsCode">
docker-compose up --force-recreate --no-deps certbot
</pre>

<p>
	سوف تشير المخرجات التي ستظهر لك إلى نجاح طلبك للشهادة:
</p>

<pre class="ipsCode">
certbot      | IMPORTANT NOTES:
certbot      |  - Congratulations! Your certificate and chain have been saved at:
certbot      |    /etc/letsencrypt/live/example.com/fullchain.pem
certbot      |    Your key file has been saved at:
certbot      |    /etc/letsencrypt/live/example.com/privkey.pem
certbot      |    Your cert will expire on 2019-03-26. To obtain a new or tweaked
certbot      |    version of this certificate in the future, simply run certbot
certbot      |    again. To non-interactively renew *all* of your certificates, run
certbot      |    "certbot renew"
certbot      |  - Your account credentials have been saved in your Certbot
certbot      |    configuration directory at /etc/letsencrypt. You should make a
certbot      |    secure backup of this folder now. This configuration directory will
certbot      |    also contain certificates and private keys obtained by Certbot so
certbot      |    making regular backups of this folder is ideal.
certbot      |  - If you like Certbot, please consider supporting our work by:
certbot      |
certbot      |    Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
certbot      |    Donating to EFF:                    https://eff.org/donate-le
certbot      |
certbot exited with code 0
</pre>

<p>
	بحصولك على الشهادات اللازمة، يمكنك الانتقال إلى تعديل تكوين Nginx لتضمين <abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">SSL</abbr>.
</p>

<h2>
	الخطوة الخامسة: تعديل تكوين خادم الويب وتعريف الخدمة
</h2>

<p>
	سيتطلب تفعيل <abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">SSL</abbr> في تكوين Nginx الخاص بنا إضافة إعادة توجيه HTTP إلى HTTPS وتحديد شهادة <abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">SSL</abbr> والمواقع الرئيسية. سيتضمن أيضًا تحديد مجموعتنا <code>Diffie-Hellman</code>، والتي سنستخدمها في <code>Perfect Forward Secrecy</code>.
</p>

<p>
	ونظرًا لأنك ستعيد إنشاء خدمة webserver لتضمين هذه الإضافات، فيمكنك إيقافها الآن:
</p>

<pre class="ipsCode">
docker-compose stop webserver
</pre>

<p>
	أنشئ بعد ذلك مجلّدًا في مجلّد المشروع الحالي من أجل مفتاحك Diffie-Hellman:
</p>

<pre class="ipsCode">
mkdir dhparam
</pre>

<p>
	ثم أنشئ مفتاحك باستخدام الأمر <code>openssl</code>:
</p>

<pre class="ipsCode">
sudo openssl dhparam -out /home/sammy/node_project/dhparam/dhparam-2048.pem 2048
</pre>

<p>
	سوف يستغرق الأمر بضع دقائق لإنشاء المفتاح.
</p>

<p>
	لإضافة معلومات Diffie-Hellman و <abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">SSL</abbr> ذات الصلة إلى تكوين Nginx، احذف أولاً ملفّ تكوين Nginx الذي أنشأته مسبقًا:
</p>

<pre class="ipsCode">
rm nginx-conf/nginx.conf
</pre>

<p>
	افتح نسخة أخرى من الملف:
</p>

<pre class="ipsCode">
nano nginx-conf/nginx.conf
</pre>

<p>
	أضف الشيفرة التالية إلى الملف لإعادة توجيه HTTP إلى HTTPS ولإضافة بيانات اعتماد <abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">SSL</abbr> والبروتوكولات وترويسات الأمان (security headers). لاتنس تعويض example.com بنطاقك الخاص:
</p>

<pre class="ipsCode">
server {
        listen 80;
        listen [::]:80;
        server_name example.com www.example.com;

        location ~ /.well-known/acme-challenge {
          allow all;
          root /var/www/html;
        }

        location / {
                rewrite ^ https://$host$request_uri? permanent;
        }
}

server {
        listen 443 <abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">ssl</abbr> http2;
        listen [::]:443 <abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">ssl</abbr> http2;
        server_name example.com www.example.com;

        server_tokens off;

        ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

        ssl_buffer_size 8k;

        ssl_dhparam /etc/<abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">ssl</abbr>/certs/dhparam-2048.pem;

        ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
        ssl_prefer_server_ciphers on;

        ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;

        ssl_ecdh_curve secp384r1;
        ssl_session_tickets off;

        ssl_stapling on;
        ssl_stapling_verify on;
        resolver 8.8.8.8;

        location / {
                try_files $uri @nodejs;
        }

        location @nodejs {
                proxy_pass http://nodejs:8080;
                add_header X-Frame-Options "SAMEORIGIN" always;
                add_header X-XSS-Protection "1; mode=block" always;
                add_header X-Content-Type-Options "nosniff" always;
                add_header Referrer-Policy "no-referrer-when-downgrade" always;
                add_header Content-Security-Policy "default-src * data: 'unsafe-eval' 'unsafe-inline'" always;
                # add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
                # enable strict transport security only if you understand the implications
        }

        root /var/www/html;
        index index.html index.htm index.nginx-debian.html;
}
</pre>

<p>
	تحدد هذه الكتلة الخاصة بالخادم HTTP هوية الخادم webroot المستخدم لطلبات تجديد Certbot على الملف .well-known/acme-challenge. وتتضمن أيضًا توجيهًا لإعادة الكتابة (rewrite directive) يوجه طلبات HTTP إلى المجلّد الجذري إلى HTTPS.
</p>

<p>
	أما الكتلة الخاصة بخادم HTTPS فيفعّل <abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">SSL</abbr> و HTTP2. لقراءة المزيد حول كيفية تكرار HTTP/2 على بروتوكولات HTTP والمزايا التي يمكن أن يوفرها لتحسين أداء موقع الويب، يرجى الاطلاع على مقدمة كيفية إعداد Nginx مع دعم HTTP/2 على أوبونتو 18.04. يتضمن هذا الجزء أيضًا سلسلة من الخيارات للتأكد من أنك تستخدم أحدث بروتوكولات <abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">SSL</abbr> والتشفيرات وأن تدبيس OSCP) OSCP stapling) قيد التشغيل. ويسمح لك تدبيس OSCP بتقديم استجابة ذات ختم زمني (time-stamped) من سلطة الإشهاد الخاصة بك أثناء عملية إنشاء الاتصال <abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">TLS</abbr> الأولي، والتي يمكنها تسريع عملية المصادقة.
</p>

<p>
	يحدّد الجزء أيضًا بيانات اعتماد <abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">SSL</abbr> و Diffie-Hellman والمواقع الرئيسية.
</p>

<p>
	ختامًا، لقد نقلنا معلومات مرور الوكيل إلى هذه الكتلة، بما في ذلك كتلة موقع تتضمّن توجيه try_files، لتوجيه الطلبات إلى حاوية تطبيق Node.js ذات تسمية بديلة، وكتلة موقع لتلك التسمية البديلة، والتي تتضمن ترويسات الأمان التي ستمكننا من الحصول على تقييمات حول أشياء مثل مواقع اختبار خادم <abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">SSL</abbr> Labs ومواقع الأمان. تتضمن هذه الترويسات خيارات <code>X-Frame-Options</code> و <code>X-Content-Type-Options</code> وسياسة الإحالة وسياسة أمان المحتوى و <code>X-XSS-Protection</code>. يتم التصريح علنًا بالترويسة HSTS (اختصار للعبارة HTTP Strict Transport Security)، فعّل هذه فقط إذا فهمت الآثار المترتبة على ذلك وعملت على تقييم الوظيفة "التحميل المسبق" (preload).
</p>

<p>
	احفظ الملف وأغلقه عند الانتهاء من التحرير.
</p>

<p>
	قبل إعادة إنشاء خدمة webserver، ستحتاج إلى إضافة بعض الأشياء إلى تعريف الخدمة في ملف docker-compose.yml ، بما في ذلك معلومات المنفذ ذات الصلة لـ HTTPS وتعريف وحدة تخزين Diffie-Hellman.
</p>

<p>
	افتح الملف:
</p>

<pre class="ipsCode">
nano docker-compose.yml
</pre>

<p>
	أضف في تعريف خدمة webserver تعيين المنفذ التالي ووحدة التخزين المسماة dhparam:
</p>

<pre class="ipsCode">
...
 webserver:
    image: nginx:latest
    container_name: webserver
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - web-root:/var/www/html
      - ./nginx-conf:/etc/nginx/conf.d
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
      - dhparam:/etc/<abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">ssl</abbr>/certs
    depends_on:
      - nodejs
    networks:
      - app-network
</pre>

<p>
	أضف بعد ذلك وحدة تخزين dhparam إلى تعريفات وحدات التخزين الخاصة بك:
</p>

<pre class="ipsCode">
...
volumes:
  ...
  dhparam:
    driver: local
    driver_opts:
      type: none
      device: /home/sammy/node_project/dhparam/
      o: bind
</pre>

<p>
	على غرار وحدة التخزين web-root، ستركّب وحدة تخزين dhparam مفتاح Diffie-Hellman المخزن على المضيف في حاوية webserver.
</p>

<p>
	احفظ الملف وأغلقه عند الانتهاء من التحرير.
</p>

<p>
	ثم أعد إنشاء خدمة webserver:
</p>

<pre class="ipsCode">
docker-compose up -d --force-recreate --no-deps webserver
</pre>

<p>
	وتحقق من خدماتك باستخدام docker-compose ps:
</p>

<pre class="ipsCode">
docker-compose ps
</pre>

<p>
	يجب أن تشاهد الإخراج يشير إلى أن العقدة وخدمات خادم الويب تعمل:
</p>

<pre class="ipsCode">
  Name                 Command               State                     Ports
----------------------------------------------------------------------------------------------
certbot     certbot certonly --webroot ...   Exit 0
nodejs      node app.js                      Up       8080/tcp
webserver   nginx -g daemon off;             Up       0.0.0.0:443-&gt;443/tcp, 0.0.0.0:80-&gt;80/tcp
</pre>

<p>
	ختامًا، يمكنك زيارة عنوان نطاقك لضمان عمل كل شيء على النحو المنتظر. انتقل في متصفحك إلى <a href="https://example.xn--com-nwe" ipsnoembed="false" rel="external nofollow">https://example.com،</a> مع الحرص على تعويض example.com باسم نطاقك. ستظهر لك صفحة الهبوط التالية:
</p>

<p>
	وينبغي أن تشاهد أيضًا رمز القفل في مؤشر أمان متصفحك. إذا كنت ترغب في ذلك، يمكنك الانتقال إلى صفحة الهبوط لاختبار <abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">SSL</abbr> Labs Server أو صفحة اختبار خادم ترويسات الأمان. يجب أن تمنح خيارات التكوين التي أدرجناها لموقعك تصنيفًا على كليهما.
</p>

<h2>
	الخطوة السادسة: تجديد الشهادات
</h2>

<p>
	تكون شهادات Let’s Encrypt صالحة لمدة 90 يومًا، لذلك ستحتاج إلى إعداد عملية تجديد تلقائية حرصًا على عدم انقضاء صلاحيتها. إحدى الطرق للقيام بذلك هي إنشاء وظيفة باستخدام أداة جدولة cron. في هذه الحالة، سنقوم بجدولة مهمة cron باستخدام سكربتٍ لتجديد شهاداتنا وإعادة تحميل تكوين Nginx الخاص بنا.
</p>

<p>
	افتح سكربتًا بالاسم <code>ssl_renew.sh</code> في مجلّد مشروعك:
</p>

<pre class="ipsCode">
nano ssl_renew.sh
</pre>

<p>
	أضف الشيفرة التالية إلى السكربت لتجديد شهاداتك وإعادة تحميل تكوين خادم الويب الخاص بك:
</p>

<pre class="ipsCode">
#!/bin/bash

/usr/local/bin/docker-compose -f /home/sammy/node_project/docker-compose.yml run certbot renew --dry-run \
&amp;&amp; /usr/local/bin/docker-compose -f /home/sammy/node_project/docker-compose.yml kill -s SIGHUP webserver
</pre>

<p>
	بالإضافة إلى تحديد موقع ملف docker-compose الثنائي، فإننا نحدد أيضًا موقع ملف docker-compose.yml الخاص بنا من أجل تشغيل أوامر docker-compose. في هذه الحالة، نستخدم docker-compose run لتشغيل حاوية certbot ولإبطال الأمر command المقدم في تعريف خدمتنا بأمر آخر: وهو الأمر الفرعي renew، والذي سيقوم بتجديد الشهادات التي تكون على وشك الانتهاء. لقد قمنا بتضمين خيار dry-run-- هنا لاختبار السكربت.
</p>

<p>
	ثم يستخدم السكربت <code>kill docker-compose</code> لإرسال إشارة SIGHUP إلى حاوية خادم الويب لإعادة تحميل تكوين Nginx. لمزيد من المعلومات حول استخدام هذه العملية لإعادة تحميل تكوين Nginx، يرجى الاطلاع على منشور مدونة Docker هذا عن نشر صورة Nginx الرسمية باستخدام Docker.
</p>

<p>
	أغلق الملف عند الانتهاء من التحرير. ثم اجعله قابلًا للتنفيذ:
</p>

<pre class="ipsCode">
chmod +x ssl_renew.sh
</pre>

<p>
	بعد ذلك، افتح ملف الجذر crontab الخاص بك لتنفيذ سكربت التجديد في مجال زمني محدد:
</p>

<pre class="ipsCode">
sudo crontab -e 
</pre>

<p>
	إذا كانت هذه هي المرة الأولى التي تقوم فيها بتحرير هذا الملف، فسيُطلب منك اختيار محرّر:
</p>

<pre class="ipsCode">
no crontab for root - using an empty one
Select an editor.  To change later, run 'select-editor'.
  1. /bin/ed
  2. /bin/nano        &lt;---- easiest
  3. /usr/bin/vim.basic
  4. /usr/bin/vim.tiny
Choose 1-4 [2]: 
...
</pre>

<p>
	أضف السطر التالي في الجزء السفلي من الملف:
</p>

<pre class="ipsCode">
...
*/5 * * * * /home/sammy/node_project/ssl_renew.sh &gt;&gt; /var/log/cron.log 2&gt;&amp;1
</pre>

<p>
	سيعيّن ذلك المجال الزمني للوظيفة على كل خمس دقائق، إذ يمكنك اختبار ما إذا كان طلبك للتجديد يعمل على النحو المنشود. لقد أنشأنا أيضًا ملف سجل، cron.log، لتسجيل المخرجات ذات الصلة من الوظيفة.
</p>

<p>
	بعد خمس دقائق، تحقق من cron.log لمعرفة ما إذا كان طلب التجديد قد نجح أم لا:
</p>

<pre class="ipsCode">
tail -f /var/log/cron.log
</pre>

<p>
	يجب أن يظهر لك الإخراج التالي مؤكِّدًا نجاح التجديد:
</p>

<pre class="ipsCode">
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates below have not been saved.)

Congratulations, all renewals succeeded. The following certs have been renewed:
  /etc/letsencrypt/live/example.com/fullchain.pem (success)
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates above have not been saved.)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Killing webserver ... done
</pre>

<p>
	يمكنك الآن تعديل ملف crontab لتعيين مجال زمني يومي. لتنفيذ السكربت كل يوم عند الظهر، على سبيل المثال، يمكنك تعديل السطر الأخير من الملف ليبدو كما يلي:
</p>

<pre class="ipsCode">
...
0 12 * * * /home/sammy/node_project/ssl_renew.sh &gt;&gt; /var/log/cron.log 2&gt;&amp;1
</pre>

<p>
	ستحتاج أيضًا إلى حذف الخيار <code>dry-run--</code> من السكربت ssl_renew.sh:
</p>

<pre class="ipsCode">
#!/bin/bash

/usr/local/bin/docker-compose -f /home/sammy/node_project/docker-compose.yml run certbot renew \
&amp;&amp; /usr/local/bin/docker-compose -f /home/sammy/node_project/docker-compose.yml kill -s SIGHUP webserver
</pre>

<p>
	ستضمن مهمة cron ألا تنقضي صلاحية شهاداتك " Let’s Encrypt" عبر تجديدها عند الاقتضاء. يمكنك أيضًا إعداد تدوير السجل باستخدام الأداة المساعدة Logrotate لتدوير وضغط ملفات السجل.
</p>

<h2>
	خاتمة
</h2>

<p>
	لقد استخدمت حاوياتٍ لإعداد وتشغيل تطبيق Node باستخدام وكيل Nginx عكسي. كما حصلت أيضًا على شهادات <abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">SSL</abbr> لنطاق تطبيقك وأعددت وظيفة cron لتجديد هذه الشهادات عند الضرورة.
</p>

<p>
	إذا كنت مهتمًا بمعرفة المزيد عن Let's Encrypt Plugins، فيرجى الاطلاع على مقالاتنا حول <a href="https://academy.hsoub.com/devops/servers/web/nginx/%D9%83%D9%8A%D9%81-%D8%AA%D8%A4%D9%85%D9%91%D9%86-%D8%AE%D8%A7%D8%AF%D9%85-%D9%88%D9%8A%D8%A8-nginx-%D8%B9%D9%84%D9%89-%D8%A3%D9%88%D8%A8%D9%86%D8%AA%D9%88-1604-r365/" rel="">استخدام المكون الإضافي</a> Nginx أو <a href="https://www.digitalocean.com/community/tutorials/how-to-use-certbot-standalone-mode-to-retrieve-let-s-encrypt-ssl-certificates-on-ubuntu-1804" rel="external nofollow">المكون الإضافي المستقل</a>.
</p>

<p>
	يمكنك أيضًا معرفة المزيد حول Docker Compose من خلال الاطلاع على الموارد التالية:
</p>

<ul>
<li>
		كيفية <a href="https://www.digitalocean.com/community/tutorials/how-to-install-docker-compose-on-ubuntu-18-04" rel="external nofollow">تثبيت Docker Compose على أوبونتو 18.04</a>.
	</li>
	<li>
		كيفية <a href="https://www.digitalocean.com/community/tutorials/how-to-configure-a-continuous-integration-testing-environment-with-docker-and-docker-compose-on-ubuntu-16-04" rel="external nofollow">تكوين بيئة اختبار تكامل مستمر مع تكوين Docker و Docker Compose على أوبونتو 16.04</a>.
	</li>
	<li>
		كيفية <a href="https://www.digitalocean.com/community/tutorials/how-to-set-up-laravel-nginx-and-mysql-with-docker-compose" rel="external nofollow">إعداد Laravel وNginx وMySQL باستخدام Docker</a>. ويعدّ <a href="https://docs.docker.com/compose/" rel="external nofollow">توثيق Docker</a> أيضًا مورداً رائعًا لمعرفة المزيد حول التطبيقات متعددة الحاويات.
	</li>
</ul>
<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-secure-a-containerized-node-js-application-with-nginx-let-s-encrypt-and-docker-compose" rel="external nofollow">How To Secure a Containerized Node.js Application with Nginx, Let's Encrypt, and Docker Compose</a> لصاحبته Kathleen Juell
</p>
]]></description><guid isPermaLink="false">814</guid><pubDate>Wed, 05 Feb 2020 06:29:05 +0000</pubDate></item><item><title>&#x62C;&#x62F;&#x648;&#x644;&#x629; &#x62A;&#x637;&#x628;&#x64A;&#x642; Node.js &#x645;&#x639; MongoDB &#x639;&#x644;&#x649; Kubernetes &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; Helm</title><link>https://academy.hsoub.com/programming/javascript/nodejs/%D8%AC%D8%AF%D9%88%D9%84%D8%A9-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-nodejs-%D9%85%D8%B9-mongodb-%D8%B9%D9%84%D9%89-kubernetes-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-helm-r813/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2020_02/5.jpg.f2fbac3809a04090ca0b62166f7ec5c6.jpg" /></p>

<p>
	<a href="https://kubernetes.io/" rel="external nofollow">Kubernetes</a> هو نظام لتشغيل التطبيقات الحديثة في حاويات على نطاق واسع. يستطيع المطورون من خلاله نشر وإدارة التطبيقات عبر مجموعات من الأجهزة. ورغم إمكانية استخدامه لتحسين الكفاءة والموثوقية في إعدادات التطبيقات ذات نسخة واحدة، فقد صُمّم Kubernetes لتشغيل نسخٍ متعددة للتطبيق عبر مجموعات من الأجهزة.
</p>

<p>
	يختار العديد من المطورين استخدام مدير الحزمة Helm عند إنشاء عمليات نشر متعددة الخدمات باستخدام Kubernetes. ويبسّط Helm عملية إنشاء موارد Kubernetes متعددة من خلال تقديم مخططات وقوالب تنسق كيفية تفاعل هذه الكائنات. كما يقدم أيضًا مخططات مُهيأة مسبقًا لمشاريع مفتوحة المصدر.
</p>

<p>
	في هذا الدرس، سوف تنشر تطبيق Node.js بقاعدة بيانات MongoDB على عنقود Kubernetes باستخدام مخططات Helm. ستستخدم مخطط تعيين النسخة المتماثلة Helm MongoDB الرسمي لإنشاء كائن StatefulSet يتكون من ثلاث علب (pods) وخدمة بدون رأس (stateless) وثلاث طلبات وحدة تخزين ثابتة PersistentVolumeClaims. ستُنشئ أيضًا مخططًا لنشر تطبيق Node.js متعدد النسخ باستخدام صورة تطبيق مخصصة. سيعكس الإعداد الذي ستنشئه في هذا البرنامج التعليمي وظيفة الشيفرة الموضحة في <a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%A8%D9%86%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-nodejs-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-docker-r809/" rel="">كيفية إعداد تطبيق node.js لسير عملٍ يعتمد على الحاويات باستخدام Docker Compose</a> وسيكون نقطة انطلاق جيدة لإنشاء تطبيق Node.js مرن مع مخزن بيانات MongoDB يتناسب مع احتياجاتك.
</p>

<h2>
	المتطلبات الأساسية
</h2>

<p>
	لإكمال هذا الدرس، ستحتاج إلى:
</p>

<ul>
<li>
		عنقود +Kubernetes 1.10 مع تفعيل التحكم في الوصول المستند إلى الدور role-based access control ‏(RBAC). سيستخدم هذا الإعداد <a href="https://www.digitalocean.com/products/kubernetes/" rel="external nofollow">عنقود DigitalOcean Kubernetes</a>، لكن تبقى لك حرية الاختيار في <a href="https://www.digitalocean.com/community/tutorials/how-to-create-a-kubernetes-1-11-cluster-using-kubeadm-on-ubuntu-18-04" rel="external nofollow">إنشاء عنقود باستخدام طريقة أخرى</a>.
	</li>
	<li>
		أداة سطر الأوامر kubectl المثبتة على جهازك المحلي أو خادم التطوير وإعدادها للاتصال بعنقودك. يمكنك قراءة المزيد حول تثبيت kubectl في <a href="https://kubernetes.io/docs/tasks/tools/install-kubectl/" rel="external nofollow">التوثيق الرسمي</a>.
	</li>
	<li>
		Docker مثبت على جهازك المحلي أو خادم التطوير. إذا كنت تعمل على نظام أوبونتو 18.04، اتبع الخطوتين 1 و 2 لكيفية <a href="https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-18-04" rel="external nofollow">تثبيت واستخدام Docker على أوبونتو 18.04</a>؛ خلاف ذلك، اتبع <a href="https://docs.docker.com/install/" rel="external nofollow">التوثيق الرسمي</a> للحصول على معلومات حول التثبيت على أنظمة التشغيل الأخرى. تأكد من إضافة مستخدمك غير الجذري إلى مجموعة Docker، كما هو موضح في الخطوة 2 من البرنامج التعليمي المرتبط.
	</li>
	<li>
		حساب Docker Hub. للحصول على نظرة عامة حول كيفية إعداده، راجع هذه <a href="https://docs.docker.com/docker-hub/" rel="external nofollow">المقدمة إلى Docker Hub</a>.
	</li>
	<li>
		مدير الحزمة Helm مثبت على جهازك المحلي أو خادم التطوير مع تثبيت Tiller على نظامك، باتباع الإرشادات الموضحة في الخطوتين 1 و 2 حول <a href="https://www.digitalocean.com/community/tutorials/how-to-install-software-on-kubernetes-clusters-with-the-helm-package-manager" rel="external nofollow">كيفية تثبيت البرامج على عناقيد Kubernetes باستخدام مدير الحزمة Helm</a>.
	</li>
</ul>
<h2>
	الخطوة الأولى: استنساخ وتحزيم التطبيق
</h2>

<p>
	لاستخدام تطبيقنا على Kubernetes، سنحتاج إلى تحزيمه حتى تتمكن <a href="https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/" rel="external nofollow">الأداة kubelet</a> من سحب الصورة.
</p>

<p>
	سنحتاج قبل تحزيم التطبيق، مع ذلك، إلى تعديل العنوان URI لاتصال MongoDB في شيفرة التطبيق للتأكد من أن تطبيقنا يستطيع الاتصال بعناصر مجموعة النسخ المتماثلة التي سننشئها باستخدام مخطط Helm mongodb-replicaset.
</p>

<p>
	ستكون خطوتنا الأولى هي استنساخ مستودع <code>node-mongo-docker-dev</code> من حساب DigitalOcean Community GitHub. يتضمن هذا المستودع شيفرة الإعداد الموضح في كيفية إعداد تطبيق node.js لسير عملٍ يعتمد على الحاويات باستخدام Docker Compose، والذي يستخدم تطبيق Node.js لشرح كيفية إعداد بيئة تطوير باستخدام Docker Compose. يمكنك العثور على مزيد من المعلومات حول التطبيق نفسه في <a href="https://www.digitalocean.com/community/tutorial_series/from-containers-to-kubernetes-with-node-js" rel="external nofollow">سلسلة من الحاويات إلى Kubernetes باستخدام Node.js</a>.
</p>

<p>
	انسخ المستودع في مجلّد يسمى node_project:
</p>

<pre class="ipsCode">
git clone https://github.com/do-community/node-mongo-docker-dev.git node_project
</pre>

<p>
	انتقل إلى المجلّد node_project:
</p>

<pre class="ipsCode">
cd node_project
</pre>

<p>
	يحتوي مجلّد node_project على ملفات ومجلدات لتطبيق معلومات سمك القرش الذي يعمل على مدخلات المستخدم. تمّ تحديثه للعمل على الحاويات وأزيلت منه معلومات التكوين الحساسة والمحددة في شيفرة التطبيق وأُعيد تشكيلها من أجل حقنها عند تنفيذ التطبيق، كما أُلغيَ تحميل حالة التطبيق إلى قاعدة بيانات MongoDB.
</p>

<p>
	لمزيد من المعلومات حول تصميم التطبيقات الحديثة عديمة الحالة، يرجى الاطلاع على هيكلة التطبيقات ل Kubernetes وتحديث التطبيقات لKubernetes.
</p>

<p>
	عندما ننشر مخطط Helm mongodb-replicaset، فسينشئ ما يلي:
</p>

<ul>
<li>
		كائن StatefulSet بثلاث علب تمثّل مجموعة النسخ المتماثلة MongoDB. سيكون لكل علبة طلب PersistentVolumeClaim مرتبط وسيحتفظ بهوية ثابتة في حالة إعادة الجدولة.
	</li>
	<li>
		مجموعة نسخ متماثلة MongoDB تتكون من العلب الموجودة في StatefulSet. سوف تشمل المجموعة واحدة ابتدائية واثنتان ثانويتان. ستُنسَخ البيانات من العلبة الابتدائية إلى الثانويتين، مما يضمن إتاحة بيانات التطبيق على أعلى مستوى.
	</li>
	<li>
		لكي يتفاعل تطبيقنا مع النسخ المتماثلة لقاعدة البيانات، سيتطلب الأمر تضمين أسماء المضيفين (hostnames) لعناصر مجموعة النسخ المتماثلة بالإضافة إلى اسم النسخة المتماثلة نفسها في العنوان URI لاتصال MongoDB في الشيفرة.
	</li>
</ul>
<p>
	يُسمّى الملف الموجود في مخزننا المستنسخ والذي يحدد معلومات اتصال قاعدة البيانات db.js. افتح هذا الملف الآن باستخدام nano أو المحرر المفضل لديك:
</p>

<pre class="ipsCode">
nano db.js
</pre>

<p>
	يشتمل الملف حاليًا على قيم ثابتة يمكن الرجوع إليها في اتصال URI لقاعدة البيانات أثناء التشغيل. تُحقًن هذه القيم الثابتة باستخدام خاصية process.env الخاصة ب Node، والتي تُعيد كائنًا يحتوي على معلوماتٍ حول بيئة المستخدم الخاصة بك في وقت التشغيل. يتيح لنا تحديد القيم ديناميكيًا في شيفرة التطبيق فصل الشيفرة عن البنية التحتية الأساسية، وهو أمر ضروري في بيئة ديناميكية وعديمة الحالة (stateless). لمزيد من المعلومات حول إعادة تشكيل شيفرة التطبيق بهذه الطريقة، راجع الخطوة الثانية من كيفية إعداد تطبيق node.js لسير عملٍ يعتمد على الحاويات باستخدام Docker Compose.
</p>

<p>
	تبدو ثوابت الاتصال URI وسلسلة URI نفسها حاليًا كما يلي:
</p>

<pre class="ipsCode">
...
const {
  MONGO_USERNAME,
  MONGO_PASSWORD,
  MONGO_HOSTNAME,
  MONGO_PORT,
  MONGO_DB
} = process.env;

...

const url = `mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@${MONGO_HOSTNAME}:${MONGO_PORT}/${MONGO_DB}?authSource=admin`;
...
</pre>

<p>
	تماشيا مع <a href="https://12factor.net/" rel="external nofollow">الطريقة 12FA</a>، لا نريد ترميز أسماء مضيفي مثيلات النسخ المتماثلة أو اسم مجموعة النسخ المتماثلة في سلسلة URI هذه. يمكننا توسيع الثابت <code>MONGO_HOSTNAME</code> الحالي ليشمل عِدّة أسماء مضيفين لعناصر مجموعة النسخ المتماثلة. لذلك سنترك الأمر على حاله. وسنحتاج، مع ذلك، إلى إضافة مجموعة متماثلة ثابتة إلى قسم الخيارات في سلسلة URI.
</p>

<p>
	أضف <code>MONGO_REPLICASET</code> إلى كل من كائن الثابت URI وسلسلة الاتصال:
</p>

<pre class="ipsCode">
...
const {
  MONGO_USERNAME,
  MONGO_PASSWORD,
  MONGO_HOSTNAME,
  MONGO_PORT,
  MONGO_DB,
  MONGO_REPLICASET
} = process.env;

...
const url = `mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@${MONGO_HOSTNAME}:${MONGO_PORT}/${MONGO_DB}?replicaSet=${MONGO_REPLICASET}&amp;authSource=admin`;
...
</pre>

<p>
	يتيح استخدام خيار replicaSet في قسم الخيارات في URI تمرير اسم مجموعة النسخ المتماثلة، والتي تتيح لنا بمعية أسماء المضيفين المحدّدة في ثابت <code>MONGO_HOSTNAME</code> الاتصال بعناصر المجموعة.
</p>

<p>
	احفظ الملف وأغلقه عند الانتهاء من التحرير.
</p>

<p>
	بعد تعديل معلومات اتصال قاعدة البيانات الخاصة بك للعمل مع مجموعات النسخ المتماثلة، يمكنك الآن تحزيم تطبيقك، وبناء الصورة باستخدام الأمر <code>docker build</code>، ودفعها إلى Docker Hub.
</p>

<p>
	ابدأ ببناء الصورة باستخدام Docker والراية <code>t-</code> التي تتيح لك تعليم الصورة باسم لا يُنسى. في هذه الحالة، علّم الصورة باسم مستخدمك Docker Hub وسَمِّها <code>node-replicas</code> أو اسمًا تختاره أنت:
</p>

<pre class="ipsCode">
docker build -t your_dockerhub_username/node-replicas .
</pre>

<p>
	تحدد النقطة <code>.</code> في الأمر أن سياق البناء هو المجلّد الحالي.
</p>

<p>
	سوف يستغرق الأمر دقيقة أو دقيقتين لبناء الصورة. بمجرد اكتماله، تحقق من صورك:
</p>

<pre class="ipsCode">
docker images
</pre>

<p>
	سوف يظهر لك الإخراج التالي:
</p>

<pre class="ipsCode">
REPOSITORY                              TAG                 IMAGE ID            CREATED             SIZE
your_dockerhub_username/node-replicas   latest              56a69b4bc882        7 seconds ago       90.1MB
node                                    10-alpine           aa57b0242b33        6 days ago
</pre>

<p>
	بعد ذلك، سجّل الدخول إلى حساب Docker Hub الذي أنشأته في المتطلبات الأساسية:
</p>

<pre class="ipsCode">
docker login -u your_dockerhub_username
</pre>

<p>
	عندما يطلب منك ذلك، أدخل كلمة مرور حساب Docker Hub. سيؤدي التسجيل بهذه الطريقة إلى إنشاء ملف <code>‎~/.docker/config.json</code> في المجلّد الرئيسي لمستخدمك غير الجذري باستخدام بيانات اعتماد Docker Hub.
</p>

<p>
	ادفع صورة التطبيق إلى Docker Hub باستخدام الأمر <code>docker push.‎</code> ولا تنس أن تعوّض your<em>dockerhub</em>username باسم مستخدمك Docker Hub:
</p>

<pre class="ipsCode">
docker push your_dockerhub_username/node-replicas
</pre>

<p>
	لديك الآن صورة للتطبيق يمكنك سحبها لتشغيل التطبيق المنسوخ باستخدام Kubernetes. ستكون الخطوة التالية هي إعداد بارامترات محدّدة لاستخدامها مع مخطط Helm ل MongoDB.
</p>

<h2>
	الخطوة الثانية: إنشاء أسرار لمجموعة النسخ المتماثلة MongoDB
</h2>

<p>
	يوفر المخطط <code>stable/mongodb-replicaset</code> خيارات مختلفة عندما يتعلق الأمر باستخدام الأسرار، وسننشئ منها خيارين لاستخدامهما في نشر المخطط:
</p>

<ul>
<li>
		سرٌّ (secret) لمجموعة ملفات النسخ المتماثلة سيعمل ككلمة مرور مشتركة بين عناصر مجموعة النسخ المتماثلة، مما يسمح بتصديق الهوية لأعضاء آخرين.
	</li>
	<li>
		سرٌّ للمستخدم المشرف في MongoDB، الذي سيتم إنشاؤه كمستخدم الجذر في قاعدة بيانات المشرف. سيتيح هذا الدور بإنشاء مستخدمين لاحقًا بأذونات محدودة عند نشر تطبيقك على الإنتاج.
	</li>
</ul>
<p>
	بإنشائنا لهذه الأسرار، سنكون قادرين على تعيين قيم المعاملات المفضلة لدينا في ملف قيم مخصص وإنشاء كائن StatefulSet ونسخة متماثلة MongoDB مع مخطط Helm.
</p>

<p>
	دعنا أولًا ننشئ ملف keyfile. سنستخدم الأمر openssl مع خيار rand لإنشاء سلسلة عشوائية بحجم 756 بايت لملف keyfile:
</p>

<pre class="ipsCode">
openssl rand -base64 756 &gt; key.txt
</pre>

<p>
	سيُشفَّر الإخراج الذي أُنشِئ باستخدام الأمر base64، مما يضمن نقل بيانات موحد، كما سيُعاد توجيهه إلى ملف يسمى key.txt، باتباع الإرشادات الواردة في وثائق مصادقة تخطيط mongodb-replicaset. يجب أن يتراوح طول المفتاح نفسه بين 6 محارف و 1024 محرفًا، ويتألف فقط من المحارف في مجموعة base64.
</p>

<p>
	يمكنك الآن إنشاء سر يسمى keyfilesecret على هذا الملف باستخدام kubectl create:
</p>

<pre class="ipsCode">
kubectl create secret generic keyfilesecret --from-file=key.txt
</pre>

<p>
	سيُنشئ هذا كائنًا سرًّا في المجال الاسمي الافتراضي، نظرًا لأننا لم ننشئ مجالًا اسميًا محددا لإعدادنا.
</p>

<p>
	سيظهر لك الإخراج التالي مشيرًا إلى إنشاء سرّك:
</p>

<pre class="ipsCode">
secret/keyfilesecret created
</pre>

<p>
	احذف key.txt:
</p>

<pre class="ipsCode">
rm key.txt
</pre>

<p>
	إذا كنت ترغب في حفظ الملف بدلاً من ذلك، فتأكد من تقييد أذوناته وإضافته إلى ملفك gitignore. لإبقائه خارج تحكم الإصدارات.
</p>

<p>
	بعد ذلك، أنشئ سرًّا لمستخدمك المشرف في MongoDB. ستكون الخطوة الأولى هي تحويل اسم المستخدم وكلمة المرور المطلوبين إلى base64.
</p>

<p>
	ابدأ بتحويل اسم مستخدم قاعدة بياناتك:
</p>

<pre class="ipsCode">
echo -n 'your_database_username' | base64
</pre>

<p>
	دوّن القيمة التي تظهر في الإخراج. بعد ذلك، حوّل كلمة مرورك:
</p>

<pre class="ipsCode">
echo -n 'your_database_password' | base64
</pre>

<p>
	دوّن القيمة الظاهرة في الإخراج هنا أيضا. افتح ملف السر:
</p>

<pre class="ipsCode">
nano secret.yaml
</pre>

<p>
	ملاحظة: تُحدّد كائنات Kubernetes عادة باستخدام YAML، الذي يمنع بصرامة علامات التبويب ويتطلب مسافتين للمسافة البادئة. إذا كنت ترغب في التحقق من تنسيق أي من ملفاتك YAML، فيمكنك استخدام linter أو اختبار صحّة صياغة (syntax) تركيبك باستخدام kubectl create مع الرايتين dry-run-- و validate--:
</p>

<pre class="ipsCode">
kubectl create -f your_yaml_file.yaml --dry-run --validate=true
</pre>

<p>
	بشكل عام، من المستحسن التحقق من صحة الصياغة قبل إنشاء الموارد باستخدام kubectl.
</p>

<p>
	أضف الشيفرة التالية إلى الملف لإنشاء سرّ يحدد المستخدم وكلمة المرور مع القيم المشفرة التي أنشأتها للتو. لا تنس تعويض القيم الوهمية هنا باسم المستخدم وكلمة المرور المشفرة:
</p>

<pre class="ipsCode">
apiVersion: v1
kind: Secret
metadata:
  name: mongo-secret
data:
  user: your_encoded_username
  password: your_encoded_password
</pre>

<p>
	نحن نستخدم هنا أسماء المفاتيح التي يتوقعها مخطط mongodb-replicaset: المستخدم وكلمة المرور. ولقد سمّينا كائن السرّ mongo-secret، ولكن تستطيع تسميته كما تشاء.
</p>

<p>
	احفظ الملف وأغلقه عند الانتهاء من التحرير. أنشئ كائن السرّ باستخدام الأمر التالي:
</p>

<pre class="ipsCode">
kubectl create -f secret.yaml
</pre>

<p>
	سيظهر لك في الإخراج ما يلي:
</p>

<pre class="ipsCode">
secret/mongo-secret created
</pre>

<p>
	يمكنك مرة أخرى إما حذف secret.yaml أو تقييد أذوناته وإضافته إلى ملفك gitignore..
</p>

<p>
	بعد إنشاء كائنات السرّ، يمكنك الانتقال إلى تحديد قيم المعاملات التي ستستخدمها مع مخطط mongodb-replicaset وإنشاء نشر MongoDB.
</p>

<h2>
	الخطوة الثالثة: تكوين المخطّط MongoDB Helm وإنشاء النشر
</h2>

<p>
	يأتي Helm مع مستودع نشط ومحفوظ يدعى stable والذي يحتوي على المخطط الذي سنستخدمه: <code>mongodb-replicaset</code>. لاستخدام هذا المخطط مع الأسرار التي أنشأناها للتو، سننشئ ملفًا به قيم معاملات التكوين يسمى <code>mongodb-values.yaml</code> ثم نثبت المخطط باستخدام هذا الملف.
</p>

<p>
	سوف يعكس ملفنا <code>mongodb-values.yaml</code> إلى حد كبير ملف <code>value.yaml</code> الافتراضي في مستودع تخزين المخطط mongodb-replicaset. ومع ذلك، سنجري التغييرات التالية على ملفنا:
</p>

<ul>
<li>
		سنعيّن المعامل auth على القيمة true لنضمن تفعيل التفويض (authorization) عند بدء اشتغال مثيلات قاعدة البيانات. هذا يعني أنه سيُطلب من جميع العملاء تصديق الهوية للوصول إلى موارد وعمليات قاعدة البيانات.
	</li>
	<li>
		سنضيف معلومات حول الأسرار التي أنشأناها في الخطوة السابقة حتى يتمكن المخطط من استخدام هذه القيم لإنشاء مجموعة النسخ المتماثلة keyfile والمستخدم المشرف.
	</li>
	<li>
		سنقلّل من سعة وحدات التخزين الثابتة PersistentVolumes المرتبطة بكل علبة Pod في مجموعة StatefulSet لاستخدام الحد الأدنى القابل للتطبيق في وحدات التخزين DigitalOcean ، المحدّد في 1 جيجابايت، رغم أنك تبقى حرًّا في تعديل هذه القيمة لتلبية متطلبات التخزين الخاصة بك.
	</li>
</ul>
<p>
	يجب عليك أولاً قبل كتابة ملف <code>mongodb-values.yaml</code>، ومع ذلك، التحقق من أنّك لديك صنف <code>StorageClass</code> أُنشِئ وأُعِدّ لتوفير موارد التخزين. سيكون لكل علبة موجودة في قاعدة بياناتك StatefulSet هوية ملازمة وما يرتبط بها من طلبات <code>PersistentVolumeClaim</code>، والتي ستوفر <code>PersistentVolume</code> ديناميكيًا للعلبة. إذا أُعيدت جدولة العلبة، فستثبّت <code>PersistentVolume</code> على أي عقدة (node) تُجدول عليها العلبة Pod (رغم أنه ينبغي حذف كل وحدة تخزين يدويًا إذا حذفت العلبة Pod أو StatefulSet المقترن بها نهائيًا).
</p>

<p>
	وبما أننا نعمل على DigitalOcean Kubernetes، فإن مزود StorageClass الافتراضي الخاص بنا يعيّن على <code>dobs.csi.digitalocean.com</code> أي وحدة التخزين DigitalOcean. ويمكننا التحقق من ذلك بكتابة:
</p>

<p>
	kubectl get storageclass
</p>

<p>
	إذا كنت تعمل عل عنقود DigitalOcean، فسترى في الإخراج مايلي:
</p>

<pre class="ipsCode">
NAME                         PROVISIONER                 AGE
do-block-storage (default)   dobs.csi.digitalocean.com   21m
</pre>

<p>
	إذا كنت لا تعمل مع مجموعة DigitalOcean، فستحتاج إلى إنشاء StorageClass وتكوين مزود من اختيارك. للحصول على تفاصيل حول كيفية القيام بذلك، يرجى الاطلاع على الوثائق الرسمية.
</p>

<p>
	الآن وبعد التأكد من تكوين StorageClass، افتح الملفّ mongodb-values.yaml لتحريره:
</p>

<pre class="ipsCode">
nano mongodb-values.yaml
</pre>

<p>
	سوف نعيّن القيم في هذا الملف للقيام بما يلي:
</p>

<ul>
<li>
		تفعيل التفويض.
	</li>
	<li>
		تحديد مراجع لملفك keyfilesecret وكائناتك mongo-secret.
	</li>
	<li>
		تحديد القيمة Gi1 لـ PersistentVolumes.
	</li>
	<li>
		تعيين اسم مجموعة النسخ المتماثلة على db.
	</li>
	<li>
		تحديد 3 نسخ متماثلة للمجموعة.
	</li>
	<li>
		تثبيت صورة mongo بأحدث إصدار.
	</li>
</ul>
<p>
	انسخ الشيفرة التالية إلى الملف:
</p>

<pre class="ipsCode">
replicas: 3
port: 27017
replicaSetName: db
podDisruptionBudget: {}
auth:
  enabled: true
  existingKeySecret: keyfilesecret
  existingAdminSecret: mongo-secret
imagePullSecrets: []
installImage:
  repository: unguiculus/mongodb-install
  tag: 0.7
  pullPolicy: Always
copyConfigImage:
  repository: busybox
  tag: 1.29.3
  pullPolicy: Always
image:
  repository: mongo
  tag: 4.1.9
  pullPolicy: Always
extraVars: {}
metrics:
  enabled: false
  image:
    repository: ssalaues/mongodb-exporter
    tag: 0.6.1
    pullPolicy: IfNotPresent
  port: 9216
  path: /metrics
  socketTimeout: 3s
  syncTimeout: 1m
  prometheusServiceDiscovery: true
  resources: {}
podAnnotations: {}
securityContext:
  enabled: true
  runAsUser: 999
  fsGroup: 999
  runAsNonRoot: true
init:
  resources: {}
  timeout: 900
resources: {}
nodeSelector: {}
affinity: {}
tolerations: []
extraLabels: {}
persistentVolume:
  enabled: true
  #storageClass: "-"
  accessModes:
    - ReadWriteOnce
  size: 1Gi
  annotations: {}
serviceAnnotations: {}
terminationGracePeriodSeconds: 30
<abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">tls</abbr>:
  enabled: false
configmap: {}
readinessProbe:
  initialDelaySeconds: 5
  timeoutSeconds: 1
  failureThreshold: 3
  periodSeconds: 10
  successThreshold: 1
livenessProbe:
  initialDelaySeconds: 30
  timeoutSeconds: 5
  failureThreshold: 3
  periodSeconds: 10
  successThreshold: 1
</pre>

<p>
	يوجد هنا تعليق على المُعامل persistentVolume.storageClass: سيؤدي حذف التعليق وتعيين قيمته على "-" إلى تعطيل التزويد الديناميكي. في حالتنا هذه، نظرًا لأننا نترك هذه القيمة غير محددة، فإن المخطط سيختار المزود الافتراضي، أي dobs.csi.digitalocean.com.
</p>

<p>
	لاحظ أيضًا وضع الولوج accessMode المقترن بالمفتاح persistentVolume: يعني ReadWriteOnce أن وحدة التخزين المتوفرة ستكون للقراءة والكتابة بواسطة عقدة واحدة فقط. يرجى الاطلاع على التوثيق لمزيد من المعلومات حول أوضاع الولوج المختلفة.
</p>

<p>
	لمعرفة المزيد حول المعاملات الأخرى المضمنة في الملف، راجع جدول التكوين المضمّن في المستودع.
</p>

<p>
	احفظ الملف وأغلقه عند الانتهاء من التحرير.
</p>

<p>
	قبل نشر مخطط <code>mongodb-replicaset</code>، ستحتاج إلى تحديث المستودع الثابت stable باستخدام الأمر helm repo update:
</p>

<pre class="ipsCode">
helm repo update
</pre>

<p>
	سيستخرج هذا أحدث معلومات المخطط من المستودع الثابت.
</p>

<p>
	أخيرًا، ثبّت المخطط باستخدام الأمر التالي:
</p>

<pre class="ipsCode">
helm install --name mongo -f mongodb-values.yaml stable/mongodb-replicaset
</pre>

<p>
	ملاحظة: قبل تثبيت أحد المخططات، يمكنك تنفيذ helm install باستخدام الخيارين run dry-- و debug-- للتحقق من البيانات التي أُنشئت لإصدارك:
</p>

<pre class="ipsCode">
helm install --name your_release_name -f your_values_file.yaml --dry-run --debug your_chart
</pre>

<p>
	لاحظ أننا نسمي إصدار Helm بالاسمmongo. سوف يشير هذا الاسم إلى هذا النشر المحدد للمخطط بخيارات التكوين التي حددناها. لقد أشرنا إلى هذه الخيارات من خلال تضمين الراية <code>f-</code> وملفنا mongodb-values.yaml.
</p>

<p>
	لاحظ أيضًا أنه نظرًا لأننا لم نضمّن الراية <code>‎--namespace</code> مع <code>helm install</code>، سيكون إنشاء كائنات المخطط في المجال الاسمي الافتراضي.
</p>

<p>
	بمجرد إنشاء الإصدار، ستظهر حالته في الإخراج، إلى جانب معلومات حول الكائنات التي أُنشِئت وإرشادات للتفاعل معها:
</p>

<pre class="ipsCode">
NAME:   mongo
LAST DEPLOYED: Tue Apr 16 21:51:05 2019
NAMESPACE: default
STATUS: DEPLOYED

RESOURCES:
==&gt; v1/ConfigMap
NAME                              DATA  AGE
mongo-mongodb-replicaset-init     1     1s
mongo-mongodb-replicaset-mongodb  1     1s
mongo-mongodb-replicaset-tests    1     0s
...
</pre>

<p>
	يمكنك الآن التحقق من إنشاء علبك pods باستخدام الأمر التالي:
</p>

<pre class="ipsCode">
kubectl get pods
</pre>

<p>
	سيظهر لك في الإخراج مايلي عند إنشاء العلب:
</p>

<pre class="ipsCode">
NAME                         READY   STATUS     RESTARTS   AGE
mongo-mongodb-replicaset-0   1/1     Running    0          67s
mongo-mongodb-replicaset-1   0/1     Init:0/3   0          8s
</pre>

<p>
	تشير المخرجات READY و STATUS هنا إلى أن العلب الموجودة في StatefulSet ليست جاهزة تمامًا: لا تزال حاويات التهيئة المرتبطة بحاويات العلب قيد التشغيل. ولأن إنشاء عناصر StatefulSet يتمّ وفق ترتيب تسلسلي، يجب أن تكون كل علبة في StatefulSet قيد التشغيل وجاهزة قبل إنشاء العلبة التالية.
</p>

<p>
	بعد إنشاء العلب وتشغيل جميع الحاويات المرتبطة بها، سيظهر لك هذا الإخراج:
</p>

<pre class="ipsCode">
NAME                         READY   STATUS    RESTARTS   AGE
mongo-mongodb-replicaset-0   1/1     Running   0          2m33s
mongo-mongodb-replicaset-1   1/1     Running   0          94s
mongo-mongodb-replicaset-2   1/1     Running   0          36s
</pre>

<p>
	يشير Running STATUS إلى أن علبك مرتبطة بالعقد وأن الحاويات المرتبطة بتلك العلب تعمل. ويشير READY إلى عدد الحاويات الموجودة في العلب. لمزيد من المعلومات، يرجى الرجوع إلى وثائق دورة حياة العلبة.
</p>

<h3>
	ملحوظة
</h3>

<p>
	إذا رأيت مراحل غير متوقعة في العمود STATUS، فتذكر أنه يمكنك استكشاف الأخطاء وإصلاحها باستخدام الأوامر التالية:
</p>

<pre class="ipsCode">
kubectl describe pods your_pod
kubectl logs your_pod
</pre>

<p>
	لكل علبة موجودة في StatefulSet اسم يجمع اسم StatefulSet مع الرقم الترتيبي للعلبة. ونظرًا لأننا أنشأنا ثلاث نسخ متماثلة، ترقَّم عناصر StatefulSet من 0 إلى 2، ولكل منها إدخال DNS ثابت يتكون من العناصر التالية:
</p>

<pre class="ipsCode">
‎$(statefulset-name)-$(ordinal).$(service name).$(namespace).svc.cluster.local.
</pre>

<p>
	في حالتنا هذه، لكلّ من StatefulSet والخدمة بدون ترويسة التي أنشأها مخطط mongodb-replicaset نفس الاسم:
</p>

<pre class="ipsCode">
kubectl get statefulset
</pre>

<pre class="ipsCode">
NAME                       READY   AGE
mongo-mongodb-replicaset   3/3     4m2s
</pre>

<pre class="ipsCode">
kubectl get svc
</pre>

<pre class="ipsCode">
NAME                              TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)     AGE
kubernetes                        ClusterIP   10.245.0.1   &lt;none&gt;        443/TCP     42m
mongo-mongodb-replicaset          ClusterIP   None         &lt;none&gt;        27017/TCP   4m35s
mongo-mongodb-replicaset-client   ClusterIP   None         &lt;none&gt;        27017/TCP   4m35s
</pre>

<p>
	هذا يعني أن العنصر الأول في StatefulSet سيحصل على إدخال DNS التالي:
</p>

<pre class="ipsCode">
mongo-mongodb-replicaset-0.mongo-mongodb-replicaset.default.svc.cluster.local
</pre>

<p>
	ولأننا نحتاج إلى أن يتصل تطبيقنا بكل نسخة من إصدارات MongoDB، فمن الضروري أن تتوفر لدينا هذه المعلومات حتى نتمكن من التواصل مباشرة مع العلب، وليس مع الخدمة. عندما ننشئ مخطط Helm للتطبيق المخصص، سنمرّر إدخالات DNS لكل علبة إلى تطبيقنا باستخدام متغيرات البيئة.
</p>

<p>
	بعد تشغيل مثيلات قاعدة بياناتك ، يمكنك البدء في إنشاء المخطط لتطبيقك Node.
</p>

<h2>
	الخطوة الرابعة: إنشاء مخطط تطبيق مخصّص وتكوين المعاملات
</h2>

<p>
	سنعمل على إنشاء مخطط Helm مخصّص لتطبيق Node وتعديل الملفات الافتراضية في مجلّد المخطّط الأساسي حتى نتيح للتطبيق العمل على مجموعة النسخ المتماثلة التي أنشأناها للتو. وسننشئ كذلك ملفات لتحديد خريطة الإعداد ConfigMap وكائنات السرّ لتطبيقنا.
</p>

<p>
	ابدأ أولاً بإنشاء مجلّد مخطط جديد يسمى nodeapp باستخدام الأمر التالي:
</p>

<pre class="ipsCode">
helm create nodeapp
</pre>

<p>
	سيؤدي هذا إلى إنشاء مجلّد بالاسم nodeapp في المجلد ‎~/node_project بالموارد التالية:
</p>

<ul>
<li>
		<p>
			ملف Chart.yaml يحتوي على معلومات أساسية حول المخطط.
		</p>
	</li>
	<li>
		<p>
			ملف value.yaml يتيح لك تعيين قيم معاملات محددة، كما فعلت مع نشر MongoDB.
		</p>
	</li>
	<li>
		<p>
			ملف helmignore. مع أنماط الملفات والمجلّد التي سيتم تجاهلها عند تحزيم المخططات.
		</p>
	</li>
	<li>
		<p>
			مجلّد templates/ بملفات القوالب التي سيولّدها Kubernetes.
		</p>
	</li>
	<li>
		<p>
			مجلّد templates/tests/ لملفات الاختبار.
		</p>
	</li>
	<li>
		<p>
			مجلّد charts/ لأي مخططات يعتمد عليها هذا المخطط.
		</p>

		<p>
			value.yaml هو الملف الأول الذي سنعدّله من هذه الملفات الافتراضية. افتح هذا الملف الآن:
		</p>
	</li>
</ul>
<pre class="ipsCode">
nano nodeapp/values.yaml
</pre>

<p>
	تشمل القيم التي سنضعها هنا ما يلي:
</p>

<ul>
<li>
		عدد النسخ المتماثلة.
	</li>
	<li>
		صورة التطبيق التي نريد استخدامها. في حالتنا، ستكون هذه هي صورة النسخ المتماثلة للعقدة التي أنشأناها في الخطوة الأولى.
	</li>
	<li>
		نوع الخدمة. في هذه الحالة، سنحدد LoadBalancer لإنشاء نقطة وصول إلى تطبيقنا لأغراض الاختبار. ونظرًا لأننا نعمل على عنقود DigitalOcean Kubernetes، فسيؤدي ذلك إلى إنشاء موازن تحميل DigitalOcean عند نشر مخططنا. يمكنك في الإنتاج تكوين مخططك لاستخدام موارد Ingress و Controllers لتوجيه حركة المرور إلى خدماتك.
	</li>
	<li>
		المنفذ المستهدف targetPort لتحديد المنفذ على العلبة Pod حيث سيتم الكشف عن تطبيقنا.
	</li>
</ul>
<p>
	لن ندخل متغيرات البيئة في هذا الملف. وبدلاً من ذلك، سننشئ قوالب لـ ConfigMap وكائنات السرّ ونضيف هذه القيم إلى بيان نشر التطبيق (application Deployment manifest) ، الموجود في ‎<code>~/node_project/nodeapp/templates/deployment.yaml</code>.
</p>

<p>
	أضف تكوين القيم التالية في ملف values.yaml:
</p>

<pre class="ipsCode">
# Default values for nodeapp.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.

replicaCount: 3

image:
  repository: your_dockerhub_username/node-replicas
  tag: latest
  pullPolicy: IfNotPresent

nameOverride: ""
fullnameOverride: ""

service:
  type: LoadBalancer
  port: 80
  targetPort: 8080
...
</pre>

<p>
	احفظ الملف وأغلقه عند الانتهاء من التحرير.
</p>

<p>
	بعد ذلك، افتح ملف secret.yaml في المجلّد nodeapp/templates:
</p>

<pre class="ipsCode">
nano nodeapp/templates/secret.yaml
</pre>

<p>
	أضف في هذا الملف قيمًا لثوابت التطبيق MONGO<em>USERNAME و MONGO</em>PASSWORD. هذه هي الثوابت التي يتوقع تطبيقك الوصول إليها أثناء التشغيل، كما هو محدّد في db.js، ملف اتصال قاعدة البيانات. وأثناء إضافتك لقيم هذه الثوابت، لا تنس استخدام القيم المرمّزة base64 التي استخدمتها سابقًا في الخطوة الثانية عند إنشاء الكائن mongo-secret. إذا كنت بحاجة إلى إعادة إنشاء هذه القيم، يمكنك العودة إلى الخطوة الثانية ونفّذ الأوامر ذات الصلة مرة أخرى.
</p>

<p>
	أضف الشيفرة التالية إلى الملف:
</p>

<pre class="ipsCode">
apiVersion: v1
kind: Secret
metadata:
  name: {{ .Release.Name }}-auth
data:
  MONGO_USERNAME: your_encoded_username
  MONGO_PASSWORD: your_encoded_password
</pre>

<p>
	يعتمد اسم كائن السرّ هذا على اسم إصدار Helm، والذي ستحدّده عند نشر مخطط التطبيق.
</p>

<p>
	احفظ الملف وأغلقه عند الانتهاء. بعد ذلك، افتح ملفًا لإنشاء ConfigMap لتطبيقك:
</p>

<pre class="ipsCode">
nano nodeapp/templates/configmap.yaml
</pre>

<p>
	سوف نحدد في هذا الملف المتغيرات المتبقية التي ينتظرها تطبيقنا: <code>MONGO_HOSTNAME</code> و <code>MONGO_PORT</code> و <code>MONGO_DB</code> و <code>MONGO_REPLICASET</code>. سيتضمن متغير <code>MONGO_HOSTNAME</code> إدخال DNS لكل مثيل في مجموعة النسخ المتماثلة، إذ أن هذا هو ما يتطلبه عنوان URI لاتصال MongoDB.
</p>

<p>
	وفقًا لتوثيق Kubernetes، عندما يقوم أحد التطبيقات بإجراء اختبارات الصلاحية والاستعداد، يجب استخدام سجلات SRV عند الاتصال بـالعلب Pods. وكما تطرقنا إليه في الخطوة الثالثة، تتبع سجلات SRV للعلبة هذا النموذج: ‎$(statefulset-name)-$(ordinal).$(service name).$(namespace).svc.cluster.local. ونظرًا لأن تطبيق StatefulSet يجري عمليات فحص الثبات والاستعداد، فسيتوجب علينا استخدام هذه المعرّفات الثابتة عند تحديد قيم المتغير MONGO_HOSTNAME.
</p>

<p>
	أضف الشيفرة التالية إلى الملف لتعريف متغيرات MONGO<em>HOSTNAME و MONGO</em>PORT و MONGO<em>DB و MONGO</em>REPLICASET. وتبقى لك الحرية في استخدام اسم آخر لقاعدة بيانات MONGO<em>DB، ولكن يجب أن تكتب قيمك MONGO</em>HOSTNAME و MONGO_REPLICASET مثلما تظهر هنا:
</p>

<pre class="ipsCode">
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-config
data:
  MONGO_HOSTNAME: "mongo-mongodb-replicaset-0.mongo-mongodb-replicaset.default.svc.cluster.local,mongo-mongodb-replicaset-1.mongo-mongodb-replicaset.default.svc.cluster.local,mongo-mongodb-replicaset-2.mongo-mongodb-replicaset.default.svc.cluster.local"  
  MONGO_PORT: "27017"
  MONGO_DB: "sharkinfo"
  MONGO_REPLICASET: "db"
</pre>

<p>
	بما أننا أنشأنا بالفعل كائن <code>StatefulSet</code> ومجموعة النسخ المتماثلة، فيجب أن تظهر أسماء المضيفين الواردة هنا في ملفك تمامًا كما تظهر في هذا المثال. إذا دمّرت هذه الكائنات وأعدت تسمية إصدار Helm ل MongoDB ، فستحتاج إلى مراجعة القيم المضمنة في ConfigMap. ينطبق الشيء نفسه على <code>MONGO_REPLICASET</code>، حيث حدّدنا اسم مجموعة النسخ المتماثلة بإصدار MongoDB.
</p>

<p>
	لاحظ أيضًا أن القيم المذكورة هنا موضوعة في شكل اقتباس، وهو ما يتوقّعه Helm لمتغيرات البيئة.
</p>

<p>
	احفظ الملف وأغلقه عند الانتهاء من التحرير.
</p>

<p>
	بعد تحديد قيم معاملات مخطّطك وإنشاء قوائم Secret و ConfigMap، يمكنك المرور لتحرير قالب نشر التطبيق لاستخدام متغيرات البيئة الخاصة بك.
</p>

<h2>
	الخطوة الخامسة: دمج متغيرات البيئة في نشر Helm
</h2>

<p>
	بعد إعدادنا لملفات التطبيق Secret و ConfigMap، سنحتاج إلى التأكد من أن نشر تطبيقنا يستطيع استخدام هذه القيم. وسنعمل أيضًا على تخصيص اختبارات الثبات والاستعداد التي حُدّدت بالفعل في بيان النشر.
</p>

<p>
	افتح قالب نشر التطبيق للتحرير:
</p>

<pre class="ipsCode">
nano nodeapp/templates/deployment.yaml
</pre>

<p>
	رغم أنّ هذا ملف YAML، فإن قوالب Helm تستخدم صياغة تركيب مختلفة عن ملفات YAML المعيارية Kubernetes لإنشاء كشوف البيانات. لمزيد من المعلومات حول القوالب، راجع توثيق Helm.
</p>

<p>
	أضف في الملف أولاً مفتاح env لمواصفات حاوية التطبيق، أسفل مفتاح imagePullPolicy وأعلى ports:
</p>

<pre class="ipsCode">
apiVersion: apps/v1
kind: Deployment
metadata:
...
  spec:
    containers:
      - name: {{ .Chart.Name }}
        image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
        imagePullPolicy: {{ .Values.image.pullPolicy }}
        env:
        ports:
</pre>

<p>
	بعد ذلك، أضف المفاتيح التالية إلى قائمة متغيرات env:
</p>

<pre class="ipsCode">
apiVersion: apps/v1
kind: Deployment
metadata:
...
  spec:
    containers:
      - name: {{ .Chart.Name }}
        image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
        imagePullPolicy: {{ .Values.image.pullPolicy }}
        env:
        - name: MONGO_USERNAME
          valueFrom:
            secretKeyRef:
              key: MONGO_USERNAME
              name: {{ .Release.Name }}-auth
        - name: MONGO_PASSWORD
          valueFrom:
            secretKeyRef:
              key: MONGO_PASSWORD
              name: {{ .Release.Name }}-auth
        - name: MONGO_HOSTNAME
          valueFrom:
            configMapKeyRef:
              key: MONGO_HOSTNAME
              name: {{ .Release.Name }}-config
        - name: MONGO_PORT
          valueFrom:
            configMapKeyRef:
              key: MONGO_PORT
              name: {{ .Release.Name }}-config
        - name: MONGO_DB
          valueFrom:
            configMapKeyRef:
              key: MONGO_DB
              name: {{ .Release.Name }}-config      
        - name: MONGO_REPLICASET
          valueFrom:
            configMapKeyRef:
              key: MONGO_REPLICASET
              name: {{ .Release.Name }}-config       
</pre>

<p>
	يتضمن كل متغير مرجعًا إلى قيمته، يعرّف إما بمفتاح <code>secretKeyRef</code>، في حالة قيم السرّ، أو <code>configMapKeyRef</code> لقيم ConfigMap. تشير هذه المفاتيح إلى ملفات Secret و ConfigMap التي أنشأناها في الخطوة السابقة.
</p>

<p>
	بعد ذلك، تحت المفتاح ports، عدّل تعريف containerPort لتحديد المنفذ الموجود في الحاوية التي سيُعرَض عليها تطبيقنا:
</p>

<pre class="ipsCode">
apiVersion: apps/v1
kind: Deployment
metadata:
...
  spec:
    containers:
    ...
      env:
    ...
      ports:
        - name: http
          containerPort: 8080
          protocol: TCP
      ...
</pre>

<p>
	دعنا نعدّل بعد ذلك، اختبارات الصلاحية والاستعداد المضمنة في بيان النشر هذا افتراضيًا. تضمن هذه الفحوصات اشتغال علب التطبيق لدينا وجاهزيتها لخدمة حركة المرور:
</p>

<ul>
<li>
		تُقيِّم اختبارات الجاهزية (Readiness) ما إذا كانت العلبة جاهزًا لخدمة حركة المرور أم لا، مع إيقاف جميع الطلبات إلى العلبة حتى تنجح عمليات الفحص.
	</li>
	<li>
		تفحص اختبارات الثبات (Liveness) سلوك التطبيق الأساسي لتحديد ما إذا كان التطبيق في الحاوية قيد التشغيل أو يتصرف كما هو متوقع. في حالة فشل اختبار الثبات، سيعيد Kubernetes تشغيل الحاوية.
	</li>
</ul>
<p>
	لمزيد من المعلومات حول كليهما، راجع المناقشة ذات الصلة في هيكلة تطبيقات لـ Kubernetes.
</p>

<p>
	في حالتنا هذه، سنبني على طلب httpGet الذي قدمه Helm افتراضيًا ونختبر ما إذا كان تطبيقنا يقبل الطلبات في نقطة نهاية sharks/ أم لا. ستجري خدمة kubelet اختبارًا بإرسال طلب GET إلى خادم Node الذي يشتغل في حاوية علب التطبيق ويستمع على المنفذ 8080. إذا كان رمز الجواب يتراوح بين 200 و 400، فسوف تستنتج kubelet أن الحاوية في وضعٍ صحي. خلاف ذلك، إذا كان الرمز 400 أو 500، فإن kubelet تعمد إمّا إلى إيقاف حركة المرور إلى الحاوية، في حالة اختبار الجاهزية، أو إعادة تشغيل الحاوية، في حالة اختبار الثبات.
</p>

<p>
	أضف التعديل التالي إلى المسار path المذكور لاختبارات الثبات والجاهزية:
</p>

<pre class="ipsCode">
apiVersion: apps/v1
kind: Deployment
metadata:
...
  spec:
    containers:
    ...
      env:
    ...
      ports:
        - name: http
          containerPort: 8080
          protocol: TCP
      livenessProbe:
        httpGet:
          path: /sharks
          port: http
      readinessProbe:
        httpGet:
          path: /sharks
          port: http
</pre>

<p>
	احفظ الملف وأغلقه عند الانتهاء من التحرير.
</p>

<p>
	أنت الآن جاهز لإنشاء إصدار التطبيق الخاص بك باستخدام Helm. نفذ الأمر التالي لتثبيت helm، والذي يتضمن اسم الإصدار وموقع مجلّد المخطط:
</p>

<pre class="ipsCode">
helm install --name nodejs ./nodeapp
</pre>

<p>
	لا تنس أنه يمكنك تنفيذ تثبيت helm باستخدام الخيارين <code>dry-run--</code> و <code>debug--</code> أولاً، كما ذكرناه في الخطوة الثالثة، من أجل التحقق من البيانات (manifests) التي أُنشئت لإصدارك.
</p>

<p>
	مرة أخرى، نظرًا لأننا لا نقوم بتضمين الراية --namespace مع helm install، سيكون إنشاء كائنات المخطط في المجال الاسمي الافتراضي.
</p>

<p>
	سيظهر لك الإخراج التالي مشيرًا إلى إنشاء إصدارك:
</p>

<pre class="ipsCode">
NAME:   nodejs
LAST DEPLOYED: Wed Apr 17 18:10:29 2019
NAMESPACE: default
STATUS: DEPLOYED

RESOURCES:
==&gt; v1/ConfigMap
NAME           DATA  AGE
nodejs-config  4     1s

==&gt; v1/Deployment
NAME            READY  UP-TO-DATE  AVAILABLE  AGE
nodejs-nodeapp  0/3    3           0          1s

...
</pre>

<p>
	مرة أخرى، سيشير الإخراج إلى حالة الإصدار، بالإضافة إلى معلومات حول الكائنات التي أُنشئت وكيفية تفاعلك معها.
</p>

<p>
	تحقق من حالة علبك:
</p>

<pre class="ipsCode">
kubectl get pods
</pre>

<pre class="ipsCode">
NAME                              READY   STATUS    RESTARTS   AGE
mongo-mongodb-replicaset-0        1/1     Running   0          57m
mongo-mongodb-replicaset-1        1/1     Running   0          56m
mongo-mongodb-replicaset-2        1/1     Running   0          55m
nodejs-nodeapp-577df49dcc-b5fq5   1/1     Running   0          117s
nodejs-nodeapp-577df49dcc-bkk66   1/1     Running   0          117s
nodejs-nodeapp-577df49dcc-lpmt2   1/1     Running   0          117s
</pre>

<p>
	تحقق من خدماتك بمجرد تشغيل العلب:
</p>

<pre class="ipsCode">
kubectl get svc
</pre>

<pre class="ipsCode">
NAME                              TYPE           CLUSTER-IP     EXTERNAL-IP       PORT(S)        AGE
kubernetes                        ClusterIP      10.245.0.1     &lt;none&gt;            443/TCP        96m
mongo-mongodb-replicaset          ClusterIP      None           &lt;none&gt;            27017/TCP      58m
mongo-mongodb-replicaset-client   ClusterIP      None           &lt;none&gt;            27017/TCP      58m
nodejs-nodeapp                    LoadBalancer   10.245.33.46   your_lb_ip        80:31518/TCP 
</pre>

<p>
	العنوان <code>EXTERNAL_IP</code> المرتبط بخدمة <code>nodejs</code> هو عنوان IP يمكّنك من الوصول إلى التطبيق. إذا رأيت حالة <pending> في عمود <code>EXTERNAL_IP</code>، فهذا يعني أن مُوازِن التحميل لا يزال قيد الإنشاء.</pending></p>

<p>
	بمجرد رؤية عنوان IP في هذا العمود، انتقل إليه في متصفحك: http://your<em>lb</em>ip.
</p>

<p>
	ينبغي أن تظهر لك صفحة الهبوط التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="33643" href="https://academy.hsoub.com/uploads/monthly_2020_02/landing_page.png.e05c19898b51d170fa58233149e0965b.png" rel=""><img alt="landing_page.png" class="ipsImage ipsImage_thumbnailed" data-fileid="33643" data-unique="data-unique" src="https://academy.hsoub.com/uploads/monthly_2020_02/landing_page.thumb.png.0453a2d8789e22f2ced631e6ef4cc08b.png"></a>
</p>

<p>
	الآن بعد أن تم تشغيل التطبيق المنسوخ، دعنا نضيف بعض بيانات الاختبار لضمان عمل التماثل بين عناصر مجموعة النسخ المتماثلة.
</p>

<h2>
	الخطوة السادسة: اختبار المتماثل في MongoDB
</h2>

<p>
	بعد تشغيل التطبيق وإتاحة الوصول إليه من خلال عنوان IP خارجي، يمكننا الآن إضافة بعض بيانات الاختبار والتأكد من أنها تُنسَخ بشكل متماثل بين عناصر مجموعة النسخ المتماثلة MongoDB.
</p>

<p>
	تأكّد أولًا من فتح صفحة الهبوط على متصفحك:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="33643" href="https://academy.hsoub.com/uploads/monthly_2020_02/landing_page.png.e05c19898b51d170fa58233149e0965b.png" rel=""><img alt="landing_page.png" class="ipsImage ipsImage_thumbnailed" data-fileid="33643" data-unique="7bs8n5mpx" src="https://academy.hsoub.com/uploads/monthly_2020_02/landing_page.thumb.png.0453a2d8789e22f2ced631e6ef4cc08b.png"></a>
</p>

<p>
	انقر على زر الحصول على معلومات القرش. ستظهر لك صفحة ذات نموذج يمكنك فيه إدخال اسم سمك القرش ووصف لسلوكه العام:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="33647" href="https://academy.hsoub.com/uploads/monthly_2020_02/shark_form.png.b40ec845129068133b5e732e342bfd66.png" rel=""><img alt="shark_form.png" class="ipsImage ipsImage_thumbnailed" data-fileid="33647" data-unique="data-unique" src="https://academy.hsoub.com/uploads/monthly_2020_02/shark_form.thumb.png.6971fc33f6aaacc36ca712524a00fe56.png"></a>
</p>

<p>
	أضف في النموذج سمكة قرش أولية من اختيارك. سنضيف لغرض التوضيح Megalodon Shark إلى حقل Shark Name، وAncient لحقل Shark Character:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="33646" href="https://academy.hsoub.com/uploads/monthly_2020_02/shark_filled.png.1d806847775f94df559bbe1fdb384b60.png" rel=""><img alt="shark_filled.png" class="ipsImage ipsImage_thumbnailed" data-fileid="33646" data-unique="data-unique" src="https://academy.hsoub.com/uploads/monthly_2020_02/shark_filled.thumb.png.e98e0d5fcef10e800b419bc3d462446d.png"></a>
</p>

<p>
	انقر على زر الإرسال. سترى صفحة بها معلومات القرش معروضة لك:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="33645" href="https://academy.hsoub.com/uploads/monthly_2020_02/shark_added.png.590ffc53c785d0534e1656c34439414f.png" rel=""><img alt="shark_added.png" class="ipsImage ipsImage_thumbnailed" data-fileid="33645" data-unique="data-unique" src="https://academy.hsoub.com/uploads/monthly_2020_02/shark_added.thumb.png.fb29efa1360d2db37a20469f551101ba.png"></a>
</p>

<p>
	انتقل الآن مرة أخرى إلى نموذج معلومات سمك القرش من خلال النقر على Sharks في شريط التنقل العلوي:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="33647" href="https://academy.hsoub.com/uploads/monthly_2020_02/shark_form.png.b40ec845129068133b5e732e342bfd66.png" rel=""><img alt="shark_form.png" class="ipsImage ipsImage_thumbnailed" data-fileid="33647" data-unique="hbkenyqbp" src="https://academy.hsoub.com/uploads/monthly_2020_02/shark_form.thumb.png.6971fc33f6aaacc36ca712524a00fe56.png"></a>
</p>

<p>
	أدخل سمكة قرش جديدة من اختيارك. سنستخدم Whale Shark مع Large:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="33648" href="https://academy.hsoub.com/uploads/monthly_2020_02/whale_shark.png.45d7ae66c3285f89a17d36df0437df33.png" rel=""><img alt="whale_shark.png" class="ipsImage ipsImage_thumbnailed" data-fileid="33648" data-unique="data-unique" src="https://academy.hsoub.com/uploads/monthly_2020_02/whale_shark.thumb.png.da7e8e839bae7f4269b385e7748fed1a.png"></a>
</p>

<p>
	بمجرد نقرك على زر الإرسال، سترى أنه القرش الجديد أضيف إلى المجموعة shark في قاعدة البيانات الخاصة بك:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="33644" href="https://academy.hsoub.com/uploads/monthly_2020_02/persisted_data.png.2b1ac9d9f8f0fa100e041cfe1fed5ee0.png" rel=""><img alt="persisted_data.png" class="ipsImage ipsImage_thumbnailed" data-fileid="33644" data-unique="data-unique" src="https://academy.hsoub.com/uploads/monthly_2020_02/persisted_data.thumb.png.151d4ac468451d8f5315159b15b6ecb8.png"></a>
</p>

<p>
	دعنا نتحقق من أن البيانات التي أدخلناها نُسخت نسخًا متماثلاً بين العناصر الأساسية والثانوية في مجموعة النسخ المتماثلة.
</p>

<p>
	استعرض قائمة علبك:
</p>

<pre class="ipsCode">
kubectl get pods
</pre>

<pre class="ipsCode">
NAME                              READY   STATUS    RESTARTS   AGE
mongo-mongodb-replicaset-0        1/1     Running   0          74m
mongo-mongodb-replicaset-1        1/1     Running   0          73m
mongo-mongodb-replicaset-2        1/1     Running   0          72m
nodejs-nodeapp-577df49dcc-b5fq5   1/1     Running   0          5m4s
nodejs-nodeapp-577df49dcc-bkk66   1/1     Running   0          5m4s
nodejs-nodeapp-577df49dcc-lpmt2   1/1     Running   0          5m4s
</pre>

<p>
	للوصول إلى صدفة mongo على علبك، يمكنك استخدام الأمر kubectl exec واسم المستخدم الذي استعملته لإنشاء mongo secret في الخطوة الثانية.
</p>

<p>
	ادخل إلى صدفة mongo على العلبة الأولى في StatefulSet باستخدام الأمر التالي:
</p>

<pre class="ipsCode">
kubectl exec -it mongo-mongodb-replicaset-0 -- mongo -u your_database_username -p --authenticationDatabase admin
</pre>

<p>
	عند يطلب منك ذلك، أدخل كلمة المرور المرتبطة باسم المستخدم هذا:
</p>

<pre class="ipsCode">
MongoDB shell version v4.1.9
Enter password:
</pre>

<p>
	سيتم تحويلك إلى صدفة إدارية:
</p>

<pre class="ipsCode">
MongoDB server version: 4.1.9
Welcome to the MongoDB shell.
...

db:PRIMARY&gt;
</pre>

<p>
	رغم أن شاشة الإدخال نفسها تتضمن هذه المعلومات، فيمكنك التحقق يدويًا لمعرفة أي عناصر مجموعة النسخ المتماثلة هو الأساسي باستخدام التابع <code>()rs.isMaster</code>:
</p>

<pre class="ipsCode">
rs.isMaster()
</pre>

<p>
	سيظهر لك الإخراج التالي، مع الإشارة إلى اسم المضيف الأساسي:
</p>

<pre class="ipsCode">
db:PRIMARY&gt; rs.isMaster()
{
        "hosts" : [
                "mongo-mongodb-replicaset-0.mongo-mongodb-replicaset.default.svc.cluster.local:27017",
                "mongo-mongodb-replicaset-1.mongo-mongodb-replicaset.default.svc.cluster.local:27017",
                "mongo-mongodb-replicaset-2.mongo-mongodb-replicaset.default.svc.cluster.local:27017"
        ],
        ...
        "primary" : "mongo-mongodb-replicaset-0.mongo-mongodb-replicaset.default.svc.cluster.local:27017",
        ...
</pre>

<p>
	انتقل بعد ذلك إلى قاعدة بياناتك sharkinfo:
</p>

<pre class="ipsCode">
use sharkinfo
</pre>

<pre class="ipsCode">
switched to db sharkinfo
</pre>

<p>
	اعرض قائمة المجموعات في قاعدة البيانات:
</p>

<pre class="ipsCode">
show collections
</pre>

<pre class="ipsCode">
sharks
</pre>

<p>
	استخرج الملفات في المجموعة:
</p>

<pre class="ipsCode">
db.sharks.find()
</pre>

<p>
	سيظهر لك في الإخراج ما يلي:
</p>

<pre class="ipsCode">
{ "_id" : ObjectId("5cb7702c9111a5451c6dc8bb"), "name" : "Megalodon Shark", "character" : "Ancient", "__v" : 0 }
{ "_id" : ObjectId("5cb77054fcdbf563f3b47365"), "name" : "Whale Shark", "character" : "Large", "__v" : 0 }
</pre>

<p>
	اخرج الآن من صدفة <code>MongoDB</code>:
</p>

<pre class="ipsCode">
Exit
</pre>

<p>
	الآن بعد أن تفحصنا البيانات الموجودة على العنصر الأساسي، دعنا نتحقق من نسخها في الثانوي. نفّذ الأمر <code>exec kubectl</code> في mongo-mongodb-replicaset-1:
</p>

<pre class="ipsCode">
kubectl exec -it mongo-mongodb-replicaset-1 -- mongo -u your_database_username -p --authenticationDatabase admin
</pre>

<p>
	بمجرد الدخول إلى الصدفة الإدارية، سنحتاج إلى استخدام التابع <code>()db.setSlaveOk</code> للسماح بعمليات القراءة من المثيل الثانوي:
</p>

<pre class="ipsCode">
db.setSlaveOk(1)
</pre>

<p>
	انتقل إلى قاعدة بيانات sharkinfo:
</p>

<pre class="ipsCode">
use sharkinfo
</pre>

<pre class="ipsCode">
switched to db sharkinfo
</pre>

<p>
	اسمح بعمليات قراءة الملفات في المجموعة <code>sharks</code>:
</p>

<pre class="ipsCode">
db.setSlaveOk(1)
</pre>

<p>
	استخرج الملفات في المجموعة:
</p>

<pre class="ipsCode">
db.sharks.find()
</pre>

<p>
	ينبغي أن تشاهد الآن نفس المعلومات التي شاهدتها عند تنفيذ هذا التابع على العنصر الأساسي:
</p>

<pre class="ipsCode">
db:SECONDARY&gt; db.sharks.find()
{ "_id" : ObjectId("5cb7702c9111a5451c6dc8bb"), "name" : "Megalodon Shark", "character" : "Ancient", "__v" : 0 }
{ "_id" : ObjectId("5cb77054fcdbf563f3b47365"), "name" : "Whale Shark", "character" : "Large", "__v" : 0 }
</pre>

<p>
	يؤكد هذا الإخراج نسخ بيانات تطبيقك بين عناصر مجموعة النسخ المتماثلة.
</p>

<h2>
	خاتمة
</h2>

<p>
	لقد تمكّنت الآن من نشر تطبيق متكرّر (replicated) ومتاح على أعلى مستوى لمعلومات سمك القرش على عنقود Kubernetes باستخدام مخططات Helm. يمكن أن يعمل هذا التطبيق التجريبي وسير العمل الموضح في هذا البرنامج التعليمي كنقطة انطلاق أثناء إنشاء مخططات مخصصة لتطبيقك والاستفادة من مستودع Helm الثابت ومستودعات التخطيط الأخرى.
</p>

<p>
	أثناء سعيك نحو الإنتاج، فكر في تنفيذ ما يلي:
</p>

<ul>
<li>
		<p>
			تسجيل الدّخول والمراقبة بشكل مركزي. يرجى الاطلاع على المناقشة ذات الصلة حول <a href="https://www.digitalocean.com/community/tutorials/modernizing-applications-for-kubernetes" rel="external nofollow">تحديث تطبيقات Kubernetes</a> للحصول على نظرة أشمل. يمكنك أيضًا الاطلاع على <a href="https://www.digitalocean.com/community/tutorials/how-to-set-up-an-elasticsearch-fluentd-and-kibana-efk-logging-stack-on-kubernetes" rel="external nofollow">كيفية إعداد حزمة</a> تسجيل دخول Elasticsearch, Fluentd and Kibana (EFK) على Kubernetes. راجع أيضًا <a href="https://www.digitalocean.com/community/tutorials/an-introduction-to-service-meshes" rel="external nofollow">مقدمة لشبكات الخدمة</a> للحصول على معلومات حول كيفية تنفيذ شبكات الخدمات مثل Istio لهذه الوظيفة.
		</p>
	</li>
	<li>
		<p>
			موارد الولوج لتوجيه حركة المرور إلى عنقودك. يعد هذا بديلاً جيدًا لـ LoadBalancer في الحالات التي تشغّل فيها خدمات متعددة، والتي تتطلب كل منها موازِنًا LoadBalancer خاصًّا بها، أو عندما ترغب في تنفيذ استراتيجيات توجيه على مستوى التطبيق (اختبارات A/B &amp; canary، على سبيل المثال). لمزيد من المعلومات، تحقّق من كيفية إعداد Nginx Ingress مع Cert-Manager على DigitalOcean Kubernetes والمناقشة ذات الصلة بالتوجيه في سياق شبكة الخدمة في <a href="https://www.digitalocean.com/community/tutorials/an-introduction-to-service-meshes" rel="external nofollow">مقدمة لشبكات الخدمة</a>.
		</p>
	</li>
	<li>
		<p>
			استراتيجيات النسخ الاحتياطي لكائناتك Kubernetes. للحصول على إرشادات حول تنفيذ النسخ الاحتياطية باستخدام Velero (سابقًا Heptio Ark) مع منتج Kubernetes الخاص بـ DigitalOcean، يرجى الاطلاع على <a href="https://www.digitalocean.com/community/tutorials/how-to-back-up-and-restore-a-kubernetes-cluster-on-digitalocean-using-heptio-ark" rel="external nofollow">كيفية عمل نسخة احتياطية</a> واستعادة عنقود Kubernetesعلى DigitalOcean باستخدام Heptio Ark.
		</p>
	</li>
</ul>
<p>
	لمعرفة المزيد حول Helm، راجع <a href="https://www.digitalocean.com/community/tutorials/an-introduction-to-helm-the-package-manager-for-kubernetes" rel="external nofollow">مقدمة إلى Helm</a>، ومدير الحزم لـ Kubernetes، وكيفية تثبيت البرامج على عناقيد Kubernetes باستخدام Helm Package Manager، و<a href="https://helm.sh/docs/" rel="external nofollow">توثيق Helm</a>.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-scale-a-node-js-application-with-mongodb-on-kubernetes-using-helm" rel="external nofollow">How To Scale a Node.js Application with MongoDB on Kubernetes Using Helm</a> لصاحبته Kathleen Juell
</p>
]]></description><guid isPermaLink="false">813</guid><pubDate>Wed, 05 Feb 2020 06:29:19 +0000</pubDate></item><item><title>&#x646;&#x642;&#x644; &#x633;&#x64A;&#x631; &#x639;&#x645;&#x644; Docker Compose &#x625;&#x644;&#x649; Kubernetes</title><link>https://academy.hsoub.com/programming/javascript/nodejs/%D9%86%D9%82%D9%84-%D8%B3%D9%8A%D8%B1-%D8%B9%D9%85%D9%84-docker-compose-%D8%A5%D9%84%D9%89-kubernetes-r812/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2020_02/4.jpg.c5afc59e543ad61f1d0b24000d28caf8.jpg" /></p>

<p>
	عند إنشائك لتطبيقات حديثة عديمة الحالة (stateless)، فإن عملية إعداد الحاويات لمكونات التطبيق ستكون هي الخطوة الأولى في النشر والتوسيع على الأنظمة الأساسية الموزعة. إذا كنت قد استخدمت Docker Compose في التطوير، فسوف تعمل على تحديث التطبيق وحاوياته بما يلي:
</p>

<ul>
<li>
		استخراج معلومات التكوين اللازمة من شيفرتك.
	</li>
	<li>
		إلغاء تحميل حالة تطبيقك.
	</li>
	<li>
		تحزيم تطبيقك من أجل الاستخدام المتكرر.
	</li>
</ul>
<p>
	سيكون لديك أيضًا تعريفات خدمة مكتوبة تحدّد كيف ستشتغل صور حاوياتك.
</p>

<p>
	لتشغيل خدماتك على نظام أساسي موزّع مثل Kubernetes، ستحتاج إلى ترجمة تعريفات خدمة Compose إلى كائنات Kubernetes. سيسمح لك ذلك بتوسيع نطاق تطبيقك بنوع من المرونة. وتعدّ kompose إحدى الأدوات التي يمكن أن تسرع عملية الترجمة إلى Kubernetes، وهي أداة تحويل تساعد المطورين على نقل سير عمل Compose إلى منسّق للحاويات مثل Kubernetes أو OpenShift.
</p>

<p>
	سوف تعمل في هذا البرنامج التعليمي على ترجمة خدمات Compose إلى كائنات Kubernetes باستخدام kompose. ستستخدم تعريفات الكائنات التي توفرها kompose كنقطة بداية، كما ستجري تعديلات للتأكد من أن إعداداتك ستستخدم الأسرار (secrets) والخدمات (services) وطلبات وحدة التخزين الثابتة (PersistentVolumeClaims) على النحو المتوقّع في Kubernetes. عند نهاية الدرس، سيكون لديك تطبيق Node.js بنسخة واحدة لقاعدة بيانات MongoDB تعمل على نظام Kubernetes. سيعكس هذا الإعداد وظائف الشيفرة الموضحة في كيفية إعداد تطبيق node.js لسير عملٍ يعتمد على الحاويات باستخدام Docker Compose وسيكون نقطة انطلاق جيدة لإنشاء حلّ جاهز للإنتاج يتناسب مع احتياجاتك.
</p>

<h2>
	المتطلبات الأساسية
</h2>

<ul>
<li>
		عنقود Kubernetes 1.10+‎ مع تفعيل التحكم في الوصول المستند إلى الدور role-based access control (RBAC)‎. سيستخدم هذا الإعداد عنقود <a href="https://www.digitalocean.com/products/kubernetes/" rel="external nofollow">DigitalOcean Kubernetes</a>، لكن تبقى لك حرية الاختيار في <a href="https://www.digitalocean.com/community/tutorials/how-to-create-a-kubernetes-cluster-using-kubeadm-on-ubuntu-18-04" rel="external nofollow">إنشاء عنقود باستخدام طريقة أخرى</a>.
	</li>
	<li>
		أداة سطر الأوامر kubectl المثبتة على جهازك المحلي أو خادم التطوير وإعدادها للاتصال بعنقودك. يمكنك قراءة المزيد حول تثبيت kubectl في التوثيق الرسمي.
	</li>
	<li>
		Docker مثبت على جهازك المحلي أو خادم التطوير. إذا كنت تعمل على نظام أوبونتو 18.04، اتبع الخطوتين 1 و 2 لكيفية تثبيت واستخدام Docker على أوبونتو 18.04؛ خلاف ذلك، اتبع التوثيق الرسمي للحصول على معلومات حول التثبيت على أنظمة التشغيل الأخرى. تأكد من إضافة مستخدمك غير الجذري إلى مجموعة Docker، كما هو موضح في الخطوة 2 من البرنامج التعليمي المرتبط.
	</li>
	<li>
		حساب Docker Hub. للحصول على نظرة عامة حول كيفية إعداده، راجع هذه المقدمة إلى Docker Hub.
	</li>
</ul>
<h2>
	الخطوة الأولى: تثبيت kompose
</h2>

<p>
	للبدء في استخدام kompose، انتقل إلى صفحة إصدارات GitHub للمشروع، وانسخ الرابط إلى الإصدار الحالي. الصق هذا الرابط في الأمر curl التالي لتنزيل أحدث إصدار من kompose:
</p>

<pre class="ipsCode">
curl -L https://github.com/kubernetes/kompose/releases/download/v1.18.0/kompose-linux-amd64 -o kompose
</pre>

<p>
	للحصول على تفاصيل حول التثبيت على أنظمة غير تابعة لنظام لينكس، يرجى الرجوع إلى إرشادات التثبيت.
</p>

<p>
	أنشئ الملف التنفيذي (binary):
</p>

<pre class="ipsCode">
chmod +x kompose
</pre>

<p>
	انقله إلى مسارك <code>PATH</code>:
</p>

<pre class="ipsCode">
sudo mv ./kompose /usr/local/bin/kompose
</pre>

<p>
	ثم تحقق من تثبيته بشكل صحيح. يمكنك إجراء فحص للإصدار:
</p>

<pre class="ipsCode">
kompose version
</pre>

<p>
	إذا كان التثبيت ناجحًا، فسيظهر لك الإخراج التالي:
</p>

<pre class="ipsCode">
Output
1.18.0 (06a2e56)
</pre>

<p>
	بعد تثبيت kompose وجاهزيته للاستخدام، يمكنك الآن استنساخ شيفرة المشروع <code>Node.js</code> الذي سيُترجَم إلى <code>Kubernetes</code>.
</p>

<h2>
	الخطوة الثانية: استنساخ وتحزيم التطبيق
</h2>

<p>
	لاستخدام تطبيقنا على Kubernetes، سنحتاج إلى استنساخ شيفرة المشروع وتحزيم التطبيق حتى تتمكن خدمة kubelet من سحب الصورة.
</p>

<p>
	ستكون خطوتنا الأولى هي استنساخ مستودع node-mongo-docker-dev من حسابDigitalOcean Community GitHub. يتضمن هذا المستودع شيفرة الإعداد الموضح في كيفية إعداد تطبيق node.js لسير عملٍ يعتمد على الحاويات باستخدام Docker Compose، والذي يستخدم تطبيق Node.js لشرح كيفية إعداد بيئة تطوير باستخدام Docker Compose. يمكنك العثور على مزيد من المعلومات حول التطبيق نفسه في سلسلة من الحاويات إلى Kubernetes باستخدام Node.js.
</p>

<p>
	استنسخ المستودع في مجلّد يسمى node_project:
</p>

<pre class="ipsCode">
git clone https://github.com/do-community/node-mongo-docker-dev.git node_project
</pre>

<p>
	انتقل إلى المجلّد node_project:
</p>

<pre class="ipsCode">
cd node_project
</pre>

<p>
	يحتوي مجلّد node_project على ملفات ومجلدات لتطبيق معلومات سمك القرش الذي يعمل على مدخلات المستخدم. لقد حُدِّث للعمل على الحاويات وأزيلت منه معلومات التكوين الحساسة والمحددة في شيفرة التطبيق وأُعيد تشكيلها من أجل حقنها عند تنفيذ التطبيق، كما أُلغيَ تحميل حالة التطبيق إلى قاعدة بيانات MongoDB.
</p>

<p>
	لمزيد من المعلومات حول تصميم التطبيقات الحديثة عديمة الحالة، يرجى الاطلاع على هيكلة التطبيقات ل Kubernetes وتحديث التطبيقات لKubernetes.
</p>

<p>
	يتضمن مجلّد المشروع ملفّا Dockerfile بتعليمات لبناء صورة التطبيق. دعنا نبني الصورة الآن على النحو الذي يتيح رفعها إلى حسابك Docker Hub واستخدامها في إعداداتك Kubernetes.
</p>

<p>
	استخدم الأمر <code>docker build</code> لبناء الصورة باستخدام الراية t-، والتي تتيح لك تعليمها باسم لا يُنسى. في هذه الحالة، علّم الصورة باسم مستخدمك Docker Hub وسمّها <code>node kubernetes</code> أو أيّ اسم من اختيارك:
</p>

<pre class="ipsCode">
docker build -t your_dockerhub_username/node-kubernetes .
</pre>

<p>
	تحدد النقطة <code>.</code> في الأمر أن سياق البناء هو المجلّد الحالي.
</p>

<p>
	سوف يستغرق الأمر دقيقة أو دقيقتين لبناء الصورة. بمجرد اكتماله، تحقق من صورك:
</p>

<pre class="ipsCode">
docker images
</pre>

<p>
	سيظهر لك الإخراج التالي:
</p>

<pre class="ipsCode">
Output
REPOSITORY                                TAG                 IMAGE ID            CREATED             SIZE
your_dockerhub_username/node-kubernetes   latest              9c6f897e1fbc        3 seconds ago       90MB
node                                      10-alpine           94f3c8956482        12 days ago         71MB
</pre>

<p>
	بعد ذلك، سجّل الدخول إلى حساب Docker Hub الذي أنشأته في المتطلبات الأساسية:
</p>

<pre class="ipsCode">
docker login -u your_dockerhub_username 
</pre>

<p>
	أدخل كلمة مرور حساب Docker Hub عندما يُطلب منك ذلك. سيؤدي التسجيل بهذه الطريقة إلى إنشاء ملف ‎~/.docker/config.json في المجلّد الرئيسي للمستخدم الخاص بك باستخدام بيانات اعتماد. Docker Hub
</p>

<p>
	ارفع صورة التطبيق إلى Docker Hub باستخدام الأمر docker push. ولا تنس أن تعوّض your<em>dockerhub</em>username باسم مستخدمك Docker Hub:
</p>

<pre class="ipsCode">
docker push your_dockerhub_username/node-kubernetes
</pre>

<p>
	لديك الآن صورة للتطبيق يمكنك سحبها لتشغيل تطبيقك على <code>Kubernetes</code>. ستكون الخطوة التالية هي ترجمة تعريفات خدمة التطبيق إلى كائنات .Kubernetes
</p>

<h2>
	الخطوة الثالثة: ترجمة الخدمات إلى كائنات Kubernetes باستخدام kompose
</h2>

<p>
	يحدّد ملف Docker Compose، المسمى هنا docker-compose.yaml، التعريفات التي ستعمل على تشغيل خدماتنا على Compose. الخدمة في "Compose" هي عبارة عن حاوية قيد التشغيل، وتحتوي تعريفات الخدمة على معلومات حول كيفية تشغيل صورة كل حاوية. في هذه الخطوة، سوف نترجم هذه التعريفات إلى كائنات Kubernetes باستخدام kompose لإنشاء ملفات .yaml سوف تحتوي هذه الملفات على خصائص كائنات Kubernetes التي تصف الحالة التي نريدها عليها.
</p>

<p>
	سوف نستخدم هذه الملفات لإنشاء أنواع مختلفة من الكائنات: أوّلها الخدمات، والتي ستضمن بقاء العُلَب (pods) التي تشغّل حاوياتنا متاحةً. ثانيها عمليّات النشر، والتي سوف تحتوي على معلومات حول الحالة التي نريد عليها العلب. ثالثها PersistentVolumeClaim لتوفير تخزين لقاعدة البيانات. رابعها خريطة الإعداد ConfigMap لمتغيرات البيئة التي تُحقَن في وقت التشغيل. وآخرها سرٌّ (secret) لمستخدم قاعدة بيانات التطبيق وكلمة المرور. ستكون بعض هذه التعريفات في الملفات التي سينشئها لناkompose ، والبعض الآخر سوف نحتاج إلى إنشائه بأنفسنا.
</p>

<p>
	سنحتاج بدايةّ إلى تعديل بعض التعاريف في ملف <code>docker-compose.yaml</code> للعمل على Kubernetes. سنُضمّن إشارة إلى صورة التطبيق التي بُنيت حديثًا في تعريف خدمتنا <code>nodejs</code>، كما سنحذف الروابط <code>bind mounts</code> والحجوم والأوامر الإضافية التي استخدمناها لتشغيل حاوية التطبيق قيد التطوير باستخدام Compose. بالإضافة إلى ذلك، سنعيد تحديد سياسات إعادة تشغيل كلتا الحاويتين بحيث تتوافق مع السلوك الذي المتوقع في Kubernetes.
</p>

<p>
	افتح الملف باستخدام nano أو المحرر المفضل لديك:
</p>

<pre class="ipsCode">
nano docker-compose.yaml
</pre>

<p>
	يبدو التعريف الحالي لخدمة تطبيق nodejs كما يلي:
</p>

<pre class="ipsCode">
...
services:
  nodejs:
    build:
      context: .
      dockerfile: Dockerfile
    image: nodejs
    container_name: nodejs
    restart: unless-stopped
    env_file: .env
    environment:
      - MONGO_USERNAME=$MONGO_USERNAME
      - MONGO_PASSWORD=$MONGO_PASSWORD
      - MONGO_HOSTNAME=db
      - MONGO_PORT=$MONGO_PORT
      - MONGO_DB=$MONGO_DB 
    ports:
      - "80:8080"
    volumes:
      - .:/home/node/app
      - node_modules:/home/node/app/node_modules
    networks:
      - app-network
    command: ./wait-for.sh db:27017 -- /home/node/app/node_modules/.bin/nodemon app.js
...
</pre>

<p>
	بعدها أَجرِ التعديلات التالية على تعريف الخدمة:
</p>

<ul>
<li>
		استخدم صورة node-kubernetes بدلاً من ملف Dockerfile المحلي.
	</li>
	<li>
		عدّل سياسة إعادة تشغيل الحاوية restart من unless-stopped إلى always.
	</li>
	<li>
		احذف قائمة الحجوم volumes وتعليمات الأوامر command.
	</li>
</ul>
<p>
	سيبدو تعريف الخدمة النهائية الآن كما يلي:
</p>

<pre class="ipsCode">
...
services:
  nodejs:
    image: your_dockerhub_username/node-kubernetes
    container_name: nodejs
    restart: always
    env_file: .env
    environment:
      - MONGO_USERNAME=$MONGO_USERNAME
      - MONGO_PASSWORD=$MONGO_PASSWORD
      - MONGO_HOSTNAME=db
      - MONGO_PORT=$MONGO_PORT
      - MONGO_DB=$MONGO_DB 
    ports:
      - "80:8080"
    networks:
      - app-network
…
</pre>

<p>
	بعد ذلك، انزل إلى تعريف الخدمة <code>db</code>. ثم أجر التعديلات التالية:
</p>

<ul>
<li>
		<p>
			غيّر سياسة إعادة التشغيل restart للخدمة always.
		</p>
	</li>
	<li>
		<p>
			احذف ملف env. فبدلاً من استخدام قيمٍ من ملف env.، سنمرّر القيم الخاصة بـ MONGO_INITDB_ROOT_USERNAME<em> و </em>MONGO_INITDB_ROOT_PASSWORD إلى حاوية قاعدة البيانات باستخدام السّر Secret الذي سننشئه في الخطوة الرابعة.
		</p>
	</li>
</ul>
<p>
	سيبدو تعريف خدمة db الآن كما يلي:
</p>

<pre class="ipsCode">
...
  db:
    image: mongo:4.1.8-xenial
    container_name: db
    restart: always
    environment:
      - MONGO_INITDB_ROOT_USERNAME=$MONGO_USERNAME
      - MONGO_INITDB_ROOT_PASSWORD=$MONGO_PASSWORD
    volumes:  
      - dbdata:/data/db   
    networks:
      - app-network
... 
</pre>

<p>
	أخيرًا، في الجزء السفلي من الملف، احذف الحجوم node_modules من مفتاح المستوى الأعلى volumes. وسيبدو المفتاح عندها كما يلي:
</p>

<pre class="ipsCode">
...
volumes:
  dbdata:
</pre>

<p>
	احفظ الملف وأغلقه عند الانتهاء من التحرير.
</p>

<p>
	سنحتاج، قبل ترجمة تعريفاتنا للخدمة، إلى كتابة ملف env. الذي سيستخدمه kompose لإنشاء خريطة الإعداد ConfigMap بمعلوماتنا غير الحساسة. الرجاء مراجعة الخطوة الثانية من <a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-nodejs-%D9%84%D8%B3%D9%8A%D8%B1-%D8%B9%D9%85%D9%84-%D9%8A%D8%B9%D8%AA%D9%85%D8%AF-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-docker-compose-r811/" rel="">كيفية إعداد تطبيق node.js لسير عملٍ يعتمد على الحاويات باستخدام Docker Compose</a> للمزيد من التوضيح حول هذا الملف.
</p>

<p>
	في ذلك الدرس، أضفنا <code>env.</code> إلى ملفنا <code>gitignore.</code> للتأكد من عدم نسخه إلى التحكم في الإصدار. هذا يعني أنه لم يُنسَخ عندما استنسخنا مستودع <code>node-mongo-docker-dev</code> في الخطوة الثانية من هذا الدرس. لذلك سوف نحتاج إلى إعادة إنشائه الآن.
</p>

<p>
	أنشئ هذا الملف إذًا:
</p>

<pre class="ipsCode">
nano .env
</pre>

<p>
	سوف يستخدم <code>kompose</code> هذا الملف لإنشاء خريطة إعداد ConfigMap لتطبيقنا. ومع ذلك، بدلاً من تعيين كافة المتغيرات من تعريف خدمة nodejs في ملفنا Compose، سنضيف فقط اسم قاعدة البيانات <code>MONGO_DB</code> و <code>MONGO_PORT</code>. سنعيّن اسم المستخدم وكلمة المرور لقاعدة البيانات بشكل منفصل عندما ننشئ يدويًا كائنًا Secret في الخطوة الرابعة.
</p>

<p>
	أضف معلومات المنفذ واسم قاعدة البيانات التالية إلى ملف <code>env.</code>. لا تتردد في إعادة تسمية قاعدة بياناتك إذا كنت ترغب في ذلك:
</p>

<pre class="ipsCode">
MONGO_PORT=27017
MONGO_DB=sharkinfo
</pre>

<p>
	احفظ الملف وأغلقه عند الانتهاء من التحرير.
</p>

<p>
	أنت الآن مستعدّ لإنشاء الملفات اعتمادًا على خصائص كائنك. ويقدم لك kompose خيارات متعددة لترجمة مواردك. إذ تستطيع:
</p>

<ul>
<li>
		إنشاء ملفات yaml بناءً على تعريفات الخدمة في ملفك docker-compose.yaml باستخدام kompose convert.
	</li>
	<li>
		إنشاء كائنات Kubernetes مباشرة باستخدام kompose up.
	</li>
	<li>
		إنشاء مخطط Helm باستخدام kompose convert -c.
	</li>
</ul>
<p>
	في الوقت الحالي، سنحوّل تعريفاتنا للخدمة إلى ملفات yaml ثم نضيف الملفات التي ينشئها kompose وننقّحها. استخدام الأمر التالي لتحويل تعريفات الخدمة إلى ملفات yaml:
</p>

<pre class="ipsCode">
kompose convert
</pre>

<p>
	يمكنك أيضًا تسمية ملفات تكوين محددة أو متعددة باستخدام الراية <code>f-</code>. بعد تنفيذ هذا الأمر، سيُخرِج kompose معلوماتٍ حول الملفات التي أنشأتها:
</p>

<pre class="ipsCode" id="ips_uid_2001_6">
INFO Kubernetes file "nodejs-service.yaml" created 
INFO Kubernetes file "db-deployment.yaml" created 
INFO Kubernetes file "dbdata-persistentvolumeclaim.yaml" created 
INFO Kubernetes file "nodejs-deployment.yaml" created 
INFO Kubernetes file "nodejs-env-configmap.yaml" created 
</pre>

<p>
	يتضمّن هذا الإخراج الملفات yaml مع خصائص خدمة ونشر وخريطة إعداد تطبيق Node، وكذلك طلب وحدة التخزين الثابتة ل dbdata ونشر قاعدة بيانات MongoDB.
</p>

<p>
	تعدّ هذه الملفات نقطة انطلاق جيدة، ولكن لكي تتطابق وظائف تطبيقنا مع الإعداد الموضح في كيفية إعداد تطبيق node.js لسير عملٍ يعتمد على الحاويات باستخدام Docker Compose، سنحتاج إلى إجراء بعض الإضافات والتغييرات على الملفات التي أنشأها kompose.
</p>

<h2>
	الخطوة الرابعة: إنشاء أسرار Kubernetes
</h2>

<p>
	لكي يعمل تطبيقنا بالطريقة التي نتوقعها، سنحتاج إلى إجراء بعض التعديلات على الملفات التي أنشأها kompose. أول هذه التغييرات هو إنشاء سرٍّ لمستخدم قاعدة البيانات وكلمة المرور وإضافتها إلى عمليات نشر التطبيق وقاعدة البيانات. ويقدّم Kubernetes طريقتين للعمل بمتغيرات البيئة: ConfigMap وSecret. وقد أنشأ kompose بالفعل خريطة إعداد ConfigMap بالمعلومات غير السرية التي ضمّنناها في ملفنا env.، لذلك سننشئ الآن سرًّا بمعلوماتنا السرية: اسم المستخدم وكلمة المرور لقاعدة البيانات.
</p>

<p>
	ستكون الخطوة الأولى في إنشاء السّرِّ يدويًا هي تحويل اسم المستخدم وكلمة المرور إلى base64، وهو نظام ترميز يسمح لك بنقل البيانات بشكل موحد، بما في ذلك البيانات الثنائية.
</p>

<p>
	حوّل أولا اسم مستخدم قاعدة بياناتك:
</p>

<pre class="ipsCode">
echo -n 'your_database_username' | base64
</pre>

<p>
	دوّن القيمة التي تظهر لك في الإخراج. بعد ذلك، حوّل كلمة مرورك:
</p>

<pre class="ipsCode">
echo -n 'your_database_password' | base64
</pre>

<p>
	دوّن القيمة الظاهرة في الإخراج هنا أيضا. افتح ملف السّر:
</p>

<pre class="ipsCode">
nano secret.yaml
</pre>

<p>
	ملاحظة: تُحدّد كائنات Kubernetes عادة باستخدام YAML الذي يمنع بصرامةٍ علامات التبويب ويتطلب مسافتين للمسافة البادئة. إذا كنت ترغب في التحقق من تنسيق أيٍّ من ملفاتك yaml ، فيمكنك استخدام linter أو اختبار صياغة (syntax) تركيبك باستخدام <code>kubectl create</code> مع الرايتين <code>dry-run--</code> و <code>validate--</code>:
</p>

<pre class="ipsCode">
kubectl create -f your_yaml_file.yaml --dry-run --validate=true
</pre>

<p>
	يُستحسن بشكل عام التحقق من صحة الصياغة قبل إنشاء الموارد باستخدام kubectl.
</p>

<p>
	أضف الشيفرة التالية إلى الملف لإنشاء سرٍّ يحدّد MONGO_USERNAME<em> و </em>MONGO_PASSWORD باستخدام القيم المشفرة التي أنشأتها للتو. تأكد من تعويض القيم الوهمية هنا باسم المستخدم وكلمة المرور المشفرة:
</p>

<ul>
<li>
		الملف <code>‎~/node_project/secret.yaml</code>:
	</li>
</ul>
<pre class="ipsCode">
apiVersion: v1
kind: Secret
metadata:
  name: mongo-secret
data:
  MONGO_USERNAME: your_encoded_username
  MONGO_PASSWORD: your_encoded_password
</pre>

<p>
	لقد سمّينا كائن السرّ mongo-secret، ولكن يمكنك تسميته كما تشاء.
</p>

<p>
	احفظ هذا الملف وأغلقه عند الانتهاء من التحرير. وكما فعلت مع ملفك env. ، تأكد من إضافة secret.yaml إلى ملفك gitignore. لإبقائه خارج نطاق التحكم في الإصدار.
</p>

<p>
	بعد تحرير secret.yaml، ستكون خطوتنا التالية هي ضمان استخدام عُلَب كلّ من التطبيق وقاعدة البيانات للقيم التي أضفناها إلى الملف. لنبدأ بإضافة إشارات مرجعية إلى السّرّ Secret في نشر التطبيق.
</p>

<p>
	افتح الملف المسمى nodejs-publish.yaml:
</p>

<pre class="ipsCode">
nano nodejs-deployment.yaml
</pre>

<p>
	تتضمن مواصفات حاوية الملف متغيرات البيئة التالية المحددة تحت مفتاح env:
</p>

<pre class="ipsCode">
apiVersion: extensions/v1beta1
kind: Deployment
...
    spec:
      containers:
      - env:
        - name: MONGO_DB
          valueFrom:
            configMapKeyRef:
              key: MONGO_DB
              name: nodejs-env
        - name: MONGO_HOSTNAME
          value: db
        - name: MONGO_PASSWORD
        - name: MONGO_PORT
          valueFrom:
            configMapKeyRef:
              key: MONGO_PORT
              name: nodejs-env
        - name: MONGO_USERNAME
</pre>

<p>
	سنحتاج إلى إضافة إشارات مرجعية إلى السرّ في متغيرات MONGO_USERNAME<em> و </em>MONGO_PASSWORD المدرجة هنا، حتى يتمكن تطبيقنا من الوصول إلى تلك القيم. وبدلاً من تضمين مفتاح configMapKeyRef للإشارة إلى خريطة الإعداد لnodejs-env ، كما هو الحال مع قيم MONGO_DB<em> و </em>MONGO_PORT،، سنُضمِّن مفتاح secretKeyRef للإشارة إلى القيم الموجودة في السرّ secret.
</p>

<p>
	أضف إشارات السرّ المرجعية التالية إلى المتغيرين <code>MONGO_USERNAME</code> و <code>MONGO_PASSWORD</code>:
</p>

<pre class="ipsCode">
apiVersion: extensions/v1beta1
kind: Deployment
...
    spec:
      containers:
      - env:
        - name: MONGO_DB
          valueFrom:
            configMapKeyRef:
              key: MONGO_DB
              name: nodejs-env
        - name: MONGO_HOSTNAME
          value: db
        - name: MONGO_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mongo-secret
              key: MONGO_PASSWORD
        - name: MONGO_PORT
          valueFrom:
            configMapKeyRef:
              key: MONGO_PORT
              name: nodejs-env
        - name: MONGO_USERNAME
          valueFrom:
            secretKeyRef:
              name: mongo-secret
              key: MONGO_USERNAME
</pre>

<p>
	احفظ الملف وأغلقه عند الانتهاء من التحرير. بعد ذلك ، سنضيف القيم نفسها إلى ملف db-publish.yaml. افتح الملف للتحرير:
</p>

<pre class="ipsCode">
nano db-deployment.yaml
</pre>

<p>
	سنضيف في هذا الملف إشارات مرجعية إلى السرّ في مفاتيح المتغيرات التالية: MONGO_INITDB_ROOT_USERNAME<em> و </em>MONGO_INITDB_ROOT_PASSWORD. تجعل صورة mongo هذه المتغيرات متاحة لتتمكّن من تعديل تهيئة نسخة قاعدة بياناتك.
</p>

<p>
	ينشئ MONGO_INITDB_ROOT_USERNAME<em> </em>وMONGO_INITDB_ROOT_PASSWORD معًا مستخدمًا جذرًا في قاعدة بيانات المشرف admin مع التأكد من تفعيل التصديق على الهوية عند بدء تشغيل حاوية قاعدة البيانات.
</p>

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

<p>
	أضف أسفل متغيرات MONGO_INITDB_ROOT_USERNAME<em> </em>وMONGO_INITDB_ROOT_PASSWORD إشارات مرجعية إلى قيم السرّ Secret:
</p>

<pre class="ipsCode">
apiVersion: extensions/v1beta1
kind: Deployment
...
    spec:
      containers:
      - env:
        - name: MONGO_INITDB_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mongo-secret
              key: MONGO_PASSWORD        
        - name: MONGO_INITDB_ROOT_USERNAME
          valueFrom:
            secretKeyRef:
              name: mongo-secret
              key: MONGO_USERNAME
        image: mongo:4.1.8-xenial
…
</pre>

<p>
	احفظ الملف وأغلقه عند الانتهاء من التحرير.
</p>

<p>
	بعد إعداد السر الخاص بك، يمكنك الانتقال إلى إنشاء خدمة قاعدة بياناتك والتأكد من أن حاوية التطبيق تحاول فقط الاتصال بقاعدة البيانات بمجرد إعدادها وتهيئتها بالكامل.
</p>

<h2>
	الخطوة الخامسة: إنشاء خدمة قاعدة البيانات وحاوية التطبيق الأولية
</h2>

<p>
	الآن بعد أن أعددنا السرّ، يمكننا الانتقال إلى إنشاء خدمة قاعدة البيانات وحاوية أولية مهمّتها استطلاع هذه الخدمة للتأكد من أن تطبيقنا يحاول فقط الاتصال بقاعدة البيانات بمجرد اكتمال إجراءات بدء تشغيل قاعدة البيانات، بما في ذلك إنشاء مستخدم وكلمة مرور MONGO_INITDB.
</p>

<p>
	للمزيد حول كيفية تنفيذ هذه الوظيفة في Compose، يرجى الاطلاع على الخطوة الرابعة من إعداد تطبيق node.js لسير عملٍ يعتمد على الحاويات باستخدام Docker Compose.
</p>

<p>
	افتح ملفًا لتحديد مواصفات خدمة قاعدة البيانات:
</p>

<pre class="ipsCode">
nano db-service.yaml  
</pre>

<p>
	أضف الشيفرة التالية إلى الملف لتعريف الخدمة:
</p>

<ul>
<li>
		الملف <code>‎~/node_project/db-service.yaml</code>:
	</li>
</ul>
<pre class="ipsCode">
apiVersion: v1
kind: Service
metadata:
  annotations: 
    kompose.cmd: kompose convert
    kompose.version: 1.18.0 (06a2e56)
  creationTimestamp: null
  labels:
    io.kompose.service: db
  name: db
spec:
  ports:
  - port: 27017
    targetPort: 27017
  selector:
    io.kompose.service: db
status:
  loadBalancer: {}
</pre>

<p>
	سيُطابِق المحدد selector الذي أدرجناه هنا كائن الخدمة هذا مع علب (Pods) قاعدة بياناتنا، والتي عُرِّفت بالتسمية <code>io.kompose.service: db</code> بواسطة kompose في ملف .db-publish.yaml لقد سمّينا أيضًا هذه الخدمة db.
</p>

<p>
	احفظ الملف وأغلقه عند الانتهاء من التحرير.
</p>

<p>
	بعد ذلك، دعنا نضيف حقلًا لحاوية أولية إلى المصفوفة containers في nodejs-deployment.yaml سيؤدي هذا إلى إنشاء حاوية أوّلية يمكننا استخدامها لتأخير حاوية تطبيقنا عن الاشتغال حتى إنشاء خدمة db ذات علبة يمكن الوصول إليها. وهذا هو أحد الاستخدامات المحتملة للحاويات الأولية. لمعرفة المزيد عن حالات الاستخدام الأخرى، يرجى الاطلاع على التوثيق الرسمي.
</p>

<p>
	افتح الآن الملف nodejs-publish.yaml:
</p>

<pre class="ipsCode">
nano nodejs-deployment.yaml
</pre>

<p>
	داخل العلبة Pod وبجانب المصفوفة containers، سنضيف حقلًا initContainers بحاوية مهمتها استقصاء الخدمة db.
</p>

<p>
	أضف الشيفرة التالية أسفل الحقلين ports وresources وفوق restartPolicy في مصفوفة nodejs containers:
</p>

<ul>
<li>
		الملف <code>‎~/node_project/nodejs-deployment.yaml</code>:
	</li>
</ul>
<pre class="ipsCode">
apiVersion: extensions/v1beta1
kind: Deployment
...
    spec:
      containers:
      ...
        name: nodejs
        ports:
        - containerPort: 8080
        resources: {}
      initContainers:
      - name: init-db
        image: busybox
        command: ['sh', '-c', 'until nc -z db:27017; do echo waiting for db; sleep 2; done;']
      restartPolicy: Always
...               
</pre>

<p>
	تستخدم هذه الحاوية الأولية صورة BusyBox، وهي صورة خفيفة الوزن تتضمن العديد من أدوات UNIX. في هذه الحالة، سنستخدم الأداة netcat لاستقصاء هل تقبل العلبة المرتبطة بالخدمة db الاتصالات TCP على المنفذ 27017.
</p>

<p>
	يكرّر أمر الحاوية command هذا وظيفة السكربت wait-for الذي حذفناه من الملف docker-compose.yaml في الخطوة الثالثة. لمزيد من التوضيح حول كيفية وعلّة استخدام تطبيقنا للسكربت wait-for ، يرجى الاطلاع على الخطوة الرابعة من كيفية إعداد تطبيق node.js لسير عملٍ يعتمد على الحاويات باستخدام Docker Compose.
</p>

<p>
	تشتغل الحاويات الأولية وفق نظام run to completion. هذا يعني في حالتنا هذه أن حاوية التطبيق Node لن تبدأ في الاشتغال حتى تبدأ حاوية قاعدة البيانات في الاشتغال وقبول الاتصالات على المنفذ 27017. يسمح لنا تعريف الخدمة db بضمان هذه الوظيفة بغض النظر عن الموقع المحدّد لحاوية قاعدة البيانات، والذي يمكنه أن يتغيّر.
</p>

<p>
	احفظ الملف وأغلقه عند الانتهاء من التحرير.
</p>

<p>
	بعد إنشائك لخدمة قاعدة البيانات ووضعك لحاوياتك الأولية للتحكم في ترتيب اشتغال الحاويات، يمكنك الانتقال إلى التحقق من متطلبات التخزين في PersistentVolumeClaim وعرض خدمة تطبيقك باستخدام LoadBalancer.
</p>

<h2>
	الخطوة السّادسة: تعديل PersistentVolumeClaim وعرض الواجهة الأمامية للتطبيق
</h2>

<p>
	قبل تشغيل التطبيق، سنجري تغييرين أخيرين لضمان توفير تخزين قاعدة البيانات بشكل صحيح، وأنّنا نستطيع عرض الواجهة الأمامية لتطبيقنا باستخدام LoadBalancer.
</p>

<p>
	دعنا نعدل أولاً مورد التخزين storage resource المحدد في PersistentVolumeClaim الذي أنشأناه. تتيح لنا هذه المطالبة (claim) توفير مساحة تخزين ديناميكية لإدارة حالة التطبيق.
</p>

<p>
	للعمل ب PersistentVolumeClaims، يجب أن يكون لديك صنف تخزين StorageClass أنشأته وأعددته لتوفير موارد التخزين. في حالتنا هذه، ونظرًا لأننا نعمل على DigitalOcean Kubernetes، يعيّن صنف التخزين provisioner الافتراضي على dobs.csi.digitalocean.com.
</p>

<p>
	يمكننا التحقق من ذلك عبر كتابة ما يلي:
</p>

<pre class="ipsCode">
kubectl get storageclass
</pre>

<p>
	إذا كنت تعمل مع مجموعة DigitalOcean، فسترى الإخراج التالي:
</p>

<pre class="ipsCode" id="ips_uid_2001_8">
NAME                         PROVISIONER                 AGE
do-block-storage (default)   dobs.csi.digitalocean.com   76m
</pre>

<p>
	إذا كنت لا تعمل على عنقود DigitalOcean، فستحتاج إلى إنشاء StorageClass وإعداد مزوّد من اختيارك. للحصول على تفاصيل حول كيفية القيام بذلك، يرجى الاطلاع على <a href="https://kubernetes.io/docs/concepts/storage/storage-classes/" rel="external nofollow">التوثيق الرسمي</a>.
</p>

<p>
	عندما ينشئ kompose الملف dbdata-persistentvolumeclaim.yaml، فإنه يعيّن مورد التخزين storage resource على سعةٍ لا تلبي الحدّ المطلوب في مزوّد الخدمة لدينا. لذلك، سنحتاج إلى تعديل PersistentVolumeClaim الخاص بنا لاستخدام السعة الدنيا القابلة للتطبيق لوحدة تخزين DigitalOcean Block المحدّدة في 1 جيجابايت. ويمكنك تعديل هذه القيمة لتلبية متطلبات التخزين الخاصة بك.
</p>

<p>
	افتح الملفّ dbdata-persistentvolumeclaim.yaml:
</p>

<pre class="ipsCode">
nano dbdata-persistentvolumeclaim.yaml
</pre>

<p>
	عوّض قيمة التخزين storage بـ 1Gi:
</p>

<ul>
<li>
		الملف <code>‎~/node_project/dbdata-persistentvolumeclaim.yaml</code>:
	</li>
</ul>
<pre class="ipsCode">
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  creationTimestamp: null
  labels:
    io.kompose.service: dbdata
  name: dbdata
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
status: {}
</pre>

<p>
	لاحظ أيضًا أنّ accessMode: ReadWriteOnce يعني أن الحجم المتوفّر كنتيجة لهذه الطلب سيكون للقراءة والكتابة من خلال عقدة واحدة فقط. يرجى الاطلاع على التوثيق لمزيد من المعلومات حول أوضاع الوصول المختلفة.
</p>

<p>
	احفظ الملف وأغلقه عند الانتهاء. بعد ذلك، افتح nodejs-service.yaml:
</p>

<pre class="ipsCode">
nano nodejs-service.yaml
</pre>

<p>
	سنعمل على الكشف عن هذه الخدمة خارجيًا باستخدام <code>‎.DigitalOcean Load Balancer</code> إذا كنت لا تستخدم عنقود DigitalOcean، فيرجى الرجوع إلى التوثيق ذا الصلة من مزوّد الخدمة السحابية للحصول على معلومات حول موازِنات الحمل الخاصة به. بدلاً من ذلك، يمكنك اتباع توثيق Kubernetes الرسمية الخاصة بإعداد عنقود عالي التوفّر باستخدام kubeadm، لكن في هذه الحالة لن تتمكن من استخدام PersistentVolumeClaims لتوفير التخزين.
</p>

<p>
	حدّد ضمن مواصفات الخدمة، LoadBalancer في الحقل type الخاصّ بالخدمة:
</p>

<ul>
<li>
		الملف <code>‎~/node_project/nodejs-service.yaml</code>:
	</li>
</ul>
<pre class="ipsCode">
apiVersion: v1
kind: Service
...
spec:
  type: LoadBalancer
  ports:
...
</pre>

<p>
	عندما ننشئ خدمة nodejs، سيُنشَأ مُوازِن للتحميل تلقائيًا، مما يوفر لنا عنوانًا خارجيًا IP يمكِّننا من الوصول إلى تطبيقنا.
</p>

<p>
	احفظ الملف وأغلقه عند الانتهاء من التحرير. بوجود جميع ملفاتنا في مكانها الصحيح، نكون على استعداد لبدء واختبار التطبيق.
</p>

<h2>
	الخطوة السابعة: بدء التطبيق والوصول إليه
</h2>

<p>
	لقد حان الوقت لإنشاء كائنات Kubernetes واختبار عمل التطبيق على النحو المتوقع.
</p>

<p>
	لإنشاء الكائنات التي حددناها، سنستخدم kubectl create مع الراية f-، والتي سوف تتيح لنا تحديد الملفات التي أنشأها لنا Compose، بالإضافة إلى الملفات التي حرّرناها. نفّذ الأمر التالي لإنشاء تطبيق Node وخدمات قاعدة بيانات MongoDB وعمليات النشر، مع Secret وConfigMap وPersistentVolumeClaim:
</p>

<pre class="ipsCode">
kubectl create -f nodejs-service.yaml,nodejs-deployment.yaml,nodejs-env-configmap.yaml,db-service.yaml,db-deployment.yaml,dbdata-persistentvolumeclaim.yaml,secret.yaml
</pre>

<p>
	سيظهر لك الإخراج التالي مشيرًا إلى إنشاء الكائنات:
</p>

<pre class="ipsCode">
Output
service/nodejs created
deployment.extensions/nodejs created
configmap/nodejs-env created
service/db created
deployment.extensions/db created
persistentvolumeclaim/dbdata created
secret/mongo-secret created
</pre>

<p>
	للتحقق من اشتغال العلب Pods، اكتب ما يلي:
</p>

<pre class="ipsCode">
kubectl get pods
</pre>

<p>
	لا تحتاج إلى تحديد فضاء اسم (namespace) هنا، لأننا أنشأنا كائناتنا في مساحة اسمية الافتراضية. إذا كنت تعمل على مساحات اسمية متعددة، فتأكد من تضمين الراية <code>n-</code> عند تنفيذ هذا الأمر، بالإضافة إلى اسم مساحتك الاسمية.
</p>

<p>
	سيظهر لك الإخراج التالي أثناء بدء تشغيل الحاوية db وحاوية تطبيقك الأولية:
</p>

<pre class="ipsCode">
Output
NAME                      READY   STATUS              RESTARTS   AGE
db-679d658576-kfpsl       0/1     ContainerCreating   0          10s
nodejs-6b9585dc8b-pnsws   0/1     Init:0/1            0          10s
</pre>

<p>
	بمجرد تشغيل تلك الحاوية وبدء تشغيل حاويات التطبيق وقاعدة البيانات، سيظهر لك هذا الإخراج:
</p>

<pre class="ipsCode">
Output
NAME                      READY   STATUS    RESTARTS   AGE
db-679d658576-kfpsl       1/1     Running   0          54s
nodejs-6b9585dc8b-pnsws   1/1     Running   0          54s
</pre>

<p>
	يشير Running STATUS إلى أن العلب الخاصة بك مرتبطة بالعُقَد وأن الحاويات المرتبطة بتلك العلب تعمل. ويشير READY إلى عدد الحاويات الموجودة في العلبة. لمزيد من المعلومات، يرجى الرجوع إلى توثيق دورة حياة العلبة.
</p>

<p>
	ملحوظة: إذا رأيت مراحل غير متوقعة في العمود STATUS، تذكر أنه يمكنك استكشاف الأخطاء وإصلاحها باستخدام الأوامر التالية:
</p>

<pre class="ipsCode">
kubectl describe pods your_pod
kubectl logs your_pod
</pre>

<p>
	بعد تشغيل حاوياتك، يمكنك الآن الوصول إلى التطبيق. للحصول على العنوان IP الخاص بـ LoadBalancer، اكتب:
</p>

<pre class="ipsCode">
kubectl get svc
</pre>

<p>
	سيظهر لك الإخراج التالي:
</p>

<pre class="ipsCode" id="ips_uid_2001_10">
NAME         TYPE           CLUSTER-IP       EXTERNAL-IP      PORT(S)        AGE
db           ClusterIP      10.245.189.250   &lt;none&gt;           27017/TCP      93s
kubernetes   ClusterIP      10.245.0.1       &lt;none&gt;           443/TCP        25m12s
nodejs       LoadBalancer   10.245.15.56     your_lb_ip       80:30729/TCP   93s
</pre>

<p>
	العنوان EXTERNAL_IP<em> المرتبط بخدمة nodejs هو عنوان IP يمكّنك من الوصول إلى التطبيق. إذا رأيت حالة <code>&lt;pending&gt;</code> في عمود </em>EXTERNAL_IP،، فهذا يعني أن مُوازِن التحميل لا يزال قيد الإنشاء.
</p>

<p>
	بمجرد رؤية عنوان IP في هذا العمود، انتقل إليه في متصفحك: http://your_lb_ip. ينبغي أن تظهر لك صفحة الهبوط التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="33625" href="https://academy.hsoub.com/uploads/monthly_2020_02/landing_page.png.b2e686b4befc92a4f6713843b1010ba9.png" rel=""><img alt="landing_page.png" class="ipsImage ipsImage_thumbnailed" data-fileid="33625" data-unique="data-unique" src="https://academy.hsoub.com/uploads/monthly_2020_02/landing_page.thumb.png.75f59fb56efbc6b9f45216a987e17837.png"></a>
</p>

<p>
	انقر على زر الحصول على معلومات القرش. ستظهر صفحةّ فيها نموذج إدخال حيث يمكنك إدخال اسم سمك القرش ووصف لسلوكه العام:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="33629" href="https://academy.hsoub.com/uploads/monthly_2020_02/shark_form.png.88acc9bf85300fd49ea1ed7a427a781c.png" rel=""><img alt="shark_form.png" class="ipsImage ipsImage_thumbnailed" data-fileid="33629" data-unique="8l4va37b2" src="https://academy.hsoub.com/uploads/monthly_2020_02/shark_form.thumb.png.18eed087e80122bee4faa891656393c0.png"></a>
</p>

<p>
	أضف في النموذج سمكة قرش من اختيارك. للتوضيح، سنضيف Megalodon Shark إلى حقل Shark Name، وAncient لحقل Shark Character:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="33628" href="https://academy.hsoub.com/uploads/monthly_2020_02/shark_filled.png.b1ceaf7f289c90117a4e3bcc979fd32d.png" rel=""><img alt="shark_filled.png" class="ipsImage ipsImage_thumbnailed" data-fileid="33628" data-unique="2gsh5h8dh" src="https://academy.hsoub.com/uploads/monthly_2020_02/shark_filled.thumb.png.f036aac9a52e04110f15cedc40a2dd5b.png"></a>
</p>

<p>
	انقر على زر الإرسال. ستظهر صفحة بها معلومات القرش معروضة لك:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="33627" href="https://academy.hsoub.com/uploads/monthly_2020_02/shark_added.png.2ecc036946022ae53f11c6b659f62454.png" rel=""><img alt="shark_added.png" class="ipsImage ipsImage_thumbnailed" data-fileid="33627" data-unique="gm53lan5o" src="https://academy.hsoub.com/uploads/monthly_2020_02/shark_added.thumb.png.cd7488b6f296b7b110c9aeef942269f8.png"></a>
</p>

<p>
	لديك الآن إعداد نسخة واحدة لتطبيق Node.js بقاعدة بيانات MongoDB تعمل على عنقود Kubernetes.
</p>

<h2>
	خاتمة
</h2>

<p>
	تعدّ الملفات التي أنشأتها في هذا الدرس نقطة انطلاق جيدة للبناء عليها أثناء تقدمك نحو الإنتاج. ويمكنك العمل أثناء تطوير تطبيقك على تنفيذ ما يلي:
</p>

<ul>
<li>
		<p>
			تسجيل الدّخول والمراقبة بشكل مركزي. يرجى الاطلاع على <a href="https://www.digitalocean.com/community/tutorials/modernizing-applications-for-kubernetes#deploying-on-kubernetes" rel="external nofollow">المناقشة ذات الصلة على موقع ديجيتال أوشن</a> حول <a href="https://www.digitalocean.com/community/tutorials/modernizing-applications-for-kubernetes" rel="external nofollow">تحديث تطبيقات Kubernetes</a> للحصول على نظرة أشمل. يمكنك أيضًا الاطلاع على <a href="https://www.digitalocean.com/community/tutorials/how-to-set-up-an-elasticsearch-fluentd-and-kibana-efk-logging-stack-on-kubernetes" rel="external nofollow">كيفية إعداد حزمة تسجيل دخول Elasticsearch، و Fluentd و Kibana ‏(EFK) على Kubernetes</a> على موقع ديجيتال أوشن. راجع أيضًا مقال <a href="https://www.digitalocean.com/community/tutorials/an-introduction-to-service-meshes" rel="external nofollow">مقدمة لشبكات الخدمة للحصول على معلومات</a> حول كيفية تنفيذ شبكات الخدمات مثل <a href="https://istio.io/" rel="external nofollow">Istio</a> لهذه الوظيفة.
		</p>
	</li>
	<li>
		<p>
			موارد الولوج لتوجيه حركة المرور إلى عنقودك. يعد هذا بديلاً جيدًا لموزع الحمل LoadBalancer في الحالات التي تشغّل فيها خدمات متعددة، والتي تتطلب كل منها موازِنًا LoadBalancer خاصًّا بها، أو عندما ترغب في تنفيذ استراتيجيات توجيه على مستوى التطبيق (اختبارات A/B &amp; canary، على سبيل المثال). لمزيد من المعلومات، تحقّق من مقال <a href="https://www.digitalocean.com/community/tutorials/how-to-set-up-an-nginx-ingress-with-cert-manager-on-digitalocean-kubernetes" rel="external nofollow">كيفية إعداد Nginx Ingress مع Cert-Manager على DigitalOcean Kubernetes</a> و<a href="https://www.digitalocean.com/community/tutorials/an-introduction-to-service-meshes#routing-and-traffic-configuration" rel="external nofollow">المناقشة ذات الصلة</a> على ديجيتال أوشن بالتوجيه في سياق شبكة الخدمة في مقدمة لشبكات الخدمة.
		</p>
	</li>
	<li>
		<p>
			استراتيجيات النسخ الاحتياطي لكائناتك Kubernetes. للحصول على إرشادات حول تنفيذ النسخ الاحتياطية باستخدام <a href="https://github.com/heptio/velero" rel="external nofollow">Velero</a> (سابقًا Heptio Ark) مع منتج Kubernetes الخاص بـ DigitalOcean، يرجى الاطلاع على مقال <a href="https://www.digitalocean.com/community/tutorials/how-to-back-up-and-restore-a-kubernetes-cluster-on-digitalocean-using-heptio-ark" rel="external nofollow">كيفية عمل نسخة احتياطية واستعادة عنقود Kubernetes</a> على DigitalOcean باستخدام Heptio Ark.
		</p>
	</li>
</ul>
<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-migrate-a-docker-compose-workflow-to-kubernetes" rel="external nofollow">How To Migrate a Docker Compose Workflow to Kubernetes</a> لصاحبته Kathleen Juell
</p>

<p>
	 
</p>

<p>
	 
</p>
]]></description><guid isPermaLink="false">812</guid><pubDate>Wed, 05 Feb 2020 06:29:31 +0000</pubDate></item><item><title>&#x625;&#x639;&#x62F;&#x627;&#x62F; &#x62A;&#x637;&#x628;&#x64A;&#x642; node.js &#x644;&#x633;&#x64A;&#x631; &#x639;&#x645;&#x644; &#x64A;&#x639;&#x62A;&#x645;&#x62F; &#x639;&#x644;&#x649; &#x627;&#x644;&#x62D;&#x627;&#x648;&#x64A;&#x627;&#x62A; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; Docker Compose</title><link>https://academy.hsoub.com/programming/javascript/nodejs/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-nodejs-%D9%84%D8%B3%D9%8A%D8%B1-%D8%B9%D9%85%D9%84-%D9%8A%D8%B9%D8%AA%D9%85%D8%AF-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-docker-compose-r811/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2020_02/3.jpg.282756292a3f9e115978de8b91deb0e3.jpg" /></p>

<p>
	إذا كنت تعمل على تطوير تطبيقك بنشاط، فإن استخدام Docker يمكنه تبسيط سير العمل وجعل عملية نشر التطبيق على الإنتاج سلسةً، إذ يمنح العمل بالحاويات في عمليّة التطوير العديد من المزايا، نذكر منها أنّ:
</p>

<ul>
<li>
		البيئات متناسقة (consistent)، مما يعني أنه يمكنك اختيار اللغات والاعتماديات التي تريدها لمشروعك دون أن تقلق بشأن تعارضات النظام.
	</li>
	<li>
		البيئات معزولة (isolated)، ممّا يسهّل اكتشاف المشكلات وإدماج الأعضاء الجدد في الفريق.
	</li>
	<li>
		البيئات محمولة (portable)، ممّا يسمح لك بتحزيم ومشاركة شيفرتك مع الآخرين.
	</li>
</ul>
<p>
	يشرح لك هذا الدرس كيفية إعداد بيئة تطوير لتطبيق Node.js بالاعتماد على Docker. ستنشئ حاويتين، واحدة من أجل تطبيق Node والأخرى لقاعدة البيانات MongoDB، وذلك باستخدام Docker Compose. ونظرًا لأن هذا التطبيق يعمل على Node وMongoDB، فسيقوم برنامج الإعداد بما يلي:
</p>

<p>
	*مزامنة شيفرة التطبيق التي على المضيف مع الشيفرة التي في الحاوية لتسهيل التعديلات أثناء التطوير.
</p>

<ul>
<li>
		التحقّق من أن التعديلات في شيفرة التطبيق تعمل دون الحاجة إلى إعادة تشغيل.
	</li>
	<li>
		إنشاء قاعدة بيانات محمية بمستخدم وكلمة مرور من أجل بيانات التطبيق.
	</li>
	<li>
		جعل هذه البيانات ثابتة ومستقرة (persistent).
	</li>
</ul>
<p>
	في نهاية هذا الدرس، سيكون لديك تطبيق لمعلومات سمك القرش يعمل بشكل جيّد على حاويات Docker:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="33620" href="https://academy.hsoub.com/uploads/monthly_2020_02/persisted_data.png.47c17dd589ec06552a39b4d678c1188e.png" rel=""><img alt="persisted_data.png" class="ipsImage ipsImage_thumbnailed" data-fileid="33620" data-unique="data-unique" src="https://academy.hsoub.com/uploads/monthly_2020_02/persisted_data.thumb.png.1713ce86ab9ab712f2d68c2e73b2d07a.png"></a>
</p>

<h2>
	المتطلبات الأساسية
</h2>

<p>
	لمتابعة هذا الدرس، ستحتاج إلى العناصر التالية:
</p>

<ul>
<li>
		خادم تطوير يشتغل على نظام أوبونتو 18.04، إضافة إلى مستخدم غير جذري يمتلك صلاحيات sudo وجدار حماية نشط. للحصول على إرشادات حول كيفية إعدادها، يرجى الاطلاع على <a href="https://academy.hsoub.com/devops/linux/%D8%A7%D9%84%D8%AA%D9%87%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%A3%D9%88%D9%84%D9%8A%D8%A9-%D9%84%D8%AE%D8%A7%D8%AF%D9%85-%D8%A3%D9%88%D8%A8%D9%88%D9%86%D8%AA%D9%88-1804-r431/" rel="">دليل إعداد الخادم الأولي</a>.
	</li>
	<li>
		تثبيت Docker على خادمك، باتباع الخطوتين الأولى و الثانية <a href="https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-18-04" rel="external nofollow">لكيفية تثبيت واستخدام Docker على أوبونتو 18.04</a>.
	</li>
	<li>
		تثبيت Compose Docker على خادمك، باتباع الخطوة الأولى من <a href="https://www.digitalocean.com/community/tutorials/how-to-install-docker-compose-on-ubuntu-18-04" rel="external nofollow">كيفية تثبيت Docker Compose على أوبونتو 18.04</a>.
	</li>
</ul>
<h2>
	الخطوة الأولى: استنساخ المشروع وتعديل الاعتماديات
</h2>

<p>
	تتمثل الخطوة الأولى في إنشاء هذا الإعداد في استنساخ شيفرة المشروع وتعديل ملف package.json الخاص به، والذي يتضمن اعتماديات المشروع. سوف نضيف nodemon إلى اعتماديات المشروع devDependencies، مع تحديد أننا سنستخدمها أثناء التطوير. يضمن تشغيل التطبيق باستخدام nodemon أنه سيُعاد تشغيله تلقائيًا عند إجراء تغييرات على الشيفرة.
</p>

<p>
	انسخ، في البداية، مستودع nodejs-mongo-mongoose من حساب DigitalOcean Community GitHub. يتضمن هذا المستودع الشيفرة من الإعداد الموضح في كيفية دمج MongoDB مع تطبيقك Node، والذي يشرح كيفية دمج قاعدة بيانات MongoDB مع تطبيق Node موجودٍ سلفًا باستخدام Mongoose.
</p>

<p>
	انسخ المستودع في مجلّد يسمى node_project:
</p>

<pre class="ipsCode">
git clone https://github.com/do-community/nodejs-mongo-mongoose.git node_project
</pre>

<p>
	انتقل إلى المجلّد node_project:
</p>

<pre class="ipsCode">
cd node_project
</pre>

<p>
	افتح ملف package.json الخاص بالمشروع باستخدام nano أو المحرّر الذي تفضّله:
</p>

<pre class="ipsCode">
nano package.json
</pre>

<p>
	أسفل اعتماديات المشروع وفوق قوس الإغلاق، أنشئ كائن devDependencies جديد يتضمن nodemon:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5199_7" style="">
<span class="pun">...</span><span class="pln">
</span><span class="str">"dependencies"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">"ejs"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^2.6.1"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"express"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^4.16.4"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"mongoose"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^5.4.10"</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="str">"devDependencies"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">"nodemon"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^1.18.10"</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">    
</span><span class="pun">}</span></pre>

<p>
	احفظ الملف وأغلقه عند الانتهاء من التحرير.
</p>

<p>
	بوضعك لشيفرة المشروع في مكانها وتعديل اعتمادياته، يمكنك الانتقال إلى إعادة بناء الشيفرة من أجل سير عملٍ يعتمد على الحاويات.
</p>

<h2>
	الخطوة الثانية: إعداد تطبيقك للعمل بالحاويات
</h2>

<p>
	يعني تعديل التطبيق من أجل سير عملٍ يعتمد على الحاويات، جعل الشيفرة أكثر نمطيةً. إذ توفر الحاويات قابلية التنقل بين البيئات، ويجب أن تعكس الشيفرة ذلك من خلال البقاء قدر الإمكان منفصلةً عن نظام التشغيل الأساسي. لتحقيق ذلك، سنعيد تشكيل شيفرتنا للاستفادة بشكل أكبر من خاصية process.env الخاصة ب Node، والتي تعيد كائنًا يحتوي على معلومات حول بيئة المستخدم في وقت التشغيل. يمكننا استخدام هذا الكائن في شيفرتنا لتعيين معلومات التكوين ديناميكيًا في وقت التشغيل باستخدام متغيرات البيئة.
</p>

<p>
	لنبدأ بالملف app.js، الذي يمثّل نقطة دخول التطبيق الرئيسية لدينا. افتح هذا الملف:
</p>

<pre class="ipsCode">
nano app.js
</pre>

<p>
	سترى فيه تعريفًا للثابتة port، بالإضافة إلى الدالّة <code>listen</code> التي تستخدم هذه الثابتة لتحديد المنفذ الذي سيستمع إليه التطبيق:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5199_9" style="">
<span class="pun">...</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> port </span><span class="pun">=</span><span class="pln"> </span><span class="lit">8080</span><span class="pun">;</span><span class="pln">
</span><span class="pun">...</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">listen</span><span class="pun">(</span><span class="pln">port</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'Example app listening on port 8080!'</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	دعنا نعيد تعريف ثابتة port للسماح بالتعيين الديناميكي في وقت التشغيل باستخدام الكائن <code>process.env</code>. لذا، أجرِ التعديلات التالية على تعريف الثابتة وعلى الدالّة <code>listen</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5199_11" style="">
<span class="pun">...</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> port </span><span class="pun">=</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">PORT </span><span class="pun">||</span><span class="pln"> </span><span class="lit">8080</span><span class="pun">;</span><span class="pln">
</span><span class="pun">...</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">listen</span><span class="pun">(</span><span class="pln">port</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(`</span><span class="typ">Example</span><span class="pln"> app listening on $</span><span class="pun">{</span><span class="pln">port</span><span class="pun">}!`);</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	يعيّن تعريفنا الثابت الجديد المنفذ port ديناميكيًا باستخدام القيمة الممرّرة في وقت التشغيل أو القيمة 8080. وبالمثل، أعدنا كتابة الدّالة لاستخدام <a href="https://wiki.hsoub.com/JavaScript/Template_Literals" rel="external">قالب نصّي حديث</a>، والذي سيُقحم قيمة المنفذ عند الاستماع للاتصالات. ونظرًا لأننا سنعيّن منافذنا في أماكن أخرى، فستغنينا هذه المراجعات عن الاضطرار إلى مراجعة هذا الملف بشكل مستمر عند تغيير بيئتنا.
</p>

<p>
	احفظ الملف وأغلقه عند الانتهاء من التحرير.
</p>

<p>
	بعد ذلك، سنعدّل معلومات اتصال قاعدة البيانات لحذف أي بيانات اعتماد في التكوينات. افتح الملف db.js الذي يحتوي على هذه المعلومات:
</p>

<pre class="ipsCode">
nano db.js
</pre>

<p>
	يقوم الملف حاليا بالأمور التالية:
</p>

<ul>
<li>
		يستورد Mongoose، كائن تخطيط المستندات (ODM) الذي نستخدمه لإنشاء مخطّطات ونماذج لبيانات التطبيق.
	</li>
	<li>
		يعيّن بيانات اعتماد قاعدة البيانات كقيم ثابتة، بما في ذلك اسم المستخدم وكلمة المرور.
	</li>
	<li>
		يتصل بقاعدة البيانات باستخدام التابع mongoose.connect.
	</li>
</ul>
<p>
	لمزيد من المعلومات حول الملفّ، يرجى الاطلاع على الخطوة الثالثة من كيفية دمج MongoDB مع تطبيق Node الخاص بك.
</p>

<p>
	ستكون خطوتنا الأولى في تعديل الملف هي إعادة تحديد الثوابت التي تتضمن معلومات حساسة. تبدو هذه الثوابت حاليًا على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5199_13" style="">
<span class="pun">...</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> MONGO_USERNAME </span><span class="pun">=</span><span class="pln"> </span><span class="str">'sammy'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> MONGO_PASSWORD </span><span class="pun">=</span><span class="pln"> </span><span class="str">'your_password'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> MONGO_HOSTNAME </span><span class="pun">=</span><span class="pln"> </span><span class="str">'127.0.0.1'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> MONGO_PORT </span><span class="pun">=</span><span class="pln"> </span><span class="str">'27017'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> MONGO_DB </span><span class="pun">=</span><span class="pln"> </span><span class="str">'sharkinfo'</span><span class="pun">;</span><span class="pln">
</span><span class="pun">...</span></pre>

<p>
	يمكنك استخدام الكائن process.env لالتقاط قيم وقت التشغيل لهذه الثوابت بدلاً من الاضطرار لكتابة هذه المعلومات يدويًا. لذا عدّل هذا المقطع من الشيفرة لتبدو على هذا النحو:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5199_15" style="">
<span class="pun">...</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  MONGO_USERNAME</span><span class="pun">,</span><span class="pln">
  MONGO_PASSWORD</span><span class="pun">,</span><span class="pln">
  MONGO_HOSTNAME</span><span class="pun">,</span><span class="pln">
  MONGO_PORT</span><span class="pun">,</span><span class="pln">
  MONGO_DB
</span><span class="pun">}</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">;</span><span class="pln">
</span><span class="pun">...</span></pre>

<p>
	احفظ الملف وأغلقه عند الانتهاء من التحرير.
</p>

<p>
	في هذه المرحلة، عدّلت الملف db.js للعمل بمتغيرات البيئة في تطبيقك، ولكنك مازلت تحتاج إلى وسيلة لتمرير هذه المتغيرات إلى تطبيقك. لننشئ ملف env. بقيمٍ يمكنك تمريرها إلى التطبيق في وقت التشغيل.
</p>

<p>
	افتح هذا الملف:
</p>

<pre class="ipsCode">
nano .env
</pre>

<p>
	سيتضمن هذا الملف المعلومات التي حذفتها من db.js وهي اسم المستخدم وكلمة المرور لقاعدة بيانات التطبيق، بالإضافة إلى إعداد المنفذ واسم قاعدة البيانات. لا تنسَ تحديث اسم المستخدم وكلمة المرور واسم قاعدة البيانات المدرجة هنا بمعلوماتك الخاصة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5199_17" style="">
<span class="pln">MONGO_USERNAME</span><span class="pun">=</span><span class="pln">sammy
MONGO_PASSWORD</span><span class="pun">=</span><span class="pln">your_password
MONGO_PORT</span><span class="pun">=</span><span class="lit">27017</span><span class="pln">
MONGO_DB</span><span class="pun">=</span><span class="pln">sharkinfo</span></pre>

<p>
	لاحظ أننا حذفنا إعدادات المضيف التي كانت في الأصل في db.js. والآن، سنعرّف المضيف على مستوى الملف Docker Compose، إلى جانب معلومات أخرى حول الخدمات والحاويات.
</p>

<p>
	احفظ هذا الملف وأغلقه عندما تنتهي من التحرير.
</p>

<p>
	نظرًا لاحتواء الملف env. على معلومات حساسة، فستحتاج إلى الحرص على تضمينه في ملفات dockerignore. و gitignore. الخاصة بمشروعك حتى لا يُنسخ إلى وحدة إدارة الإصدار أو إلى حاوياتك.
</p>

<p>
	افتح ملفك dockerignore.:
</p>

<pre class="ipsCode">
nano .dockerignore
</pre>

<p>
	أضف السطر التالي إلى أسفل الملف:
</p>

<pre class="ipsCode">
...
.gitignore
.env
</pre>

<p>
	احفظ الملف وأغلقه عند الانتهاء من التحرير.
</p>

<p>
	يحتوي الملف gitignore. الموجود في هذا المستودع على env. أصلًا، ولكن لا تتردد في التحقق من وجوده:
</p>

<pre class="ipsCode">
nano .gitignore
</pre>

<pre class="ipsCode">
...
.env
...
</pre>

<p>
	تمكنت في هذه المرحلة من استخراج المعلومات الحساسة من شيفرة المشروع واتخذت تدابير للتحكم في كيفية ومكان نسخ هذه المعلومات. يمكنك الآن العمل على تمتين الشيفرة الخاصّة بالاتصال بقاعدة البيانات بُغية تحسينها من أجل سير عملٍ يعتمد على الحاويات.
</p>

<h2>
	الخطوة الثالثة: تعديل إعدادات اتصال قاعدة البيانات
</h2>

<p>
	ستكون خطوتنا التالية هي جعل طريقة اتصال قاعدة البيانات أكثر متانة عن طريق إضافة شيفرة تعالج الحالات التي يفشل فيها تطبيقنا في الاتصال بقاعدة البيانات. ويُعدّ تقديم هذا المستوى من المرونة لشيفرة التطبيق ممارسةً ينصح بها عند التعامل مع الحاويات باستخدام "Compose".
</p>

<p>
	افتح الملفّ db.js لتحريره:
</p>

<pre class="ipsCode">
nano db.js
</pre>

<p>
	ستظهر لك الشفرة التي أضفناها سابقًا، إلى جانب ثابتة العنوان url الخاصة بالمعرّف URI لاتصال Mongo و<a href="https://mongoosejs.com/docs/api.html#mongoose_Mongoose-connect" rel="external nofollow">التابع connect</a> فيMongoose:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5199_19" style="">
<span class="pun">...</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  MONGO_USERNAME</span><span class="pun">,</span><span class="pln">
  MONGO_PASSWORD</span><span class="pun">,</span><span class="pln">
  MONGO_HOSTNAME</span><span class="pun">,</span><span class="pln">
  MONGO_PORT</span><span class="pun">,</span><span class="pln">
  MONGO_DB
</span><span class="pun">}</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> url </span><span class="pun">=</span><span class="pln"> </span><span class="pun">`</span><span class="pln">mongodb</span><span class="pun">:</span><span class="com">//${MONGO_USERNAME}:${MONGO_PASSWORD}@${MONGO_HOSTNAME}:${MONGO_PORT}/${MONGO_DB}?authSource=admin`;</span><span class="pln">

mongoose</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="pln">url</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">useNewUrlParser</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">});</span></pre>

<p>
	يقبل التابع connect حاليًا خيارًا يطلب من Mongoose استخدام <a href="https://mongoosejs.com/docs/deprecations.html" rel="external nofollow">مفسّر URL الجديد</a> الخاص بـMongo. دعنا نضيف بعض الخيارات الإضافية إلى هذا التابع لتعريف المعاملات (paramaters) لمحاولات إعادة الاتصال. يمكننا القيام بذلك عبر إنشاء ثابتة options تتضمّن المعلومات ذات الصلة، بالإضافة إلى خيار مفسّر عناوين URL الجديد. أسفل ثوابت Mongo، أضف التعريف التالي للثابتة options:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5199_21" style="">
<span class="pun">...</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  MONGO_USERNAME</span><span class="pun">,</span><span class="pln">
  MONGO_PASSWORD</span><span class="pun">,</span><span class="pln">
  MONGO_HOSTNAME</span><span class="pun">,</span><span class="pln">
  MONGO_PORT</span><span class="pun">,</span><span class="pln">
  MONGO_DB
</span><span class="pun">}</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> options </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  useNewUrlParser</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
  reconnectTries</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Number</span><span class="pun">.</span><span class="pln">MAX_VALUE</span><span class="pun">,</span><span class="pln">
  reconnectInterval</span><span class="pun">:</span><span class="pln"> </span><span class="lit">500</span><span class="pun">,</span><span class="pln"> 
  connectTimeoutMS</span><span class="pun">:</span><span class="pln"> </span><span class="lit">10000</span><span class="pun">,</span><span class="pln">
</span><span class="pun">};</span><span class="pln">
</span><span class="pun">...</span></pre>

<p>
	يطلب الخيار reconnectTries من Mongoose مواصلة محاولة الاتصال إلى أجل غير محدّد، في حين تحدد reconnectInterval الفترة الفاصلة بين محاولتي اتصال بوحدة الجزء من الألف من الثانية. ويحدّد connectTimeoutMS الفترة التي سينتظرها برنامج التشغيل Mongo قبل إعلان فشل محاولة الاتصال في 10 ثوانٍ.
</p>

<p>
	يمكننا الآن استخدام الثابتة الجديدة options في التابع connect لضبط إعدادات اتصال Mongoose بدقّةٍ. سنضيف أيضًا <a href="https://wiki.hsoub.com/JavaScript/Promise" rel="external">كائن وعدٍ (promise)</a> لمعالجة أخطاء الاتصال المحتملة.
</p>

<p>
	يبدو التابع connect الخاصّ ب Mongoose حاليًا على هذا النحو:
</p>

<pre class="ipsCode">
...
mongoose.connect(url, {useNewUrlParser: true});
</pre>

<p>
	احذف التابع connect الموجود حاليًا وعوّضه بالشيفرة التالية التي تتضمن الثابتة options والوعد:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5199_23" style="">
<span class="pun">...</span><span class="pln">
mongoose</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="pln">url</span><span class="pun">,</span><span class="pln"> options</span><span class="pun">).</span><span class="pln">then</span><span class="pun">(</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'MongoDB is connected'</span><span class="pun">);</span><span class="pln">
</span><span class="pun">})</span><span class="pln">
  </span><span class="pun">.</span><span class="kwd">catch</span><span class="pun">(</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">err</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	في حالة اتصال ناجح، تسجّل دالّتنا رسالةً مناسبةً. وإلا فإنها تُمسك الخطأ وتسجّله، مما يتيح لنا استكشاف الأخطاء وإصلاحها.
</p>

<p>
	سيبدو الملف النهائي كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5199_25" style="">
<span class="kwd">const</span><span class="pln"> mongoose </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'mongoose'</span><span class="pun">);</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  MONGO_USERNAME</span><span class="pun">,</span><span class="pln">
  MONGO_PASSWORD</span><span class="pun">,</span><span class="pln">
  MONGO_HOSTNAME</span><span class="pun">,</span><span class="pln">
  MONGO_PORT</span><span class="pun">,</span><span class="pln">
  MONGO_DB
</span><span class="pun">}</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> options </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  useNewUrlParser</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
  reconnectTries</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Number</span><span class="pun">.</span><span class="pln">MAX_VALUE</span><span class="pun">,</span><span class="pln">
  reconnectInterval</span><span class="pun">:</span><span class="pln"> </span><span class="lit">500</span><span class="pun">,</span><span class="pln">
  connectTimeoutMS</span><span class="pun">:</span><span class="pln"> </span><span class="lit">10000</span><span class="pun">,</span><span class="pln">
</span><span class="pun">};</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> url </span><span class="pun">=</span><span class="pln"> </span><span class="pun">`</span><span class="pln">mongodb</span><span class="pun">:</span><span class="com">//${MONGO_USERNAME}:${MONGO_PASSWORD}@${MONGO_HOSTNAME}:${MONGO_PORT}/${MONGO_DB}?authSource=admin`;</span><span class="pln">

mongoose</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="pln">url</span><span class="pun">,</span><span class="pln"> options</span><span class="pun">).</span><span class="pln">then</span><span class="pun">(</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'MongoDB is connected'</span><span class="pun">);</span><span class="pln">
</span><span class="pun">})</span><span class="pln">
  </span><span class="pun">.</span><span class="kwd">catch</span><span class="pun">(</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">err</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	احفظ الملف وأغلقه عندما تنتهي من التحرير.
</p>

<p>
	لقد أضفت الآن مرونةً إلى شيفرة التطبيق لمعالجة الحالات التي قد يفشل فيها التطبيق في الاتصال بقاعدة البيانات. بوضعك لهذه الشيفرة في مكانها الصحيح، يمكنك الانتقال إلى تعريف خدماتك باستخدام "Compose".
</p>

<h2>
	الخطوة الرابعة: تعريف الخدمات باستخدام Docker Compose
</h2>

<p>
	بإعادة تشكيل الشيفرة، تكون مستعدًا لتحرير الملف docker-compose.yml بتعريفات خدماتك. الخدمة في Compose هي عبارة عن حاويةٍ قيد التشغيل، وتحتوي تعريفات الخدمة، التي ستتضمّنها في ملف docker-compose.yml، على معلومات حول كيفية اشتغال كل حاويةٍ صورةٍ (container image). تتيح لك الأداة "Compose" تعريف خدمات متعددة لإنشاء تطبيقات متعددة الحاويات.
</p>

<p>
	قبل تعريف خدماتنا، سنضيف، مع ذلك، أداةً إلى مشروعنا تسمى <a href="https://github.com/Eficode/wait-for" rel="external nofollow">wait-for</a> للتأكد من أن التطبيق لا يحاول الاتصال بقاعدة البيانات فقط بعد اكتمال مهام بدء تشغيل قاعدة البيانات. يستخدم هذا السكربت المجمِّع netcat لاستقصاء ما إذا كان المضيف والمنفذ المعيّنان يقبلان اتصالات TCP أم لا. ويتيح لك استخدامه التحكم في محاولات التطبيق للاتصال بقاعدة البيانات عن طريق اختبار جاهزية قاعدة البيانات لقبول الاتصالات من عدمها.
</p>

<p>
	رغم أنّ "Compose" يتيح لك تحديد الاعتماديات بين الخدمات باستخدام <a href="https://docs.docker.com/compose/compose-file/#depends_on" rel="external nofollow">الخيار depends<em>on</em></a><em>، فإنّ هذا الترتيب يعتمد على ما إذا كانت الحاوية تعمل أم لا وليس على جاهزيتها. لن يكون استخدام depends</em>on هو الأمثل لإعدادنا، لأننا نريد أن يتّصل تطبيقنا بقاعدة البيانات فقط عند اكتمال مهام بدء التشغيل فيها، بما في ذلك إضافة مستخدم وكلمة مرور إلى استيثاق قاعدة البيانات admin. لمزيد من المعلومات حول استخدام wait-for والأدوات الأخرى للتحكم في ترتيب بدء التشغيل، يرجى الاطلاع على التوصيات ذات الصلة في <a href="https://docs.docker.com/compose/startup-order/" rel="external nofollow">توثيق Compose</a>.
</p>

<p>
	افتح ملفًا يسمى wait-for.sh:
</p>

<pre class="ipsCode">
nano wait-for.sh
</pre>

<p>
	انسخ الشيفرة التالية في هذا الملف لإنشاء دالة الاستقصاء:
</p>

<pre class="ipsCode">
#!/bin/sh

# original script: https://github.com/eficode/wait-for/blob/master/wait-for

TIMEOUT=15
QUIET=0

echoerr() {
  if [ "$QUIET" -ne 1 ]; then printf "%s\n" "$*" 1&gt;&amp;2; fi
}

usage() {
  exitcode="$1"
  cat &lt;&lt; USAGE &gt;&amp;2
Usage:
  $cmdname host:port [-t timeout] [-- command args]
  -q | --quiet                        Do not output any status messages
  -t TIMEOUT | --timeout=timeout      Timeout in seconds, zero for no timeout
  -- COMMAND ARGS                     Execute command with args after the test finishes
USAGE
  exit "$exitcode"
}

wait_for() {
  for i in `seq $TIMEOUT` ; do
    nc -z "$HOST" "$PORT" &gt; /dev/null 2&gt;&amp;1

    result=$?
    if [ $result -eq 0 ] ; then
      if [ $# -gt 0 ] ; then
        exec "$@"
      fi
      exit 0
    fi
    sleep 1
  done
  echo "Operation timed out" &gt;&amp;2
  exit 1
}

while [ $# -gt 0 ]
do
  case "$1" in
    *:* )
    HOST=$(printf "%s\n" "$1"| cut -d : -f 1)
    PORT=$(printf "%s\n" "$1"| cut -d : -f 2)
    shift 1
    ;;
    -q | --quiet)
    QUIET=1
    shift 1
    ;;
    -t)
    TIMEOUT="$2"
    if [ "$TIMEOUT" = "" ]; then break; fi
    shift 2
    ;;
    --timeout=*)
    TIMEOUT="${1#*=}"
    shift 1
    ;;
    --)
    shift
    break
    ;;
    --help)
    usage 0
    ;;
    *)
    echoerr "Unknown argument: $1"
    usage 1
    ;;
  esac
done

if [ "$HOST" = "" -o "$PORT" = "" ]; then
  echoerr "Error: you need to provide a host and port to test."
  usage 2
fi

wait_for "$@"
</pre>

<p>
	احفظ الملف وأغلقه عند الانتهاء من إضافة الشيفرة.
</p>

<p>
	اجعل السكربتَ قابلًا للتنفيذ:
</p>

<pre class="ipsCode">
chmod +x wait-for.sh
</pre>

<p>
	بعد ذلك، افتح الملف docker-compose.yml:
</p>

<pre class="ipsCode">
nano docker-compose.yml
</pre>

<p>
	ابدأ بتعريف خدمة تطبيق nodejs عبر إضافة الشيفرة التالية إلى الملف:
</p>

<pre class="ipsCode">
version: '3'

services:
  nodejs:
    build:
      context: .
      dockerfile: Dockerfile
    image: nodejs
    container_name: nodejs
    restart: unless-stopped
    env_file: .env
    environment:
      - MONGO_USERNAME=$MONGO_USERNAME
      - MONGO_PASSWORD=$MONGO_PASSWORD
      - MONGO_HOSTNAME=db
      - MONGO_PORT=$MONGO_PORT
      - MONGO_DB=$MONGO_DB 
    ports:
      - "80:8080"
    volumes:
      - .:/home/node/app
      - node_modules:/home/node/app/node_modules
    networks:
      - app-network
    command: ./wait-for.sh db:27017 -- /home/node/app/node_modules/.bin/nodemon app.js
</pre>

<p>
	يتضمّن تعريف خدمة nodejs الخيارات التالية:
</p>

<ul>
<li>
		<p>
			<code>build</code>: هذا يحدد خيارات التكوين، بما في ذلك context وdockerfile، والتي ستُطبّق عندما يبني Docker صورة التطبيق. إذا كنت ترغب في استخدام صورة موجودة من سجلٍّ مثل <a href="https://hub.docker.com/" rel="external nofollow">Docker Hub</a>، فيمكنك استخدام <a href="https://docs.docker.com/compose/compose-file/#image" rel="external nofollow">التعليمة image</a> بدلاً من ذلك، مع معلومات حول اسم المستخدم والمستودع ووسم الصورة.
		</p>
	</li>
	<li>
		<p>
			<code>context</code>: هذا يحدّد سياق البناء الخاص ببناء الصورة، وهو في هذه الحالة مجلّد المشروع الحالي.
		</p>
	</li>
	<li>
		<p>
			<code>dockerfile</code> : هذا يحدّد الملفّ Dockerfile في مجلّد المشروع الحالي لكي يستخدم Compose هذا الملفّ لبناء صورة التطبيق. لمزيد من المعلومات حول هذا الملف، يرجى الاطلاع على [كيفية إنشاء تطبيق Node.js باستخدام Docker](رابط المقال الأول في هذه السلسلة).
		</p>
	</li>
	<li>
		<p>
			<code>image</code> و <code>container_name</code>: لإعطاء اسم لكلّ من الصورة والحاوية.
		</p>
	</li>
	<li>
		<p>
			<code>restart</code>: هذا يحدّد سياسة إعادة تشغيل الحاوية. تكون القيمة الافتراضية هي no، لكننا أعدنا تعيينها في yes ما لم يتم إيقافها.
		</p>
	</li>
	<li>
		<p>
			<code>env_file</code>: هذا يطلب من Compose إضافة متغيرات البيئة من ملفٍّ يسمى env. موجود في سياق البناء.
		</p>
	</li>
	<li>
		<p>
			<code>environment</code>: يتيح لك استخدام هذا الخيار إضافة إعدادات اتصال Mongo التي حدّدتها في ملف env. لاحظ أننا لا نعيّن NODE<em>ENV على development، لأن هذا هو السلوك الافتراضي لـ Express إذا لم يتم تعيين NODE</em>ENV. عند الانتقال إلى الإنتاج، يمكنك تعيين هذا على production لإتاحة التخزين المؤقت للعرض والتقليل من رسائل الخطأ المطوّلة. لاحظ أيضًا أننا حددنا حاوية قاعدة البيانات db كمضيف، كما وضّحناه في الخطوة الثانية.
		</p>
	</li>
	<li>
		<p>
			<code>port</code>: هذا يوجّه المنفذ 80 على المضيف إلى المنفذ 8080 على الحاوية.
		</p>
	</li>
	<li>
		<p>
			<code>Volumes</code>: نضمّن هنا نوعين من الربط:
		</p>
	</li>
	<li>
		<p>
			الأول هو وصل ترابطي (bind mount) يحمّل شيفرة التطبيق على المضيف إلى المجلّد /home/node/app في الحاوية. سيسهل هذا الأمر التطوير بسرعة، إذ سيتم إدخال أي تغييرات تجريها على شيفرة المضيف في الحاوية على الفور.
		</p>
	</li>
	<li>
		<p>
			والثاني هو عبارة عن <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D9%85%D8%B4%D8%A7%D8%B1%D9%83%D8%A9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%A8%D9%8A%D9%86-%D8%AD%D8%A7%D9%88%D9%8A%D8%A9-docker-%D9%88%D8%A7%D9%84%D9%85%D8%B6%D9%8A%D9%81-r314/" rel="">حجمٍ مُسمّى</a> node<em>modules. عندما ينفّذ Docker التعليمة <code>npm install</code> المدرجة في Dockerfile، سينشئ npm مجلّدًا جديدًا node</em>modules على الحاوية يتضمن الحزم المطلوبة لتشغيل التطبيق. ومع ذلك، سيحجب الوصل الترابطي الذي أنشأناه المجلّدَ node<em>modules حديث الإنشاء. ونظرًا لأن node</em>modules على المضيف فارغٌ، فسيعمل الوصل على تعيين مجلّدٍ فارغ على الحاوية، مع تحييد المجلّد node<em>modules الجديد ومنع التطبيق من بدء التشغيل. ويعمل الحجم node</em>modules المسمى على حلّ هذه المشكلة من خلال تثبيت محتويات المجلّد /home/node/app/node_modules وربطه مع الحاوية، مع إخفاء الربط.
		</p>
	</li>
</ul>
<p>
	ضع في حسبانك النقاط التالية عند استخدام هذه الطريقة:
</p>

<ul>
<li>
		<p>
			سيعمل الربط على تحميل محتويات مجلّد node_modules على الحاوية إلى المضيف وسيكون هذا المجلّد مِلكًا للجذر root، مادام الحجم المسمى أنشئ بواسطة Docker.
		</p>
	</li>
	<li>
		<p>
			إذا كان لديك مجلّد node<em>modules موجود مسبقًا على المضيف، فسيحيِّد المجلّد node</em>modules الذي أنشئ على الحاوية. يفترض الإعداد الذي نبنيه في هذا الدرس أنك لا تملك مجلّدًا node_modules موجود مسبقًا وأنك لن تعمل مع npm على مضيفك. هذا يتماشى مع <a href="https://12factor.net/" rel="external nofollow">طريقة اثني عشر عامل لتطوير التطبيقات</a> التي تقلل من الاعتماديات بين بيئات التنفيذ.
		</p>
	</li>
	<li>
		<p>
			<code>networks</code>: هذا يحدّد أن خدمة التطبيق ستنضم إلى الشبكة app-network التي سنعرّفها في أسفل الملف.
		</p>
	</li>
	<li>
		<p>
			<code>command</code>: يتيح لك هذا الخيار تعيين الأمر الذي يجب تنفيذه عندما يشغّل Compose الصورة. لاحظ أن هذا سوف يحيّد تعليمات CMD التي وضعناها في تطبيق Dockerfile الخاص بنا. نشغِّل هنا التطبيق باستخدام السكربت wait-for، الذي سيستقصي الخدمة db على المنفذ 27017 لاختبار جاهزية خدمة قاعدة البيانات من عدمها. بمجرد نجاح اختبار الاستعداد، سينفذ السكربت الأمر الذي حددناه، ‎/home/node/app/node_modules/.bin/nodemon app.js، لتشغيل التطبيق عبر nodemon. وسيضمن هذا إعادة تحميل أي تعديلات مستقبلية نجريها على شيفرتنا دون الحاجة إلى إعادة تشغيل التطبيق.
		</p>
	</li>
</ul>
<p>
	بعد ذلك، أنشئ خدمة db من خلال إضافة الشيفرة التالية أسفل تعريف خدمة التطبيق:
</p>

<ul>
<li>
		الملف <code>‎~/node_project/docker-compose.yml</code>:
	</li>
</ul>
<pre class="ipsCode">
...
  db:
    image: mongo:4.1.8-xenial
    container_name: db
    restart: unless-stopped
    env_file: .env
    environment:
      - MONGO_INITDB_ROOT_USERNAME=$MONGO_USERNAME
      - MONGO_INITDB_ROOT_PASSWORD=$MONGO_PASSWORD
    volumes:  
      - dbdata:/data/db   
    networks:
      - app-network 
</pre>

<p>
	تظل بعض الإعدادات التي حددناها لخدمة nodejs كما هي، لكننا أجرينا أيضًا التعديلات التالية على تعريفات image و environment و volumes:
</p>

<ul>
<li>
		<code>image</code>: لإنشاء هذه الخدمة، سيسحب Compose صورة 4.1.8-xenial الخاصة ب Mongo من Docker Hub. نحن نثبّت هنا إصدارًا معينًا لتجنب التعارضات المستقبلية المحتملة عند تغير صورة Mongo. لمزيد من المعلومات حول تثبيت الإصدار، يرجى الاطلاع على توثيق Docker حول <a href="https://docs.docker.com/develop/develop-images/dockerfile_best-practices/" rel="external nofollow">أفضل ممارسات Dockerfile</a>.
	</li>
	<li>
		MONGO<em>INITDB</em>ROOT<em>USERNAME و MONGO</em>INITDB<em>ROOT</em>PASSWORD: تجعل صورة mongo متغيرات البيئة هذه متاحةً لكي تستطيع تعديل التهيئة الأولية لمثيل قاعدة بياناتك. وينشئ MONGO<em>INITDB</em>ROOT<em>USERNAME و MONGO</em>INITDB<em>ROOT</em>PASSWORD معًا مستخدمًا root في استيثاق admin لقاعدة البيانات مع التأكد من تفعيل الاستيثاق عند بدء تشغيل الحاوية. لقد عيّننا MONGO<em>INITDB</em>ROOT<em>USERNAME و MONGO</em>INITDB<em>ROOT</em>PASSWORD باستخدام قيم مأخوذة من الملف env.، والتي نمرّرها إلى الخدمة db عبر الخيار env_file. يعني القيام بذلك أن مستخدم التطبيق sammy سيكون مستخدمًا أساسيًا في مثيل قاعدة البيانات، مع إمكانية الوصول إلى جميع الصلاحيات الإدارية والتشغيلية لهذا الدور. عند العمل على الإنتاج، سترغب في إنشاء مستخدم تطبيق مخصص ذي صلاحيات محدّدة بدقّة.
	</li>
</ul>
<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		ملاحظة: ضع في حسبانك أن هذه المتغيرات لن تكون سارية المفعول إذا شغلت الحاوية بمجلّد بيانات موجود مسبقًا.
	</p>
</blockquote>

<ul>
<li>
		dbdata:/data/db: سيثبّت الحجم المسمى dbdata البيانات المخزنة في مجلّد البيانات الافتراضي ‎/data/db الخاص بـ Mongo . سيضمن هذا أنك لن تفقد البيانات في الحالات التي توقف فيها أو تحذف الحاويات.
	</li>
</ul>
<p>
	لقد أضفنا أيضًا خدمة db إلى شبكة app-network باستخدام الخيار networks.
</p>

<p>
	كخطوة أخيرة، أضف تعريفات الحجم والشبكة إلى أسفل الملف:
</p>

<pre class="ipsCode">
...
networks:
  app-network:
    driver: bridge

volumes:
  dbdata:
  node_modules:
</pre>

<p>
	تتيح شبكة المستخدم الجسرية app-network التواصل بين حاوياتنا مادامت موجودة على نفس المضيف العفريت Docker. يعمل هذا على تبسيط حركة المرور والاتصال داخل التطبيق، إذ يفتح جميع المنافذ بين الحاويات على نفس الشبكة الجسرية، دون تعريض أي منفذ للعالم الخارجي. وبالتالي، فإن حاويات db و nodejs تستطيع التواصل مع بعضها البعض، وسنحتاج فقط إلى فتح المنفذ 80 للوصول الأمامي (front-end access) إلى التطبيق.
</p>

<p>
	يعرّف مفتاح المستوى الأعلى volumes الحجمين dbdata و node_modules. وعندما ينشئ Docker حجومًا، تُخزّن محتوياتها في جزء من ملفات المضيف النظامية، /var/lib/docker/volumes/ ، التي يديرها Docker. تخزّن محتويات كل حجمٍ في مجلّد تحت /var/lib/docker/volumes/ وتُثبت على أي حاوية تستخدم الحجم. وبهذه الطريقة، ستكون بيانات معلومات سمك القرش التي سيقوم المستخدمون بإنشائها مثبّتة في الحجم dbdata حتى لو أزلنا الحاوية db وأعدنا إنشاءها.
</p>

<p>
	سيبدو ملف docker-compose.yml النهائي كما يلي:
</p>

<pre class="ipsCode">
version: '3'

services:
  nodejs:
    build:
      context: .
      dockerfile: Dockerfile
    image: nodejs
    container_name: nodejs
    restart: unless-stopped
    env_file: .env
    environment:
      - MONGO_USERNAME=$MONGO_USERNAME
      - MONGO_PASSWORD=$MONGO_PASSWORD
      - MONGO_HOSTNAME=db
      - MONGO_PORT=$MONGO_PORT
      - MONGO_DB=$MONGO_DB
    ports:
      - "80:8080"
    volumes:
      - .:/home/node/app
      - node_modules:/home/node/app/node_modules
    networks:
      - app-network
    command: ./wait-for.sh db:27017 -- /home/node/app/node_modules/.bin/nodemon app.js 

  db:
    image: mongo:4.1.8-xenial
    container_name: db
    restart: unless-stopped
    env_file: .env
    environment:
      - MONGO_INITDB_ROOT_USERNAME=$MONGO_USERNAME
      - MONGO_INITDB_ROOT_PASSWORD=$MONGO_PASSWORD
    volumes:     
      - dbdata:/data/db
    networks:
      - app-network  

networks:
  app-network:
    driver: bridge

volumes:
  dbdata:
  node_modules:  
</pre>

<p>
	احفظ الملف وأغلقه عند الانتهاء من التحرير.
</p>

<p>
	بوضعك لتعريفات الخدمة في مكانها الصحيح، تكون مستعدًّا لبدء تشغيل التطبيق.
</p>

<h2>
	الخطوة الخامسة: اختبار التطبيق
</h2>

<p>
	بعد إنشاء وتحرير ملف docker-compose.yml ، يمكنك إنشاء خدماتك باستخدام الأمر docker-compose up. يمكنك أيضًا اختبار ثبات بياناتك عبر إيقاف حاوياتك وحذفها باستخدام Docker.
</p>

<p>
	ابدأ أولًا ببناء صور الحاوية وإنشاء الخدمات عبر تنفيذ docker-compose up باستخدام الراية d- ، والذي سيشغّل حاويات nodejs و db في الخلفية:
</p>

<pre class="ipsCode">
docker-compose up -d
</pre>

<p>
	سيظهر لك الإخراج الذي يؤكد أن خدماتك أُنشئت فعلًا:
</p>

<pre class="ipsCode">
...
Creating db ... done
Creating nodejs ... done
</pre>

<p>
	يمكنك أيضًا الحصول على معلومات أكثر تفصيلاً حول عمليات التشغيل بعرضك لإخراج سجل الخدمات:
</p>

<pre class="ipsCode">
docker-compose logs
</pre>

<p>
	سيظهر لك إخراج مثل هذا إذا اشتغل كل شيء بشكل صحيح:
</p>

<pre class="ipsCode">
...
nodejs    | [nodemon] starting `node app.js`
nodejs    | Example app listening on 8080!
nodejs    | MongoDB is connected
...
db        | 2019-02-22T17:26:27.329+0000 I ACCESS   [conn2] Successfully authenticated as principal sammy on admin
</pre>

<p>
	يمكنك أيضًا التحقق من حالة حاوياتك باستخدام <code>docker-compose ps</code>:
</p>

<pre class="ipsCode">
docker-compose ps
</pre>

<p>
	سيظهر لك الإخراج التالي مشيرًا إلى أن حاوياتك تعمل:
</p>

<pre class="ipsCode">
 Name               Command               State          Ports        
----------------------------------------------------------------------
db       docker-entrypoint.sh mongod      Up      27017/tcp           
nodejs   ./wait-for.sh db:27017 --  ...   Up      0.0.0.0:80-&gt;8080/tcp
</pre>

<p>
	بعد تشغيل خدماتك، يمكنك تصفّح: http://your<em>server</em>ip في المتصفح. ستظهر لك صفحة الهبوط على هذا النحو:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="33619" href="https://academy.hsoub.com/uploads/monthly_2020_02/landing_page.png.d234f5bef996aff42b4efc241fe8208b.png" rel=""><img alt="landing_page.png" class="ipsImage ipsImage_thumbnailed" data-fileid="33619" data-unique="data-unique" src="https://academy.hsoub.com/uploads/monthly_2020_02/landing_page.thumb.png.969460c6c1a9955eced6fbfab04dc82c.png"></a>
</p>

<p>
	انقر على زر الحصول على معلومات القرش. ستظهر لك صفحة بنموذج إدخال يمكنك إدخال الاسم ووصف السلوك العام لسمك القرش:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="33623" href="https://academy.hsoub.com/uploads/monthly_2020_02/shark_form.png.8a71a79ac15960127d88d0c173ac3a71.png" rel=""><img alt="shark_form.png" class="ipsImage ipsImage_thumbnailed" data-fileid="33623" data-unique="data-unique" src="https://academy.hsoub.com/uploads/monthly_2020_02/shark_form.thumb.png.aaf4006cc08afaddc542aa9c09be35f1.png"></a>
</p>

<p>
	أضف سمكة قرش من اختيارك في النموذج. لأغراض هذا العرض التوضيحي، سنضيف Megalodon Shark إلى حقل Shark Name، وAncient إلى حقل Shark Character:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="33622" href="https://academy.hsoub.com/uploads/monthly_2020_02/shark_filled.png.71286c7b967eadb0e87973359313f0e2.png" rel=""><img alt="shark_filled.png" class="ipsImage ipsImage_thumbnailed" data-fileid="33622" data-unique="data-unique" src="https://academy.hsoub.com/uploads/monthly_2020_02/shark_filled.thumb.png.11ba0353102c901a96a641ff9268c32b.png"></a>
</p>

<p>
	انقر على زر الإرسال. ستظهر لك صفحة بها معلومات القرش معروضة:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="33621" href="https://academy.hsoub.com/uploads/monthly_2020_02/shark_added.png.d94f3df95a8672cce78cf028aa811027.png" rel=""><img alt="shark_added.png" class="ipsImage ipsImage_thumbnailed" data-fileid="33621" data-unique="data-unique" src="https://academy.hsoub.com/uploads/monthly_2020_02/shark_added.thumb.png.bee88d2c528f77cccc196c145ee732ed.png"></a>
</p>

<p>
	كخطوة أخيرة، يمكننا اختبار ثبات البيانات التي أدخلتها للتو إذا حذفنا حاوية قاعدة البيانات.
</p>

<p>
	ارجع مرة أخرى إلى الطرفية، واكتب الأمر التالي لإيقاف وحذف الحاويات والشبكة:
</p>

<pre class="ipsCode" id="ips_uid_5199_31">
docker-compose down

</pre>

<p>
	لاحظ أننا لم ندرج الخيار <code>‎--volumes</code>؛ وبالتالي، لن يُحذف الحجم dbdata.
</p>

<p>
	يؤكد الإخراج التالي حذف الحاويات والشبكة:
</p>

<pre class="ipsCode">
Stopping nodejs ... done
Stopping db     ... done
Removing nodejs ... done
Removing db     ... done
Removing network node_project_app-network
</pre>

<p>
	أعد الآن إنشاء الحاويات:
</p>

<pre class="ipsCode">
docker-compose up -d
</pre>

<p>
	ثم عد إلى نموذج معلومات سمك القرش:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="33623" href="https://academy.hsoub.com/uploads/monthly_2020_02/shark_form.png.8a71a79ac15960127d88d0c173ac3a71.png" rel=""><img alt="shark_form.png" class="ipsImage ipsImage_thumbnailed" data-fileid="33623" data-unique="yd3fq96bw" src="https://academy.hsoub.com/uploads/monthly_2020_02/shark_form.thumb.png.aaf4006cc08afaddc542aa9c09be35f1.png"></a>
</p>

<p>
	أدخل سمكة قرش جديدة من اختيارك. سنستعمل هنا Whale Shark وLarge:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="33624" href="https://academy.hsoub.com/uploads/monthly_2020_02/whale_shark.png.cb1e25a3c17d8a4643f7ea4e2b1d42dc.png" rel=""><img alt="whale_shark.png" class="ipsImage ipsImage_thumbnailed" data-fileid="33624" data-unique="data-unique" src="https://academy.hsoub.com/uploads/monthly_2020_02/whale_shark.thumb.png.93492987f7cd02fc84546615b559e084.png"></a>
</p>

<p>
	بمجرد النقر على زر الإرسال، سترى أن القرش الجديد أُضيف إلى مجموعة سمك القرش في قاعدة بياناتك دون أن تفقد البيانات التي أدخلتها سابقًا:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="33620" href="https://academy.hsoub.com/uploads/monthly_2020_02/persisted_data.png.47c17dd589ec06552a39b4d678c1188e.png" rel=""><img alt="persisted_data.png" class="ipsImage ipsImage_thumbnailed" data-fileid="33620" data-unique="gmit107g6" src="https://academy.hsoub.com/uploads/monthly_2020_02/persisted_data.thumb.png.1713ce86ab9ab712f2d68c2e73b2d07a.png"></a>
</p>

<p>
	إنّ تطبيقك يشتغل الآن على حاويات Docker مع تفعيل خاصيتي ثبات البيانات ومزامنة الشيفرة.
</p>

<h2>
	خاتمة
</h2>

<p>
	باتباع هذا الدرس، أنشأت إعداد تطويرٍ لتطبيقك Node باستخدام حاويات Docker. لقد تمكنت من جعل مشروعك أكثر نمطيّةً وأكثر قابلية للنقل من خلال استخراج المعلومات الحساسة وفصل حالة التطبيق عن شيفرة التطبيق. لقد أعددت أيضًا تكوين ملف الشيفرة المتداولة docker-compose.yml الذي تستطيع مراجعته كلما تغيرت احتياجاتك ومتطلبات التطوير الخاصة بك.
</p>

<p>
	قد تكون مهتمًا، أثناء عمليتك التطويرية ، بمعرفة المزيد حول تصميم التطبيقات بسير عمل يعتمد على الحاويات وعلى Cloud Native. يرجى الاطلاع على <a href="https://www.digitalocean.com/community/tutorials/architecting-applications-for-kubernetes" rel="external nofollow">تصميم التطبيقات لKubernetes</a> و<a href="https://www.digitalocean.com/community/tutorials/modernizing-applications-for-kubernetes" rel="external nofollow">تحديث التطبيقات ل Kubernetes</a> إذا كانت لغتك الإنجليزية جيدة لمزيد من المعلومات حول هذه المواضيع.
</p>

<p>
	لمعرفة المزيد حول الشيفرة المستخدم في هذا الدرس، يرجى الاطلاع على <a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%A8%D9%86%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-nodejs-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-docker-r809/" rel="">كيفية إنشاء تطبيق Node.js باستخدام Docker</a> و<a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%AF%D9%85%D8%AC-%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-mongodb-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D9%83-node-r810/" rel="">كيفية دمج MongoDB مع تطبيق Node الخاص بك</a>. وللحصول على معلومات حول نشر تطبيق Node باستخدام وكيل عكسي Nginx يعتمد على الحاويات، يرجى الاطلاع على <a href="https://www.digitalocean.com/community/tutorials/how-to-secure-a-containerized-node-js-application-with-nginx-let-s-encrypt-and-docker-compose" rel="external nofollow">كيفية تأمين تطبيق Node.js في حاويات باستخدام Nginx و Let’s Encrypt و Docker Compose</a>.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/containerizing-a-node-js-application-for-development-with-docker-compose" rel="external nofollow">Containerizing a Node.js Application for Development With Docker Compose</a> لصاحبته Kathleen Juell
</p>
]]></description><guid isPermaLink="false">811</guid><pubDate>Wed, 05 Feb 2020 06:29:44 +0000</pubDate></item></channel></rss>
