<?xml version="1.0"?>
<rss version="2.0"><channel><title>&#x627;&#x644;&#x628;&#x631;&#x645;&#x62C;&#x629;: Express</title><link>https://academy.hsoub.com/programming/javascript/nodejs/express/?d=2</link><description>&#x627;&#x644;&#x628;&#x631;&#x645;&#x62C;&#x629;: Express</description><language>ar</language><item><title>&#x628;&#x646;&#x627;&#x621; &#x648;&#x627;&#x62C;&#x647;&#x629; &#x628;&#x631;&#x645;&#x62C;&#x64A;&#x629; &#x645;&#x62A;&#x648;&#x627;&#x641;&#x642;&#x629; &#x645;&#x639; REST &#x641;&#x64A; Express.js &#x627;&#x644;&#x642;&#x633;&#x645; &#x627;&#x644;&#x631;&#x627;&#x628;&#x639;: &#x627;&#x644;&#x633;&#x645;&#x627;&#x62D;&#x64A;&#x627;&#x62A; &#x648;&#x627;&#x644;&#x627;&#x62E;&#x62A;&#x628;&#x627;&#x631;&#x627;&#x62A; &#x627;&#x644;&#x645;&#x624;&#x62A;&#x645;&#x62A;&#x629;</title><link>https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%A8%D9%86%D8%A7%D8%A1-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-%D9%85%D8%AA%D9%88%D8%A7%D9%81%D9%82%D8%A9-%D9%85%D8%B9-rest-%D9%81%D9%8A-expressjs-%D8%A7%D9%84%D9%82%D8%B3%D9%85-%D8%A7%D9%84%D8%B1%D8%A7%D8%A8%D8%B9-%D8%A7%D9%84%D8%B3%D9%85%D8%A7%D8%AD%D9%8A%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%A4%D8%AA%D9%85%D8%AA%D8%A9-r2334/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_05/-----REST---Node.js-TypeScript-----.png.68c1831273abe6720a2d0a11cd8b6005.png" /></p>
<p>
	ننهي في هذا المقال اﻷخير من سلسلتنا ما بدأناه في <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%A8%D9%86%D8%A7%D8%A1-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-%D9%85%D8%AA%D9%88%D8%A7%D9%81%D9%82%D8%A9-%D9%85%D8%B9-rest-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-expressjs-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-r2327/" rel="">إنشاء واجهة برمجية REST باستخدام Express.js</a>، ونناقش فيه موضوع السماحيات permissions والاختبارات المؤتمتة لعمل التطبيق.
</p>

<h2 id="">
	سماحيات المستخدم
</h2>

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

<h3 id="and2">
	عامل مقارنة البتات AND (<code>&amp;</code>) وقوة العدد 2
</h3>

<p>
	نستخدم العامل <code>&amp;</code> المضمّن في جافا سكريبت ﻹدارة السماحيات. إذ نخزّن كل التصريحات ضمن كيان واحد ورقم واحد يخص كل مستخدم. ثم باستخدام التمثيل الثنائي (<code>0100</code> للرقم 4 مثلًا) للرقم الخاص بالمستخدم والعامل <code>&amp;</code> يمكننا معرفة التصريحات المعطاة له. لا تعر اهتمامًا كبيرًا بالرياضيات طالما أن الفكرة سهلة الاستخدام.
</p>

<p>
	نعرّف كل نوع من التصريحات (رايات سماحية permission flags) على شكل قوّة للرقم 2 أي: 2, 4, 8, 16… ، وهكذا حتى نحصل على حد أعظمي مكوّن من 31 راية.
</p>

<p>
	لنأخذ مثالًا عمليًا عن مدوّنة تقدم محتوى صوتي إضافة إلى النصي تُعطى فيها السماحيات التالية:
</p>

<ul>
	<li>
		1: مؤلف ويمكنه التعديل على النص.
	</li>
	<li>
		2: مصوّر ويمكنه التعديل على الصور.
	</li>
	<li>
		4: معلّق ويمكنه تغيير الملف الصوتي الخاص بكل فقرة نصية.
	</li>
	<li>
		8: مترجم يمكنه التعديل على الترجمات.
	</li>
</ul>

<p>
	وباستخدام النهج السابق يمكن إعطاء كل أنواع السماحيات للمستخدمين، وإليك أمثلة عن ذلك:
</p>

<ul>
	<li>
		<strong>المستخدم له صلاحية تعديل النص</strong>:سيُمنح الرقم 1 فقط.
	</li>
	<li>
		<strong>المستخدم له صلاحية تعديل النص والتعليق عليه</strong>: سيُمنح الرقم <code>5=1+4</code>.
	</li>
	<li>
		<strong>المستخدم له صلاحية تعديل النص والصور</strong>: سيُمنح الرقم <code>3=2+1</code>
	</li>
	<li>
		<strong>المستخدم له صلاحية تعديل النص والصور والترجمة</strong>: سيُمنح الرقم <code>11=1+2+8</code>
	</li>
	<li>
		<strong>المستخدم مدير له جميع الصلاحيات الحالية والمستقبلية</strong>: سيُمنح الرقم <code>2,147,483,647</code> وهو أعلى عدد صحيح مكوّن من 32 بت.
	</li>
</ul>

<p>
	لكن كيف سيجري اﻷمر؟
</p>

<p>
	لنفرض الحالة التي يحمل فيها المستخدم الرقم 12 الذي يُمثّل ثنائيًا كالتالي <code>00001100</code>، ويحاول التعليق الصوتي ذو التصريح رقم 4 الذي يُمثل ثنائيًا كالتالي <code>00000100</code>. نستخدم اﻵن العملية <code>&amp;</code> على الرقمين السابقين كالتالي: <code>00001100 &amp; 00000100</code> ستكون النتيجة <code>00000100</code> وذلك بمقارنة قيمة كل خانة من الأول مه مقابلتها في الثاني ووضع النتيجة <code>1</code> إذا حملت الخانتين القيمة <code>1</code> وصفر فيما عدا ذلك. إن النتيجة هي الرقم 4 وبالتالي يُسمح له بالتعليق الصوتي. نطبق نفس الخوارزمية على رقم المستخدم والصلاحية التي يريد استخدامها فإن كانت نتيجة العملية <code>&amp;</code> هي <code>0</code> يُمنع من استخدام الصلاحية وإلا سيُسمح له باستخدامها (جرّب أرقامًا أخرى!).
</p>

<h3 id="-1">
	كتابة شيفرة رايات السماحيات
</h3>

<p>
	نخزن رايات السماحيات ضمن المجلد <code>common</code> لأن منطق العملية قد يتكرر في وحدات أخرى مستقبلًا، ثم ننشئ الملف <code>common/middleware/common.permissionflag.enum.ts</code> ليحمل قيم بعض الرايات:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1530_6" style=""><span class="kwd">export</span><span class="pln"> </span><span class="kwd">enum</span><span class="pln"> </span><span class="typ">PermissionFlag</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    FREE_PERMISSION </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln">
    PAID_PERMISSION </span><span class="pun">=</span><span class="pln"> </span><span class="lit">2</span><span class="pun">,</span><span class="pln">
    ANOTHER_PAID_PERMISSION </span><span class="pun">=</span><span class="pln"> </span><span class="lit">4</span><span class="pun">,</span><span class="pln">
    ADMIN_PERMISSION </span><span class="pun">=</span><span class="pln"> </span><span class="lit">8</span><span class="pun">,</span><span class="pln">
    ALL_PERMISSIONS </span><span class="pun">=</span><span class="pln"> </span><span class="lit">2147483647</span><span class="pun">,</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	<strong>ملاحظة</strong>: طالما أن التطبيق هو مجرد مثال، اخترنا أن تكون أسماء الصلاحيات معممة.
</p>

<p>
	لنعد اﻵن إلى الدالة <code>()addUser</code> ضمن كائن DAO لاستبدال الرقم <code>1</code> بالقيمة <code>PermissionFlag.FREE_PERMISSION</code> بعد إدراج الملف السابق في مكان مناسب. كما يمكن إدراج هذا الملف ضمن ملف جديد لوحدة وسيطة تضم صنف متفرّد يُدعى <code>PermissionFlag.FREE_PERMISSION</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1530_8" 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"> </span><span class="pun">{</span><span class="pln"> </span><span class="typ">PermissionFlag</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> from </span><span class="str">'./common.permissionflag.enum'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> debug from </span><span class="str">'debug'</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> log</span><span class="pun">:</span><span class="pln"> debug</span><span class="pun">.</span><span class="typ">IDebugger</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> debug</span><span class="pun">(</span><span class="str">'app:common-permission-middleware'</span><span class="pun">);</span></pre>

<p>
	وبدلًا من إنشاء عدة دوال وسيطة مشابهة، نستخدم نمط المصنع factory pattern ﻹنشاء مصنع للتوابع أو الدوال (أو ببساطة مصنع). تسمح لنا دالة المصنع توليد دوال وسيطة -عند تهيئة المسار- تتحقق من راية أية سماحية مطلوبة. وهكذا نتفادى تكرار الدوال الوسيطة عندما نضيف راية جديدة.
</p>

<p>
	إليك شيفرة المصنع الذي يوّلد الدوال الوسيطة التي تتحقق من رايات السماحيات:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1530_10" style=""><span class="pln">permissionFlagRequired</span><span class="pun">(</span><span class="pln">requiredPermissionFlag</span><span class="pun">:</span><span class="pln"> </span><span class="typ">PermissionFlag</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">
        req</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">,</span><span class="pln">
        res</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Response</span><span class="pun">,</span><span class="pln">
        next</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">NextFunction</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">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">const</span><span class="pln"> userPermissionFlags </span><span class="pun">=</span><span class="pln"> parseInt</span><span class="pun">(</span><span class="pln">
                res</span><span class="pun">.</span><span class="pln">locals</span><span class="pun">.</span><span class="pln">jwt</span><span class="pun">.</span><span class="pln">permissionFlags
            </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">userPermissionFlags </span><span class="pun">&amp;</span><span class="pln"> requiredPermissionFlag</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"> </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">status</span><span class="pun">(</span><span class="lit">403</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">
        </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">
            log</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">
    </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_1530_12" style=""><span class="kwd">async</span><span class="pln"> onlySameUserOrAdminCanDoThisAction</span><span class="pun">(</span><span class="pln">
    req</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">,</span><span class="pln">
    res</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Response</span><span class="pun">,</span><span class="pln">
    next</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">NextFunction</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"> userPermissionFlags </span><span class="pun">=</span><span class="pln"> parseInt</span><span class="pun">(</span><span class="pln">res</span><span class="pun">.</span><span class="pln">locals</span><span class="pun">.</span><span class="pln">jwt</span><span class="pun">.</span><span class="pln">permissionFlags</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">
        req</span><span class="pun">.</span><span class="pln">params </span><span class="pun">&amp;&amp;</span><span class="pln">
        req</span><span class="pun">.</span><span class="pln">params</span><span class="pun">.</span><span class="pln">userId </span><span class="pun">&amp;&amp;</span><span class="pln">
        req</span><span class="pun">.</span><span class="pln">params</span><span class="pun">.</span><span class="pln">userId </span><span class="pun">===</span><span class="pln"> res</span><span class="pun">.</span><span class="pln">locals</span><span class="pun">.</span><span class="pln">jwt</span><span class="pun">.</span><span class="pln">userId
    </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"> next</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">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">userPermissionFlags </span><span class="pun">&amp;</span><span class="pln"> </span><span class="typ">PermissionFlag</span><span class="pun">.</span><span class="pln">ADMIN_PERMISSION</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"> next</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"> res</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">403</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">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	نضيف اﻵن دالة وسيطة جديدة إلى الملف <code>users.middleware.ts</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1530_14" style=""><span class="kwd">async</span><span class="pln"> userCantChangePermission</span><span class="pun">(</span><span class="pln">
    req</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">,</span><span class="pln">
    res</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Response</span><span class="pun">,</span><span class="pln">
    next</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">NextFunction</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">
        </span><span class="str">'permissionFlags'</span><span class="pln"> in req</span><span class="pun">.</span><span class="pln">body </span><span class="pun">&amp;&amp;</span><span class="pln">
        req</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">permissionFlags </span><span class="pun">!==</span><span class="pln"> res</span><span class="pun">.</span><span class="pln">locals</span><span class="pun">.</span><span class="pln">user</span><span class="pun">.</span><span class="pln">permissionFlags
    </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">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">
            errors</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="str">'User cannot change permission flags'</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">
        next</span><span class="pun">();</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	وطالما أن الدالة السابقة تعتمد على القيمة <code>res.locals.user</code>، باﻹمكان نشرها ضمن الدالة <code>()validateUserExists</code> قبل استدعاء <code>()next</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1530_16" style=""><span class="com">// ...</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">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    res</span><span class="pun">.</span><span class="pln">locals</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">
    next</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="com">// ...</span></pre>

<p>
	إن ما فعلناه ضمن الدالة <code>()validateUserExists</code> ينفي ضرورة تنفيذه ضمن الدالة <code>()validateSameEmailBelongToSameUser</code>، لهذا يمكن حذف استدعاء قاعدة البيانات فيه واستبداله بالقيمة التي يمكن نضمّن أنها مخزّنة في <code>res.locals</code>:
</p>

<pre class="ipsCode">-        const user = await userService.getUserByEmail(req.body.email);
-        if (user &amp;&amp; user.id === req.params.userId) {
+        if (res.locals.user._id === req.params.userId) {
</pre>

<p>
	نستطيع اﻵن ربط منطق تحديد السماحيات بالملف <code>users.routes.config.ts</code>.
</p>

<h3 id="-2">
	طلب التصريحات
</h3>

<p>
	ندرج بداية اﻷداة الوسيطة الجديدة والملف <code>enum</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1530_18" style=""><span class="kwd">import</span><span class="pln"> jwtMiddleware from </span><span class="str">'../auth/middleware/jwt.middleware'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> permissionMiddleware from </span><span class="str">'../common/middleware/common.permission.middleware'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="typ">PermissionFlag</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> from </span><span class="str">'../common/middleware/common.permissionflag.enum'</span><span class="pun">;</span></pre>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1530_20" style=""><span class="kwd">this</span><span class="pun">.</span><span class="pln">app
    </span><span class="pun">.</span><span class="pln">route</span><span class="pun">(`/</span><span class="pln">users</span><span class="pun">`)</span><span class="pln">
    </span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="pln">
        jwtMiddleware</span><span class="pun">.</span><span class="pln">validJWTNeeded</span><span class="pun">,</span><span class="pln">
        permissionMiddleware</span><span class="pun">.</span><span class="pln">permissionFlagRequired</span><span class="pun">(</span><span class="pln">
            </span><span class="typ">PermissionFlag</span><span class="pun">.</span><span class="pln">ADMIN_PERMISSION
        </span><span class="pun">),</span><span class="pln">
        </span><span class="typ">UsersController</span><span class="pun">.</span><span class="pln">listUsers
    </span><span class="pun">)</span><span class="pln">
</span><span class="com">// ...</span></pre>

<p>
	وتذكّر أن استدعاء دالة المصنع يُعيد دالة وسيطة، وبالتالي تُعيد مرجعًا إلى الدوال الوسيطة اﻷخرى التي لا تنتج عنها دون أن تُنفّذ.
</p>

<p>
	من القيود اﻷخرى هو منع الوصول إلى المسارات التي تضم معرّف المستخدم <code>userId</code>، ما لم يكن صاحب التصريح هو صاحب المعرّف أو المدير:
</p>

<pre class="ipsCode">             .route(`/users/:userId`)
-            .all(UsersMiddleware.validateUserExists)
+            .all(
+                UsersMiddleware.validateUserExists,
+                jwtMiddleware.validJWTNeeded,
+                permissionMiddleware.onlySameUserOrAdminCanDoThisAction
+            )
             .get(UsersController.getUserById)
</pre>

<p>
	وعلينا أيضًا منع المستخدمين من رفع مستوى سماحياتهم عن طريق إضافة القيمة <code>UsersMiddleware.userCantChangePermission</code> قبل المرجع إلى الدالة في نهاية الوجهات التي تقود إلى العمليتين <code>PUT</code> و <code>PATCH</code>.
</p>

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

<p>
	باﻹمكان تنفيذ اﻷمر بإضافة دالة مولّدة أخرى بعد كل مرجع أضفناه إلى التصريح <code>userCantChangePermission</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1530_22" style=""><span class="pln">permissionMiddleware</span><span class="pun">.</span><span class="pln">permissionFlagRequired</span><span class="pun">(</span><span class="pln">
    </span><span class="typ">PermissionFlag</span><span class="pun">.</span><span class="pln">PAID_PERMISSION
</span><span class="pun">),</span></pre>

<p>
	وهكذا نكون مستعدين ﻹعادة تشغيل Node.js والتجريب.
</p>

<h3 id="-3">
	الاختبار اليدوي للتصريحات
</h3>

<p>
	لنحاول بداية أن نحصل على قائمة المستخدمين دون مفتاح:
</p>

<pre class="ipsCode">curl --include --request GET 'localhost:3000/users' \
--header 'Content-Type: application/json'
</pre>

<p>
	ستكون الاستجابة <code>HTTP 401</code> لاننا بحاجة إلى مفتاح JWT صالح، لهذا سنعيد المحاولة باستخدام مفتاح الوصول الذي حصلنا عليه في مقالنا السابق:
</p>

<pre class="ipsCode">curl --include --request GET 'localhost:3000/users' \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer $REST_API_EXAMPLE_ACCESS"
</pre>

<p>
	وسنحصل هذه المرة أيضًا على الاستجابة <code>HTTP 403</code> لأن المفتاح صالح لكن الوصول إلى نقطة الوصول هذه مرفوض لعدم امتلاك الطلب التصريح <code>ADMIN_PERMISSION</code>.
</p>

<p>
	,وبالطبع لن نحتاج إلى تصريح كهذا للحصول على سجل المستخدم نفسه:
</p>

<pre class="ipsCode">curl --request GET "localhost:3000/users/$REST_API_EXAMPLE_ID" \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer $REST_API_EXAMPLE_ACCESS"
</pre>

<p>
	وستكون الاستجابة كالتالي:
</p>

<pre class="ipsCode">{
    "_id": "UdgsQ0X1w",
    "email": "marcos.henrique@toptal.com",
    "permissionFlags": 1,
    "__v": 0
}
</pre>

<p>
	وبالمقابل، يُفترض أن يفشل طلب تحديث سجلات المستخدم نفسه لأن قيمة السماحية التي يمتلكها هي <code>1</code> (مجاني <code>FREE_PERMISSION</code><span class="ipsEmoji">?</span>
</p>

<pre class="ipsCode">curl --include --request PATCH "localhost:3000/users/$REST_API_EXAMPLE_ID" \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer $REST_API_EXAMPLE_ACCESS" \
--data-raw '{
    "firstName": "Marcos"
}'
</pre>

<p>
	والاستجابة كما نتوقع هي : <code>HTTP 403</code>.
</p>

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

	<p data-gramm="false">
		للتمرين: اقترح تغيير رايات السماحية <code>permissionFlags</code> الخاصة بالمستخدم ضمن قاعدة البيانات المحلية على جهازك، ثم اطلب توليد مفتاح JWT عن طريق العملية <code>POST</code> إلى الوجهة <code>auth/</code> يضم السماحيات الجديدة. حاول بعد ذلك تحديث بيانات السجل مجددًا. وتذكر استخدام القيم الرقمية لأحد التصريحين <code>PAID_PERMISSION</code> أو <code>ALL_PERMISSIONS</code>، لأن التصريح <code>ADMIN_PERMISSION</code> لا يسمح لك بتحديث بياناتك ولا بيانات أي مستخدم آخر وفقًا لمنطق العمل في تطبيقنا.
	</p>
</blockquote>

<p>
	إن متطلبات استعلام الوجهة <code>auth/</code> من خلال الطلب <code>POST</code> يُظهر سيناريو أمني لا بد من الانتباه إليه. فعندما يغير مالك الموقع سماحيات مستخدم، كأن يحاول إلغاء مستخدم يسئ السلوك، فلن يشعر المستخدم بذلك حتى يحصل تحديث على مفتاح JWT الذي يمتلكه، لأن التحقق من السماحيات يجري على بيانات مفتاح JWT لتقليل الوصول إلى قاعدة البيانات.
</p>

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

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

<h2 id="-4">
	الاختبارات المؤتمتة
</h2>

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

<p>
	وبدلًا من الغوص في تفاصيل كتابة اختبارات وشيفرة اختبارات، سنعرض آليات أساسية تقدّم مجموعة اختبارت يمكن لنا البناء عليها.
</p>

<h3 id="-5">
	التعامل مع البيانات الناتجة عن الاختبارات
</h3>

<p>
	قبل أن نؤتمت الاختبارات، من الجيد التفكير بما سيحدث للبيانات الناتجة عن الاختبارات. إذ نستخدم في تطبيقنا Docker Compose لتشغيل قاعدة البيانات المحلية، وتؤثر الاختبارات على قاعدة البيانات بترك مجموعة جديدة من السجلات بعد كل اختبار. لا يمثل هذا اﻷمر مشكلة في أغلب اﻷوقات، لكن إن رأيت أنه كذلك، لا بأس بتعديل الملف <code>docker-compose.yml</code> ﻹنشاء قاعدة بيانات جديدة لأغراض الاختبار.
</p>

<p>
	عادة ما يُجري المطورين اختباراتهم المؤتمتة في الواقع كجزء من خط التسليم المستمر. ولتنفيذ اﻷمر، من المنطقي إعداد طريقة ﻹنشاء قاعدة بيانات مؤقتة لتنفيذ كل اختبار. لهذا نستخدم Mocha و Chai و SuperTest لإنشاء اختباراتنا:
</p>

<pre class="ipsCode">npm i --save-dev chai mocha supertest @types/chai @types/express @types/mocha @types/supertest ts-node
</pre>

<p>
	يدير Mocha تطبيقنا ويًجري الاختبارات، بينما يسهّل Chai قراءة تعابير الاختبارات، ويسهّل SuperTest اختبارات التكامل بين الواجهتين أو اختبارات طرف إلى طرف end-to-end بين الواجهة البرمجية RESTوعميل ريست.
</p>

<p>
	علينا بداية تعديل السكريبت <code>package.json</code>:
</p>

<pre class="ipsCode">"scripts": {
// ...
    "test": "mocha -r ts-node/register 'test/**/*.test.ts' --unhandled-rejections=strict",
    "test-debug": "export DEBUG=* &amp;&amp; npm test"
},
</pre>

<p>
	تسمح لنا الشيفرة السابقة بتنفيذ الاختبارات في مجلد جديد ندعوه <code>test</code>.
</p>

<h3 id="-6">
	اختبار وصفي
</h3>

<p>
	لتجربة هيكلية الاختبار، سننشئ الملف <code>test/app.test.ts</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1530_24" style=""><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> expect </span><span class="pun">}</span><span class="pln"> from </span><span class="str">'chai'</span><span class="pun">;</span><span class="pln">
describe</span><span class="pun">(</span><span class="str">'Index Test'</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">
    it</span><span class="pun">(</span><span class="str">'should always pass'</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">
        expect</span><span class="pun">(</span><span class="kwd">true</span><span class="pun">).</span><span class="pln">to</span><span class="pun">.</span><span class="pln">equal</span><span class="pun">(</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></pre>

<p>
	قد تبدو الصياغة غير مألوفة لكنها صحيحة. إذ نعرف الاختبار بتوقع سلوك معين <code>()expect</code> ضمن الكتلة <code>()it</code> التي تُستدعى بدورها ضمن الكتلة <code>()describe</code>.
</p>

<p>
	ننفذ التعليمة التالية في الطرفية:
</p>

<pre class="ipsCode">npm run test
</pre>

<p>
	ومن المفترض أن نرى النتيجة التالية:
</p>

<pre class="ipsCode">&gt; mocha -r ts-node/register 'test/**/*.test.ts' --unhandled-rejections=strict

  Index Test
    ✓ should always pass


  1 passing (6ms)
</pre>

<p>
	وهكذا تكون مكتبات الاختبار جاهزة للعمل.
</p>

<h3 id="-7">
	تحضير الاختبارات للعمل
</h3>

<p>
	ليبقى خرج الاختبارات واضحًا، علينا إيقاف عمل المكتبة <code>winston</code> التي تسجّل الطلبات كليًا خلال تنفيذ الاختبارات، لهذا غيّر الفرع <code>else</code> (ليس في وضع التنقيح طبعًا) في الملف <code>app.ts</code> للتحقق من وجود الدالة <code>()it</code> من Mocha:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1530_26" style=""><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">DEBUG</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
     loggerOptions</span><span class="pun">.</span><span class="pln">meta </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">
</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">typeof</span><span class="pln"> global</span><span class="pun">.</span><span class="pln">it </span><span class="pun">===</span><span class="pln"> </span><span class="str">'function'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
</span><span class="pun">+</span><span class="pln">        loggerOptions</span><span class="pun">.</span><span class="pln">level </span><span class="pun">=</span><span class="pln"> </span><span class="str">'http'</span><span class="pun">;</span><span class="pln"> </span><span class="com">// for non-debug test runs, squelch entirely</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>app.ts</code> ليكون عرضة للاختبارات، لهذا سنضيف السطر <code>export default</code> في نهاية الملف <code>app.ts</code> قبل السطر <code>()server.listen</code> مباشرة، لأن <code>()listen</code> يعيد الكائن <code>http.Server</code>.
</p>

<p>
	تحقق من أن كل شيئ على ما يرام بتنفيذ اﻷمر: <code>npm run test</code>.
</p>

<h3 id="-8">
	الاختبار المؤتمت اﻷول على واجهتنا البرمجية
</h3>

<p>
	حتى نبدأ إعداد الاختبارات على المستخدمين، سننشئ الملف <code>test/users/users.test.ts</code> ونبدؤه بإدراج المكتبات واﻹعتماديات اللازمة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1530_28" style=""><span class="kwd">import</span><span class="pln"> app from </span><span class="str">'../../app'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> supertest from </span><span class="str">'supertest'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> expect </span><span class="pun">}</span><span class="pln"> from </span><span class="str">'chai'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> shortid from </span><span class="str">'shortid'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> mongoose from </span><span class="str">'mongoose'</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">let</span><span class="pln"> firstUserIdTest </span><span class="pun">=</span><span class="pln"> </span><span class="str">''</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"> firstUserBody </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    email</span><span class="pun">:</span><span class="pln"> </span><span class="pun">`</span><span class="pln">marcos</span><span class="pun">.</span><span class="pln">henrique</span><span class="pun">+</span><span class="pln">$</span><span class="pun">{</span><span class="pln">shortid</span><span class="pun">.</span><span class="pln">generate</span><span class="pun">()}</span><span class="lit">@toptal</span><span class="pun">.</span><span class="pln">com</span><span class="pun">`,</span><span class="pln">
    password</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Sup3rSecret!23'</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">server</span><span class="pun">.</span><span class="pln">listen</span><span class="pun">`</span><span class="pln">

</span><span class="kwd">let</span><span class="pln"> accessToken </span><span class="pun">=</span><span class="pln"> </span><span class="str">''</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">let</span><span class="pln"> refreshToken </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"> newFirstName </span><span class="pun">=</span><span class="pln"> </span><span class="str">'Jose'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> newFirstName2 </span><span class="pun">=</span><span class="pln"> </span><span class="str">'Paulo'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> newLastName2 </span><span class="pun">=</span><span class="pln"> </span><span class="str">'Faraco'</span><span class="pun">;</span></pre>

<p>
	ننشئ تاليًا الكتلة الخارجية <code>()describe</code> مع بعض اﻹعدادات والتعريفات:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1530_30" style=""><span class="pln">describe</span><span class="pun">(</span><span class="str">'users and auth endpoints'</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">
    </span><span class="kwd">let</span><span class="pln"> request</span><span class="pun">:</span><span class="pln"> supertest</span><span class="pun">.</span><span class="typ">SuperAgentTest</span><span class="pun">;</span><span class="pln">
    before</span><span class="pun">(</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">
        request </span><span class="pun">=</span><span class="pln"> supertest</span><span class="pun">.</span><span class="pln">agent</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">
    after</span><span class="pun">(</span><span class="kwd">function</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="com">//وأغلق الاتصال  Express.js أغلق خادم shut down the Express.js</span><span class="pln">
        </span><span class="com">//بأننا انتهينا Mocha ثم أخبر  MongoDB مع</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">
            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">done</span><span class="pun">);</span><span class="pln">
        </span><span class="pun">});</span><span class="pln">
    </span><span class="pun">});</span><span class="pln">will later hold a value returned by our API
</span><span class="pun">});</span></pre>

<p>
	تُستدعى الدوال التي نمررها إلى <code>()before</code> و <code>()after</code> قبل وبعد كل الاختبارات التي نعرّفها عند استدعاء <code>()it</code> ضمن نفس الكتلة <code>()describe</code>. وللدالة التي نمررها إلى <code>()after</code> دالة أخرى <code>done</code> نستدعيها بمجرّد انتهينا من إعادة التطبيق واتصاله مع قاعدة البيانات إلى الوضع الأساسي.
</p>

<p>
	<strong>ملاحظة</strong>: لو لم نستخدم الدالة <code>()after</code>، ستتوقف Mocha عن الاستجابة بعد إكمال كل اختبار بنجاح. وننصح هنا باستدعاء Mocha دومًا مع الوسيط <code>exit--</code> لتفادي هذا السلوك مع أنه ينطوي على ثغرة. فلو توقف الاختبار عن الاستجابة لأسباب أخرى، نتيجة وعد لم يُكتب بطريقة صحيحة في الاختبار أو التطبيق مثلًا، عندها لن تنتظر Mocha وتعطي رسالة بنجاح الاختبار حتى لو حدث خطأ في التطبيق مما يزيد تعقيد عملية تنقيح اﻷخطاء
</p>

<p>
	أصبحنا اﻵن جاهزين ﻹضافة اختبارات فردية طرف-إلى-طرف ضمن الكتلة <code>()describe</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1530_32" style=""><span class="pln">it</span><span class="pun">(</span><span class="str">'should allow a POST to /users'</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">async</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">
    </span><span class="kwd">const</span><span class="pln"> res </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/users'</span><span class="pun">).</span><span class="pln">send</span><span class="pun">(</span><span class="pln">firstUserBody</span><span class="pun">);</span><span class="pln">

    expect</span><span class="pun">(</span><span class="pln">res</span><span class="pun">.</span><span class="pln">status</span><span class="pun">).</span><span class="pln">to</span><span class="pun">.</span><span class="pln">equal</span><span class="pun">(</span><span class="lit">201</span><span class="pun">);</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">not</span><span class="pun">.</span><span class="pln">to</span><span class="pun">.</span><span class="pln">be</span><span class="pun">.</span><span class="pln">empty</span><span class="pun">;</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">to</span><span class="pun">.</span><span class="pln">be</span><span class="pun">.</span><span class="pln">an</span><span class="pun">(</span><span class="str">'object'</span><span class="pun">);</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">id</span><span class="pun">).</span><span class="pln">to</span><span class="pun">.</span><span class="pln">be</span><span class="pun">.</span><span class="pln">a</span><span class="pun">(</span><span class="str">'string'</span><span class="pun">);</span><span class="pln">
    firstUserIdTest </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">id</span><span class="pun">;</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	تُنشئ هذه الدالة مستخدمًا جديدًا وفريدًا لأن البريد اﻹلكتروني للمستخدم قد ولد سابقًا باستخدام <code>shortid</code>. بضم المتغير <code>request</code> عميل SuperTest الذي يسمح لنا تنفيذ طلبات HTTP إلى الواجهة البرمجية. وعلى جميع الطلبات أن تستخدم <code>await</code> لهذا تكون جميع الدوال التي نمررها إلى <code>()it</code> غير متزامنة <code>async</code>. ثم نستخدم بعد ذلك <code>()expect</code> من Chai لاختبار مختلف نواحي النتيجة.
</p>

<p>
	جرب تنفذ اﻷمر اﻵن لترى كيف يعمل الاختبار.
</p>

<h3 id="-9">
	سلسلة من الاختبارات
</h3>

<p>
	سنضيف جميع كتل <code>()it</code> التالية ضمن الكتلة <code>()describe</code>، ولا بد من إضافتها وفق الترتيب المعروض تاليًا كي تعمل جميعها مع المتغيّرات التي نغيّرها مثل <code>firstUserIdTest</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1530_34" style=""><span class="pln">it</span><span class="pun">(</span><span class="str">'should allow a POST to /auth'</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">async</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">
    </span><span class="kwd">const</span><span class="pln"> res </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/auth'</span><span class="pun">).</span><span class="pln">send</span><span class="pun">(</span><span class="pln">firstUserBody</span><span class="pun">);</span><span class="pln">
    expect</span><span class="pun">(</span><span class="pln">res</span><span class="pun">.</span><span class="pln">status</span><span class="pun">).</span><span class="pln">to</span><span class="pun">.</span><span class="pln">equal</span><span class="pun">(</span><span class="lit">201</span><span class="pun">);</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">not</span><span class="pun">.</span><span class="pln">to</span><span class="pun">.</span><span class="pln">be</span><span class="pun">.</span><span class="pln">empty</span><span class="pun">;</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">to</span><span class="pun">.</span><span class="pln">be</span><span class="pun">.</span><span class="pln">an</span><span class="pun">(</span><span class="str">'object'</span><span class="pun">);</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">accessToken</span><span class="pun">).</span><span class="pln">to</span><span class="pun">.</span><span class="pln">be</span><span class="pun">.</span><span class="pln">a</span><span class="pun">(</span><span class="str">'string'</span><span class="pun">);</span><span class="pln">
    accessToken </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">accessToken</span><span class="pun">;</span><span class="pln">
    refreshToken </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">refreshToken</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_1530_36" style=""><span class="pln">it</span><span class="pun">(</span><span class="str">'should allow a GET from /users/:userId with an access token'</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">async</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">
    </span><span class="kwd">const</span><span class="pln"> res </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> request
        </span><span class="pun">.</span><span class="kwd">get</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">firstUserIdTest</span><span class="pun">}`)</span><span class="pln">
        </span><span class="pun">.</span><span class="kwd">set</span><span class="pun">({</span><span class="pln"> </span><span class="typ">Authorization</span><span class="pun">:</span><span class="pln"> </span><span class="pun">`</span><span class="typ">Bearer</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">accessToken</span><span class="pun">}`</span><span class="pln"> </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">
    expect</span><span class="pun">(</span><span class="pln">res</span><span class="pun">.</span><span class="pln">status</span><span class="pun">).</span><span class="pln">to</span><span class="pun">.</span><span class="pln">equal</span><span class="pun">(</span><span class="lit">200</span><span class="pun">);</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">not</span><span class="pun">.</span><span class="pln">to</span><span class="pun">.</span><span class="pln">be</span><span class="pun">.</span><span class="pln">empty</span><span class="pun">;</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">to</span><span class="pun">.</span><span class="pln">be</span><span class="pun">.</span><span class="pln">an</span><span class="pun">(</span><span class="str">'object'</span><span class="pun">);</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">_id</span><span class="pun">).</span><span class="pln">to</span><span class="pun">.</span><span class="pln">be</span><span class="pun">.</span><span class="pln">a</span><span class="pun">(</span><span class="str">'string'</span><span class="pun">);</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">_id</span><span class="pun">).</span><span class="pln">to</span><span class="pun">.</span><span class="pln">equal</span><span class="pun">(</span><span class="pln">firstUserIdTest</span><span class="pun">);</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">email</span><span class="pun">).</span><span class="pln">to</span><span class="pun">.</span><span class="pln">equal</span><span class="pun">(</span><span class="pln">firstUserBody</span><span class="pun">.</span><span class="pln">email</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	يدفع ذلك الطلب <code>GET</code> من المسار <code>userID:</code> (والذي يحمل مفتاح التشفير) إلى التحقق من أن بيانات المستخدم في الاستجابة تتطابق مع ما أرسلناه أساسًا.
</p>

<h3 id="-10">
	الاختبارات المتداخلة وتجاوز الاختبارات وعزلها
</h3>

<p>
	يمكن للكتل <code>()it</code>في Mocha أن تضم كتل <code>()describe</code>، أي يمكن أن تتداخل الاختبارات، وهذا ما سنفعله في اختبارنا التالي. سيجعل ذلك الاعتماديات المتعاقبة أوضح في نواتج الاختبار كما سنراها في النهاية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1530_38" style=""><span class="pln">describe</span><span class="pun">(</span><span class="str">'with a valid access token'</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">
    it</span><span class="pun">(</span><span class="str">'should allow a GET from /users'</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">async</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">
        </span><span class="kwd">const</span><span class="pln"> res </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> request
            </span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(`/</span><span class="pln">users</span><span class="pun">`)</span><span class="pln">
            </span><span class="pun">.</span><span class="kwd">set</span><span class="pun">({</span><span class="pln"> </span><span class="typ">Authorization</span><span class="pun">:</span><span class="pln"> </span><span class="pun">`</span><span class="typ">Bearer</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">accessToken</span><span class="pun">}`</span><span class="pln"> </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">
        expect</span><span class="pun">(</span><span class="pln">res</span><span class="pun">.</span><span class="pln">status</span><span class="pun">).</span><span class="pln">to</span><span class="pun">.</span><span class="pln">equal</span><span class="pun">(</span><span class="lit">403</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">});</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	لا تغطي الاختبارات الفعالة ما نتوقعه نجاحه فقط، بل ما نتوقعه أن يفشل أيضًا. إذ حاولنا هنا الحصول على قائمة بأسماء المستخدمين وتوقعنا أن تكون النتيجة الخطأ 403 لأن المستخدم الجديد سيحمل التصريح الافتراضي ولا يسمح له بمثل هذا الطلب إلى نقطة الوصول. وهكذا يمكننا الاستمرار في كتابة اختبارات ضمن الكتلة <code>()describe</code> الجديدة.
</p>

<p>
	وطالما أننا ناقشنا الميزات التي استخدمناها في بقية شيفرة الاختبار، يمكنك الاطلاع عليها في <a href="https://github.com/makinhs/toptal-rest-series/blob/toptal-article-03/test/users/users.test.ts#L73" rel="external nofollow">المستودع</a> الخاص بالتطبيق.
</p>

<p>
	مر مع تزوّدنا المكتبة Mocha بميزات عدة قد تجدها مفيدة أثناء تطوير وتنقيح الاختبارات منها:
</p>

<ol>
	<li>
		التابع <code>()skip</code>:ويستخدم لتفادي اختبار وحيد أو كتلة كاملة من الاختبارات. فعندما نستبدل <code>()it</code> بالدالة <code>()it.skip</code> (وكذلك الأمر مع <code>()describe</code>)، لن يعمل الاختبار أو الاختبارات محط الاهتمام بل ستؤجل ويشار إليها بالعبارة "بانتظار التنفيذ pending" في الخرج الأخير للمكتبة Mocha.
	</li>
	<li>
		التابع <code>()only</code>: يُستخدم لتجاهل جميع الاختبارات غير الموسومة بالوسم <code>only.</code>، ولن يظهر أي شيء في الخرج على أنه "بانتظار التنفيذ".
	</li>
	<li>
		المعامل <code>bail--</code>: باﻹمكان استخدام هذا المعامل ضمن سطر تعريف Mocha في الملف <code>package.json</code> ﻹيقاف الاختبارات بمجرد فشل إحداها. ولهذا اﻷمر فائدة خاصة في تطبيق الواجهة البرمجية الذي نبنيه، لأننا أعددنا الاختبارات لتكون متعاقبة، وبالتالي عند فشل أول اختبار ستتوقف Mocha مباشرة بدلًا من المتابعة واﻹشارة إلى أخطاء في بقية الاختبارات المتعلقة بالاختبار الفاشل علمًا بأنها قد تكون ناجحة لكنها فشلت نتيجة لفشل أول اختبار.
	</li>
</ol>

<p>
	عند تنفيذ مجموعة اختباراتنا اﻵن باستخدام سطر اﻷوامر <code>npm run test</code>، سنلاحظ إخفاق ثلاث اختبارات (لو أردنا ترك الدوال الثلاث التي تعتمد على شيفرة غير منجزة حتى اللحظة، فمن المناسب جدًا لتطبيق التابع <code>()skip</code> عليها).
</p>

<p>
	أخفقت الاختبارات الثلاث السابقة نظرًا لوجود قطعتين مفقودتين من شيفرة التطبيق حتى اللحظة، اﻷولى موجودة ضمن الملف <code>npm run test</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8152_28" style=""><span class="kwd">this</span><span class="pun">.</span><span class="pln">app</span><span class="pun">.</span><span class="pln">put</span><span class="pun">(`/</span><span class="pln">users</span><span class="pun">/:</span><span class="pln">userId</span><span class="pun">/</span><span class="pln">permissionFlags</span><span class="pun">/:</span><span class="pln">permissionFlags</span><span class="pun">`,</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    jwtMiddleware</span><span class="pun">.</span><span class="pln">validJWTNeeded</span><span class="pun">,</span><span class="pln">
    permissionMiddleware</span><span class="pun">.</span><span class="pln">onlySameUserOrAdminCanDoThisAction</span><span class="pun">,</span><span class="pln">

    </span><span class="com">//من الضروري وجود كتلتي الشيفرة السابقتين كجزء من الأداة الوسيطة</span><span class="pln">
    </span><span class="com">//لأنها تغطي فقط .all() على الرغم من وجود مرجع إليهما ضمن الدوال</span><span class="pln">
    </span><span class="com">// /users/:userId, وليس كل شيء تختها في الشجرة</span><span class="pln">

    permissionMiddleware</span><span class="pun">.</span><span class="pln">permissionFlagRequired</span><span class="pun">(</span><span class="pln">
        </span><span class="typ">PermissionFlag</span><span class="pun">.</span><span class="pln">FREE_PERMISSION
    </span><span class="pun">),</span><span class="pln">
    </span><span class="typ">UsersController</span><span class="pun">.</span><span class="pln">updatePermissionFlags</span><span class="pun">,</span><span class="pln">
</span><span class="pun">]);</span></pre>

<p>
	وعلينا أن نعدّل ثانيًا الملف <code>users.controller.ts</code> لأننا وضعنا مرجعًا إلى دالة غير موجودة فيه. لهذا علينا إضافة السطر ب<code>;import { PatchUserDto } from '../dto/patch.user.dto'</code> بالقرب من أعلى الصفحة وإضافة الدالة المفقودة إلى الصنف:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8152_30" style=""><span class="kwd">async</span><span class="pln"> updatePermissionFlags</span><span class="pun">(</span><span class="pln">req</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">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"> patchUserDto</span><span class="pun">:</span><span class="pln"> </span><span class="typ">PatchUserDto</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        permissionFlags</span><span class="pun">:</span><span class="pln"> parseInt</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">permissionFlags</span><span class="pun">),</span><span class="pln">
    </span><span class="pun">};</span><span class="pln">
    log</span><span class="pun">(</span><span class="kwd">await</span><span class="pln"> usersService</span><span class="pun">.</span><span class="pln">patchById</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">id</span><span class="pun">,</span><span class="pln"> patchUserDto</span><span class="pun">));</span><span class="pln">
    res</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">send</span><span class="pun">();</span><span class="pln">
</span><span class="pun">}</span></pre>

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

<ol>
	<li>
		فكر بطرق تمنع فيها الشيفرة مجددًا المستخدم من تغيير رايات السماحية <code>permissionFlags</code> الخاصة به وتسمح مع ذلك باختبار نقاط الوصول المقيّدة بالتصريحات.
	</li>
	<li>
		أنشئ ونفّذ منطق عمل (بما في ذلك الاختبارات المتعلقة به) يعالج تغيير الماحيات <code>permissionFlags</code> من خلال الواجهة البرمجية (انتبه لمعضلة الدجاجة والبيضة هنا: فكيف يمكن لشخص معين الحصول على تصريح لتغيير التصريحات؟)
	</li>
</ol>

<p>
	نفّذ اﻵن اﻷمر <code>npm run test</code> ومن المفترض أن تجري الاختبارات بنجاح ويكون الخرج منسقًا بالشكل التالي:
</p>

<pre class="ipsCode">  Index Test
    ✓ should always pass

  users and auth endpoints
    ✓ should allow a POST to /users (76ms)
    ✓ should allow a POST to /auth
    ✓ should allow a GET from /users/:userId with an access token
    with a valid access token
      ✓ should allow a GET from /users
      ✓ should disallow a PATCH to /users/:userId
      ✓ should disallow a PUT to /users/:userId with an nonexistent ID
      ✓ should disallow a PUT to /users/:userId trying to change the permission flags
      ✓ should allow a PUT to /users/:userId/permissionFlags/2 for testing
      with a new permission level
        ✓ should allow a POST to /auth/refresh-token
        ✓ should allow a PUT to /users/:userId to change first and last names
        ✓ should allow a GET from /users/:userId and should have a new full name
        ✓ should allow a DELETE from /users/:userId


  13 passing (231ms)
</pre>

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

<h3 id="-11">
	تنقيح التطبيق من خلال الاختبارات
</h3>

<p>
	يمكن للمطورين الذين يعانون من إخفاقات غير متوقعة لشيفرتهم تعزيز قدرة شيفرات التنقيح لكل من <code>winston</code>و Node.js عند تنفيذ سلسلة الاختبارات. فمن السهل مثلًا التركيز على استعلامات Mongoose التي تُنفّذ باستخدام اﻷمر <code>DEBUG=mquery npm run test</code> (لاحظ عدم وجود البادئة <code>export</code> والعامل <code>&amp;&amp;</code> في الوسط مما يجعل بيئة العمل تستمر لتنفيذ أوامر أخرى).
</p>

<p>
	من الممكن أيضًا عرض خرج جميع عمليات التنقيح باستخدام <code>npm run test-debug</code> ويساعدنا في ذلك طريقة اﻹعداد التي اتبعناها في الملف <code>package.json</code>
</p>

<p>
	وبهذا نكون قد بنينا واجهة برمجية REST قابلة للتوسع ومدعومة بقاعدة بيانات MongoDB وسلسلة من الاختبارات المناسبة، لكن هناك بعض النقص.
</p>

<h2 id="expressjs">
	اﻷمان في تطبيقات Express.js
</h2>

<p>
	ينبغي دائمًا الاطلاع على توثيق ي عند العمل مع التطبيقات المبنية عليها وكحد أدنى ما يتعلق <a href="https://expressjs.com/en/advanced/best-practice-security.html" rel="external nofollow">بأفضل ممارسات اﻷمان</a>:
</p>

<ul>
	<li>
		دعم إعدادات <abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل"><abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">TLS</abbr></abbr>.
	</li>
	<li>
		إضافة أداة وسيطة للتحكم بمعدّل الطلبات إلى الواجهة البرمجية مثل express-limit-rate
	</li>
	<li>
		التأكد من أمان اعتمديات npm (من الممكن تنفيذ البرنامج باستخدام <code>npm audit</code>، أو استخدام <code>snyk</code>).
	</li>
	<li>
		استخدام المكتبة <a href="https://helmetjs.github.io/" rel="external nofollow">Helmet</a> للمساعدة في حماية التطبيق من نقاط الضعف الشائعة وهذا ما سنضيفه مباشرة إلى تطبيقنا:
	</li>
</ul>

<pre class="ipsCode">npm i --save helmet
</pre>

<p>
	ومن ثم ندرجه ضمن الملف <code>app.ts</code> ونضيف الاستدعاء <code>()app.use</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1530_40" style=""><span class="kwd">import</span><span class="pln"> helmet from </span><span class="str">'helmet'</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">helmet</span><span class="pun">());</span></pre>

<p>
	ولا يعني استخدام <code>Helmet</code> أنك بأمان، فكل خطوة ولو كانت بسيطة لتأمين التطبيق لها فائدتها.
</p>

<h2 id="docker">
	احتواء الواجهة البرمجية ضمن Docker
</h2>

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

<pre class="ipsCode">FROM node:14-slim

RUN mkdir -p /usr/src/app

WORKDIR /usr/src/app

COPY . .

RUN npm install

EXPOSE 3000

CMD ["node", "./dist/app.js"]
</pre>

<p>
	تبدأ هذه اﻹعدادات بالسطر <code>node:14-slim</code> وهي النسخة الرسمية من Docker، ثم تشغّل الواجهة البرمجية ضمن الحاوية. يمكن أن تتغير هذه اﻹعدادات من حالة إلى أخرى، لكن هذه اﻹعدادات التي تبدو عامة مناسبة لمشروعنا.
</p>

<p>
	ولبناء هذه النسخة نفّذ الأوامر التالية في جذر المشروع مستبدلًا <code>tag_your_image_here</code> بما يناسب):
</p>

<pre class="ipsCode">docker build . -t tag_your_image_here
</pre>

<p>
	ثم نفّذ ما يلي لتشغيل الواجهة ضمن الحاوية:
</p>

<pre class="ipsCode">docker run -p 3000:3000 tag_your_image_here
</pre>

<p>
	يمكن اﻵن لقاعدة البيانات MongoDB و Node.js استخدام Docker، لكن علينا تشغيلهما بطريقتين مختلفتين. ونترك لك البحث عن كيفية إضافة تطبيق Node.js اﻷساسي إلى الملف <code>docker-compose.yml</code> كي يعمل التطبيق ككل من خلال أمر <code>docker-compose</code> واحد.
</p>

<h2 id="rest">
	خلاصة: استكشاف مهارات أخرى في بناء واجهات برمجية REST
</h2>

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

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

<p>
	نوصي على مستوى بناء الواجهات البرمجية بالاطلاع على مواصفات بناء واجهات متوافقة مع <a href="https://swagger.io/resources/open-api/" rel="external nofollow">OpenAPI</a>. وننصح أيضًا المهتمين بتطوير المشاريع، تجربة إطار العمل <a href="https://nestjs.com/" rel="external nofollow">NestJS</a> المبني على أساس Express.js، فهو أكثر قوة وتجريدًا. لهذا، من الجيد العمل على مشروعنا كي تعتاد العمل مع أساسيات Express.js بداية. ولا ننسى بالطبع إطار العمل <a href="https://www.toptal.com/graphql/creating-your-first-graphql-api" rel="external nofollow">GraphQL</a> الذي لا يقل أهمية عنه، إذ يلقى رواجًا وانتشارًا كبديل عن الواجهات البرمجية REST.
</p>

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

<pre class="ipsCode">can(['update', 'delete'], '(model name here)', { creator: 'me' });
</pre>

<p>
	بدلاً من الدوال الوسيطة المخصصة لهذا اﻷمر.
</p>

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

<ol>
	<li>
		<p>
			تستكشف اختبار الوحدة Unit test كي تختبر كل وحدة برمجية على حدى. يمكن استخدام Mocha و Chai لهذا الغرض أيضًا.
		</p>
	</li>
	<li>
		<p>
			تلق نظرة على أدوات تغطية التعليمات البرمجية، والتي تساعد في تحديد الثغرات في مجموعة الاختبارات من خلال عرض أسطر الأوامر التي لا تُنفَّذ أثناء الاختبار. تُمكّنك هذه اﻷدوات من استكمال الاختبارات في تطبيقنا، بما يناسبك، لكنها قد لا تكشف عن كل الحالات التي تسبب المشاكل، مثل الحالة التي تدرس إمكانية تعديل المستخدم لسماحياته من خلال طلب <code>PATCH</code> إلى الوجهة <code>users /:userId/</code>.
		</p>
	</li>
	<li>
		<p>
			تجرب أساليب أخرى للاختبارات المؤتمتة. لقد استخدمنا مبدأ <a href="https://www.toptal.com/freelance/your-boss-won-t-appreciate-tdd-try-bdd" rel="external nofollow">التطوير القائم على السلوك</a> (BDD) من خلال الدالة <code>()expect</code> من CHAI، لكن المكتبة تدعم أيضًا الدوال <code>()should</code> و <code>()assert</code>. ومن الجيد أيضًا تعلم مكتبات اختبار أخرى، مثل <a href="https://jestjs.io/" rel="external nofollow">JEST</a>.
		</p>
	</li>
</ol>

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

<p>
	للاطلاع على المشروع بصيغته النهائية راجع <a href="https://github.com/makinhs/toptal-rest-series" rel="external nofollow">المستودع المخصص</a> على جيتهب أو حمله من هنا مباشرة.<a class="ipsAttachLink" data-fileext="rar" data-fileid="150219" href="https://academy.hsoub.com/applications/core/interface/file/attachment.php?id=150219&amp;key=1ae01970edb3c8bd138d4e6b5d500d72" rel="">rest-series.rar</a>
</p>

<p>
	ترجمة -وبتصرف للقسم الثاني من مقال <a href="https://www.toptal.com/express-js/nodejs-typescript-rest-api-pt-3" rel="external nofollow">Building a Node.js TypeScript REST <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr>, Part 3 MongoDB, Authentication, and </a><a href="https://www.toptal.com/express-js/nodejs-typescript-rest-api-pt-3" rel="external nofollow">Automated Tests</a>
</p>

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

<ul>
	<li>
		المقال السابق:<a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%A8%D9%86%D8%A7%D8%A1-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-%D9%85%D8%AA%D9%88%D8%A7%D9%81%D9%82%D8%A9-%D9%85%D8%B9-rest-%D9%81%D9%8A-%D8%A8%D9%8A%D8%A6%D8%A9-nodejs-%D8%A7%D9%84%D9%82%D8%B3%D9%85-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%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%88%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%8A%D8%AB%D8%A7%D9%82-r2331/" rel=""> بناء واجهة برمجية متوافقة مع REST في بيئة Node.js القسم الثالث: قاعدة البيانات MongoDB والاستيثاق</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-express-%D9%88%D8%A8%D9%8A%D8%A6%D8%A9-node-r2168/" rel="">مدخل إلى إطار عمل الويب Express وبيئة Node</a>
	</li>
	<li>
		ا<a href="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/" rel="">لاستيثاق عبر مفتاح المستخدم المشفر (token authentication) في تطبيق Node.js و React</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D8%AF%D9%88%D9%86%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-6-%D8%A7%D8%B9%D8%AA%D8%A8%D8%A7%D8%B1%D8%A7%D8%AA-%D9%86%D8%B4%D8%B1-%D9%85%D8%B4%D8%A7%D8%B1%D9%8A%D8%B9-nodejs-%D9%88express-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r24/?tab=comments#comment-1354" rel="">اعتبارات نشر مشاريع Node.js وExpress على الويب</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2334</guid><pubDate>Fri, 31 May 2024 12:00:01 +0000</pubDate></item><item><title>&#x628;&#x646;&#x627;&#x621; &#x648;&#x627;&#x62C;&#x647;&#x629; &#x628;&#x631;&#x645;&#x62C;&#x64A;&#x629; &#x645;&#x62A;&#x648;&#x627;&#x641;&#x642;&#x629; &#x645;&#x639; REST &#x641;&#x64A; Express.js &#x627;&#x644;&#x642;&#x633;&#x645; &#x627;&#x644;&#x62B;&#x627;&#x644;&#x62B;: &#x642;&#x627;&#x639;&#x62F;&#x629; &#x627;&#x644;&#x628;&#x64A;&#x627;&#x646;&#x627;&#x62A; MongoDB &#x648;&#x627;&#x644;&#x627;&#x633;&#x62A;&#x64A;&#x62B;&#x627;&#x642;</title><link>https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%A8%D9%86%D8%A7%D8%A1-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-%D9%85%D8%AA%D9%88%D8%A7%D9%81%D9%82%D8%A9-%D9%85%D8%B9-rest-%D9%81%D9%8A-expressjs-%D8%A7%D9%84%D9%82%D8%B3%D9%85-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%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%88%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%8A%D8%AB%D8%A7%D9%82-r2331/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_05/-----REST---Node.js-TypeScript-----.png.70733a8c871efd5a2cab3d249a3f5189.png" /></p>
<p>
	نكمل في هذا المقال تطوير تطبيق الواجهة البرمجية REST الذي بدأناه في <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%A8%D9%86%D8%A7%D8%A1-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-%D9%85%D8%AA%D9%88%D8%A7%D9%81%D9%82%D8%A9-%D9%85%D8%B9-rest-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-expressjs-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-r2327/" rel="">مقال سابق</a>، حيث نضيف للتطبيق المميزات التالية:
</p>

<ul>
	<li>
		<strong>Mongoose</strong> التي تسمح لنا بالعمل مع قاعدة البيانات MongoDB بدلًا من كائن DAO المقيم في الذاكرة.
	</li>
	<li>
		<strong>استيثاق Authentication</strong> ﻹضافة إمكانيات منح اﻷذونات كي يتمكن مستثمرو الواجهة البرمجية من استخدام مفتاح تشفير ويب JSON Web Token للوصول بأمان إلى نقاط وصول الواجهة البرمجية.
	</li>
	<li>
		<strong>اختبارات مؤتمتة</strong>: باستخدام إطار عمل الاختبارات Mocha، والمكتبة Chai والوحدة البرمجية SuperTest، للمساعدة في التحقق من المحدودية عند زيادة حجم الشيفرة وتغيّرها.
	</li>
</ul>

<p>
	كما سنضيف تباعًا مكتبات للتحقق من صحة المدخلات ومكتبات خاصة بآمان التطبيق، ,وسنكتسب بعض الخبرة في التعامل مع مدير الحاويات Docker.
</p>

<h2 id="mongodb">
	تثبيت MongoDB على هيئة حاوية
</h2>

<p>
	لنبدأ باستبدال قاعدة البيانات المقيمة في الذاكرة التي استخدمناها مسبقا بقاعدة بيانات حقيقة هي MongoDB. باﻹمكان طبعًا تثبيت القاعدة محليًا على جهازك، لكن ونظرًا للاختلافات بين بيئات التشغيل (إصدارات مختلفة لنظام التشغيل مثلًا) والتي قد تسبب بعض المشكلات، سنستخدم مدير الحاويات <a href="https://academy.hsoub.com/devops/cloud-computing/docker/" rel="">Docker</a> وهو أداة معيارية حاليًا في صناعة البرمجيات. وكل ما عليك فعله هو تثبيت <a href="https://docs.docker.com/engine/install/" rel="external nofollow">Docker</a> ثم <a href="https://docs.docker.com/compose/install/" rel="external nofollow">Docker Compose</a>، وتأكد بعدها من التثبيت بتنفيذ اﻷمر <code>docker -v</code> على الطرفية كي ترى نسخة Docker Composer التي ثُبتت على جهازك.
</p>

<p>
	لتشغيل MongoDBضمن جذر المشروع، سننشئ أولًا ملف YAML يُدعى <code>docker.compose.yml</code> يضم الشيفرة التالية:
</p>

<pre class="ipsCode">version: '3'
services:
 mongo:
    image: mongo
    volumes:
     - ./data:/data/db
    ports:
     - "27017:27017"
</pre>

<p>
	يتيح Docker Composer تشغيل عدة حاويات في نفس الوقت باستخدام ملف تهيئة واحد. لهذا سنستخدمه اﻵن لتشغيل MongoDB دون أن نثبتها على الجهاز:
</p>

<pre class="ipsCode">sudo docker-compose up -d
</pre>

<p>
	يشغّل اﻷمر <code>up</code> الحاوية المحددة، مصغيًا إلى منفذ MongoDB27017، بينما تفصل <code>d-</code> اﻷمر عن الطرفية ليعمل التطبيق مستقلًا، وستكون النتيجة مشابهة لما يلي إذا جرى كل شيء دون مشاكل:
</p>

<pre class="ipsCode">Creating network "toptal-rest-series_default" with the default driver
Creating toptal-rest-series_mongo_1 ... done
</pre>

<p>
	تنشئ التعليمة السابقة أيضًا الملجد في جذر المشروع، لهذا عليك إضافة سطر بالمجلد <code>data</code> في الملف الذي ينتهي باللاحقة <code>gitignore</code>.
</p>

<p>
	وعندما نريد إيقاف الحاوية التي تحتوي MongoDB، علينا فقط تنفيذ الأمر <code>sudo docker -compose down</code>:
</p>

<pre class="ipsCode">Stopping toptal-rest-series_mongo_1 ... done
Removing toptal-rest-series_mongo_1 ... done
Removing network toptal-rest-series_default
</pre>

<p>
	هذا كل ما تحتاج معرفته لتشغيل MongoDB مع الواجهة البرمجية REST. تأكد اﻵن من تنفيذ اﻷمر <code>sudo docker-compose up -d</code> حتى تكون MongoDB جاهزة للاستخدام مع التطبيق.
</p>

<h2 id="mongoosemongodb">
	استخدام Mongoose للوصول إلى MongoDB
</h2>

<p>
	نستخدم مكتبة نمذجة البيانات <a href="https://mongoosejs.com/" rel="external nofollow">Mongoose</a> في الاتصال مع قاعدة البيانات MongoDB. وعلى الرغم من سهول استخدام Mongoose، سيفيدك الاطلاع على <a href="https://mongoosejs.com/docs/guide.html" rel="external nofollow">توثيقها</a> لتعلّم كل اﻹمكانيات التي تقدمها للتطبيقات الفعلية. استخدم سطر اﻷوامر التالي لتثبيت Mongoose:
</p>

<pre class="ipsCode">npm i mongoose
</pre>

<p>
	لنبدأ بإعداد خدمة Mongoose ﻹدارة الاتصال مع قاعدة البيانات MongoDB، وطالما أن عدة موارد تتشارك هذه الخدمة، سنضيفها إلى الملجد <code>common</code> مباشرة. نستخدم أيضًا الكائن <code>mongooseOptions</code> لتخصيص خيارات Mongoose التالية (على الرغم من أن استخدامه ليس إلزاميًا):
</p>

<ul>
	<li>
		<code>useNeewUrlPrser</code>: إن لم يُضبط هذا الخيار على القيمة <code>true</code> ستعرض Mongoose تحذير بأن هذه الخاصية عرضة للإهمال في النسخ المستقبلية depreciation warning.
	</li>
	<li>
		<code>useUnified Topology</code>: يوصي توثيق Mongoose بضبط قيمة هذا الخيار على <code>true</code>، وذلك لاستخدام محرك إدارة اتصال أحدث.
	</li>
	<li>
		<code>serverSelectionTimeoutMS</code>: لغايات تصميمية تتعلق بتطبيقنا، وفي حال نسيت تشغيل MongoDB قبل Node.js، سيكون اختيارك مدة أقل من المدة الافتراضية 30 ثانية سبيلًا لعرض معلومات عن MongoDB مباشرةً بانتظار استجابة الواجهة الخلفية.
	</li>
	<li>
		<code>useFindAndModify</code>: تلغي القيمة <code>false</code> لهذا الخيار عرض التحذير deprecation warning، وقد أشار توثيق Mongoose إلى التحذير الناتج عن هذا الخيار من بين جميع الخيارات اﻷخرى. إذ يدفع هذا الخيار Mongoose لاستخدام ميزات أصلية أحدث لقاعدة البيانات MongoDB بدلًا من كائنات Mongoose أقدم.
	</li>
</ul>

<p>
	إليك الشيفرة النهائية للملف <code>common/services/mongoose.service.ts</code> بعد جمع الخيارات السابقة مع بعض اﻹعدادات المتعلقة بإعادة محاولة الاتصال:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1227_14" style=""><span class="kwd">import</span><span class="pln"> mongoose from </span><span class="str">'mongoose'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> debug from </span><span class="str">'debug'</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> log</span><span class="pun">:</span><span class="pln"> debug</span><span class="pun">.</span><span class="typ">IDebugger</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> debug</span><span class="pun">(</span><span class="str">'app:mongoose-service'</span><span class="pun">);</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">MongooseService</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">private</span><span class="pln"> 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="kwd">private</span><span class="pln"> mongooseOptions </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="pun">,</span><span class="pln">
     serverSelectionTimeoutMS</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5000</span><span class="pun">,</span><span class="pln">
     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="pun">};</span><span class="pln">

    </span><span class="kwd">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">connectWithRetry</span><span class="pun">();</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    getMongoose</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"> mongoose</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    connectWithRetry </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">
     log</span><span class="pun">(</span><span class="str">'Attempting MongoDB connection (will retry if needed)'</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="str">'mongodb://localhost:27017/api-db'</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">mongooseOptions</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">
     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">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">const</span><span class="pln"> retrySeconds </span><span class="pun">=</span><span class="pln"> </span><span class="lit">5</span><span class="pun">;</span><span class="pln">
     log</span><span class="pun">(</span><span class="pln">
     </span><span class="pun">`</span><span class="typ">MongoDB</span><span class="pln"> connection unsuccessful </span><span class="pun">(</span><span class="pln">will retry </span><span class="pun">#</span><span class="pln">$</span><span class="pun">{++</span><span class="kwd">this</span><span class="pln">
      </span><span class="pun">.</span><span class="pln">count</span><span class="pun">}</span><span class="pln"> after $</span><span class="pun">{</span><span class="pln">retrySeconds</span><span class="pun">}</span><span class="pln"> seconds</span><span class="pun">):`,</span><span class="pln">
     err
     </span><span class="pun">);</span><span class="pln">
     setTimeout</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">connectWithRetry</span><span class="pun">,</span><span class="pln"> retrySeconds </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><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="kwd">new</span><span class="pln"> </span><span class="typ">MongooseService</span><span class="pun">();</span></pre>

<p>
	تأكد أنك تميّز بين الدالة <code>()connect</code> التي توفرها مكتبة Mongoose وبين دالة الخدمة الخاصة بنا <code>connectWithRetry</code>:
</p>

<ul>
	<li>
		تحاول <code>mongoose.connect</code> الاتصال بخدمة MongoDB الخاصة بنا (التي نشغلها باستخدام <code>docker-compose</code>) والذي ينتهي بعد زمن يُحدده الخيار <code>serverSelectionTimeoutMS</code> بالميلي ثانية.
	</li>
	<li>
		تعيد الدالة <code>connectWithRetry</code> محاولة الاتصال السابقة في حال بدأ التطبيق العمل قبل عمل خدمة MongoDB. وطالما أنها دالة بانية لصنف متفرّد singleton، وبالتالي ستعمل هذه الدالة مرة واحدة، لكنها ستحاول استدعاء <code>()connect</code> باستمرار يتخلل ذلك فترة توقف مدتها <code>retrySeconds</code> ثانية عند ينتهي الوقت المخصص لمحاولة الاتصال.
	</li>
</ul>

<p>
	ستكون الخطوة التالية استبدال البيانات المقيمة في الذاكرة بقاعدة البيانات MongoDB!
</p>

<h3 id="mongodb-1">
	إزالة قاعدة البيانات المقيمة في الذاكرة وإضافة MongoDB
</h3>

<p>
	استخدمنا سابقًا قاعدة البيانات المقيمة في الذاكرة كي نركّز في عملنا على بقية الوحدات البرمجية التي نبنيها. ولكي نستخدم MongoDB اﻵن، علينا أن نعيد بناء الملف <code>users.dao.ts</code> بالكامل، ويتطلب اﻷمر بداية إدراج اعتمادية أخرى:
</p>

<pre class="ipsCode">import mongooseService from '../../common/services/mongoose.service';
</pre>

<p>
	نزيل اﻵن كل ما يتعلق بتعريف الصنف <code>UserDao</code> ما عدا الدالة البانية، ثم نعيد بناءه بإنشاء تخطيط <code>Schema</code> لبيانات المستخدم يعمل مع Mongoose قبل الدالة البانية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1227_16" style=""><span class="typ">Schema</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> mongooseService</span><span class="pun">.</span><span class="pln">getMongoose</span><span class="pun">().</span><span class="typ">Schema</span><span class="pun">;</span><span class="pln">

userSchema </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="typ">Schema</span><span class="pun">({</span><span class="pln">
    _id</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</span><span class="pun">,</span><span class="pln">
    email</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</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"> type</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</span><span class="pun">,</span><span class="pln"> select</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">
    firstName</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</span><span class="pun">,</span><span class="pln">
    lastName</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</span><span class="pun">,</span><span class="pln">
    permissionFlags</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Number</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="kwd">false</span><span class="pln"> </span><span class="pun">});</span><span class="pln">

</span><span class="typ">User</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> mongooseService</span><span class="pun">.</span><span class="pln">getMongoose</span><span class="pun">().</span><span class="pln">model</span><span class="pun">(</span><span class="str">'Users'</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">userSchema</span><span class="pun">);</span></pre>

<p>
	تعرّف الشيفرة السابقة مجموعة Mongoose الخاصة بنا، وتضيف ميزة خاصة لا تمتلكها قاعدة البيانات المقيمة في الذاكرة وهي الخيار <code>select:false</code> للحقل <code>password</code> لإخفاء هذا الحقل كلما طلبنا قائمة بأسماء المستخدمين.
</p>

<p>
	قد يبدو تخطيط المستخدم مألوفًا فهو يشبه تخطيط كائن نقل البيانات DTO الذي تعاملنا معه، لكن الفرق الرئيسي هو أننا نعرّف الحقول التي يجب أن تتواجد ضمن مجموعة MongoDB التي تُدعى <code>Users</code>، بينما يُعرّف الكائن DTO الحقول المقبولة في طلبات HTTP. مع ذلك لن يتغير هذا الجزء المتعلق بكائن DTO، إذ سندرج كائنات DTO الثلاث التي عرّفناها سابقًا في أعلى الملف <code>users.dao.ts</code>. لكن وقبل كتابة شيفرة العمليات اﻷساسية CRUD على قاعدة البيانات، سنجري تغييرين على كائنات DTO.
</p>

<h3 id="dtoid_id">
	التغير اﻷول على كائن DTO: الحقل <code>id_</code> مقابل الحقل <code>id</code>
</h3>

<p>
	نزيل الحقل <code>id</code> من كائنات DT لأن Mongoose تقدم الحقل <code>id_</code> تلقائيًا، إذ يأتي ضمن معاملات طلب الوجهة (المسار route). وانتبه إلى أن نماذج Mongoose تزوّدنا بمستخلص افتراضي للمعرّف <code>id</code>، لهذا عطلّنا هذا الخيار كالتالي:<code>{ id: false }</code> لتفادي أي التباس. ولأن هذا اﻷمر يعطّل مرجعنا إلى <code>user.id</code> ضمن الدالة الوسيطة <code>()validateSameEmailBelongToSameUser</code>، سنتحتاج إلى <code>user._id</code> بدلًا عنه.
</p>

<p>
	تستخدم بعض قواعد البيانات <code>id</code> وتستخدم أخرى <code>id_</code> للإشارة إلى حقل المعرّف. لكن في تطبيقنا الذي يستخدم Mongoose، انتبهنا ببساطة إلى أي ترميز نستخدمه في كل مرحلة من مراحل كتابة الشيفرة، لكن مع ذلك سيظهر الاختلاف لمستثمري الواجهة البرمجية كما يوضح الشكل التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="150079" href="https://academy.hsoub.com/uploads/monthly_2024_05/01_id_exposure.png.c4db85a12c458c21828979aa9bb16aa8.png" rel=""><img alt="01 id exposure" class="ipsImage ipsImage_thumbnailed" data-fileid="150079" data-unique="k7kdkv73d" src="https://academy.hsoub.com/uploads/monthly_2024_05/01_id_exposure.thumb.png.3d9b8260c8e390bd0cf10e6255a0e43d.png"> </a>
</p>

<p>
	(تظهر الصورة أعلاه استخدام وظهور معرفات المستخدم في مشروع الواجهة البرمجية REST بصورته النهائية. ولاحظ كيف يختلف ترميز حقل المعرّف باختلاف مصدر المعرّف سواء كان معامل لطلب مباشر، أو بيانات مشفرة بطريقة JWT أو سجل قاعدة بيانات مُحضر حديثًا).
</p>

<h3 id="">
	التغيير الثاني: إعداد السماحيات المبنية على تفعيل رايات محددة
</h3>

<p>
	سنغيّر أيضًا الراية <code>permissionLevel</code> إلى <code>permissionFlags</code> في كائنات DTO لنعكس عملنا على منظومة سماحيات أكثر تعقيدًا، إضافة إلى تعريف مخطط Mongoose السابق <code>userSchema</code>.
</p>

<h3 id="dtodry">
	كائنات DTO ومبدأ عدم التكرار DRY
</h3>

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

<p>
	يمكننا بعد الانتهاء من التغييرات على كائنات DTO تنفيذ توابع العمليات اﻷساسية CRUD (بعد الدالة البانية <code>UsersDao</code>)، وسنبدأ بعملية إنشاء مستخدم جديد:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1227_18" style=""><span class="kwd">async</span><span class="pln"> addUser</span><span class="pun">(</span><span class="pln">userFields</span><span class="pun">:</span><span class="pln"> </span><span class="typ">CreateUserDto</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"> userId </span><span class="pun">=</span><span class="pln"> shortid</span><span class="pun">.</span><span class="pln">generate</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="kwd">this</span><span class="pun">.</span><span class="typ">User</span><span class="pun">({</span><span class="pln">
     _id</span><span class="pun">:</span><span class="pln"> userId</span><span class="pun">,</span><span class="pln">
     </span><span class="pun">...</span><span class="pln">userFields</span><span class="pun">,</span><span class="pln">
     permissionFlags</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="kwd">await</span><span class="pln"> user</span><span class="pun">.</span><span class="pln">save</span><span class="pun">();</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> userId</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	لاحظ أننا نلغي قيمة <code>permissionFlags</code> التي قد يرسلها مستثمر الواجهة البرمجية عبر <code>userFields</code>, ونستبدلها بالقيمة <code>1</code>.
</p>

<p>
	ننتقل تاليًا إلى عملية القراءة التي لها وظائف الحصول على مستخدم من خلال معرّفه أو من خلال البريد اﻹلكتروني أو الحصول على قائمة المستخدمين ضمن صفحات:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1227_20" style=""><span class="kwd">async</span><span class="pln"> getUserByEmail</span><span class="pun">(</span><span class="pln">email</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="kwd">return</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="typ">User</span><span class="pun">.</span><span class="pln">findOne</span><span class="pun">({</span><span class="pln"> email</span><span class="pun">:</span><span class="pln"> email </span><span class="pun">}).</span><span class="pln">exec</span><span class="pun">();</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">async</span><span class="pln"> getUserById</span><span class="pun">(</span><span class="pln">userId</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="kwd">return</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="typ">User</span><span class="pun">.</span><span class="pln">findOne</span><span class="pun">({</span><span class="pln"> _id</span><span class="pun">:</span><span class="pln"> userId </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">exec</span><span class="pun">();</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">async</span><span class="pln"> getUsers</span><span class="pun">(</span><span class="pln">limit </span><span class="pun">=</span><span class="pln"> </span><span class="lit">25</span><span class="pun">,</span><span class="pln"> page </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="kwd">return</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</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="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="pun">.</span><span class="pln">skip</span><span class="pun">(</span><span class="pln">limit </span><span class="pun">*</span><span class="pln"> page</span><span class="pun">)</span><span class="pln">
     </span><span class="pun">.</span><span class="pln">exec</span><span class="pun">();</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	ستكفي دالة DAO واحدة لتحديث مستخدم لأن دالة Mongoose في الخلفية <code>findOneAndUpdate</code> قادرة على تحديث المستند بأكمله أو تحديث جزء منه. وانتبه إلى أن دالتنا الخاصة تأخذ أحد القيمتين <code>PatchUserDto</code> أو <code>PutUserDto</code> للمعامل <code>userFields</code> من خلال استخدام نوع الاجتماع (<code>|</code>) في TypeScript:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1227_22" style=""><span class="kwd">async</span><span class="pln"> updateUserById</span><span class="pun">(</span><span class="pln">
    userId</span><span class="pun">:</span><span class="pln"> string</span><span class="pun">,</span><span class="pln">
    userFields</span><span class="pun">:</span><span class="pln"> </span><span class="typ">PatchUserDto</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> </span><span class="typ">PutUserDto</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"> existingUser </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="typ">User</span><span class="pun">.</span><span class="pln">findOneAndUpdate</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"> userId </span><span class="pun">},</span><span class="pln">
     </span><span class="pun">{</span><span class="pln"> $set</span><span class="pun">:</span><span class="pln"> userFields </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">exec</span><span class="pun">();</span><span class="pln">

    </span><span class="kwd">return</span><span class="pln"> existingUser</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يبلغ الخيار <code>new: true</code> مكتبة Mongoose بأن تعيد الكائن كما هو بعد التحديث بدلًا عما كانه أصلًا.
</p>

<p>
	أما عملية الحذف فهي موجزة في Mongoose:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1227_24" style=""><span class="kwd">async</span><span class="pln"> removeUserById</span><span class="pun">(</span><span class="pln">userId</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="kwd">return</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="typ">User</span><span class="pun">.</span><span class="pln">deleteOne</span><span class="pun">({</span><span class="pln"> _id</span><span class="pun">:</span><span class="pln"> userId </span><span class="pun">}).</span><span class="pln">exec</span><span class="pun">();</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	قد يلاحظ القارئ أن كل استدعاء للدوال اﻷعضاء للصنف <code>User</code> مقترن باستدعاء للدالة <code>()exec</code>، وهذا اﻷمر اختياري لكنه مفضّل بين مطوري Mongoose لأنه يقدم طريقة أفضل في تتبع حالة المكدس عند التنقيح.
</p>

<p>
	علينا اﻻن بعد الانتهاء من كتابة شيفرة كائن DAO تحديث الملف <code>users.service.ts</code> قليلًا حتى يتماشى مع الدوال الجديدة. ولا حاجة هنا ﻹعادة بناء الملف، بل فقط ثلاث لمسات بسيطة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1227_26" style=""><span class="pun">@@</span><span class="pln"> </span><span class="pun">-</span><span class="lit">16</span><span class="pun">,</span><span class="lit">3</span><span class="pln"> </span><span class="pun">+</span><span class="lit">16</span><span class="pun">,</span><span class="lit">3</span><span class="pln"> </span><span class="pun">@@</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">UsersService</span><span class="pln"> </span><span class="kwd">implements</span><span class="pln"> CRUD </span><span class="pun">{</span><span class="pln">
     </span><span class="kwd">async</span><span class="pln"> list</span><span class="pun">(</span><span class="pln">limit</span><span class="pun">:</span><span class="pln"> number</span><span class="pun">,</span><span class="pln"> page</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">
</span><span class="pun">-</span><span class="pln">     </span><span class="kwd">return</span><span class="pln"> </span><span class="typ">UsersDao</span><span class="pun">.</span><span class="pln">getUsers</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">UsersDao</span><span class="pun">.</span><span class="pln">getUsers</span><span class="pun">(</span><span class="pln">limit</span><span class="pun">,</span><span class="pln"> page</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="lit">20</span><span class="pun">,</span><span class="lit">3</span><span class="pln"> </span><span class="pun">+</span><span class="lit">20</span><span class="pun">,</span><span class="lit">3</span><span class="pln"> </span><span class="pun">@@</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">UsersService</span><span class="pln"> </span><span class="kwd">implements</span><span class="pln"> CRUD </span><span class="pun">{</span><span class="pln">
     </span><span class="kwd">async</span><span class="pln"> patchById</span><span class="pun">(</span><span class="pln">id</span><span class="pun">:</span><span class="pln"> string</span><span class="pun">,</span><span class="pln"> resource</span><span class="pun">:</span><span class="pln"> </span><span class="typ">PatchUserDto</span><span class="pun">):</span><span class="pln"> </span><span class="typ">Promise</span><span class="pun">&lt;</span><span class="pln">any</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="kwd">return</span><span class="pln"> </span><span class="typ">UsersDao</span><span class="pun">.</span><span class="pln">patchUserById</span><span class="pun">(</span><span class="pln">id</span><span class="pun">,</span><span class="pln"> resource</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">UsersDao</span><span class="pun">.</span><span class="pln">updateUserById</span><span class="pun">(</span><span class="pln">id</span><span class="pun">,</span><span class="pln"> resource</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="lit">24</span><span class="pun">,</span><span class="lit">3</span><span class="pln"> </span><span class="pun">+</span><span class="lit">24</span><span class="pun">,</span><span class="lit">3</span><span class="pln"> </span><span class="pun">@@</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">UsersService</span><span class="pln"> </span><span class="kwd">implements</span><span class="pln"> CRUD </span><span class="pun">{</span><span class="pln">
     </span><span class="kwd">async</span><span class="pln"> putById</span><span class="pun">(</span><span class="pln">id</span><span class="pun">:</span><span class="pln"> string</span><span class="pun">,</span><span class="pln"> resource</span><span class="pun">:</span><span class="pln"> </span><span class="typ">PutUserDto</span><span class="pun">):</span><span class="pln"> </span><span class="typ">Promise</span><span class="pun">&lt;</span><span class="pln">any</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="kwd">return</span><span class="pln"> </span><span class="typ">UsersDao</span><span class="pun">.</span><span class="pln">putUserById</span><span class="pun">(</span><span class="pln">id</span><span class="pun">,</span><span class="pln"> resource</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">UsersDao</span><span class="pun">.</span><span class="pln">updateUserById</span><span class="pun">(</span><span class="pln">id</span><span class="pun">,</span><span class="pln"> resource</span><span class="pun">);</span><span class="pln">
     </span><span class="pun">}</span></pre>

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

<ul>
	<li>
		نستخدم اﻵن الدالة <code>()updateUserById</code> لتنفيذ العمليتين <code>PUT</code> و <code>PATCH</code> (والسبب كما ذكرنا سابقًا أننا ندعم النهج النمطي لتنفيذ واجهات برمجية REST، بعيدًا عن بعض التوصيات التي لا تدعم استخدام <code>PUT</code> في إنشاء موارد جديدة غير موجودة أصلًا كي لا تسمح الواجهة الخلفية لمستثمريها التحكم بتولد المعرّفات ID).
	</li>
	<li>
		طالما أن كائن DAO الجديد يستخدم المعاملين <code>limit</code> و <code>page</code>، سنمررهما إلى الدالة <code>()getUsers</code>.
	</li>
</ul>

<p>
	تقدّم الهيكلية السابقة نموذجًا متماسكًا، إذ يرفض أية محاولة مثلًا لاستبدال MongoDB Mongoose بمكتبات أخرى مثل TypefORM و PostgreSL. ولتنفيذ مثل هذه التغييرات لا بد من إعادة بناء كل دالة من دوال DAO مع الحفاظ على بصمة هذه الدوال signature لتتفق مع بقية الشيفرة.
</p>

<h3 id="restmongoose">
	اختبار الواجهة البرمجية REST المبنية على Mongoose
</h3>

<p>
	لنشغّل الواجهة البرمجية باستخدام سطر اﻷوامر <code>npm start</code>، ونحاول إنشاء مستخدم:
</p>

<pre class="ipsCode">curl --request POST 'localhost:3000/users' \
--header 'Content-Type: application/json' \
--data-raw '{
    "password":"secr3tPass!23",
    "email":"marcos.henrique@toptal.com"
}'
</pre>

<p>
	يتضمن كائن الاستجابة معرّف المستخدم الجديد:
</p>

<pre class="ipsCode">{
    "id": "7WYQoVZ3E"
}
</pre>

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

<pre class="ipsCode">REST_API_EXAMPLE_ID="put_your_id_here"
</pre>

<p>
	يبدو تحديث مستخدم كالتالي:
</p>

<pre class="ipsCode">curl --include --request PATCH "localhost:3000/users/$REST_API_EXAMPLE_ID" \
--header 'Content-Type: application/json' \
--data-raw '{
    "firstName": "Marcos",
    "lastName": "Silva"
}'
</pre>

<p>
	من المفترض ان تبدأ الاستجابة بالترويسة <code>HTTP/1.1 204 No Content</code>. (لاحظ أنه بدون <code>include--</code> لن تُطبع أية استجابة). ولكي تتحقق من نجاح الطلبين السابقين نفّذ ما يلي:
</p>

<pre class="ipsCode">curl --request GET "localhost:3000/users/$REST_API_EXAMPLE_ID" \
--header 'Content-Type: application/json' \
--data-raw '{
    "firstName": "Marcos",
    "lastName": "Silva"
}'
</pre>

<p>
	تعرض الاستجابة الحقول المتوقعة ومن بينها <code>id_</code> الذي ناقشناه سابقًا:
</p>

<pre class="ipsCode">{
    "_id": "7WYQoVZ3E",
    "email": "marcos.henrique@toptal.com",
    "permissionFlags": 1,
    "__v": 0,
    "firstName": "Marcos",
    "lastName": "Silva"
}
</pre>

<p>
	لاحظ وجود حقل خاص هو <code>v__</code>  يُستخدم من قبل Mongoose لتحديد الإصدار وسيتغير في كل مرة يُحدَّث فيها نفس السجّل.
</p>

<p>
	دعونا اﻵن نطلب قائمة بأسماء المستخدمين:
</p>

<pre class="ipsCode">curl --request GET 'localhost:3000/users' \
--header 'Content-Type: application/json'
</pre>

<p>
	وستكون الاستجابة نفسها، لكنها مغلفة بالقوسين <code>[]</code>.
</p>

<p>
	بعد أن تأكدنا أن كلمة المرور قد حُفظت بأمان، سنتحقق من إمكانية حذف المستخدم:
</p>

<pre class="ipsCode">curl --include --request DELETE "localhost:3000/users/$REST_API_EXAMPLE_ID" \
--header 'Content-Type: application/json'
</pre>

<p>
	نتوقع أن نحصل هنا على الاستجابة 204 مجددًا.
</p>

<p>
	قد تتسائل إن كان حقل كلمة المرور يعمل كما نتوقع، طالما أننا حددنا الخيار <code>select: false</code> في تعريف تخطيط Mongoose وبالتالي لن يظهر الحقل عند الاستجابة للطلب <code>GET</code>. لهذا سنكرر إنشاء مستخدم جديد ونتحرى اﻷمر (لا تنسى تخزين المعرّف الجديد لاستخدامات لاحقة).
</p>

<h3 id="mongodb-2">
	كلمات المرور المخفية والتنقيح المباشر للبيانات في حاوية MongoDB
</h3>

<p>
	للتحقق من التخزين اﻵمن لكلمات المرور (مشفرّة بدلًا من أن تظهر كما هي)، يمكن للمطورين تفحّص MongoDB مباشرة. وأحد الطرق الممكنة استخدام واجهة سطر اﻷوامر المعيارية <code>mongo</code> من داخل حاوية Docker قيد التشغيل:
</p>

<pre class="ipsCode">sudo docker exec -it toptal-rest-series_mongo_1 mongo
</pre>

<p>
	من هناك نفّذ اﻷمر <code>use api-db</code> يتبعه <code>()db.users.find().pretty</code> لتحصل على بيانات جميع المستخدمين بما فيها كلمات المرور. أما لمن يفضل العمل على واجهات رسومية، بإمكانهم تثبيت عميل MongoDB مثل <a href="https://robomongo.org/" rel="external nofollow">Robo 3T</a>:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="150080" href="https://academy.hsoub.com/uploads/monthly_2024_05/02_robo3T_mongo_client.png.f00c331fb03c576e5d142344e580d0cd.png" rel=""><img alt="02 robo3t mongo client" class="ipsImage ipsImage_thumbnailed" data-fileid="150080" data-unique="r1ynl478q" src="https://academy.hsoub.com/uploads/monthly_2024_05/02_robo3T_mongo_client.thumb.png.621dd2f59eb8cb471f914ffd6f1b2605.png"> </a>
</p>

<p>
	تُعد البادئة <code>$...argon2</code> جزءًا من التنسيق النصي PHC وقد خّزنت دون تعديل عن قصد. فإظهار عبارة Argon2 ومعاملاتها العامة لن يساعد المخترقين على تحديد كلمة النص السرياﻷصلية إن استطاعوا سرقة قاعدة البيانات. ويمكن تعزيز كلمة المرور أكثر باستخدام التغفيل <a href="https://auth0.com/blog/adding-salt-to-hashing-a-better-way-to-store-passwords/" rel="external nofollow">salting</a>، وهي تقنية سنستخدمها لاحقًا مع مفتاح JWT.
</p>

<p>
	تأكدنا اﻵن أن Mongoose ترسل البيانات بنجاح إلى MongoDB، لكن كيف نتأكد أن مستثمري الواجهة البرمجية سيرسلون بيانات مناسبة ضمن طلباتهم إلى الوجهات الخاصة المستخدم؟
</p>

<h2 id="expressvalidator">
	إضافة المكتبة express-validator
</h2>

<p>
	هنالك طرق عدة للتحقق من الطلبات، سنستخدم منها في تطبيقنا المكتبة express-validator وهي مكتبة مستقرة وسهلة الاستخدام وتتمتع <a href="https://express-validator.github.io/docs/" rel="external nofollow">بتوثيق</a> جيد. وعلى الرغم من إمكانية استخدام وظيفة التحقق المضمنة مع Mongoose، لكن الميزات التي Express.js يقدمها express-validator أكثر. إذ تقدم لك المكتبة متحققًا جاهزًا من صحة البريد اﻹلكتروني، بينما عليك كتابة شيفرته بنفسك في Mongoose.
</p>

<p>
	سنثبت اﻵن المكتبة express-validator:
</p>

<pre class="ipsCode">npm i express-validator
</pre>

<p>
	ولكي نحدد الحقول التي نتحقق من صحتها، نستخدم التابع <code>()body</code> الذي سندرجه في الملف <code>users.routes.config.ts</code>. يتحقق بعدها التابع <code>()body</code>من الحقول ويولّد قائمة باﻷخطاء مخزّنة في الكائن <code>express.Request</code> في حال أخفق الطلب، ونحتاج عندها إلى أداة وسيطة كي تستفيد من تلك القائمة وتتحقق من اﻷخطاء.
</p>

<p>
	وطالما أننا نستخدم نفس المنطق على بقية المسارات، سننشئ الملف <code>common/middleware/body.validation.middleware.ts</code> الذي يضم الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1227_30" 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"> </span><span class="pun">{</span><span class="pln"> validationResult </span><span class="pun">}</span><span class="pln"> from </span><span class="str">'express-validator'</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">BodyValidationMiddleware</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    verifyBodyFieldsErrors</span><span class="pun">(</span><span class="pln">
     req</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">,</span><span class="pln">
     res</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Response</span><span class="pun">,</span><span class="pln">
     next</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">NextFunction</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"> errors </span><span class="pun">=</span><span class="pln"> validationResult</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">errors</span><span class="pun">.</span><span class="pln">isEmpty</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"> res</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"> errors</span><span class="pun">:</span><span class="pln"> errors</span><span class="pun">.</span><span class="pln">array</span><span class="pun">()</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">
    </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="kwd">new</span><span class="pln"> </span><span class="typ">BodyValidationMiddleware</span><span class="pun">();</span></pre>

<p>
	وهكذا سنكون قادرين على التعامل مع أية أخطاء تولّدها الدالة <code>()body</code>. لنضف اﻵن ما يلي إلى الملف <code>users.routes.config.ts</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1227_32" style=""><span class="kwd">import</span><span class="pln"> </span><span class="typ">BodyValidationMiddleware</span><span class="pln"> from </span><span class="str">'../common/middleware/body.validation.middleware'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> body </span><span class="pun">}</span><span class="pln"> from </span><span class="str">'express-validator'</span><span class="pun">;</span></pre>

<p>
	نستطيع اﻵن تحديث وجهاتنا كالتالي:
</p>

<pre class="ipsCode">@@ -15,3 +17,6 @@ export class UsersRoutes extends CommonRoutesConfig {
     .post(
-     UsersMiddleware.validateRequiredUserBodyFields,
+     body('email').isEmail(),
+     body('password')
+     .isLength({ min: 5 })
+     .withMessage('Must include password (5+ characters)'),
+     BodyValidationMiddleware.verifyBodyFieldsErrors,
     UsersMiddleware.validateSameEmailDoesntExist,
@@ -28,3 +33,10 @@ export class UsersRoutes extends CommonRoutesConfig {
     this.app.put(`/users/:userId`, [
-     UsersMiddleware.validateRequiredUserBodyFields,
+     body('email').isEmail(),
+     body('password')
+     .isLength({ min: 5 })
+     .withMessage('Must include password (5+ characters)'),
+     body('firstName').isString(),
+     body('lastName').isString(),
+     body('permissionFlags').isInt(),
+     BodyValidationMiddleware.verifyBodyFieldsErrors,
     UsersMiddleware.validateSameEmailBelongToSameUser,
@@ -34,2 +46,11 @@ export class UsersRoutes extends CommonRoutesConfig {
     this.app.patch(`/users/:userId`, [
+     body('email').isEmail().optional(),
+     body('password')
+     .isLength({ min: 5 })
+     .withMessage('Password must be 5+ characters')
+     .optional(),
+     body('firstName').isString().optional(),
+     body('lastName').isString().optional(),
+     body('permissionFlags').isInt().optional(),
+     BodyValidationMiddleware.verifyBodyFieldsErrors,
     UsersMiddleware.validatePatchEmail,
</pre>

<p>
	لا تنس إضافة <code>BodyValidationMiddleware.verifyBodyFieldsErrors</code> في كل وجهة بعد أي سطر يضم الدالة <code>()body</code> وإلا لن يكون لها فائدة.
</p>

<p>
	لاحظ أيضًا كيف حدّثنا الوجهتين <code>PUT</code> و <code>POST</code> لاستخدام express-validater بدلًا من الدالة <code>validateRequiredUserBodyFields</code> التي بنيناها بأنفسنا. لأنهما الوجهتان الوحيدتان اللتان تستخدمان تلك الدالة. ويمكن حذف شيفرتها من الملف <code>users.middleware.ts</code>.
</p>

<p>
	هذا كل ما في اﻷمر، شغّل Node.js وجرّب باستخدام أي عميل REST تفضلّه كيف تتعامل الواجهة البرمجية مع المدخلات المختلفة. ولا تنس اﻹطلاع على توثيق express-validator لترى اﻹمكانيات اﻷخرى التي تقدّمها. فما فعلنا هو مجرد نقطة انطلاق.
</p>

<h2 id="-1">
	الاستيثاق والسماحيات (منح التصريحات)
</h2>

<p>
	تتيح واجهتنا البرمجية مجموعة من نقاط الوصول endpoints لمستثمري الواجهة كي ينشئوا ويعدّلوا المستخدمين ويطلبو قوائم بهم، وتمنح كل نقطة منها وصولًا عامًا غير محدود. وتمنع أنماط الاستخدام أي مستخدم من تغيير بيانات آخر، كما تمنع أي دخلاء من الوصول إلى أية نقطة لا نريد وصولًا عامًا إليها.
</p>

<p>
	تتضمن القيود السابقة ناحيتين أساسيتين نختصرهما بالعبارة "auth" وتعني الإذن: اﻷولى هي الاستيثاق authentication وتعني معرفة صاحب الطلب، والثانية هي الترخيص أو التصريح authorization أي السماح لصاحب الطلب بتنفيذ طلبه أو لا. لهذا انتبه جيدًا إلى الناحية التي تُناقش، وخاصة في طلبات HTTP المعيارية، فالحالة <code>401 Unauthorized</code> تتعلق بالاستيثاق، أما <code>403 Forbidden</code> فتتعلق بالتصريح ولهذا السبب سنستخدم الاصطلاح "auth" ليعني الاستيثاق، ونستخدم المصطلح سماحيات permissions لمواضيع التصاريح.
</p>

<p>
	هنالك العديد من نُهج الاستيثاق التي يمكنك الاطلاع عليها، بما في ذلك الخدمات التي يقدمها طرف ثالث مثل Ath0، لكننا سنستخدم في تطبيقنا نهجًا أبسط مبني على مفاتيح JWT لكنه قابل للتوسع.
</p>

<p>
	يتكون مفتاح JWT من نص JSON مشفّر مع بعض البيانات الوصفية التي لا تتعلق بالاستيثاق، وتضم في حالتنا البريد اﻹلكتروني للمستخدم ورايات السماحيات permissions flags. كما يضم نص JSON نصًا سرًيا Secret للتحقق من سلامة البيانات الوصفية.
</p>

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

<p>
	لكن في أي مكان سنكتب الشيفرة المناسبة؟ يمكن من خلال الدوال الوسيطة استخدامها عندما تكون ضمن إعدادات الوجهات (المسارات).
</p>

<h3 id="-2">
	إضافة وحدة الاستيثاق
</h3>

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

<p>
	سنضيف قبل إنشاء أداة وسيطة middleware لتوليد مفاتيح JWT دالة خاصة إلى الملف <code>users.dao.ts</code> تستخلص حقل كلمة المرور، لأننا أعددنا Mongoose لتمنع عرضه:
</p>

<pre class="ipsCode">async getUserByEmailWithPassword(email: string) {
    return this.User.findOne({ email: email })
     .select('_id email permissionFlags +password')
     .exec();
}
</pre>

<p>
	أضف ما يلي في الملف <code>users.dao.ts</code>:
</p>

<pre class="ipsCode">async getUserByEmailWithPassword(email: string) {
    return UsersDao.getUserByEmailWithPassword(email);
}
</pre>

<p>
	ننشئ اﻵن المجلد <code>auth</code> في جذر المشروع، ونضيف نقطة وصول لنسمح لمستثمري الواجهة البرمجية بتوليد مفاتيح JWT. لهذا ننشئ أولًا الوحدة الوسيطة<br>
	<code>auth/middleware/auth.middleware.ts</code> على شكل صنف متفرّد singleton يُدعى <code>AuthMiddleware</code>، كما سنحتاج إلى استيراد بعض المكتبات:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1227_34" 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"> usersService from </span><span class="str">'../../users/services/users.service'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> as argon2 from </span><span class="str">'argon2'</span><span class="pun">;</span></pre>

<p>
	ننشئ ضمن الصنف <code>AuthMiddleware</code> دالة تتحقق من وجود ثبوتيات صالحة ضمن طلبات مستخدمي الواجهة البرمجية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1227_36" style=""><span class="kwd">async</span><span class="pln"> verifyUserPassword</span><span class="pun">(</span><span class="pln">
    req</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">,</span><span class="pln">
    res</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Response</span><span class="pun">,</span><span class="pln">
    next</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">NextFunction</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"> any </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> usersService</span><span class="pun">.</span><span class="pln">getUserByEmailWithPassword</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">email
    </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">)</span><span class="pln"> </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"> user</span><span class="pun">.</span><span class="pln">password</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">await</span><span class="pln"> argon2</span><span class="pun">.</span><span class="pln">verify</span><span class="pun">(</span><span class="pln">passwordHash</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">password</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">body </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"> user</span><span class="pun">.</span><span class="pln">_id</span><span class="pun">,</span><span class="pln">
     email</span><span class="pun">:</span><span class="pln"> user</span><span class="pun">.</span><span class="pln">email</span><span class="pun">,</span><span class="pln">
     permissionFlags</span><span class="pun">:</span><span class="pln"> user</span><span class="pun">.</span><span class="pln">permissionFlags</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"> next</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="com">// يساعد في الحماية من محاولات الاختراق</span><span class="pln">
    res</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"> errors</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="str">'Invalid email and/or password'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">});</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	ولكي تتأكد الدالة الوسيطة من وجود الحقلين <code>email</code> و <code>password</code> في جسم الطلب <code>req.body</code>، نستخدم express-validator، عندما نهيئ لاحقًا المسار لاستخدام الدالة <code>()verifyUserPassword</code>.
</p>

<h3 id="jwt">
	تخزين النص السري ضمن مفتاح JWT
</h3>

<p>
	نحتاج إلى سر كي نوّلد مفتاح JWT، الذي نستخدمه في تعليم المفتاح وللتحقق من صحة المفاتيح القادمة مع طلبات العملاء. سنخزّن هذا النص السريضمن ملف متغيّر بيئة <code>env.</code> منفصل بدلًا من كتابته ضمن ملف TypeScript، ولن يُدفع هذا الملف إلى مستودع الشيفرة. وكما جرت العادة، أضفنا الملف <code>env.example.</code> إلى المستودع لمساعدة المطورين على فهم أي المتغيرات مطلوبة عند إنشاء الملف <code>env.</code> الحقيقي. ونحتاج في حالتنا إلى متغير يُدعى <code>JWT_SECRET</code> يُخزّن سر مفتاح JWT على هيئة نص. وتذكّر أن تغيّر هذا النص السريفي نسختك إن أردت العمل على التطبيق انطلاقًا من المستودع الذي يضم المشروع المكتمل في نهاية هذه السلسلة من المقالات. كما تجدر اﻹشارة هنا إلى ضرورة اتباع <a href="https://auth0.com/blog/a-look-at-the-latest-draft-for-jwt-bcp/" rel="external nofollow">الممارسات اﻷفضل</a> عند استخدام مفاتيح JWT بالتمييز بين المفاتيح وفق البيئة (تطوير، إنتاج،…).
</p>

<p>
	ينبغي على الملف <code>env.</code> الموجود في جذر المشروع اتباع التنسيق التالي، لكن ليس بالضرورة استخدام نفس السر:
</p>

<pre class="ipsCode">JWT_SECRET=My!@!Se3cr8tH4sh3
</pre>

<p>
	ومن الطرق السهلة لتحميل هذه المتغيرات ضمن التطبيق استخدام مكتبة تُدعى <code>dotenv</code>:
</p>

<pre class="ipsCode">npm i dotenv
</pre>

<p>
	وكل ما تحتاجه لتهيئتها هو استدعاء الدالة <code>()dotenv.config</code> حالما تشغّل التطبيق. لهذا أضف الشيفرة التالية أعلى الملف <code>app.ts</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1227_38" style=""><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">const</span><span class="pln"> dotenvResult </span><span class="pun">=</span><span class="pln"> dotenv</span><span class="pun">.</span><span class="pln">config</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">dotenvResult</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">throw</span><span class="pln"> dotenvResult</span><span class="pun">.</span><span class="pln">error</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<h3 id="-3">
	متحكم الاستيثاق
</h3>

<p>
	آخر المتطلبات التي تلزمنا قبل توليد مفتاح JWT هو تثبيت المكتبة <code>jsonwebtoken</code> وأنواع TypeScript الخاصة بها:
</p>

<pre class="ipsCode">npm i jsonwebtoken
npm i --save-dev @types/jsonwebtoken
</pre>

<p>
	لننشئ اﻵن المتحكم <code>auth/controllers/auth.controller.ts</code>، ولا حاجة ﻹدراج المكتبة <code>dotenv</code> لأن إدراجها في ملف التطبيق <code>app.ts</code> جعل محتوى ملف متغيرات البيئة متاحًا ضمن كامل التطبيق من خلال كائن Node.js العام <code>process</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1227_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">import</span><span class="pln"> debug from </span><span class="str">'debug'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> jwt from </span><span class="str">'jsonwebtoken'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> crypto from </span><span class="str">'crypto'</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> log</span><span class="pun">:</span><span class="pln"> debug</span><span class="pun">.</span><span class="typ">IDebugger</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> debug</span><span class="pun">(</span><span class="str">'app:auth-controller'</span><span class="pun">);</span><span class="pln">

</span><span class="com">/**
* تُنشر هذه القيمة تلقائيًا من ملف متغيرات البيئة الذي عليك إنشاءه بنفسك في جذر المشروع
*في المستودع لمعرفة التنسيق المطلوب .env.example اطلع على الملف
*/</span><span class="pln">
</span><span class="com">// @ts-expect-error</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> jwtSecret</span><span class="pun">:</span><span class="pln"> string </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">JWT_SECRET</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> tokenExpirationInSeconds </span><span class="pun">=</span><span class="pln"> </span><span class="lit">36000</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">AuthController</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">async</span><span class="pln"> createJWT</span><span class="pun">(</span><span class="pln">req</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Response</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"> refreshId </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">userId </span><span class="pun">+</span><span class="pln"> jwtSecret</span><span class="pun">;</span><span class="pln">
     </span><span class="kwd">const</span><span class="pln"> salt </span><span class="pun">=</span><span class="pln"> crypto</span><span class="pun">.</span><span class="pln">createSecretKey</span><span class="pun">(</span><span class="pln">crypto</span><span class="pun">.</span><span class="pln">randomBytes</span><span class="pun">(</span><span class="lit">16</span><span class="pun">));</span><span class="pln">
     </span><span class="kwd">const</span><span class="pln"> hash </span><span class="pun">=</span><span class="pln"> crypto
     </span><span class="pun">.</span><span class="pln">createHmac</span><span class="pun">(</span><span class="str">'sha512'</span><span class="pun">,</span><span class="pln"> salt</span><span class="pun">)</span><span class="pln">
     </span><span class="pun">.</span><span class="pln">update</span><span class="pun">(</span><span class="pln">refreshId</span><span class="pun">)</span><span class="pln">
     </span><span class="pun">.</span><span class="pln">digest</span><span class="pun">(</span><span class="str">'base64'</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">refreshKey </span><span class="pun">=</span><span class="pln"> salt</span><span class="pun">.</span><span class="kwd">export</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">req</span><span class="pun">.</span><span class="pln">body</span><span class="pun">,</span><span class="pln"> jwtSecret</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
     expiresIn</span><span class="pun">:</span><span class="pln"> tokenExpirationInSeconds</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"> res
     </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">
     </span><span class="pun">.</span><span class="pln">send</span><span class="pun">({</span><span class="pln"> accessToken</span><span class="pun">:</span><span class="pln"> token</span><span class="pun">,</span><span class="pln"> refreshToken</span><span class="pun">:</span><span class="pln"> hash </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">err</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
     log</span><span class="pun">(</span><span class="str">'createJWT error: %O'</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"> res</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">send</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="kwd">export</span><span class="pln"> </span><span class="kwd">default</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">AuthController</span><span class="pun">();</span></pre>

<p>
	تربط المكتبة المفتاح الجديد بالنص السري الذي حددناه <code>jwtSecret</code>. كما سنوّلد مُوهِم (أو مُغفِّل salt) ومعمّي hash باستخدام الوحدة البرمجية اﻷصلية <code>crypto</code> في Node.js، وبعدها ننشئ المفتاح الذي يتمكن مستثمرو الواجهة من تحديث مفتاح JWT الحالي، وهذا اﻷمر مفيد بشكل خاص عند توسيع التطبيق.
</p>

<p>
	لكن ما الفرق بين <code>refreshKey</code> و <code>refreshToken</code> و <code>accessToken</code>؟ تُرسل هذه المفاتيح إلى مستثمر الواجهة البرمجية والغاية من ذلك هو: استخدام <code>accessToken</code> للطلبات التي تقع خارج إطار الوصول العام، و <code>refreshToken</code> لطلب استبدال مفتاح <code>accessToken</code> منتهي الصلاحية. ويستخدم المفتاح <code>refreshKey</code> لتمرير المتغير <code>salt</code> مشفّرًا ضمن المفتاح <code>refreshToken</code> إلى دالة التحديث الوسيطة التي سنشرحها لاحقًا.
</p>

<p>
	تجدر الملاحظة إلى أننا اعتمدنا على المكتبة <code>jsonwebtoken</code> في تحديد مدة صلاحية المفتاح، فإن انتهت صلاحية مفتاح لا بد من إعادة الاستيثاق من جديد:
</p>

<h3 id="restnodejs">
	مسار التحقق اﻷساسية في الواجهة البرمجية REST المبنية على Node.js
</h3>

<p>
	لنهيئ نقطة الوصول ضمن الملف <code>auth/auth.routes.config.ts</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1227_42" style=""><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="typ">CommonRoutesConfig</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> from </span><span class="str">'../common/common.routes.config'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> authController from </span><span class="str">'./controllers/auth.controller'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> authMiddleware from </span><span class="str">'./middleware/auth.middleware'</span><span class="pun">;</span><span class="pln">
</span><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"> </span><span class="typ">BodyValidationMiddleware</span><span class="pln"> from </span><span class="str">'../common/middleware/body.validation.middleware'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> body </span><span class="pun">}</span><span class="pln"> from </span><span class="str">'express-validator'</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">export</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">AuthRoutes</span><span class="pln"> extends </span><span class="typ">CommonRoutesConfig</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">constructor</span><span class="pun">(</span><span class="pln">app</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Application</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
     super</span><span class="pun">(</span><span class="pln">app</span><span class="pun">,</span><span class="pln"> </span><span class="str">'AuthRoutes'</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    configureRoutes</span><span class="pun">():</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Application</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">app</span><span class="pun">.</span><span class="pln">post</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">
     body</span><span class="pun">(</span><span class="str">'email'</span><span class="pun">).</span><span class="pln">isEmail</span><span class="pun">(),</span><span class="pln">
     body</span><span class="pun">(</span><span class="str">'password'</span><span class="pun">).</span><span class="pln">isString</span><span class="pun">(),</span><span class="pln">
     </span><span class="typ">BodyValidationMiddleware</span><span class="pun">.</span><span class="pln">verifyBodyFieldsErrors</span><span class="pun">,</span><span class="pln">
     authMiddleware</span><span class="pun">.</span><span class="pln">verifyUserPassword</span><span class="pun">,</span><span class="pln">
     authController</span><span class="pun">.</span><span class="pln">createJWT</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">this</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">
</span><span class="pun">}</span></pre>

<p>
	لا تنس إضافة التالي إلى الملف <code>app.ts</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1227_44" style=""><span class="com">// ...</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="typ">AuthRoutes</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> from </span><span class="str">'./auth/auth.routes.config'</span><span class="pun">;</span><span class="pln">
</span><span class="com">// ...</span><span class="pln">
routes</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">AuthRoutes</span><span class="pun">(</span><span class="pln">app</span><span class="pun">));</span><span class="pln"> </span><span class="com">//Userroutes قد يكون قبل أو بعد</span><span class="pln">
</span><span class="com">// ...</span></pre>

<p>
	أصبحنا اﻵن مستعدين ﻹعادة تشغيل Node.js واختبار الشيفرة، ولا بد من استخدام نفس الثبوتيات التي استخدمناها عند إنشاء المستخدم سابقًا:
</p>

<pre class="ipsCode">curl --request POST 'localhost:3000/auth' \
--header 'Content-Type: application/json' \
--data-raw '{
    "password":"secr3tPass!23",
    "email":"marcos.henrique@toptal.com"
}'
</pre>

<p>
	ستكون الاستجابة شبيهة بالتالي:
</p>

<pre class="ipsCode">{
    "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJVZGdzUTBYMXciLCJlbWFpbCI6Im1hcmNvcy5oZW5yaXF1ZUB0b3B0YWwuY29tIiwicHJvdmlkZXIiOiJlbWFpbCIsInBlcm1pc3Npb25MZXZlbCI6MSwicmVmcmVzaEtleSI6ImtDN3JFdDFHUmNsWTVXM0N4dE9nSFE9PSIsImlhdCI6MTYxMTM0NTYzNiwiZXhwIjoxNjExMzgxNjM2fQ.cfI_Ey4RHKbOKFdVGsowePZlMeX3fku6WHFu0EMjFP8",
    "refreshToken": "cXBHZ2tJdUhucERaTVpMWVNTckhNenQwcy9Bd0VIQ2RXRnA4bVBJbTBuQVorcS9Qb2xOUDVFS2xEM1RyNm1vTGdoWWJqb2xtQ0NHcXhlWERUcG81d0E9PQ=="
}
</pre>

<p>
	وكما فعلنا سابقًا، سنستخدم متغيرات البيئة لاستخدام القيم السابقة بشكل ملائم:
</p>

<pre class="ipsCode">REST_API_EXAMPLE_ACCESS="put_your_access_token_here"
REST_API_EXAMPLE_REFRESH="put_your_refresh_token_here"
</pre>

<p>
	وهكذا نكون قد حصلنا على مفتاح الوصول ومفتاح التحديث لكننا نحتاج إلى أداة وسيطة للاستفادة منهما.
</p>

<h3 id="jwt-1">
	وحدة وسيطة للتعامل مع JWT
</h3>

<p>
	نحتاج بداية إلى نوع TypeScript جديد للتعامل مع بنية المفتاح JWT بشكلها غير المرمّز، لهذا أنشئ الملف الذي يضم الشيفرة التالية <code>common/types/jwt.ts</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1227_47" style=""><span class="kwd">export</span><span class="pln"> type </span><span class="typ">Jwt</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    refreshKey</span><span class="pun">:</span><span class="pln"> string</span><span class="pun">;</span><span class="pln">
    userId</span><span class="pun">:</span><span class="pln"> string</span><span class="pun">;</span><span class="pln">
    permissionFlags</span><span class="pun">:</span><span class="pln"> string</span><span class="pun">;</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	لنكتب اﻵن شيفرة الدوال الوسيطة التي تتحقق من وجود مفتاح تحديث، والتحقق منه، والتحقق من مفتاح JWT. وسنضع الدوال الثلاث في الملف الجديد <code>common/types/jwt.ts</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1227_49" 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"> jwt from </span><span class="str">'jsonwebtoken'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> crypto from </span><span class="str">'crypto'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="typ">Jwt</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> from </span><span class="str">'../../common/types/jwt'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> usersService from </span><span class="str">'../../users/services/users.service'</span><span class="pun">;</span><span class="pln">

</span><span class="com">// @ts-expect-error</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> jwtSecret</span><span class="pun">:</span><span class="pln"> string </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">JWT_SECRET</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">JwtMiddleware</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    verifyRefreshBodyField</span><span class="pun">(</span><span class="pln">
     req</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">,</span><span class="pln">
     res</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Response</span><span class="pun">,</span><span class="pln">
     next</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">NextFunction</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">req</span><span class="pun">.</span><span class="pln">body </span><span class="pun">&amp;&amp;</span><span class="pln"> req</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">refreshToken</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"> next</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"> res
     </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">
     </span><span class="pun">.</span><span class="pln">send</span><span class="pun">({</span><span class="pln"> errors</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="str">'Missing required field: refreshToken'</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="kwd">async</span><span class="pln"> validRefreshNeeded</span><span class="pun">(</span><span class="pln">
     req</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">,</span><span class="pln">
     res</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Response</span><span class="pun">,</span><span class="pln">
     next</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">NextFunction</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"> any </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> usersService</span><span class="pun">.</span><span class="pln">getUserByEmailWithPassword</span><span class="pun">(</span><span class="pln">
     res</span><span class="pun">.</span><span class="pln">locals</span><span class="pun">.</span><span class="pln">jwt</span><span class="pun">.</span><span class="pln">email
     </span><span class="pun">);</span><span class="pln">
     </span><span class="kwd">const</span><span class="pln"> salt </span><span class="pun">=</span><span class="pln"> crypto</span><span class="pun">.</span><span class="pln">createSecretKey</span><span class="pun">(</span><span class="pln">
     </span><span class="typ">Buffer</span><span class="pun">.</span><span class="pln">from</span><span class="pun">(</span><span class="pln">res</span><span class="pun">.</span><span class="pln">locals</span><span class="pun">.</span><span class="pln">jwt</span><span class="pun">.</span><span class="pln">refreshKey</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="kwd">const</span><span class="pln"> hash </span><span class="pun">=</span><span class="pln"> crypto
     </span><span class="pun">.</span><span class="pln">createHmac</span><span class="pun">(</span><span class="str">'sha512'</span><span class="pun">,</span><span class="pln"> salt</span><span class="pun">)</span><span class="pln">
     </span><span class="pun">.</span><span class="pln">update</span><span class="pun">(</span><span class="pln">res</span><span class="pun">.</span><span class="pln">locals</span><span class="pun">.</span><span class="pln">jwt</span><span class="pun">.</span><span class="pln">userId </span><span class="pun">+</span><span class="pln"> jwtSecret</span><span class="pun">)</span><span class="pln">
     </span><span class="pun">.</span><span class="pln">digest</span><span class="pun">(</span><span class="str">'base64'</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">hash </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">refreshToken</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">body </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"> user</span><span class="pun">.</span><span class="pln">_id</span><span class="pun">,</span><span class="pln">
     email</span><span class="pun">:</span><span class="pln"> user</span><span class="pun">.</span><span class="pln">email</span><span class="pun">,</span><span class="pln">
     permissionFlags</span><span class="pun">:</span><span class="pln"> user</span><span class="pun">.</span><span class="pln">permissionFlags</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"> next</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"> res</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"> errors</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="str">'Invalid refresh token'</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">

    validJWTNeeded</span><span class="pun">(</span><span class="pln">
     req</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">,</span><span class="pln">
     res</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Response</span><span class="pun">,</span><span class="pln">
     next</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">NextFunction</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">req</span><span class="pun">.</span><span class="pln">headers</span><span class="pun">[</span><span class="str">'authorization'</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"> authorization </span><span class="pun">=</span><span class="pln"> req</span><span class="pun">.</span><span class="pln">headers</span><span class="pun">[</span><span class="str">'authorization'</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="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">authorization</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="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"> res</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">send</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">locals</span><span class="pun">.</span><span class="pln">jwt </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">
      authorization</span><span class="pun">[</span><span class="lit">1</span><span class="pun">],</span><span class="pln">
      jwtSecret
     </span><span class="pun">)</span><span class="pln"> as </span><span class="typ">Jwt</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="pun">}</span><span class="pln"> </span><span class="kwd">catch</span><span class="pln"> </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="kwd">return</span><span class="pln"> res</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">403</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">
     </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"> res</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">send</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="kwd">export</span><span class="pln"> </span><span class="kwd">default</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">JwtMiddleware</span><span class="pun">();</span></pre>

<p>
	تتحقق الدالة <code>()validRefreshNeeded</code> أيضًا إذا ما كان مفتاح التحديث صحيحًا من أجل معرّف مستخدم محدد. فإن كان كذلك، سنستخدم الدالة <code>authController.createJWT</code> لتوليد مفتاح JWT جديد للمستخدم.
</p>

<p>
	وتتحقق الدالة <code>()validJWTNeeded</code>, إن أرسل مستثمر الواجهة البرمجية مفتاح JWT صالح ضمن ترويسة طلب HTTP وفق الصيغة <code>Authorization: Bearer &lt;token&gt;</code> (وهذا أيضًا للأسف تضارب آخر بين الاستيثاق والتصريح).
</p>

<p>
	علينا اﻵن تهيئة مسار جديد لتحديث المفتاح، وقد شُفِّرت ضمنه رايات السماحية.
</p>

<h3 id="jwt-2">
	مسار لتحديث مفاتيح JWT
</h3>

<p>
	سندرج اﻷداة الوسطية الجديدة ضمن الملف <code>auth.routes.config.ts</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1227_52" style=""><span class="kwd">import</span><span class="pln"> jwtMiddleware from </span><span class="str">'./middleware/jwt.middleware'</span><span class="pun">;</span></pre>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1227_54" style=""><span class="kwd">this</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="pln">auth</span><span class="pun">/</span><span class="pln">refresh</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">
    jwtMiddleware</span><span class="pun">.</span><span class="pln">validJWTNeeded</span><span class="pun">,</span><span class="pln">
    jwtMiddleware</span><span class="pun">.</span><span class="pln">verifyRefreshBodyField</span><span class="pun">,</span><span class="pln">
    jwtMiddleware</span><span class="pun">.</span><span class="pln">validRefreshNeeded</span><span class="pun">,</span><span class="pln">
    authController</span><span class="pun">.</span><span class="pln">createJWT</span><span class="pun">,</span><span class="pln">
</span><span class="pun">]);</span></pre>

<p>
	سنختبر الآن إن كانت تعمل كما هو مطلوب باستخدام المفتاحين <code>accessToken</code> و <code>refreshToken</code> اللذان حصلنا عليهما سابقًا:
</p>

<pre class="ipsCode">curl --request POST 'localhost:3000/auth/refresh-token' \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer $REST_API_EXAMPLE_ACCESS" \
--data-raw "{
    \"refreshToken\": \"$REST_API_EXAMPLE_REFRESH\"
}"
</pre>

<p>
	علينا أن نتوقع الحصول على مفتاحين جديدين <code>accessToken</code> و <code>refreshToken</code> كي نستخدمهما لاحقًا. بإمكانك اﻵن تجريب كيف تتحقق الواجهة الخلفية من المفتاحين السابقين، وكيف تحدد عدد المرات التي يمكنك فيها الحصول على مفاتيح جديدة.
</p>

<p>
	واﻵن سيتمكن مستثمرو الواجهة الخلفية من إنشاء مفاتيح JWT والتحقق منها وتحديثها.
</p>

<h2 id="-4">
	الخلاصة
</h2>

<p>
	أكملنا في هذا المقال العمل على تطبيقنا الذي يُنشئ واجهة برمجة REST باستخدام TypeScript و Express.js في بيئة Node.js وقد انتقلنا في هذا القسم من قاعدة بيانات مؤقتة مقيمة في الذاكرة إلى استخدام قاعدة بيانات MongoDB بالاستعانة بالمكتبة Mongoose كما عملنا على تنفيذ آلية للتحقق من صحة المدخلات باستخدام مفاتيح ويب JWT والمكتبة <code>jsonwebtoken</code>. أما عن مفاهيم السماحيات وكيفية تنفيذها لتتكامل مع مفاتيح JWT فهذا ما سنراه في المقال التالي.
</p>

<p>
	ترجمة -وبتصرف- للقسم اﻷول من المقال <a href="https://www.toptal.com/express-js/nodejs-typescript-rest-api-pt-3" rel="external nofollow">Building a Node.js TypeScript REST <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr>, Part 3 MongoDB, Authentication, and Automated Tests</a>
</p>

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

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%A8%D9%86%D8%A7%D8%A1-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-%D9%85%D8%AA%D9%88%D8%A7%D9%81%D9%82%D8%A9-%D9%85%D8%B9-rest-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-expressjs-%D8%A7%D9%84%D9%82%D8%B3%D9%85-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D9%86%D9%85%D8%A7%D8%B0%D8%AC-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D9%88%D8%B3%D9%8A%D8%B7%D8%A9-%D9%88%D8%A7%D9%84%D8%AE%D8%AF%D9%85%D8%A7%D8%AA-r2329/" rel="">بناء واجهة برمجية متوافقة مع REST باستخدام Express.js القسم الثاني: نماذج البيانات والبرمجيات الوسيطة والخدمات</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/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%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-%D9%85%D8%B9-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-mongoose-r2171/" rel="">تطبيق عملي لتعلم Express - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/servers/databases/%D9%85%D9%82%D8%A7%D8%B1%D9%86%D8%A9-%D8%A8%D9%8A%D9%86-mysql-%D9%88-mongodb-r627/" rel="">مقارنة بين MySQL و MongoDB</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2331</guid><pubDate>Sun, 26 May 2024 12:08:01 +0000</pubDate></item><item><title>&#x628;&#x646;&#x627;&#x621; &#x648;&#x627;&#x62C;&#x647;&#x629; &#x628;&#x631;&#x645;&#x62C;&#x64A;&#x629; &#x645;&#x62A;&#x648;&#x627;&#x641;&#x642;&#x629; &#x645;&#x639; REST &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; Express.js &#x627;&#x644;&#x642;&#x633;&#x645; &#x627;&#x644;&#x62B;&#x627;&#x646;&#x64A;: &#x646;&#x645;&#x627;&#x630;&#x62C; &#x627;&#x644;&#x628;&#x64A;&#x627;&#x646;&#x627;&#x62A; &#x648;&#x627;&#x644;&#x628;&#x631;&#x645;&#x62C;&#x64A;&#x627;&#x62A; &#x627;&#x644;&#x648;&#x633;&#x64A;&#x637;&#x629; &#x648;&#x627;&#x644;&#x62E;&#x62F;&#x645;&#x627;&#x62A;</title><link>https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%A8%D9%86%D8%A7%D8%A1-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-%D9%85%D8%AA%D9%88%D8%A7%D9%81%D9%82%D8%A9-%D9%85%D8%B9-rest-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-expressjs-%D8%A7%D9%84%D9%82%D8%B3%D9%85-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D9%86%D9%85%D8%A7%D8%B0%D8%AC-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D9%88%D8%B3%D9%8A%D8%B7%D8%A9-%D9%88%D8%A7%D9%84%D8%AE%D8%AF%D9%85%D8%A7%D8%AA-r2329/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_05/-----REST---Node.js-TypeScript-------.png.8c8f3d8076413d240535be9818e41d7f.png" /></p>
<p>
	نكمل في هذا المقال ما بدأناه في <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%A8%D9%86%D8%A7%D8%A1-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-%D9%85%D8%AA%D9%88%D8%A7%D9%81%D9%82%D8%A9-%D9%85%D8%B9-rest-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-expressjs-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-r2327/" rel="">مقالنا السابق</a> الذي يتحدث عن بناء واجهة برمجية REST، وكنا قد خصصناه لشرح النقاط التالية:
</p>

<ul>
	<li>
		استخدام <code>npm</code> في إنشاء واجهة خلفية من الصفر.
	</li>
	<li>
		تحضير الاعتماديات اللازمة مثل TypeScript.
	</li>
	<li>
		استخدام الوحدة البرمجية <code>debug</code> المضمنة في بيئة Node.js.
	</li>
	<li>
		بناء هيكلية مشروع Express.js وتسجيل اﻷحداث التي تقع أثناء تشغيل التطبيق باستخدام Winston.
	</li>
</ul>

<p>
	إن كنت تشعر أن المفاهيم التي تحدثنا عنها واضحة بالنسبة إليك، انسخ <a href="https://github.com/makinhs/toptal-rest-series/tree/toptal-article-01" rel="external nofollow">رابط المشروع</a>، وانتقل إلى الفرع <code>toptal-article-01</code> ثم تابع القراءة
</p>

<h2 id="">
	خدمات الواجهة البرمجية ريست والبرمجيات الوسيطة والمتحكمات ونماذج البيانات
</h2>

<p>
	سنفصّل في مقالنا هذا النقاط التالية:
</p>

<ul>
	<li>
		<strong>الخدمات Services</strong>: التي تجعل الشيفرة أكثر وضوحًا بترتيب العمليات المنطقية ضمن دوال يمكن للبرمجيات الوسيطة والمتحكمات استدعاءها.
	</li>
	<li>
		<strong>البرمجيات الوسيطة Middleware</strong>: التي تقيِّم حالة المتطلبات prerequisites قبل أن تستدعي Express.js دالة المتحكم المناسب.
	</li>
	<li>
		<strong>المتحكمات Controllers</strong>: تستخدم الخدمات لمعالجة الطلبات قبل إرسال نتيجة الطلب إلى العميل.
	</li>
	<li>
		<strong>نماذج البيانات Models</strong>: تصف بياناتنا وتساعد في عمليات التحقق التي نجريها أثناء تصريف التطبيق.
	</li>
</ul>

<p>
	سنضيف أيضًا قاعدة بيانات بسيطة ولا تصلح بالطبع لنسخة اﻹنتاج، فغايتها الوحيدة تسهيل فهم تطبيقنا ومتابعته، وتمهّد للمقال التالي الذي يتحدث عن الاتصال بقاعدة البيانات، والتكامل مع القاعدة MongoDB والمكتبة Mongoose.
</p>

<h2 id="daosdtos">
	الخطوات اﻷولى للعمل مع DAOs و DTOs وقاعدة بياناتنا المؤقتة
</h2>

<p>
	لن تستخدم قاعدة البيانات في هذه المرحلة ملفات لتخزين البيانات، بل ستحتفظ ببيانات المستخدمين ضمن مصفوفة، أي أن البيانات ستزول بمجرد إغلاق Node.js. ستدعم قاعدة البيانات العمليات اﻷساسية CRUD ( إنشاء Create، قراءة Read، تحديث Update، حذف Delete).
</p>

<p>
	ونشير إلى مفهومين نستخدمهما هنا وهما:
</p>

<ul>
	<li>
		كائنات الوصول إلى البيانات Data access Objects واختصارًا DAOs.
	</li>
	<li>
		كائنات نقل البيانات Data transfer Objects واختصارًا DTOs.
	</li>
</ul>

<p>
	يُستخدم DAO للاتصال بقاعدة بيانات محددة وتنفيذ عمليات CRUD عليها، بينما يحتوي DTO البيانات الخام التي يرسلها DAO أو التي يستقبلها من قاعدة البيانات.
</p>

<p>
	وبعبارة أخرى، تتوافق كائنات DTO مع أنواع نماذج البيانات، بينما تُعد كائنات DAO خدمات تستخدم كائنات DTO. وهكذا قد تكون كائنات DTO أكثر تعقيدًا وفقًا لمستوى تداخل البيانات في بنية قاعدة البيانات، بينما تكون نسخة واحدة من كائن DAO مسؤولة عن فعل محدد على سطر واحد فقط من قاعدة البيانات.
</p>

<h2 id="dto">
	لماذا تُستخدم كائنات DTO
</h2>

<p>
	يساعد استخدام كائنات DTO كي تتوافق كائنات TypeScript مع نماذج البيانات الخاصة بنا في الحفاظ على تناسق قاعدة البيانات كما سنرى لاحقًا. لكن هنالك نقص واضح، فلا يمكن أن تقدم كائنات DTO ولا حتى TypeScript نفسها أي نوع من التحقق التلقائي مما يُدخله المستخدم، لأن ذلك يحدث أثناء تنفيذ التطبيق. فعندما يصل أحد المدخلات إلى نقطة وصول الواجهة البرمجية، فقد يكون لهذا المُدخل:
</p>

<ul>
	<li>
		حقول زائدة.
	</li>
	<li>
		حقول مطلوبة مفقودة (كتلك التي لا تبدأ بالمحرف <code>?</code>).
	</li>
	<li>
		نوع بيانات الحقل لا تطابق نوع البيانات المحدد في نموذج البيانات الذي يعتمد على TypeScript.
	</li>
</ul>

<p>
	فلن تتحقق TypeScript (أو جافا سكريبت التي ستُنقل الشيفرة إليها) من تلك المدخلات، لهذا من المهم ألا ننسى عمليات التحقق، وخاصة عندما تكون الواجهة البرمجية متاحة للعموم. قد تساعدك في ذلك حزم مثل <a href="https://www.npmjs.com/package/ajv" rel="external nofollow">ajv</a>، لكنها تعمل عادة بتعريف كائنات لها تخطيط مخصص لمكتبة محددة بدلًا من كائنات TypeScript الأصلية (ستلعب مكتبة Mongoose هذا الدور كما سنلاحظ في مقال تالٍ).
</p>

<p>
	وقد يخطر في بالك السؤال التالي: "هل علي استخدام كلًا من كائنات DAO و DTO إن توفّر ما هو أبسط؟"من اﻷفضل تفادي استخدام كائنات DTO في مشاريع Express.js/TypeScript حقيقة صغيرة إلا في الحالة التي تخطط فيها توسيع هذه المشاريع لتصبح متوسطة الحجم.
</p>

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

<h2 id="resttypescript">
	نموذج المستخدم في الواجهة البرمجية REST على مستوى TypeScript
</h2>

<p>
	نعرّف بداية ثلاث كائنات DTO للمستخدم، لهذا ننشئ مجلدًا يُدعى <code>dto</code> ضمن المجلد <code>user</code>، ثم ننشئ ملفًا يُدعى <code>create.user.dto.ts</code> يضم الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_876_9" style=""><span class="kwd">export</span><span class="pln"> </span><span class="kwd">interface</span><span class="pln"> </span><span class="typ">CreateUserDto</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    id</span><span class="pun">:</span><span class="pln"> string</span><span class="pun">;</span><span class="pln">
    email</span><span class="pun">:</span><span class="pln"> string</span><span class="pun">;</span><span class="pln">
    password</span><span class="pun">:</span><span class="pln"> string</span><span class="pun">;</span><span class="pln">
    firstName</span><span class="pun">?:</span><span class="pln"> string</span><span class="pun">;</span><span class="pln">
    lastName</span><span class="pun">?:</span><span class="pln"> string</span><span class="pun">;</span><span class="pln">
    permissionLevel</span><span class="pun">?:</span><span class="pln"> number</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

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

<p>
	ولا بد من تحديث الكائن بأكمله عند استخدام الاستعلام <code>PUT</code>، وسيكون الحقلان الاختياريين اﻵن ضروريان. لهذا أنشئ الملف <code>put.user.dto.ts</code> في نفس المجلد السابق ليضم الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_876_11" style=""><span class="kwd">export</span><span class="pln"> </span><span class="kwd">interface</span><span class="pln"> </span><span class="typ">PutUserDto</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    id</span><span class="pun">:</span><span class="pln"> string</span><span class="pun">;</span><span class="pln">
    email</span><span class="pun">:</span><span class="pln"> string</span><span class="pun">;</span><span class="pln">
    password</span><span class="pun">:</span><span class="pln"> string</span><span class="pun">;</span><span class="pln">
    firstName</span><span class="pun">:</span><span class="pln"> string</span><span class="pun">;</span><span class="pln">
    lastName</span><span class="pun">:</span><span class="pln"> string</span><span class="pun">;</span><span class="pln">
    permissionLevel</span><span class="pun">:</span><span class="pln"> number</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	وبالنسبة إلى طلبات <code>PATCH</code>، باﻹمكان استخدام الميزة <code>partial</code> من TypeScript والتي تنشئ نوعًا جديدًا بنسخ نوع آخر وجعل كل حقوله اختيارية. وهكذا ستكون شيفرة الملف <code>patch.user.dto.ts</code> هي فقط الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_876_13" style=""><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="typ">PutUserDto</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> from </span><span class="str">'./put.user.dto'</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">export</span><span class="pln"> </span><span class="kwd">interface</span><span class="pln"> </span><span class="typ">PatchUserDto</span><span class="pln"> extends </span><span class="typ">Partial</span><span class="pun">&lt;</span><span class="typ">PutUserDto</span><span class="pun">&gt;</span><span class="pln"> </span><span class="pun">{}</span></pre>

<p>
	لننشئ اﻵن قاعدة البيانات المؤقتة في الذاكرة، لهذا ننشئ أولًا المجلد <code>daos</code> داخل المجلد <code>user</code> ومن ثم نضيف الملف <code>users.dao.ts</code>.
</p>

<p>
	ندرج أولًا كائنات DTO التي أنشأناها:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_876_15" style=""><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="typ">CreateUserDto</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> from </span><span class="str">'../dto/create.user.dto'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="typ">PatchUserDto</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> from </span><span class="str">'../dto/patch.user.dto'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="typ">PutUserDto</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> from </span><span class="str">'../dto/put.user.dto'</span><span class="pun">;</span></pre>

<p>
	وللتعامل مع معرّفات المستخدمين IDs، سنضيف المكتبة <code>shortid</code> باستخدام الطرفية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_876_17" style=""><span class="pln">npm i shortid
npm i </span><span class="pun">--</span><span class="pln">save</span><span class="pun">-</span><span class="pln">dev </span><span class="lit">@types</span><span class="pun">/</span><span class="pln">shortid</span></pre>

<p>
	بالعودة إلى الملف <code>users.dao.ts</code>، سندرج المكتبة <code>shortid</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_876_19" style=""><span class="kwd">import</span><span class="pln"> shortid from </span><span class="str">'shortid'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> debug from </span><span class="str">'debug'</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> log</span><span class="pun">:</span><span class="pln"> debug</span><span class="pun">.</span><span class="typ">IDebugger</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> debug</span><span class="pun">(</span><span class="str">'app:in-memory-dao'</span><span class="pun">);</span></pre>

<p>
	بإمكاننا اﻵن إنشاء صنف يُدعى <code>UserDao</code> يبدو كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_876_21" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">UsersDao</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    users</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Array</span><span class="pun">&lt;</span><span class="typ">CreateUserDto</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="kwd">constructor</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        log</span><span class="pun">(</span><span class="str">'Created new instance of UsersDao'</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="kwd">new</span><span class="pln"> </span><span class="typ">UsersDao</span><span class="pun">();</span></pre>

<p>
	سنستخدم في هذا الصنف نمط التصميم المتفرد <a href="https://wiki.hsoub.com/Design_Patterns/singleton" rel="external">singleton</a> وبالتالي سيقدم هذا الصنف نفس النسخة، ونفس مصفوفة المستخدمين <code>users</code> عندما ندرجه ضمن ملفات أخرى. والسبب أن Node.js تخزّن هذا الملف مؤقتًا كلما أُدرج، وتجري كل عمليات الإدراج عند إقلاع التطبيق. أي سيُسلم كل ملف يشير إلى الملف <code>users.dao.ts</code> مرجعًا إلى النسخة <code>()new UsersDao</code> التي صُدِّرت في أول مرة يعالج فيها Node.js هذا الملف.
</p>

<p>
	سنرى طريقة العمل هذه عندما نستخدم الصنف لاحقًا في المقال، ونستخدم هذا النمط من اﻷصناف الشائعة في TypeScript/Express.js مع تقدمنا في تطوير التطبيق.
</p>

<p>
	<strong>ملاحظة</strong>: من سلبيات استخدام نمط التصميم singleton صعوبة كتابة اختبارات وحدة لها، لكننا لن نلاحظ هذه السلبية في الكثير من حالات استخدامنا لهذه الأصناف، لأنها لا تضم متغيرات أعضاء تحتاج إلى إعادة ضبط قيمها. أما بالنسبة للحالات التي يجب فيها إعادة ضبط المتغيرات اﻷعضاء في singleton سنترك اﻷمر كتمرين للقارئ كي يفكّر في انتهاج طريقة للحل تعتمد على فكرة حقن الاعتمادية dependency injection.
</p>

<p>
	أما اﻵن، سنضيف العمليات اﻷساسية للتعامل مع قواعد البيانات CRUD إلى الصنف كدوال، وستكون بداية دالة إنشاء مستخدم كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_876_24" style=""><span class="kwd">async</span><span class="pln"> addUser</span><span class="pun">(</span><span class="pln">user</span><span class="pun">:</span><span class="pln"> </span><span class="typ">CreateUserDto</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">id </span><span class="pun">=</span><span class="pln"> shortid</span><span class="pun">.</span><span class="pln">generate</span><span class="pun">();</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">users</span><span class="pun">.</span><span class="pln">push</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"> user</span><span class="pun">.</span><span class="pln">id</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	وستأتي دالة استرجاع أسماء المستخدمين بأسلوبين: اﻷول هو "قراءة كل الموارد (جميع المستخدمين المسجلين)" واﻵخر "استرداد مستخدم من خلال المعرّف ID فقط":
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_876_26" style=""><span class="kwd">async</span><span class="pln"> getUsers</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">this</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">

</span><span class="kwd">async</span><span class="pln"> getUserById</span><span class="pun">(</span><span class="pln">userId</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="kwd">return</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">users</span><span class="pun">.</span><span class="pln">find</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"> id</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"> user</span><span class="pun">.</span><span class="pln">id </span><span class="pun">===</span><span class="pln"> userId</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	أما الدالة التي تحدّث سجلات المستخدمين فقد تعيد كتابة الكائن بالكامل (الاستعلام <code>PUT</code>) أو جزء منه (<code>PATCH</code><span class="ipsEmoji">?</span>
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_876_28" style=""><span class="kwd">async</span><span class="pln"> putUserById</span><span class="pun">(</span><span class="pln">userId</span><span class="pun">:</span><span class="pln"> string</span><span class="pun">,</span><span class="pln"> user</span><span class="pun">:</span><span class="pln"> </span><span class="typ">PutUserDto</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"> objIndex </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">users</span><span class="pun">.</span><span class="pln">findIndex</span><span class="pun">(</span><span class="pln">
        </span><span class="pun">(</span><span class="pln">obj</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"> string </span><span class="pun">})</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> obj</span><span class="pun">.</span><span class="pln">id </span><span class="pun">===</span><span class="pln"> userId
    </span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">users</span><span class="pun">.</span><span class="pln">splice</span><span class="pun">(</span><span class="pln">objIndex</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</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="pun">`</span><span class="pln">$</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"> updated via put</span><span class="pun">`;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">async</span><span class="pln"> patchUserById</span><span class="pun">(</span><span class="pln">userId</span><span class="pun">:</span><span class="pln"> string</span><span class="pun">,</span><span class="pln"> user</span><span class="pun">:</span><span class="pln"> </span><span class="typ">PatchUserDto</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"> objIndex </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">users</span><span class="pun">.</span><span class="pln">findIndex</span><span class="pun">(</span><span class="pln">
        </span><span class="pun">(</span><span class="pln">obj</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"> string </span><span class="pun">})</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> obj</span><span class="pun">.</span><span class="pln">id </span><span class="pun">===</span><span class="pln"> userId
    </span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">let</span><span class="pln"> currentUser </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">users</span><span class="pun">[</span><span class="pln">objIndex</span><span class="pun">];</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> allowedPatchFields </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
        </span><span class="str">'password'</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'firstName'</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'lastName'</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'permissionLevel'</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="kwd">let</span><span class="pln"> field </span><span class="kwd">of</span><span class="pln"> allowedPatchFields</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">field in user</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="com">// @ts-ignore</span><span class="pln">
            currentUser</span><span class="pun">[</span><span class="pln">field</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">field</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">this</span><span class="pun">.</span><span class="pln">users</span><span class="pun">.</span><span class="pln">splice</span><span class="pun">(</span><span class="pln">objIndex</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> currentUser</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="pun">{</span><span class="pln">user</span><span class="pun">.</span><span class="pln">id</span><span class="pun">}</span><span class="pln"> patched</span><span class="pun">`;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	وكما ذكرنا سابقًا، فعلى الرغم من التصريح عن <code>UserDto</code> في طريقة تعريف الدوال السابقة، لا يقدم TypeScript أي طريقة للتحقق من اﻷنواع في زمن التنفيذ، ويعني ذلك أن:
</p>

<ul>
	<li>
		الدالة <code>()putUserById</code> ستحتوي ثغرة تسمح لمستخدمي الواجهة البرمجية بتخزين قيم لحقول ليست جزءًا من النموذج الذي يعرّفه كائن DTO.
	</li>
	<li>
		<code>()patchUserById</code> تعتمد هذه الدالة على على قائمة مكررة من أسماء الحقول والتي يجب أن تبقى متزامنة مع النموذج. وبدون وجود تزامن بين هذه القائمة والنموذج قد يستخدم النموذج القائمة التي يمثلها الكائن الذي حُدِّث، وبالتالي سيتجاهل قيم الحقول التي هي في الواقع جزء من النموذج الذي عرّفه كائن DTO لكنها لم تخزّن ضمنه سابقًا.
	</li>
</ul>

<p>
	سنعالج هاتين الحالتين بالشكل الصحيح لاحقًا عندما نتعامل مع تطبيقنا على مستوى قاعدة البيانات.
</p>

<p>
	أما العملية اﻷخيرة فهي عملية الحذف، وستكون دالتها كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_876_30" style=""><span class="kwd">async</span><span class="pln"> removeUserById</span><span class="pun">(</span><span class="pln">userId</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="kwd">const</span><span class="pln"> objIndex </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">users</span><span class="pun">.</span><span class="pln">findIndex</span><span class="pun">(</span><span class="pln">
        </span><span class="pun">(</span><span class="pln">obj</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"> string </span><span class="pun">})</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> obj</span><span class="pun">.</span><span class="pln">id </span><span class="pun">===</span><span class="pln"> userId
    </span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">users</span><span class="pun">.</span><span class="pln">splice</span><span class="pun">(</span><span class="pln">objIndex</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">return</span><span class="pln"> </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"> removed</span><span class="pun">`;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	وكنقطة إضافية، نعلم أن من شروط التسجيل الصحيح لمستخدم جديد هو عدم تكرار البريد اﻹلكتروني، لهذا سنضيف الدالة <code>getUserByEmail</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_876_32" style=""><span class="kwd">async</span><span class="pln"> getUserByEmail</span><span class="pun">(</span><span class="pln">email</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="kwd">const</span><span class="pln"> objIndex </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">users</span><span class="pun">.</span><span class="pln">findIndex</span><span class="pun">(</span><span class="pln">
        </span><span class="pun">(</span><span class="pln">obj</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> email</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"> obj</span><span class="pun">.</span><span class="pln">email </span><span class="pun">===</span><span class="pln"> email
    </span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">let</span><span class="pln"> currentUser </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">users</span><span class="pun">[</span><span class="pln">objIndex</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">currentUser</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"> currentUser</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="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>
	<strong>ملاحظة</strong>: في الحالات الحقيقة، قد تتصل بقاعدة البيانات من خلال مكتبات موجودة مسبقًا مثل Mongoose و Sequelize والتي تقدّم آلية لتنفيذ كل العمليات اﻷساسية التي تحتاجها. لهذا لن نتوسّع في شرح طريقة إنجاز الدوال السابقة.
</p>

<h2 id="rest">
	طبقة الخدمات في الواجهة البرمجية REST لتطبيقنا
</h2>

<p>
	بعد أن أنشأنا كائن DAO أساسي مقيم في الذاكرة، بإمكاننا إنشاء خدمة تستدعي دوال CRUD. وطالما أن هذه الدوال مطلوبة لكل خدمة تتصل بقاعدة بيانات، سننشئ الواجهة <code>CRUD</code> التي تضم التوابع التي نحتاجها في كل مرة ننفذ فيها خدمة جديدة.
</p>

<p>
	تتمتع معظم بيئات التطوير المتكاملة التي نعمل عليها حاليًا ميزة توليد الشيفرة التي تمكننا من إضافة الدوال التي ننجزها في كل مرة نحتاجها مما يقلل كمية الشيفرة المكررة التي علينا كتابتها.
</p>

<p>
	أنشئ ضمن المجلد <code>common</code> مجلدّا بالاسم <code>interfaces</code> ثم أنشئ الملف <code>crud.interface.ts</code> وأضف إليه الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_876_34" style=""><span class="kwd">export</span><span class="pln"> </span><span class="kwd">interface</span><span class="pln"> CRUD </span><span class="pun">{</span><span class="pln">
    list</span><span class="pun">:</span><span class="pln"> </span><span class="pun">(</span><span class="pln">limit</span><span class="pun">:</span><span class="pln"> number</span><span class="pun">,</span><span class="pln"> page</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="typ">Promise</span><span class="pun">&lt;</span><span class="pln">any</span><span class="pun">&gt;;</span><span class="pln">
    create</span><span class="pun">:</span><span class="pln"> </span><span class="pun">(</span><span class="pln">resource</span><span class="pun">:</span><span class="pln"> any</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="typ">Promise</span><span class="pun">&lt;</span><span class="pln">any</span><span class="pun">&gt;;</span><span class="pln">
    putById</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"> string</span><span class="pun">,</span><span class="pln"> resource</span><span class="pun">:</span><span class="pln"> any</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="typ">Promise</span><span class="pun">&lt;</span><span class="pln">string</span><span class="pun">&gt;;</span><span class="pln">
    readById</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"> string</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="typ">Promise</span><span class="pun">&lt;</span><span class="pln">any</span><span class="pun">&gt;;</span><span class="pln">
    deleteById</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"> string</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="typ">Promise</span><span class="pun">&lt;</span><span class="pln">string</span><span class="pun">&gt;;</span><span class="pln">
    patchById</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"> string</span><span class="pun">,</span><span class="pln"> resource</span><span class="pun">:</span><span class="pln"> any</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="typ">Promise</span><span class="pun">&lt;</span><span class="pln">string</span><span class="pun">&gt;;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	لننشئ اﻵن المجلد <code>services</code> ضمن المجلد <code>users</code> وضمنه الملف <code>users.service.ts</code> ونزوّده بالشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_876_36" style=""><span class="kwd">import</span><span class="pln"> </span><span class="typ">UsersDao</span><span class="pln"> from </span><span class="str">'../daos/users.dao'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> CRUD </span><span class="pun">}</span><span class="pln"> from </span><span class="str">'../../common/interfaces/crud.interface'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="typ">CreateUserDto</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> from </span><span class="str">'../dto/create.user.dto'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="typ">PutUserDto</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> from </span><span class="str">'../dto/put.user.dto'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="typ">PatchUserDto</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> from </span><span class="str">'../dto/patch.user.dto'</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">UsersService</span><span class="pln"> </span><span class="kwd">implements</span><span class="pln"> CRUD </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">async</span><span class="pln"> create</span><span class="pun">(</span><span class="pln">resource</span><span class="pun">:</span><span class="pln"> </span><span class="typ">CreateUserDto</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">UsersDao</span><span class="pun">.</span><span class="pln">addUser</span><span class="pun">(</span><span class="pln">resource</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="kwd">async</span><span class="pln"> deleteById</span><span class="pun">(</span><span class="pln">id</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="kwd">return</span><span class="pln"> </span><span class="typ">UsersDao</span><span class="pun">.</span><span class="pln">removeUserById</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">async</span><span class="pln"> list</span><span class="pun">(</span><span class="pln">limit</span><span class="pun">:</span><span class="pln"> number</span><span class="pun">,</span><span class="pln"> page</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">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="typ">UsersDao</span><span class="pun">.</span><span class="pln">getUsers</span><span class="pun">();</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="kwd">async</span><span class="pln"> patchById</span><span class="pun">(</span><span class="pln">id</span><span class="pun">:</span><span class="pln"> string</span><span class="pun">,</span><span class="pln"> resource</span><span class="pun">:</span><span class="pln"> </span><span class="typ">PatchUserDto</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">UsersDao</span><span class="pun">.</span><span class="pln">patchUserById</span><span class="pun">(</span><span class="pln">id</span><span class="pun">,</span><span class="pln"> resource</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="kwd">async</span><span class="pln"> readById</span><span class="pun">(</span><span class="pln">id</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="kwd">return</span><span class="pln"> </span><span class="typ">UsersDao</span><span class="pun">.</span><span class="pln">getUserById</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">async</span><span class="pln"> putById</span><span class="pun">(</span><span class="pln">id</span><span class="pun">:</span><span class="pln"> string</span><span class="pun">,</span><span class="pln"> resource</span><span class="pun">:</span><span class="pln"> </span><span class="typ">PutUserDto</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">UsersDao</span><span class="pun">.</span><span class="pln">putUserById</span><span class="pun">(</span><span class="pln">id</span><span class="pun">,</span><span class="pln"> resource</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="kwd">async</span><span class="pln"> getUserByEmail</span><span class="pun">(</span><span class="pln">email</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="kwd">return</span><span class="pln"> </span><span class="typ">UsersDao</span><span class="pun">.</span><span class="pln">getUserByEmail</span><span class="pun">(</span><span class="pln">email</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="kwd">new</span><span class="pln"> </span><span class="typ">UsersService</span><span class="pun">();</span></pre>

<p>
	كانت خطوتنا الأولى إدراج الكائن DAO ثم اعتماديات الواجهة ثم نوع TypeScript الخاص بكل كائن DTO. سنعمل اﻵن على إنجاز الخدمة <code>UserService</code> كصنف متفرّد كما فعلنا مع الكائن DAO.
</p>

<p>
	تستدعي جميع دوال الواجهة <code>CRUD</code> الدوال التي تقبلها من <code>UsersDao</code>، وبالتالي عندما يحين الوقت لاستبدال الكائن DAO، لن نغيّر أي شيء في المشروع، ما عدا بعض التعديلات في هذا الملف حيث تُستدعى دوال DAO.
</p>

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

<h2 id="asyncawaitnodejs">
	التعليمتان Async/Await في Node.js
</h2>

<p>
	قد ترى أن استخدام <code>async</code> مع دوال الخدمة بلا معنى، وهذا صحيح حاليًا. فجميع هذه الدوال تعيد قيمها مباشرة، دون استخدام الوعود <code>promise</code> أو <code>await</code> داخليًا. لكننا فقط أردنا تحضير الشيفرة اﻷساسية للخدمات التي ستستخدم <code>async</code>. وبالمثل ستجد أن جميع الاستدعاءات لهذه الدوال تستخدم <code>await</code>.
</p>

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

<p>
	و اﻵن وقد انتهينا من إنجاز الكائن DAO والخدمات، سنعود إلى المتحكم بالمستخدم.
</p>

<h2 id="rest-1">
	بناء متحكم خاص بالواجهة البرمجية REST
</h2>

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

<p>
	علينا قبل أن نبدأ تثبيت مكتبة لتأمين تشفير كلمة المرور:
</p>

<pre class="ipsCode">npm i argon2
</pre>

<p>
	لنبدأ بإنشاء مجلد يُدعى <code>controllers</code> ضمن المجلد <code>users</code> وننشئ ضمنه الملف <code>users.controller.ts</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_876_38" style=""><span class="com">//ﻹضافة الأنواع إلى كائنات الطلب والاستجابة express ندرج</span><span class="pln">
</span><span class="com">//العائدة إلى دوال المتحكم</span><span class="pln">
</span><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="com">//ندرج خدمة المستخدم التي أنشأناها مؤخرًا</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> usersService from </span><span class="str">'../services/users.service'</span><span class="pun">;</span><span class="pln">

</span><span class="com">//لتشفير كلمة المرور argon2 ندرج المكتبة</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> argon2 from </span><span class="str">'argon2'</span><span class="pun">;</span><span class="pln">

</span><span class="com">//وفق سياق مخصص كما شرحنا في المقال السابق debug نستخدم المكتبة</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> debug from </span><span class="str">'debug'</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> log</span><span class="pun">:</span><span class="pln"> debug</span><span class="pun">.</span><span class="typ">IDebugger</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> debug</span><span class="pun">(</span><span class="str">'app:users-controller'</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">UsersController</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">async</span><span class="pln"> listUsers</span><span class="pun">(</span><span class="pln">req</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">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"> users </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> usersService</span><span class="pun">.</span><span class="pln">list</span><span class="pun">(</span><span class="lit">100</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">);</span><span class="pln">
        res</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">send</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">

    </span><span class="kwd">async</span><span class="pln"> getUserById</span><span class="pun">(</span><span class="pln">req</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">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"> user </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> usersService</span><span class="pun">.</span><span class="pln">readById</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">id</span><span class="pun">);</span><span class="pln">
        res</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">send</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">async</span><span class="pln"> createUser</span><span class="pun">(</span><span class="pln">req</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Response</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">body</span><span class="pun">.</span><span class="pln">password </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> argon2</span><span class="pun">.</span><span class="pln">hash</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">password</span><span class="pun">);</span><span class="pln">
        </span><span class="kwd">const</span><span class="pln"> userId </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> usersService</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">
        res</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">send</span><span class="pun">({</span><span class="pln"> id</span><span class="pun">:</span><span class="pln"> userId </span><span class="pun">});</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="kwd">async</span><span class="pln"> patch</span><span class="pun">(</span><span class="pln">req</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Response</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">req</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"> </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">password </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> argon2</span><span class="pun">.</span><span class="pln">hash</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">password</span><span class="pun">);</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        log</span><span class="pun">(</span><span class="kwd">await</span><span class="pln"> usersService</span><span class="pun">.</span><span class="pln">patchById</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">id</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">
        res</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">send</span><span class="pun">();</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="kwd">async</span><span class="pln"> put</span><span class="pun">(</span><span class="pln">req</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Response</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">body</span><span class="pun">.</span><span class="pln">password </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> argon2</span><span class="pun">.</span><span class="pln">hash</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">password</span><span class="pun">);</span><span class="pln">
        log</span><span class="pun">(</span><span class="kwd">await</span><span class="pln"> usersService</span><span class="pun">.</span><span class="pln">putById</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">id</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">
        res</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">send</span><span class="pun">();</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="kwd">async</span><span class="pln"> removeUser</span><span class="pun">(</span><span class="pln">req</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        log</span><span class="pun">(</span><span class="kwd">await</span><span class="pln"> usersService</span><span class="pun">.</span><span class="pln">deleteById</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">id</span><span class="pun">));</span><span class="pln">
        res</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">send</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="kwd">new</span><span class="pln"> </span><span class="typ">UsersController</span><span class="pun">();</span></pre>

<p>
	<strong>ملاحظة</strong>: تعيد اﻷسطر السابقة الاستجابة <code>HTTP 204 No Content</code> وتعني أن الطلب قد أنجز، لكن لا يوجد محتوى إضافي لإعادته مع جسم الاستجابة.
</p>

<p>
	بعد اﻹنتهاء من كتابة شيفرة المتحكم على شكل متفرّد، أصبحنا جاهزين لكتابة شيفرة الوحدة الوسيطة، وهي الوحدة البرمجية اﻷخرى التي تعتمد على نموذج كائن الواجهة البرمجية REST التجريبي وخدمته وهي الأداة الوسيطة.
</p>

<h2 id="restnodejsexpressjs">
	أداة وسيطة REST باستخدام Node.js و Express.js
</h2>

<p>
	ما الذي يمكن أن تقدمه أداة وسيطة مبنية باستخدام Express.js ؟ بداية عمليات التحقق من صحة البيانات وهذا أمر شديد اﻷهمية، لنبدأ إذًا بإضافة آليات تحقق بسيطة من الطلبات قبل وصولها إلى متحكم المستخدم:
</p>

<ul>
	<li>
		التأكد من وجود حقول معينة لبيانات المستخدم مثل <code>email</code> و <code>password</code> وهي ضرورية ﻹنشاء مستخدم أو تحديث بياناته.
	</li>
	<li>
		التأكد من عدم استخدام البريد اﻹلكتروني المدخل من قبل.
	</li>
	<li>
		التحقق من عدم تغيير حقل البريد اﻹلكتروني بعد إنشاء المستخدم (لأننا سنستخدمه للسهولة كمعرّف أساسي للمستخدم).
	</li>
	<li>
		التحقق من وجود مستخدم محدد مسبقًا.
	</li>
</ul>

<p>
	ولتعمل آليات التحقق السابقة مع Express.js، لابد من كتابتها على شكل دوال تتوافق مع نمط Express.js وذلك ﻹدارة نقل التحكم باستخدام الدالة <code>()next</code> كما شرحنا في المقال السابق. لهذا سنتحتاج إلى ملف جديد <code>users/middleware/users.middleware.ts</code> نضع فيه الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_876_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">import</span><span class="pln"> userService from </span><span class="str">'../services/users.service'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> debug from </span><span class="str">'debug'</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> log</span><span class="pun">:</span><span class="pln"> debug</span><span class="pun">.</span><span class="typ">IDebugger</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> debug</span><span class="pun">(</span><span class="str">'app:users-controller'</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">UsersMiddleware</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="kwd">new</span><span class="pln"> </span><span class="typ">UsersMiddleware</span><span class="pun">();</span></pre>

<p>
	نضيف اﻵن بعض دوال اﻷداة الوسيطة إلى جسم الصنف:
</p>

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

</span><span class="kwd">async</span><span class="pln"> validateRequiredUserBodyFields</span><span class="pun">(</span><span class="pln"> req</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Response</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">NextFunction</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">req</span><span class="pun">.</span><span class="pln">body </span><span class="pun">&amp;&amp;</span><span class="pln"> req</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">email </span><span class="pun">&amp;&amp;</span><span class="pln"> req</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"> </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">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> res</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="typ">Missing</span><span class="pln"> required fields email and password</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="kwd">async</span><span class="pln"> validateSameEmailDoesntExist</span><span class="pun">(</span><span class="pln"> req</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Response</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">NextFunction</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"> </span><span class="kwd">await</span><span class="pln"> userService</span><span class="pun">.</span><span class="pln">getUserByEmail</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">email</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">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> res</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="typ">User</span><span class="pln"> email already exists </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"> next</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">async</span><span class="pln"> validateSameEmailBelongToSameUser</span><span class="pun">(</span><span class="pln"> req</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Response</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">NextFunction</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"> </span><span class="kwd">await</span><span class="pln"> userService</span><span class="pun">.</span><span class="pln">getUserByEmail</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">email</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"> user</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">userId</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"> </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">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="typ">Invalid</span><span class="pln"> email </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">//بالشكل الصحيح this نستخدم هنا الدالة السهمية كي نربط التعليمة validatePatchEmail = async ( req: express.Request, res: express.Response, next: express.NextFunction ) =&gt; { if (req.body.email) { log('Validating email', req.body.email);</span><span class="pln">

</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">validateSameEmailBelongToSameUser</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"> </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="kwd">else</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"> </span><span class="pun">};</span><span class="pln">

</span><span class="kwd">async</span><span class="pln"> validateUserExists</span><span class="pun">(</span><span class="pln"> req</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Response</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">NextFunction</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"> </span><span class="kwd">await</span><span class="pln"> userService</span><span class="pun">.</span><span class="pln">readById</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">userId</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">)</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"> </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">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="typ">User</span><span class="pln"> $</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">userId</span><span class="pun">}</span><span class="pln"> not found</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></pre>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_876_44" style=""><span class="kwd">async</span><span class="pln"> extractUserId</span><span class="pun">(</span><span class="pln">
    req</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">,</span><span class="pln">
    res</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Response</span><span class="pun">,</span><span class="pln">
    next</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">NextFunction</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">body</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">userId</span><span class="pun">;</span><span class="pln">
    next</span><span class="pun">();</span><span class="pln">
</span><span class="pun">}</span></pre>

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

<h2 id="-1">
	تجميع كل الوحدات: إعادة تشكيل الوجهات
</h2>

<p>
	بعد أن انتهينا من إنجاز مختلف نواحي معمارية التطبيق، سنعود إلى الملف <code>users.routes.config.ts</code> الذي عرّفناه في المقال السابق، والذي يستدعي اﻷداة الوسيطة والمتحكمات وكلاهما يعتمد على خدمة المستخدم والتي تتطلب بدورها نموذج المستخدم.
</p>

<p>
	سيكون الملف بشكله النهائي كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_876_46" style=""><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="typ">CommonRoutesConfig</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> from </span><span class="str">'../common/common.routes.config'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="typ">UsersController</span><span class="pln"> from </span><span class="str">'./controllers/users.controller'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="typ">UsersMiddleware</span><span class="pln"> from </span><span class="str">'./middleware/users.middleware'</span><span class="pun">;</span><span class="pln">
</span><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">export</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">UsersRoutes</span><span class="pln"> extends </span><span class="typ">CommonRoutesConfig</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">constructor</span><span class="pun">(</span><span class="pln">app</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Application</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        super</span><span class="pun">(</span><span class="pln">app</span><span class="pun">,</span><span class="pln"> </span><span class="str">'UsersRoutes'</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    configureRoutes</span><span class="pun">():</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Application</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">app
           </span><span class="pun">.</span><span class="pln">route</span><span class="pun">(`/</span><span class="pln">users</span><span class="pun">`)</span><span class="pln">
           </span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="typ">UsersController</span><span class="pun">.</span><span class="pln">listUsers</span><span class="pun">)</span><span class="pln">
           </span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="pln">
                </span><span class="typ">UsersMiddleware</span><span class="pun">.</span><span class="pln">validateRequiredUserBodyFields</span><span class="pun">,</span><span class="pln">
                </span><span class="typ">UsersMiddleware</span><span class="pun">.</span><span class="pln">validateSameEmailDoesntExist</span><span class="pun">,</span><span class="pln">
                </span><span class="typ">UsersController</span><span class="pun">.</span><span class="pln">createUser
            </span><span class="pun">);</span><span class="pln">

        </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">app</span><span class="pun">.</span><span class="pln">param</span><span class="pun">(`</span><span class="pln">userId</span><span class="pun">`,</span><span class="pln"> </span><span class="typ">UsersMiddleware</span><span class="pun">.</span><span class="pln">extractUserId</span><span class="pun">);</span><span class="pln">
        </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">app
           </span><span class="pun">.</span><span class="pln">route</span><span class="pun">(`/</span><span class="pln">users</span><span class="pun">/:</span><span class="pln">userId</span><span class="pun">`)</span><span class="pln">
           </span><span class="pun">.</span><span class="pln">all</span><span class="pun">(</span><span class="typ">UsersMiddleware</span><span class="pun">.</span><span class="pln">validateUserExists</span><span class="pun">)</span><span class="pln">
           </span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="typ">UsersController</span><span class="pun">.</span><span class="pln">getUserById</span><span class="pun">)</span><span class="pln">
           </span><span class="pun">.</span><span class="kwd">delete</span><span class="pun">(</span><span class="typ">UsersController</span><span class="pun">.</span><span class="pln">removeUser</span><span class="pun">);</span><span class="pln">

        </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">app</span><span class="pun">.</span><span class="pln">put</span><span class="pun">(`/</span><span class="pln">users</span><span class="pun">/:</span><span class="pln">userId</span><span class="pun">`,</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
            </span><span class="typ">UsersMiddleware</span><span class="pun">.</span><span class="pln">validateRequiredUserBodyFields</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">UsersMiddleware</span><span class="pun">.</span><span class="pln">validateSameEmailBelongToSameUser</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">UsersController</span><span class="pun">.</span><span class="pln">put</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">app</span><span class="pun">.</span><span class="pln">patch</span><span class="pun">(`/</span><span class="pln">users</span><span class="pun">/:</span><span class="pln">userId</span><span class="pun">`,</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
            </span><span class="typ">UsersMiddleware</span><span class="pun">.</span><span class="pln">validatePatchEmail</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">UsersController</span><span class="pun">.</span><span class="pln">patch</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">this</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">
</span><span class="pun">}</span></pre>

<p>
	أعدنا هنا تعريف الوجهات بإضافة أداة وسيطة لتقييم منطق العمل واختيار دوال المتحكم المناسبة لمعالجة الطلب إن كان كل شيء صحيحًا. كما استخدمنا الدالة <code>()param</code> التي تقدمها Express.js لاستخلاص قيمة الحقل <code>userId</code>.
</p>

<p>
	كما مررنا الدالة <code>validateUserExists</code> العائدة للأداة الوسيطة <code>UserMiddleware</code> في جميع الدوال <code>()all.</code> كي تُستدعى قبل وصول أي طلب <code>GET</code> أو <code>PUT</code> أو <code>PATCH</code> أو <code>DELETE</code> إلى نقطة الوصول <code>user/:usersId/</code>. أي لا حاجة أن تكون <code>validateUserExists</code> ضمن مصفوفة الدوال اﻹضافية التي نمررها إلى <code>()put.</code> أو <code>()patch.</code>، إذ تُستدعى قبل هذه الدوال.
</p>

<p>
	كما عززنا قابلية الاستخدام المتكرر للأداة الوسيطة بطريقة أخرى أيضًا، وذلك بتمرير الدالة <code>UsersMiddleware.validateRequiredUserBodyFields</code> كي تُستخدم ضمن سياق استخدام <code>POST</code> و <code>PUT</code>، إذ نعيد دمجها في دوال وسيطة أخرى.
</p>

<p>
	<strong>تنبيه ﻹخلاء المسؤولية</strong>: ما فعلناه اﻵن هو آلية بسيطة للتحقق من صحة المدخلات، لكن عليك التفكير بكل القيود التي يجب وضعها في الشيفرة عندما تتعامل مع مشاريع حقيقية. ولكي نتوخى البساطة، افترضنا أن المستخدم ليس قادرا على تغيير بريده اﻹلكتروني.
</p>

<h2 id="restexpresstypescript">
	اختبار الواجهة البرمجية REST المبنية باستخدام Express/TypeScript
</h2>

<p>
	نستطيع اﻵن تصريف وتشغيل تطبيق Node.js، وبمجرد أن يعمل سنكون قادرين على اختبار وجهات الواجهة البرمجية باستخدام عميل REST مثل Postman أو cURL.
</p>

<p>
	سنجرّب أولاً الحصول على قائمة المستخدمين:
</p>

<pre class="ipsCode">curl --request GET 'localhost:3000/users' \
--header 'Content-Type: application/json'
</pre>

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

<pre class="ipsCode">curl --request POST 'localhost:3000/users' \
--header 'Content-Type: application/json'
</pre>

<p>
	لاحظ كيف ستكون النتيجة هي خطأ يرسله التطبيق من خلال اﻷداة الوسيطة:
</p>

<pre class="ipsCode">{
    "error": "Missing required fields email and password"
}
</pre>

<p>
	وﻹصلاح اﻷمر، سنرسل طلبًا صحيحًا إلى المورد <code>users/</code>:
</p>

<pre class="ipsCode">curl --request POST 'localhost:3000/users' \
--header 'Content-Type: application/json' \
--data-raw '{
    "email": "marcos.henrique@toptal.com",
    "password": "sup3rS3cr3tPassw0rd!23"
}'
</pre>

<p>
	سنرى هذه المرة استجابة شبيهة بالتالي:
</p>

<pre class="ipsCode">{
    "id": "ksVnfnPVW"
}
</pre>

<p>
	إن هذا المعرف <code>id</code> هو المعرّف الخاص بالمستخدم الجديد وقد يكون مختلفًا على جهازك. ولتسهيل بقية الاختبارات، يمكنك تنفيذ بقية اﻷوامر باستخدام المعرّف الذي حصلت عليه (على افتراض أنك تستخدم بيئة تشغيل شبيه بنظام لينكس):
</p>

<pre class="ipsCode">REST_API_EXAMPLE_ID="put_your_id_here"
</pre>

<p>
	بإمكانك أن ترى اﻵن الاستجابة التي تحصل عليها عند تنفيذ الطلب <code>GET</code> باستخدام المتغّير السابق:
</p>

<pre class="ipsCode">curl --request GET "localhost:3000/users/$REST_API_EXAMPLE_ID" \
--header 'Content-Type: application/json'
</pre>

<p>
	وتستطيع أيضًا تعديل المورد (المستخدم) بأكمله من خلال تنفيذ الطلب <code>PUT</code>:
</p>

<pre class="ipsCode">curl --request PUT "localhost:3000/users/$REST_API_EXAMPLE_ID" \
--header 'Content-Type: application/json' \
--data-raw '{
    "email": "marcos.henrique@toptal.com",
    "password": "sup3rS3cr3tPassw0rd!23",
    "firstName": "Marcos",
    "lastName": "Silva",
    "permissionLevel": 8
}'
</pre>

<p>
	كما تستطيع اختبار آلية التحقق بتغيير عنوان البريد اﻹلكتروني، ومن المفترض عندها ظهور رسالة خطأ.
</p>

<p>
	لاحظ أيضًا أن استخدام الطلب <code>PUT</code> لتحديث مورد ذو معرّف محدد، لا بد لنا -كمستخدمين للواجهة البرمجية- أن نرسل كائن الطلب بأكمله كي يتوافق مع معايير نموذج REST. فلو أردنا مثلًا تعديل الاسم اﻷخير فقط <code>lastName</code>، باستخدام <code>PUT</code>، لا بد من إرسال الكائن بأكمله لتنجح عملية التحديث. لكن من السهل في حالة كهذه استخدام الطلب <code>PATCH</code> لأنه يعمل ضمن قيود REST، وبإمكانك عندها إرسال قيمة <code>lastName</code> فقط:
</p>

<pre class="ipsCode">curl --request PATCH "localhost:3000/users/$REST_API_EXAMPLE_ID" \
--header 'Content-Type: application/json' \
--data-raw '{
    "lastName": "Faraco"
}'
</pre>

<p>
	وتذكر أن التمييز بين <code>PUT</code> و <code>PATCH</code> في شيفرتنا اﻷساسية عائد إلى أسلوب إعداد الوجهات عن طريق استخدام دوال الأداة الوسيطة التي أضفناها.
</p>

<h3 id="putpatch">
	هل نستخدم <code>PUT</code> أو <code>PATCH</code> أو كلاهما؟
</h3>

<p>
	قد ترى أنه لا ضرورة ملحة لاستخدام <code>PUT</code> نظرًا لمرونة <code>PATCH</code>، وبالفعل تتبنى بعض الواجهات البرمجية الفكرة. وقد يصّر البعض على دعم <code>PUT</code> كي تتوافق الواجهة البرمجية تمامًا مع REST. مع ذلك، قد يكون إنشاء وجهات تدعم <code>PUT</code> أمرًا مناسبًا لبعض حالات الاستخدام الشائعة.
</p>

<p>
	وفي واقع اﻷمر هذه النقاط هي موضع نقاشات أعمق، لهذا دعمنا في تطبيقنا استخدام <code>PUT</code> وكذلك <code>PATCH</code>، لكننا نشجعك على الاطلاع والبحث أكثر عندما تكون مستعدًا.
</p>

<p>
	إن حاولت الحصول على قائمة المستخدمين مجددًا، سترى المستخدم الجديد وقد حّدثت بياناته:
</p>

<pre class="ipsCode">[
    {
        "id": "ksVnfnPVW",
        "email": "marcos.henrique@toptal.com",
        "password": "$argon2i$v=19$m=4096,t=3,p=1$ZWXdiTgb922OvkNAdh9acA$XUXsOHaRN4uVg5ltIwwO+SPLxvb9uhOKcxoLER1e/mM",
        "firstName": "Marcos",
        "lastName": "Faraco",
        "permissionLevel": 8
    }
]
</pre>

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

<pre class="ipsCode">curl --request DELETE "localhost:3000/users/$REST_API_EXAMPLE_ID" \
--header 'Content-Type: application/json'
</pre>

<p>
	إن حاولت اﻵن الحصول على قائمة المستخدمين مجددًا، فلن ترى المستخدم الذي أنشأته سابقًا. وهكذا نكون قد أنجزنا جميع العمليات اﻷساسية CRUD.
</p>

<h2 id="-2">
	الخلاصة
</h2>

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

<p>
	كما أنشأنا بنية شديدة البساطة لتخزين البيانات هدفها الوحيد تنفيذ بعض الاختبارات في هذه المرحلة، ومن ثم ستُستبدل بشيء عملي أكثر في مقالات قادمة.
</p>

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

<ul>
	<li>
		استبدال قاعدة البيانات المؤقتة بقاعدة بيانات MongoDB، واستخدام المكتبة Mongoose لتسهيل كتابة الشيفرة.
	</li>
	<li>
		إضافة طبقة أمان والتحكم بالوصول من خلال مقاربة لا تعتمد على حالة التطبيق باستخدام JWT.
	</li>
	<li>
		إعداد اختبارات مؤتمتة تسمح لنا بتوسيع تطبيقنا.
	</li>
</ul>

<p>
	بإمكانك اﻵن الاطلاع على الشيفرة النهائية حتى هذه المرحلة من <a href="https://github.com/makinhs/toptal-rest-series/tree/toptal-article-02" rel="external nofollow">هنا</a>.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.toptal.com/express-js/nodejs-typescript-rest-api-pt-2" rel="external nofollow">Building a Node.js TypeScript REST <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr>,Part2: Models, Middleware and Services</a>
</p>

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

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%A8%D9%86%D8%A7%D8%A1-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-%D9%85%D8%AA%D9%88%D8%A7%D9%81%D9%82%D8%A9-%D9%85%D8%B9-rest-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-expressjs-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-r2327/" rel="">بناء واجهة برمجية متوافقة مع REST باستخدام Express.js -الجزء الأول</a>
	</li>
	<li>
		<p>
			<a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AF%D9%84%D9%8A%D9%84%D9%83-%D9%84%D8%B1%D8%A8%D8%B7-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-openai-api-%D9%85%D8%B9-nodejs-r2233/" rel="">دليلك لربط واجهة OpenAI <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> مع Node.js</a>
		</p>
	</li>
	<li>
		<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-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-express-%D9%88%D8%A8%D9%8A%D8%A6%D8%A9-node-r2168/" rel="">مدخل إلى إطار عمل الويب Express وبيئة Node</a>
		</p>
	</li>
</ul>
]]></description><guid isPermaLink="false">2329</guid><pubDate>Mon, 20 May 2024 12:09:00 +0000</pubDate></item><item><title>&#x628;&#x646;&#x627;&#x621; &#x648;&#x627;&#x62C;&#x647;&#x629; &#x628;&#x631;&#x645;&#x62C;&#x64A;&#x629; &#x645;&#x62A;&#x648;&#x627;&#x641;&#x642;&#x629; &#x645;&#x639; REST &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; Express.js -&#x627;&#x644;&#x62C;&#x632;&#x621; &#x627;&#x644;&#x623;&#x648;&#x644;</title><link>https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%A8%D9%86%D8%A7%D8%A1-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-%D9%85%D8%AA%D9%88%D8%A7%D9%81%D9%82%D8%A9-%D9%85%D8%B9-rest-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-expressjs-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-r2327/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_05/-----REST---Node.js-TypeScript--Express.js.png.dee07cc7ce4283aacb6e26d0793776f6.png" /></p>
<p>
	نتحدث في هذه السلسلة من المقالات عن خطوات بناء تطبيق بسيط يمثل واجهة خلفية على هيئة واجهة برمجية <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> باستخدام إطار عمل Express.js ولغة البرمجة  TypeScript.
</p>

<h2 id="restnodejs">
	كيف أكتب واجهة برمجية REST في بيئة Node.js
</h2>

<p>
	غالبًا ما تكون المكتبة <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/" rel="">Express.js</a> هي الخيار اﻷول من بين إطارات عمل Node.js عند كتابة واجهة خلفية لتكون واجهة برمجية REST. وعلى الرغم من أنها تدعم أيضًا بناء صفحات وقوالب HTML، لكننا سنركز في هذه السلسلة من المقالات على بناء واجهة خلفية باستخدام لغة TypeScript كي نسمح لأي واجهة أمامية أو واجهة خلفية خارجية (خادم آخر) من الاستعلام منها، لهذا عليك أن:
</p>

<ul>
	<li>
		تمتلك <a href="https://academy.hsoub.com/programming/javascript/%D8%AA%D8%B9%D9%84%D9%85-%D9%84%D8%BA%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-%D9%85%D9%86-%D8%A7%D9%84%D8%B5%D9%81%D8%B1-%D8%AD%D8%AA%D9%89-%D8%A7%D9%84%D8%A7%D8%AD%D8%AA%D8%B1%D8%A7%D9%81-r2046/" rel="">معرفة أساسية بلغة جافا سكريبت</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> ومطلعًا على معمارية <a href="https://academy.hsoub.com/programming/general/%D8%B4%D8%B1%D8%AD-%D9%81%D9%84%D8%B3%D9%81%D8%A9-restful-%D8%AA%D8%B9%D9%84%D9%85-%D9%83%D9%8A%D9%81-%D8%AA%D8%A8%D9%86%D9%8A-%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-rest-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r635/" rel="">REST</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> (يفضل النسخة 14 وما بعد).
	</li>
</ul>

<p>
	سنبدأ من الطرفية أو (محرر سطر اﻷوامر) لإنشاء مجلّد خاص بالمشروع، ثم ننفّذ اﻷمر
</p>

<pre class="ipsCode" id="ips_uid_6482_9"> run npm init </pre>

<p>
	يُنشئ هذا الأمر بعض الملفات الأساسية التي نحتاجها لمشروع Node.js.
</p>

<p>
	ثم نضيف بعد ذلك إطار العمل Express.js وبعض المكتبات المفيدة اﻷخرى لمشروعنا من خلال الأمر التالي:
</p>

<pre class="ipsCode">npm i express debug winston express-winston cors
</pre>

<p>
	وبالطبع هناك أسباب وجيهة كي يفضّل مطوّرو Node.js المكتبات السابقة:
</p>

<ul>
	<li>
		<code>debug</code>: هي وحدة برمجية تُستخدم لتفادي استخدام اﻷمر <code>()console.log</code> أثناء تطوير التطبيقات. إذ تستخدم لترشيح العبارات التي نريد تنقيحها عند محاولة حل المشاكل التي تواجهنا. وباﻹمكان إيقافها كليًا في نسخة اﻹنتاج بدلًا من إزالتها يدويًا.
	</li>
	<li>
		<code>winston</code>: الوحدة المسؤولة عن تسجيل الطلبات القادمة إلى الواجهة البرمجية والاستجابات (واﻷخطاء) التي تعيدها الواجهة. وتتكامل<br>
		<code>express-winston</code> مباشرة مع Express.js لهذا ستكون شيفرة واجهة برمجية المتعلقة بعملية إدارة السجلات التي تؤديها <code>winston</code> جاهزة.
	</li>
	<li>
		<code>cors</code>: هي جزء من أداة Express.js الوسيطة والتي تسمح لنا بمشاركة الموارد ذات اﻷصول المختلطة <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" rel="external nofollow">cross-origin resource sharing</a>. وبدون هذه المكتبة لن تتمكن الواجهة البرمجية من تخديم سوى الواجهة الأمامية الموجودة في نفس النطاق الفرعي الذي يحوي الواجهة الخلفية.
	</li>
</ul>

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

<pre class="ipsCode">npm i --save-dev @types/cors @types/express @types/debug source-map-support tslint typescript
</pre>

<p>
	نحتاج الاعتماديات السابقة لتفعيل TypeScript في شيفرة تطبيقنا، واﻷنواع التي تستخدمها Express.js والاعتماديات اﻷخرى. وسيوّفر هذا اﻷمر وقتك عند استخدام بيئات تطوير متكاملة مثل WebStorm أو VSCode التي تتيح لك ميزات اﻹكمال التلقائي لبعض التوابع أثناء كتابة الشيفرة.
</p>

<p>
	ينبغي أن تكون الاعتمادات بشكلها النهائي ضمن الملف <code>package.json</code> كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6482_11" 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">"debug"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^4.2.0"</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="pun">,</span><span class="pln">
    </span><span class="str">"express-winston"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^4.0.5"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"winston"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^3.3.3"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"cors"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^2.8.5"</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">"@types/cors"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^2.8.7"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"@types/debug"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^4.1.5"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"@types/express"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^4.17.2"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"source-map-support"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^0.5.16"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"tslint"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^6.0.0"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"typescript"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^3.7.5"</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	وهكذا تكون جميع الاعتماديات اللازمة لعمل تطبيقنا جاهزة!
</p>

<h2 id="resttypescript">
	هيكيلية مشروع واجهة برمجية REST باستخدام TypeScript
</h2>

<p>
	سنستخدم في مشروعنا ثلاث ملفات وهي:
</p>

<ol>
	<li>
		<code>app.ts/.</code>
	</li>
	<li>
		<code>common/common.routes.config.ts/.</code>
	</li>
	<li>
		<code>users/users.routes.config.ts/.</code>
	</li>
</ol>

<p>
	إن الفكرة من استخدام مجلدين (<code>common</code> و <code>users</code>) في مشروعنا هو تكوين وحدتين لكل منهما مسؤولياتها الخاصة. وبالتالي قد نعطي الوحدتين بعض أو كل الميزات التالية:
</p>

<ul>
	<li>
		<strong>تهيئة الوجهة Rourte</strong>: لتعريف الطلبات التي يمكن أن تتعامل معها الواجهة البرمجية.
	</li>
	<li>
		<strong>خدمات Services</strong>: لتنفيذ مهام مثل الاتصال بقاعدة البيانات وتنفيذ استعلامات أو الاتصال بخدمات خارجية ضرورية للاستعلام.
	</li>
	<li>
		<strong>وسيط أو برمجية وسيطة middleware</strong>: للتحقق من صلاحية طلب معيّن قبل أن يتعامل المتحكم النهائي بالمسار مع تفاصيل الاستعلام.
	</li>
	<li>
		<strong>وحدات Models</strong>: لتعريف وحدات البيانات التي تطابق تخطيط قاعدة بيانات محددة، لتسهيل تخزين البيانات واستعادتها.
	</li>
	<li>
		<strong>متحكّمات controllers</strong>: لفصل معلومات تهيئة المسار أو الوجهة التي سننتقل إليها route configuration عن الشيفرة التي تعالج في النهاية (بعد المرور على أية برامج وسيطة) طلب هذا المسار أو تستدعي دوال خدمة من مستوى أعلى عند الحاجة، وتعيد الاستجابة على هذا الطلب إلى العميل.
	</li>
</ul>

<p>
	للمجلد هيكلية ذات تصميم بسيط متوافق مع الواجهة البرمجية.
</p>

<h2 id="routestypescript">
	ملفات مسارات Routes شائعة الاستخدام في TypeScript
</h2>

<p>
	سنعمل على تنظيم ملفات Routes في تطبيق Express.js ،لذا سننشئ الملف <code>common.routes.config.ts</code> في المجلّد <code>common</code> ونضع فيه الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6482_13" 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">export</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">CommonRoutesConfig</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    app</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Application</span><span class="pun">;</span><span class="pln">
    name</span><span class="pun">:</span><span class="pln"> string</span><span class="pun">;</span><span class="pln">

    </span><span class="kwd">constructor</span><span class="pun">(</span><span class="pln">app</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Application</span><span class="pun">,</span><span class="pln"> name</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="kwd">this</span><span class="pun">.</span><span class="pln">app </span><span class="pun">=</span><span class="pln"> app</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">name </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">
    getName</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">this</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">
</span><span class="pun">}</span></pre>

<p>
	إن الطريقة التي ننشئ فيها المسارات routes هنا اختيارية، لكن، وطالما أننا نعمل مع TypeScript، فمن الجيد أن نتدرب على بناء المسارات باستخدام الوراثة من خلال التعليمة <code>extends</code> كما سنرى بعد قليل.
</p>

<p>
	إذ ننشئ صنفًا رئيسيًا لتحديد سلوك وملامح مشتركة بين جميع مسارات التطبيق وسيكون لجميع الملفات في هذا المشروع السلوك ذاته، كما سيكون لها اسم وإمكانية الوصول إلى كائن Express.js اﻷساسي في تطبيقنا <code>Application</code> ثم ننشئ صنفًا فرعيًا منه لتحديد سلوك مسارات معينة.
</p>

<p>
	الآن، يمكننا أن نبدأ في إنشاء ملف مسارات المستخدمين  في مجلد المستخدمين <code>users</code>، دعونا ننشئ ملف باسم <code>users.routes.config.ts</code> ونكتب بداخله الشيفرة البرمجية التالية
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6482_15" style=""><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="typ">CommonRoutesConfig</span><span class="pun">}</span><span class="pln"> from </span><span class="str">'../common/common.routes.config'</span><span class="pun">;</span><span class="pln">
</span><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">export</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">UsersRoutes</span><span class="pln"> extends </span><span class="typ">CommonRoutesConfig</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">constructor</span><span class="pun">(</span><span class="pln">app</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Application</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        super</span><span class="pun">(</span><span class="pln">app</span><span class="pun">,</span><span class="pln"> </span><span class="str">'UsersRoutes'</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	هنا، نقوم باستيراد الصنف <code>CommonRoutesConfig</code>  ونوسعه إلى صنف جديد <code>UsersRoutes</code>. ونرسل من خلال الدالة البنائية <code>constructor</code> التطبيق (أي كائن <code>express.Application</code> الرئيسي) واسم <code>UsersRoutes </code>إلى دالة البناء الخاصة بـ <code>CommonRoutesConfig</code>.
</p>

<p>
	قد يبدو المثال بسيطًا لكن عند توسيع الأمر ليشمل عدة ملفات routes سيساعدنا ذلك في تفادي تكرار الشيفرة.
</p>

<p>
	لنفترض أننا سنحتاج إلى إضافة ميزات جديدة في هذا الملف، مثل إدارة السجلات وتسجيل الأحداث، عندها بإمكاننا إضافة الحقول الضرورية إلى الصنف <code>CommonRoutesConfig</code> وعندها ستتمكن جميع اﻷصناف المشتقة منه من الوصول إلى هذه الميزة.
</p>

<h2 id="typescript">
	استخدام دوال TypeScript المجرّدة لتقديم وظائف متشابه بين اﻷصناف
</h2>

<p>
	ماذا لو أردنا الحصول على وظائف متشابهة ضمن اﻷصناف المختلفة (مثل تهيئة نقاط الاتصال بالواجهات البرمجية)، على الرغم من اختلاف طرق تنفيذ هذه الوظائف من صنف ﻵخر؟ أحد الخيارات المتاحة هو استخدام ميزة تُدعى التجريد abstraction في TypeScript.
</p>

<p>
	لنحاول إنشاء دالة مجرّدة بسيطة جدًا يرثها الصنف <code>UsersRoutes</code> (وبقية أصناف التوجيه التي قد ننشئها لاحقًا) من الصنف <code>CommonRoutesConfig</code>. ولنفترض أننا سنجبر كل الوجهات على امتلاك دالة (حتى نتمكن من استدعائها من الدالة البانية المشتركة) تُدعى<code>()configureRoutes</code>، وفيها نصرّح عن نقاط الوصول الخاصة بكل مورد من موارد الصنف. ولتنفيذ اﻷمر، سنضيف هذه اﻷشياء إلى الملف <code>common.routes.config.ts</code>:
</p>

<ol>
	<li>
		الكلمة المحجوزة <code>abstract</code> إلى السطر الذي يضم الكلمة <code>class</code> كي نفعّل خاصية التجريد لهذا الصنف.
	</li>
	<li>
		تصريح عن دالة جديدة في نهاية الصنف <code>abstract configureRoutes(): express.Application</code>، تجبر أي صنف مشتق من الصنف <code>CommonRoutesConfig</code> على تقديم آلية تطابق توقيع الدالة function signature، وسيرمي مصرّف TypeScript خطأ إن لم يجد آلية كهذه.
	</li>
	<li>
		استدعاء الدالة <code>()this.configureRoutes</code> في نهاية الدالة البانية طالما أننا متأكدين من وجود هذه الدالة.
	</li>
</ol>

<p>
	ستبدو الشيفرة اﻵن كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6482_17" 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">export</span><span class="pln"> </span><span class="kwd">abstract</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">CommonRoutesConfig</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    app</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Application</span><span class="pun">;</span><span class="pln">
    name</span><span class="pun">:</span><span class="pln"> string</span><span class="pun">;</span><span class="pln">

    </span><span class="kwd">constructor</span><span class="pun">(</span><span class="pln">app</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Application</span><span class="pun">,</span><span class="pln"> name</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="kwd">this</span><span class="pun">.</span><span class="pln">app </span><span class="pun">=</span><span class="pln"> app</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">name </span><span class="pun">=</span><span class="pln"> name</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">configureRoutes</span><span class="pun">();</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    getName</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">this</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">
    </span><span class="kwd">abstract</span><span class="pln"> configureRoutes</span><span class="pun">():</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Application</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	وهكذا ينبغي على كل صنف مشتق من الصنف <code>CommonRoutesConfig</code> أن يمتلك دالة <code>()configureRoutes</code> تُدعى تُعيد كائنًا من النوع <code>express.Application</code>، وبالتالي لا بد من تحديث الملف <code>users.routes.config.ts</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6482_19" style=""><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="typ">CommonRoutesConfig</span><span class="pun">}</span><span class="pln"> from </span><span class="str">'../common/common.routes.config'</span><span class="pun">;</span><span class="pln">
</span><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">export</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">UsersRoutes</span><span class="pln"> extends </span><span class="typ">CommonRoutesConfig</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">constructor</span><span class="pun">(</span><span class="pln">app</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Application</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        super</span><span class="pun">(</span><span class="pln">app</span><span class="pun">,</span><span class="pln"> </span><span class="str">'UsersRoutes'</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    configureRoutes</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="com">// (we'll add the actual route configuration here next)</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">this</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">

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

<p>
	دعنا نراجع ما فعلناه حتى الآن:
</p>

<p>
	أدرجنا بداية الملف <code>common.routes.config</code> ومن ثم الوحدة البرمجية <code>express</code>، وعرفنا بعد ذلك الصنف <code>UserRoutes</code> الذي أردناه أن يرث الصنف اﻷساسي <code>CommonRoutesConfig</code> وبالتالي سيضمّ الدالة <code>()configureRoutes</code> ويقدّم آلية لتنفيذها.
</p>

<p>
	وﻹرسال المعلومات عبر الصنف <code>CommonRoutesConfig</code>، نستخدم الدالة البانية للصنف التي تتوقع تمرير كائن <code>express.Application</code> إليها، وهذا ما سنشرحه بتفاصيل أكثر لاحقًا. نمرر من خلال الدالة <code>()super</code> التطبيق إلى الدالة البانية للصنف <code>CommonRoutesConfig</code> واسم الوجهة (وهي في هذه الحالة <code>UsersRoutes</code>). وتستدعي الدالة <code>()super</code> بدورها الدالة <code>()configureRoutes</code>.
</p>

<h2 id="expressjs">
	تهيئة وجهات Express.js الخاصة بنقاط الوصول إلى المستخدمين
</h2>

<p>
	ستكون الدالة المكان الذي ننشئ فيه نقاط الوصول بين المستخدم والواجهة البرمجية REST. وفيها نستخدم <a href="https://expressjs.com/en/4x/api.html#app" rel="external nofollow">التطبيق</a> مع وظائف <a href="https://expressjs.com/en/4x/api.html#app.route" rel="external nofollow">التوجيه</a> من خلال Express.js.
</p>

<p>
	تكمن الفكرة في استخدام الدالة <code>()app.route</code> لتفادي تكرار الشيفرة، وهذا اﻷمر سهل نسبيًا طالما أننا ننشئ واجهة برمجية REST ذات موارد محددة تمامًا. إن المورد اﻷساسي في تطبيقنا هو <code>users</code>، ولدينا حالتان:
</p>

<ul>
	<li>
		عندما يريد مستدعي الواجهة البرمجية إنشاء مستخدم جديد أو الحصول على قائمة بالمستخدمين الموجودين، لا بد أن يكون اسم المورد فقط <code>users</code> في نهاية المسار إلى المورد (لا نريد الخوض في هذه الحالة في فلترة أو تنظيم نتائج الاستعلام أو غيرها من العمليات في هذا التطبيق).
	</li>
	<li>
		عندما يريد المستدعي أن ينفّذ عملية ما على سجل مستخدم، وعندها لابد أن يكون نمط المسار إلى المورد كالتالي: <code>users/:userId</code>.
	</li>
</ul>

<p>
	تتيح آلية عمل الدالة <code>()route.</code> في Express.js التعامل مع طلبات HTTP بأسلوب متسلسل أنيق، لأن جميع التوابع <code>()get.</code> و <code>()post.</code> وغيرها، ستعيد نفس النسخة من الكائن التي يعيدها التابع <code>()route.</code>. لهذا سننهي عملية التهيئة كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6482_21" style=""><span class="pln">configureRoutes</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">app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(`/</span><span class="pln">users</span><span class="pun">`)</span><span class="pln">
        </span><span class="pun">.</span><span class="kwd">get</span><span class="pun">((</span><span class="pln">req</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">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">
            res</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">send</span><span class="pun">(`</span><span class="typ">List</span><span class="pln"> </span><span class="kwd">of</span><span class="pln"> users</span><span class="pun">`);</span><span class="pln">
        </span><span class="pun">})</span><span class="pln">
        </span><span class="pun">.</span><span class="pln">post</span><span class="pun">((</span><span class="pln">req</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">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">
            res</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">send</span><span class="pun">(`</span><span class="typ">Post</span><span class="pln"> to users</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">app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(`/</span><span class="pln">users</span><span class="pun">/:</span><span class="pln">userId</span><span class="pun">`)</span><span class="pln">
        </span><span class="pun">.</span><span class="pln">all</span><span class="pun">((</span><span class="pln">req</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Response</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">NextFunction</span><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">// /users/:userId يُنفّذ البرنامج الوسيط هذه الدالة قبل أي استعلام</span><span class="pln">
            </span><span class="com">// لكنه لا ينفذ شيئًا اﻵن</span><span class="pln">
            </span><span class="com">// next() بل يمرر ببساطة التحكم إلى الدالة التالية في التطبيق تحت    </span><span class="pln">

        next</span><span class="pun">();</span><span class="pln">
        </span><span class="pun">})</span><span class="pln">
        </span><span class="pun">.</span><span class="kwd">get</span><span class="pun">((</span><span class="pln">req</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">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">
            res</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">send</span><span class="pun">(`</span><span class="pln">GET requested </span><span class="kwd">for</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">userId</span><span class="pun">}`);</span><span class="pln">
        </span><span class="pun">})</span><span class="pln">
        </span><span class="pun">.</span><span class="pln">put</span><span class="pun">((</span><span class="pln">req</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">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">
            res</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">send</span><span class="pun">(`</span><span class="pln">PUT requested </span><span class="kwd">for</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">userId</span><span class="pun">}`);</span><span class="pln">
        </span><span class="pun">})</span><span class="pln">
        </span><span class="pun">.</span><span class="pln">patch</span><span class="pun">((</span><span class="pln">req</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">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">
            res</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">send</span><span class="pun">(`</span><span class="pln">PATCH requested </span><span class="kwd">for</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">userId</span><span class="pun">}`);</span><span class="pln">
        </span><span class="pun">})</span><span class="pln">
        </span><span class="pun">.</span><span class="kwd">delete</span><span class="pun">((</span><span class="pln">req</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">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">
            res</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">send</span><span class="pun">(`</span><span class="pln">DELETE requested </span><span class="kwd">for</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">userId</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">this</span><span class="pun">.</span><span class="pln">app</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تتيح الشيفرة السابقة لعميل الواجهة البرمجية المتوافقة مع REST استدعاء نقطة الوصول <code>users</code> باستخدام أحد الاستعلامين <code>POST</code> أو <code>GET</code>، وتتيح له بنفس اﻷسلوب استدعاء نقطة الوصول <code>users/:userId</code> من خلال استعلامات <code>GET</code> أو <code>PUT</code> أو <code>PATCH</code> أو <code>DELETE</code>.
</p>

<p>
	كما أضفنا إلى نقطة الوصول <code>users/:userId</code> برنامج وسيط يستخدم الدالة <code>()all</code> التي تُنفَّذ قبل أي استدعاء للدوال <code>()get</code> أو <code>()put</code> أو <code>()patch</code> أو <code>()delete</code>. وسيكون لهذه الدالة أهميتها عندما ننشئ لاحقًا مسارات يصل إليها فقط المستخدمين المستوثقين.
</p>

<p>
	وقد تلاحظ أن جميع الدوال <code>()all</code> -وأية أجزاء من البرنامج الوسيط- تمتلك ثلاثة أنواع من الحقول <code>Request</code> و <code>Response</code> و <code>NextFunction</code>:
</p>

<ul>
	<li>
		النوع <a href="https://expressjs.com/en/4x/api.html#req" rel="external nofollow">Request</a> هو طريقة Express.js لتقديم طلبات HTTP التي يعالجها. ويُحدّث هذا النوع ويوسّع نوع <a href="https://nodejs.org/api/http.html#http_class_http_incomingmessage" rel="external nofollow">Node.js</a> اﻷصلي الذي يتعامل مع الطلبات.
	</li>
	<li>
		النوع <a href="https://expressjs.com/en/4x/api.html#res" rel="external nofollow">Response</a> هو طريقة Express.js لتقديم استجابات HTTP التي يعالجها. ويُحدّث هذا النوع ويوسّع نوع <a href="https://nodejs.org/api/http.html#http_class_http_serverresponse" rel="external nofollow">Node.js</a> اﻷصلي الذي يتعامل مع الطلبات.
	</li>
	<li>
		كما يستخدم الحقل <code>NextFunction</code> الذي لا يقل أهمية عن الاثنين السابقين كدالة استدعاء تسمح بتمرير التحكم إلى أية دوال أخرى يضمها الوسيط. وتتشارك جميع البرامج الوسيطة نفس كائنات الطلب والاستجابة قبل أن يُرسل المتحكم الاستجابة إلى صاحب الطلب في النهاية.
	</li>
</ul>

<h2 id="apptsnodejs">
	الملف <code>app.ts</code> المدخل إلى Node.js
</h2>

<p>
	بعد أن وضعنا هيكلية بسيطة للتوجّه في التطبيق، ننتقل إلى تهيئة مدخل entry point إليه، لهذا سننشئ الملف <code>app.ts</code> في المجلد الجذري للمشروع ونبدؤه بالشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6482_23" 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"> </span><span class="pun">*</span><span class="pln"> as http from </span><span class="str">'http'</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> as winston from </span><span class="str">'winston'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> as expressWinston from </span><span class="str">'express-winston'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> cors from </span><span class="str">'cors'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="typ">CommonRoutesConfig</span><span class="pun">}</span><span class="pln"> from </span><span class="str">'./common/common.routes.config'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="typ">UsersRoutes</span><span class="pun">}</span><span class="pln"> from </span><span class="str">'./users/users.routes.config'</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> debug from </span><span class="str">'debug'</span><span class="pun">;</span></pre>

<p>
	هناك إدراجان فقط جديدان في هذا الملف هما:
</p>

<ul>
	<li>
		<code>http</code>: وهو وحدة برمجية أصلية في Node.js، نحتاجها في تشغيل تطبيق Express.js.
	</li>
	<li>
		<code>body-parser</code>: وهو وسيط يأتي مع Express.js، ويفسّر الطلب (صيغة JSON في حالتنا) قبل وصول التحكم إلى معالج الطلب الذي حددناه.
	</li>
	<li>
		ننتقل بعد إدراج الملفات إلى التصريح عن المتغيرات التي نريد استخدامها:
	</li>
</ul>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6482_25" style=""><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="typ">Application</span><span class="pln"> </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"> server</span><span class="pun">:</span><span class="pln"> http</span><span class="pun">.</span><span class="typ">Server</span><span class="pln"> </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">
</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="pun">;</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> routes</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Array</span><span class="pun">&lt;</span><span class="typ">CommonRoutesConfig</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="kwd">const</span><span class="pln"> debugLog</span><span class="pun">:</span><span class="pln"> debug</span><span class="pun">.</span><span class="typ">IDebugger</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> debug</span><span class="pun">(</span><span class="str">'app'</span><span class="pun">);</span></pre>

<p>
	تعيد الدالة <code>()express</code> كائن تطبيق Express.js اﻷساسي الذي نمرره عبر تطبيقنا، من خلال إضافته بدايةً إلى الكائن <code>http.Server</code> (نحتاج إلى تشغيله بعد تهيئة الكائن <code>express.Application</code> الخاص بتطبيقنا)
</p>

<p>
	نترقب اﻵن الطلبات إلى المنفذ 3000 الذي تفهمه TypeScript على أنه من النوع <code>Number</code> بدلًا من المنافذ المعيارية مثل 80 لطلبات HTTP و 443 لطلبات HTTPS التي تُستخدم نمطيًا للاتصال مع الواجهة الأمامية للتطبيق.
</p>

<h3 id="3000">
	لماذا المنفذ 3000؟
</h3>

<p>
	لا توجد قاعدة تنص على أن المنفذ يجب أن يكون 3000، وسيُختار رقم المنفذ اعتباطيًا إن لم نخصص واحدًا. لكن الرقم 3000 يستخدم بكثرة في أمثلة توثيق Node.js و Express.js لهذا أكملنا على هذا النحو!
</p>

<h3 id="nodejs">
	هل يمكن أن تتشارك Node.js المنفذ مع الواجهه اﻷمامية؟
</h3>

<p>
	يمكننا أن نشغّل التطبيق محليًا على منفذ مخصص حتى لو أردنا من الواجهة الخلفية أن تستجيب للطلبات على المنافذ المعيارية. يتطلب اﻷمر خادم وكيل عكسي <a href="https://www.google.com/url?q=https://academy.hsoub.com/devops/servers/web/nginx/%25D9%2583%25D9%258A%25D9%2581%25D9%258A%25D8%25A9-%25D8%25A5%25D8%25B9%25D8%25AF%25D8%25A7%25D8%25AF-nginx-%25D9%2583%25D9%2588%25D8%25B3%25D9%258A%25D8%25B7-%25D8%25B9%25D9%2583%25D8%25B3%25D9%258A-reverse-proxy-%25D9%2584%25D9%2580apache-r19/&amp;sa=D&amp;source=docs&amp;ust=1706941090814111&amp;usg=AOvVaw0xVJMq5-DxAhkKwALlZYMA" rel="external nofollow">reverse proxy</a> له نطاق رئيسي أو فرعي يستقبل الطلبات على أحد المنفذين 80 أو 443 ثم يعيد توجيهها إلى المنفذ الداخلي 3000.
</p>

<p>
	تتبع المصفوفة <code>routes</code> ملفات التوجيه الخاصة بنا لأغراض التنقيح كما سنرى، ونرى أخيرا كيف ينتهي <code>debugLog</code> بدالة مشابهة للدالة <code>console.log</code>، لكنها أفضل من ناحية إمكانية الضبط الدقيق، إذ تغطي تلقائيًا ما نريد أن ندعو به ملفاتنا أو وحداتنا البرمجية (دعوناه في حالتنا "app" عندما مررناه كنص إلى الدالة البانية <code>()debug</code>).
</p>

<p>
	أصبحنا اﻵن جاهزين لتهيئة جميع وحدات Express.js الوسيطة والوجهات إلى الواجهة البرمجية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7714_9" style=""><span class="com">// JSON نضيف هنا وسيط لتفسير كل الطلبات القادمة بصيغة</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">

</span><span class="com">// CORS نضيف هنا وسيطًا للسماح بالطلبات مختلطة الأصول</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">
</span><span class="com">//expressWinston نحضّر هنا إعدادات الوحدة الوسيطة المخصصة ﻹدارة التسجيل</span><span class="pln">
</span><span class="com">//Exprress.js التي تعالجها HTTP والتي تسجّل جميع طلبات</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> loggerOptions</span><span class="pun">:</span><span class="pln"> expressWinston</span><span class="pun">.</span><span class="typ">LoggerOptions</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    transports</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="kwd">new</span><span class="pln"> winston</span><span class="pun">.</span><span class="pln">transports</span><span class="pun">.</span><span class="typ">Console</span><span class="pun">()],</span><span class="pln">
    format</span><span class="pun">:</span><span class="pln"> winston</span><span class="pun">.</span><span class="pln">format</span><span class="pun">.</span><span class="pln">combine</span><span class="pun">(</span><span class="pln">
        winston</span><span class="pun">.</span><span class="pln">format</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(),</span><span class="pln">
        winston</span><span class="pun">.</span><span class="pln">format</span><span class="pun">.</span><span class="pln">prettyPrint</span><span class="pun">(),</span><span class="pln">
        winston</span><span class="pun">.</span><span class="pln">format</span><span class="pun">.</span><span class="pln">colorize</span><span class="pun">({</span><span class="pln"> all</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="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">DEBUG</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    loggerOptions</span><span class="pun">.</span><span class="pln">meta </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">
</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">expressWinston</span><span class="pun">.</span><span class="pln">logger</span><span class="pun">(</span><span class="pln">loggerOptions</span><span class="pun">));</span><span class="pln">

</span><span class="com">//Express.js إلى مصفوفتنا بعد إرسال كائن UserRoutes نضيف</span><span class="pln">
</span><span class="com">//كي تضاف الوجهات إلى التطبيق</span><span class="pln">
routes</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">UsersRoutes</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">
</span><span class="kwd">const</span><span class="pln"> runningMessage </span><span class="pun">=</span><span class="pln"> </span><span class="pun">`</span><span class="typ">Server</span><span class="pln"> running at http</span><span class="pun">:</span><span class="com">//localhost:${port}`;</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"> express</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">:</span><span class="pln"> express</span><span class="pun">.</span><span class="typ">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">
    res</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">send</span><span class="pun">(</span><span class="pln">runningMessage</span><span class="pun">)</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	يرتبط <code>expressWinston.logger</code> تلقائيًا بالمكتبة Express.js ويسجل التفاصيل من خلال نفس البنية التحتية التي يستخدمها <code>debug</code>، وذلك لكل طلب مكتمل. وستنسق الخيارات التي مررناها إليه وتلوّن خرج الطرفية التي تعرض السجلات، إضافة إلى عرض سجلات أكثر تفصيلًا (وهو الأمر الافتراضي) عندما نفعّل نمط التنقيح.
</p>

<p>
	وتجدر الملاحظة إلى ضرورة تعريف وجهاتنا بعد إعداد <code>expressWinston.logger</code>.
</p>

<p>
	وأخيرًا نأتي إلى اﻷمر اﻷكثر أهمية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7714_7" style=""><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"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    routes</span><span class="pun">.</span><span class="pln">forEach</span><span class="pun">((</span><span class="pln">route</span><span class="pun">:</span><span class="pln"> </span><span class="typ">CommonRoutesConfig</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        debugLog</span><span class="pun">(`</span><span class="typ">Routes</span><span class="pln"> configured </span><span class="kwd">for</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">route</span><span class="pun">.</span><span class="pln">getName</span><span class="pun">()}`);</span><span class="pln">
    </span><span class="pun">});</span><span class="pln">
    </span><span class="com">//console.log الحالة الوحيدة التي لن تحاشى فيها استخدام</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">runningMessage</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span></pre>

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

<h2 id="packagejsontypescript">
	تحديث الملف <code>package.json</code> لنقل شيفرة TypeScript إلى جافا سكريبت وتشغيل التطبيق
</h2>

<p>
	بعد إنجاز البنية اﻷساسية للتطبيق وتحضيره للتشغيل، نحتاج أولًا إلى بعض اﻹعدادات لتمكين نقل transpilation شيفرة TypeScript:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6482_33" style=""><span class="pun">{</span><span class="pln">
  </span><span class="str">"compilerOptions"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">"target"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"es2016"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"module"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"commonjs"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"outDir"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"./dist"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"strict"</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">"esModuleInterop"</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">"inlineSourceMap"</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></pre>

<p>
	نضيف أخيرًا بعض اللمسات النهائية على الملف <code>package.json</code> على هيئة سكربتات:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6482_35" 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">"tsc &amp;&amp; node --unhandled-rejections=strict ./dist/app.js"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"debug"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"export DEBUG=* &amp;&amp; npm run start"</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="pln">
</span><span class="pun">},</span></pre>

<p>
	يعمل السكربت <code>test</code> كملف مؤقت سنستبدله لاحقًا.
</p>

<p>
	تنتمي الوحدة <code>tsc</code> في السكربت <code>start</code> إلى TypeScript، وهو المسؤول عن نقل شيفرة TypeScript إلى جافا سكريبت التي ستظهر في المجلد <code>dist</code>. ثم نشغّل النسخة المبنية من التطبيق باستخدام التعليمة <code>node ./dist/app.js</code>.
</p>

<p>
	نمرر الوسيط <code>unhandled-rejections=strict--</code> إلى Node.js (حتى في النسخ 16 وأعلى) لإيقاف التنفيذ عند ظهور خطأ غير محسوب في الشيفرة، ويسهّل ذلك معرفة سبب الخطأ وتصحيحه وهذا اﻷسلوب أوضح من الخيار اﻵخر وهو الاعتماد على كائن السجلات <code>expressWinston.errorLogger</code> الذي يزوّدك بقائمة اﻷخطاء بعد توقف المصرّف. ومعنى ذلك أننا سنترك Node.js يعمل على الرغم من وجود خطأ غير محسوب في الشيفرة وقد يسبب ذلك سلوكًا غير متوقع للخادم وظهور أخطاء أخرى قد تكون أكثر تعقيدًا.
</p>

<p>
	يستدعي السكربت <code>debug</code> السكربت <code>start</code> لكنه يعرّف أولًا متغير البيئة <code>DEBUG</code>. ولهذا المتغير تأثير في تمكين جميع عبارات <code>()debugLog</code> (إضافة إلى تلك التي تقدمها Express.js، والتي تستخدم نفس وحدة التنقيح <code>debug</code> التي نستخدمها) لعرض تفاصيل مفيدة على الطرفية، وإلا ستختفي هذه التفاصيل عند تشغيل الخادم في وضع اﻹنتاج باستخدام التعليمة <code>npm start</code>.
</p>

<p>
	جرّب تنفيذ اﻷمر <code>npm run debug</code>. بنفسك، وقارن نتائج الخرج على الطرفية مع تلك التي تنتج عن تنفيذ <code>npm start</code>.
</p>

<p>
	<strong>تلميح</strong>: بإمكانك تحديد خرج التنقيح ليعطي فقط عبارات <code>()debugLog</code> الموجودة في الملف <code>app.ts</code>، وذلك باستخدام <code>DEBUG=app</code> بدلًا من <code>*\=DEBUG</code>. فالوحدة <code>debug</code> مرنة عمومًا، وهذه الميزة ليست استثناءً.
</p>

<p>
	قد يحتاج مستخدمي ويندوز استبدال <code>export</code> بالتعليمة <code>SET</code> لأن <code>export</code> هي الطريقة التي تعمل على لينكس و ماك أو إس. أما إن أردت دعم عدة بيئات تطوير في تطبيقك، جرّب الحزمة<a href="https://www.npmjs.com/package/cross-env" rel="external nofollow">cross-env package</a> التي تزوّدك بحلول واضحة لهذه المسألة.
</p>

<h2 id="">
	اختبار الواجهة الخلفية
</h2>

<p>
	مع تنفيذ أحد اﻷمرين <code>npm run debug</code> أو <code>npm start</code> ستكون الواجهة الخلفية جاهزة لتلقي الطلبات على المنفذ 3000. يمكننا عندها استخدام أحد المكتبات cURL أو <a href="https://www.postman.com/" rel="external nofollow">Postman</a> أو <a href="https://insomnia.rest/" rel="external nofollow">Insomnia</a> لاختبار الواجهة الخلفية.
</p>

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

<pre class="ipsCode">curl --request GET 'localhost:3000/users/12345'
</pre>

<p>
	ستعيد عندها الواجهة الخلفية الاستجابة: <code>GET requested for id 12345</code>.
</p>

<p>
	وعند استخدام <code>POST</code>:
</p>

<pre class="ipsCode">curl --request POST 'localhost:3000/users' \
--data-raw ''
</pre>

<p>
	وغيرها من أنواع الطلبات، ستعيد الواجهة الخلفية نفس الاستجابة.
</p>

<h2 id="-1">
	الخلاصة
</h2>

<p>
	بدأنا في هذا المقال في إنشاء واجهة برمجية REST بتهيئة المشروع من الصفر ومن ثم دخلنا في أساسيات إطار العمل Express.js. خطونا بعد ذلك أولى خطواتنا في احتراف TypeScript عن طريق بناء نموذج <code>UsersRoutesConfig</code> يرث <code>CommonRoutesConfig</code> وسنعيد استخدام هذا النموذج في الجزء الثاني من هذه السلسلة. أنهينا العمل بعد ذلك بتهيئة ملف المدخل <code>app.ts</code> لاستخدام الوجهات، ومن ثم تهيئة ملف <code>package.json</code> بالسكربتات اللازمة لبناء وتشغيل التطبيق.
</p>

<p>
	وعلى الرغم من استخدام أساسيات الواجهة البرمجية REST مع Express.js و TypeScript في مقالنا، فسوف نركز في المقال التالي على بناء متحكمات مناسبة للموارد والتعرف على نماذج أخرى مثل الخدمات والوحدات الوسيطة والمتحكمات وغيرها من الوحدات البرمجية.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.toptal.com/express-js/nodejs-typescript-rest-api-pt-1" rel="external nofollow">Building a Node.js TypeScript REST <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> Part1 Express.js</a>
</p>

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

<ul>
	<li>
		<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 وExpress</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AF%D9%84%D9%8A%D9%84%D9%83-%D9%84%D8%B1%D8%A8%D8%B7-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-openai-api-%D9%85%D8%B9-nodejs-r2233/" rel="">دليلك لربط واجهة OpenAI <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> مع Node.js</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/general/%D8%B4%D8%B1%D8%AD-%D9%81%D9%84%D8%B3%D9%81%D8%A9-restful-%D8%AA%D8%B9%D9%84%D9%85-%D9%83%D9%8A%D9%81-%D8%AA%D8%A8%D9%86%D9%8A-%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-rest-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r635/" rel="">شرح فلسفة RESTful - تعلم كيف تبني واجهات REST البرمجية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D8%AF%D9%88%D9%91%D9%86%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-nodejs-%D9%88-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-r19/" rel="">إنشاء مدوّنة باستخدام Node.js و Express (الجزء الأول)</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2327</guid><pubDate>Mon, 13 May 2024 12:01:00 +0000</pubDate></item><item><title>&#x625;&#x646;&#x634;&#x627;&#x621; &#x645;&#x643;&#x62A;&#x628;&#x629; &#x645;&#x62D;&#x644;&#x64A;&#x629; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; Express:&#x627;&#x633;&#x62A;&#x645;&#x627;&#x631;&#x627;&#x62A; &#x62D;&#x630;&#x641; &#x627;&#x644;&#x645;&#x624;&#x644;&#x641;&#x64A;&#x646; &#x648;&#x627;&#x644;&#x62A;&#x639;&#x62F;&#x64A;&#x644; &#x639;&#x644;&#x649; &#x627;&#x644;&#x643;&#x62A;&#x628;</title><link>https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-express%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-%D8%AD%D8%B0%D9%81-%D8%A7%D9%84%D9%85%D8%A4%D9%84%D9%81%D9%8A%D9%86-%D9%88%D8%A7%D9%84%D8%AA%D8%B9%D8%AF%D9%8A%D9%84-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D9%83%D8%AA%D8%A8-r2232/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_01/---.png.9d24df5bf769203d7f393da47239d4bd.png" /></p>
<p>
	يوضح هذا المقال كيفية تعريف صفحة لحذف كائن المؤلف وتحديث كائن الكتاب للتعرّف على المزيد حول الاستمارات Forms في إطار عمل Express.
</p>

<h2 id="">
	استمارة حذف مؤلف
</h2>

<p>
	سنوضح كيفية تعريف صفحة لحذف كائنات المؤلف <code>Author</code>، إذ ستكون استراتيجيتنا -كما ناقشنا في <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B1%D8%A7%D8%A8%D8%B9-%D8%B9%D8%B1%D8%B6-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%88%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-r2193/" rel="">قسم تصميم الاستمارة</a>- هي السماح فقط بحذف الكائنات التي لا تشير إليها كائنات أخرى، وهذا يعني أننا لن نسمح بحذف المؤلف <code>Author</code> إذا أشار إليه كتاب <code>Book</code>، ويعني ذلك من حيث التقديم أن الاستمارة يجب أن تؤكد عدم وجود كتب مرتبطة بالمؤلف قبل حذفه. tإذا كان هناك كتب مرتبطة به، فيجب أن تعرضها وتوضح أنه يجب حذفها قبل حذف كائن المؤلف <code>Author</code>.
</p>

<h3 id="get">
	متحكم وجهة Get
</h3>

<p>
	افتح الملف ‎/controllers/authorController.js، ثم ابحث عن تابع المتحكم <code>author_delete_get()‎</code> المُصدَّر وضع مكانه الشيفرة البرمجية التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1875_6" style=""><span class="com">// عرض استمارة حذف مؤلف‫ في طلب GET</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">author_delete_get </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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="com">// الحصول على تفاصيل المؤلف وجميع كتبه على التوازي</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> </span><span class="pun">[</span><span class="pln">author</span><span class="pun">,</span><span class="pln"> allBooksByAuthor</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</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">
    </span><span class="typ">Author</span><span class="pun">.</span><span class="pln">findById</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">exec</span><span class="pun">(),</span><span class="pln">
    </span><span class="typ">Book</span><span class="pun">.</span><span class="pln">find</span><span class="pun">({</span><span class="pln"> author</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="str">"title summary"</span><span class="pun">).</span><span class="pln">exec</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">author </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">
    res</span><span class="pun">.</span><span class="pln">redirect</span><span class="pun">(</span><span class="str">"/catalog/authors"</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">render</span><span class="pun">(</span><span class="str">"author_delete"</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="str">"Delete Author"</span><span class="pun">,</span><span class="pln">
    author</span><span class="pun">:</span><span class="pln"> author</span><span class="pun">,</span><span class="pln">
    author_books</span><span class="pun">:</span><span class="pln"> allBooksByAuthor</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	يحصل المتحكم على معرّف نسخة المؤلف <code>Author</code> لحذفه من معامل <a href="https://academy.hsoub.com/programming/general/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%B9%D9%86%D9%88%D8%A7%D9%86-url-%D9%88%D8%A3%D9%86%D9%88%D8%A7%D8%B9%D9%87-r1435/" rel="">عنوان URL</a> (وهو <code>req.params.id</code>)، ويستخدم <a href="https://wiki.hsoub.com/JavaScript/await" rel="external"><code>await</code></a> لانتظار الوعد الذي أعاده التابع <code>Promise.all()‎</code> للانتظار بطريقة غير متزامنة لسجل المؤلف المُحدَّد وجميع الكتب المرتبطة به على التوازي، ويصيّر العرض author_delete.pug عند اكتمال كلتا العمليتين، مع تمرير متغيرات <code>title</code> و <code>author</code> و <code>author_books</code>.
</p>

<p>
	<strong>ملاحظة</strong>: إن لم يُعِد التابع <code>findById()‎</code> أيّ نتائج، فلن يكون المؤلف موجودًا في قاعدة البيانات، وبالتالي لا يوجد شيء يمكن حذفه، لذا نعيد التوجيه مباشرةً إلى قائمة جميع المؤلفين.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1875_8" style=""><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">author </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">
  res</span><span class="pun">.</span><span class="pln">redirect</span><span class="pun">(</span><span class="str">"/catalog/authors"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<h3 id="post">
	متحكم طلب وجهة Post
</h3>

<p>
	ابحث عن تابع المتحكم <code>author_delete_post()‎</code> المُصدَّر وضع مكانه الشيفرة البرمجية التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1875_10" style=""><span class="com">// ‫معالجة حذف مؤلف في طلب POST</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">author_delete_post </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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="com">// الحصول على تفاصيل المؤلف وجميع كتبه على التوازي</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> </span><span class="pun">[</span><span class="pln">author</span><span class="pun">,</span><span class="pln"> allBooksByAuthor</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</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">
    </span><span class="typ">Author</span><span class="pun">.</span><span class="pln">findById</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">exec</span><span class="pun">(),</span><span class="pln">
    </span><span class="typ">Book</span><span class="pun">.</span><span class="pln">find</span><span class="pun">({</span><span class="pln"> author</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="str">"title summary"</span><span class="pun">).</span><span class="pln">exec</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">allBooksByAuthor</span><span class="pun">.</span><span class="pln">length </span><span class="pun">&gt;</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="com">// المؤلف لديه كتب، لذا اعرض باست‫خدام طريقة وجهة GET نفسها</span><span class="pln">
    res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">"author_delete"</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="str">"Delete Author"</span><span class="pun">,</span><span class="pln">
      author</span><span class="pun">:</span><span class="pln"> author</span><span class="pun">,</span><span class="pln">
      author_books</span><span class="pun">:</span><span class="pln"> allBooksByAuthor</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">});</span><span class="pln">
    </span><span class="kwd">return</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="com">// المؤلف ليس لديه كتب، لذا احذف الكائن وأعِد التوجيه إلى قائمة المؤلفين</span><span class="pln">
    </span><span class="kwd">await</span><span class="pln"> </span><span class="typ">Author</span><span class="pun">.</span><span class="pln">findByIdAndRemove</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">authorid</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">"/catalog/authors"</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://academy.hsoub.com/programming/general/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%B9%D9%86%D9%88%D8%A7%D9%86-url-%D9%88%D8%A3%D9%86%D9%88%D8%A7%D8%B9%D9%87-r1435/" rel="">عنوان URL</a>، ثم يحصل على المؤلف والكتب المرتبطة به باستخدام طريقة وِجهة <code>GET</code> نفسها. إن لم يكن هناك كتب، فإننا نحذف كائن المؤلف ونعيد التوجيه إلى قائمة جميع المؤلفين، وإذا كان هناك كتب، فسنعيد عرض الاستمارة مع تمرير المؤلف وقائمة الكتب لحذفها.
</p>

<p>
	<strong>ملاحظة</strong>: يمكننا التحقق من أن استدعاء التابع <code>findById()‎</code> يعيد أيّ نتائج، فإن لم يكن الأمر كذلك، نعرض قائمة جميع المؤلفين مباشرةً. لقد تركنا الشيفرة كما هي موضحة أعلاه للإيجاز، إذ ستظل تعيد قائمة المؤلفين إن لم يُعثَر على المعرّف، ولكن ذلك سيحدث بعد التابع <code>findByIdAndRemove()‎</code>.
</p>

<h3 id="view">
	العرض View
</h3>

<p>
	أنشئ العرض ‎/views/author_delete.pug وضع فيه النص التالي:
</p>

<pre class="ipsCode">extends layout

block content
  h1 #{title}: #{author.name}
  p= author.lifespan

  if author_books.length

    p #[strong Delete the following books before attempting to delete this author.]

    div(style='margin-left:20px;margin-top:20px')

      h4 Books

      dl
      each book in author_books
        dt
          a(href=book.url) #{book.title}
        dd #{book.summary}

  else
    p Do you really want to delete this Author?

    form(method='POST' action='')
      div.form-group
        input#authorid.form-control(type='hidden',name='authorid', required='true', value=author._id )

      button.btn.btn-primary(type='submit') Delete
</pre>

<p>
	يوسّع هذا العرض قالب التخطيط Layout من خلال تعديل الكتلة <code>content</code>، إذ يعرض تفاصيل المؤلف في أعلى الملف، ثم يتضمن تعليمة شرطية تعتمد على عدد كتب المؤلف <code>author_books</code> (تعليمات <code>if</code> و <code>else</code>) كما يلي:
</p>

<ul>
	<li>
		إذا كان هناك كتب مرتبطة بالمؤلف، فستسرد الصفحة الكتب وتشير إلى أنه يجب حذفها قبل حذف هذا المؤلف <code>Author</code>.
	</li>
	<li>
		إن لم يكن هناك كتب، فستعرض الصفحة طلبًا لتأكيد الحذف.
	</li>
	<li>
		إذا نُقِر على زر الحذف Delete، فسيُرسَل معرّف المؤلف إلى الخادم في طلب <code>POST</code> وسيُحذَف سجل المؤلف.
	</li>
</ul>

<h3 id="deletecontrol">
	إضافة عنصر تحكم الحذف Delete Control
</h3>

<p>
	سنضيف الآن عنصر تحكم الحذف إلى عرض تفاصيل المؤلف، إذ تُعَد صفحة التفاصيل مكانًا جيدًا لحذف سجل منه.
</p>

<p>
	<strong>ملاحظة</strong>: سيكون عنصر التحكم مرئيًا فقط للمستخدمين المُصرَّح لهم عند التقديم الكامل، ولكن ليس لدينا نظام تصريح حاليًا.
</p>

<p>
	افتح العرض <code>author_detail.pug</code> وأضِف الأسطر التالية إلى نهايته:
</p>

<pre class="ipsCode">hr
p
  a(href=author.url+'/delete') Delete author
</pre>

<p>
	يجب أن يظهر عنصر التحكم الآن بوصفه رابطًا في صفحة تفاصيل المؤلف كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2024_01/01_locallibary_express_author_detail_delete.png.a4a0ea6c24069ec2addb344025e9952c.png" data-fileid="142265" data-fileext="png" rel=""><img alt="01 locallibary express author detail delete" class="ipsImage ipsImage_thumbnailed" data-fileid="142265" data-unique="day0jlulq" src="https://academy.hsoub.com/uploads/monthly_2024_01/01_locallibary_express_author_detail_delete.png.a4a0ea6c24069ec2addb344025e9952c.png"> </a>
</p>

<h3 id="-1">
	كيف تبدو استمارة حذف المؤلف؟
</h3>

<p>
	شغّل التطبيق وافتح متصفحك على العنوان "http://localhost:3000/‎"، ثم حدّد رابط جميع المؤلفين All authors، واختر مؤلفًا معينًا، ثم حدّد رابط حذف المؤلف Delete author.
</p>

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

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2024_01/02_locallibary_express_author_delete_nobooks.png.87d8f7959becfc9215db002043571ed5.png" data-fileid="142266" data-fileext="png" rel=""><img alt="02 locallibary express author delete nobooks" class="ipsImage ipsImage_thumbnailed" data-fileid="142266" data-unique="7fsiv1f34" src="https://academy.hsoub.com/uploads/monthly_2024_01/02_locallibary_express_author_delete_nobooks.png.87d8f7959becfc9215db002043571ed5.png"> </a>
</p>

<p>
	إذا كان لدى المؤلف كتب، فسيُقدَّم عرض يشبه ما يلي، ثم يمكنك حذف الكتب من صفحات تفاصيلها (بعد تقديم الشيفرة البرمجية المتعلقة بذلك):
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2024_01/03_locallibary_express_author_delete_withbooks.png.a204476fe605842b4a2fccbe8319a8d1.png" data-fileid="142267" data-fileext="png" rel=""><img alt="03 locallibary express author delete withbooks" class="ipsImage ipsImage_thumbnailed" data-fileid="142267" data-unique="46ns9832m" src="https://academy.hsoub.com/uploads/monthly_2024_01/03_locallibary_express_author_delete_withbooks.png.a204476fe605842b4a2fccbe8319a8d1.png"> </a>
</p>

<p>
	<strong>ملاحظة</strong>: يمكن تقديم الصفحات الأخرى الخاصة بحذف الكائنات باستخدام الطريقة نفسها، لذا تركناها كتحدٍ لك.
</p>

<h2 id="-2">
	استمارة تحديث كتاب
</h2>

<p>
	سنوضح كيفية تعريف صفحة لتحديث كائنات الكتاب <code>Book</code>، إذ تشبه معالجة استمارة تحديث كتاب إلى حدٍ كبير معالجة استمارة إنشاء كتاب، باستثناء أنه يجب عليك ملء الاستمارة في وِجهة <code>GET</code> بقيمٍ من قاعدة البيانات.
</p>

<h3 id="get-1">
	متحكم وجهة Get
</h3>

<p>
	افتح الملف "‎/controllers/bookController.js"، ثم ابحث عن تابع المتحكم <code>book_update_get()‎</code> المُصدَّر وضع مكانه الشيفرة البرمجية التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1875_12" style=""><span class="com">// عرض استمارة تحديث كتاب‫ في طلب GET</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">book_update_get </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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="com">// الحصول على الكتاب والمؤلفين وأنواع الكتب للاستمارة</span><span class="pln">
 </span><span class="kwd">const</span><span class="pln"> </span><span class="pun">[</span><span class="pln">book</span><span class="pun">,</span><span class="pln"> allAuthors</span><span class="pun">,</span><span class="pln"> allGenres</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</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">
    </span><span class="typ">Book</span><span class="pun">.</span><span class="pln">findById</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">populate</span><span class="pun">(</span><span class="str">"author"</span><span class="pun">).</span><span class="pln">populate</span><span class="pun">(</span><span class="str">"genre"</span><span class="pun">).</span><span class="pln">exec</span><span class="pun">(),</span><span class="pln">
    </span><span class="typ">Author</span><span class="pun">.</span><span class="pln">find</span><span class="pun">().</span><span class="pln">sort</span><span class="pun">({</span><span class="pln"> family_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">exec</span><span class="pun">(),</span><span class="pln">
    </span><span class="typ">Genre</span><span class="pun">.</span><span class="pln">find</span><span class="pun">().</span><span class="pln">sort</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">exec</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">book </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">
    </span><span class="kwd">const</span><span class="pln"> err </span><span class="pun">=</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">"Book not found"</span><span class="pun">);</span><span class="pln">
    err</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">
    </span><span class="kwd">return</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="com">// ميّز أنواع الكتب المختارة بوصفّها مُحدَّدة</span><span class="pln">
  </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">const</span><span class="pln"> genre </span><span class="kwd">of</span><span class="pln"> allGenres</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="kwd">const</span><span class="pln"> book_g </span><span class="kwd">of</span><span class="pln"> book</span><span class="pun">.</span><span class="pln">genre</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">genre</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"> book_g</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">
        genre</span><span class="pun">.</span><span class="pln">checked </span><span class="pun">=</span><span class="pln"> </span><span class="str">"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">

  res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">"book_form"</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="str">"Update Book"</span><span class="pun">,</span><span class="pln">
    authors</span><span class="pun">:</span><span class="pln"> allAuthors</span><span class="pun">,</span><span class="pln">
    genres</span><span class="pun">:</span><span class="pln"> allGenres</span><span class="pun">,</span><span class="pln">
    book</span><span class="pun">:</span><span class="pln"> book</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	يحصل المتحكم على معرّف الكتاب <code>Book</code> لتحديثه من معامل عنوان URL (هو <code>req.params.id</code>)، وينتظر (باستخدام <code>await</code>) الوعد الذي أعاده التابع <code>Promise.all()‎</code> للحصول على سجل الكتاب <code>Book</code> المحدد (مع ملء حقلي نوع الكتاب والمؤلف) وجميع سجلات المؤلف <code>Author</code> ونوع الكتاب <code>Genre</code>. تتحقق الدالة عند اكتمال العمليات من العثور على أيّ كتب، فإن لم يُعثَر على أيٍّ منها، فسترسل خطأ عدم العثور على الكتاب "Book not found" إلى البرمجية الوسيطة لمعالجة الخطأ.
</p>

<p>
	<strong>ملاحظة</strong>: لا يُعَد عدم العثور على أيّ كتاب كنتيجة خطأً في البحث، ولكنه كذلك في هذا التطبيق لأننا نعلم أنه يجب أن يكون هناك سجل كتاب مطابق. تطبّق الشيفرة السابقة اختبار المقارنة (<code>book===null</code>) في دالة رد النداء، ولكن يمكن أن تضيف التابع <code>orFail()‎</code> إلى السلسلة التعاقبية الخاصة بالاستعلام.
</p>

<p>
	نميّز بعد ذلك أنواع الكتب المختارة حاليًا بوصفها مُحدَّدة، ثم نقدّم العرض book_form.pug، ونمرر متغيرات <code>title</code> والكتاب وجميع المؤلفين <code>authors</code> وجميع أنواع الكتب <code>genres</code>.
</p>

<h3 id="post-1">
	متحكم وجهة Post
</h3>

<p>
	ابحث عن تابع المتحكم <code>book_update_post()‎</code> المُصدَّر وضع مكانه الشيفرة البرمجية التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1875_14" style=""><span class="com">// معالجة تحديث كتاب‫ في طلب POST</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">book_update_post </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">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="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="typ">Array</span><span class="pun">.</span><span class="pln">isArray</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">genre</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">body</span><span class="pun">.</span><span class="pln">genre </span><span class="pun">=</span><span class="pln">
        </span><span class="kwd">typeof</span><span class="pln"> req</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">genre </span><span class="pun">===</span><span class="pln"> </span><span class="str">"undefined"</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">req</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">genre</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">

  </span><span class="com">// التحقق من صحة الحقول وتطهيرها</span><span class="pln">
  body</span><span class="pun">(</span><span class="str">"title"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Title must not be empty."</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">trim</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">isLength</span><span class="pun">({</span><span class="pln"> min</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="pun">.</span><span class="pln">escape</span><span class="pun">(),</span><span class="pln">
  body</span><span class="pun">(</span><span class="str">"author"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Author must not be empty."</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">trim</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">isLength</span><span class="pun">({</span><span class="pln"> min</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="pun">.</span><span class="pln">escape</span><span class="pun">(),</span><span class="pln">
  body</span><span class="pun">(</span><span class="str">"summary"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Summary must not be empty."</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">trim</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">isLength</span><span class="pun">({</span><span class="pln"> min</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="pun">.</span><span class="pln">escape</span><span class="pun">(),</span><span class="pln">
  body</span><span class="pun">(</span><span class="str">"isbn"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"ISBN must not be empty"</span><span class="pun">).</span><span class="pln">trim</span><span class="pun">().</span><span class="pln">isLength</span><span class="pun">({</span><span class="pln"> min</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">escape</span><span class="pun">(),</span><span class="pln">
  body</span><span class="pun">(</span><span class="str">"genre.*"</span><span class="pun">).</span><span class="pln">escape</span><span class="pun">(),</span><span class="pln">

  </span><span class="com">// طلب العملية بعد التحقق من صحة البيانات وتطهيرها</span><span class="pln">
  asyncHandler</span><span class="pun">(</span><span class="kwd">async</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="com">// استخراج أخطاء التحقق من صحة البيانات من الطلب</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> errors </span><span class="pun">=</span><span class="pln"> validationResult</span><span class="pun">(</span><span class="pln">req</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"> book </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Book</span><span class="pun">({</span><span class="pln">
      title</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">title</span><span class="pun">,</span><span class="pln">
      author</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">author</span><span class="pun">,</span><span class="pln">
      summary</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">summary</span><span class="pun">,</span><span class="pln">
      isbn</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">isbn</span><span class="pun">,</span><span class="pln">
      genre</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">typeof</span><span class="pln"> req</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">genre </span><span class="pun">===</span><span class="pln"> </span><span class="str">"undefined"</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"> req</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">genre</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="com">// هذا مطلوب، أو سيجري إسناد معرّف جديد</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">errors</span><span class="pun">.</span><span class="pln">isEmpty</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="com">// الحصول على جميع المؤلفين وأنواع الكتب للاستمارة</span><span class="pln">
      </span><span class="kwd">const</span><span class="pln"> </span><span class="pun">[</span><span class="pln">allAuthors</span><span class="pun">,</span><span class="pln"> allGenres</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</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">
        </span><span class="typ">Author</span><span class="pun">.</span><span class="pln">find</span><span class="pun">().</span><span class="pln">exec</span><span class="pun">(),</span><span class="pln">
        </span><span class="typ">Genre</span><span class="pun">.</span><span class="pln">find</span><span class="pun">().</span><span class="pln">exec</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">for</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">const</span><span class="pln"> genre </span><span class="kwd">of</span><span class="pln"> allGenres</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">book</span><span class="pun">.</span><span class="pln">genre</span><span class="pun">.</span><span class="pln">indexOf</span><span class="pun">(</span><span class="pln">genre</span><span class="pun">.</span><span class="pln">_id</span><span class="pun">)</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> </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">
          genre</span><span class="pun">.</span><span class="pln">checked </span><span class="pun">=</span><span class="pln"> </span><span class="str">"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">
      res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">"book_form"</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="str">"Update Book"</span><span class="pun">,</span><span class="pln">
        authors</span><span class="pun">:</span><span class="pln"> allAuthors</span><span class="pun">,</span><span class="pln">
        genres</span><span class="pun">:</span><span class="pln"> allGenres</span><span class="pun">,</span><span class="pln">
        book</span><span class="pun">:</span><span class="pln"> book</span><span class="pun">,</span><span class="pln">
        errors</span><span class="pun">:</span><span class="pln"> errors</span><span class="pun">.</span><span class="pln">array</span><span class="pun">(),</span><span class="pln">
      </span><span class="pun">});</span><span class="pln">
      </span><span class="kwd">return</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="com">// البيانات الواردة من الاستمارة صالحة، لذا حدّث السجل</span><span class="pln">
      </span><span class="kwd">const</span><span class="pln"> thebook </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> </span><span class="typ">Book</span><span class="pun">.</span><span class="pln">findByIdAndUpdate</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"> book</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{});</span><span class="pln">
      </span><span class="com">// أعِد التوجيه إلى صفحة تفاصيل الكتاب</span><span class="pln">
      res</span><span class="pun">.</span><span class="pln">redirect</span><span class="pun">(</span><span class="pln">thebook</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">
  </span><span class="pun">}),</span><span class="pln">
</span><span class="pun">];</span></pre>

<p>
	تُعَد هذه الشيفرة البرمجية مشابهة جدًا لشيفرة وِجهة Post التي استخدمناها <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-express-%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-%D8%A7%D9%84%D9%83%D8%AA%D8%A8-%D9%88%D8%A7%D9%84%D9%85%D8%A4%D9%84%D9%81%D9%8A%D9%86-r2231/" rel="">لإنشاء كتاب Book</a>، إذ نتحقق أولًا من صحة بيانات الكتاب القادمة من الاستمارة ونطهرها ونستخدمها لإنشاء كائن كتاب <code>Book</code> جديد مع ضبط قيمة <code>‎_id</code> الخاصة به على معرّف الكائن المُراد تحديثه. إذا كان هناك أخطاء عند التحقق من صحة البيانات، فسنعيد تصيير الاستمارة، بالإضافة إلى عرض البيانات التي أدخلها المستخدم والأخطاء وقوائم أنواع الكتب والمؤلفين. إذا لم يكن هناك أخطاء، فسنستدعي التابع <code>Book.findByIdAndUpdate()‎</code> لتحديث مستند الكتاب <code>Book</code>، ثم نعيد التوجيه إلى صفحة تفاصيله.
</p>

<h3 id="view-1">
	العرض View
</h3>

<p>
	ليست هناك حاجة لتغيير عرض الاستمارة (‎/views/book_form.pug)، إذ يناسب هذا القالب إنشاء الكتاب وتحديثه.
</p>

<h3 id="-3">
	إضافة زر تحديث
</h3>

<p>
	افتح العرض book_detail.pug وتأكّد من وجود روابط لكل من حذف وتحديث الكتب أسفل الصفحة كما يلي:
</p>

<pre class="ipsCode">hr
  p
    a(href=book.url+'/delete') Delete Book
  p
    a(href=book.url+'/update') Update Book
</pre>

<p>
	يجب أن تكون قادرًا الآن على تحديث الكتب من صفحة تفاصيل الكتاب.
</p>

<h3 id="-4">
	كيف تبدو استمارة تحديث الكتاب؟
</h3>

<p>
	شغّل التطبيق، وافتح متصفحك على العنوان "http://localhost:3000/‎"، ثم حدّد رابط جميع الكتب All books، واختر كتابًا معينًا، ثم حدّد رابط تحديث الكتاب Update Book. يجب أن تبدو الاستمارة تمامًا مثل صفحة إنشاء كتاب Create book، ولكن مع العنوان "Update book"، وتكون الاستمارة مملوءة مسبقًا بقيم السجل.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2024_01/04_locallibary_express_book_update_noerrors.png.3f6655517aa804e821e529f0dcd0ca55.png" data-fileid="142268" data-fileext="png" rel=""><img alt="04 locallibary express book update noerrors" class="ipsImage ipsImage_thumbnailed" data-fileid="142268" data-unique="1wb5fi9c7" src="https://academy.hsoub.com/uploads/monthly_2024_01/04_locallibary_express_book_update_noerrors.thumb.png.ca74a88768eda1b5dfa6e8e90c79047b.png"> </a>
</p>

<p>
	<strong>ملاحظة</strong>: يمكن تقديم الصفحات الأخرى الخاصة بتحديث الكائنات باستخدام الطريقة نفسها، لذا تركناها كتحدٍ لك.
</p>

<p>
	ترجمة -وبتصرُّف- للمقالين <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Delete_author_form" rel="external nofollow">Delete Author form</a> و <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Update_Book_form" rel="external nofollow">Update Book form</a>.
</p>

<h2 id="-5">
	اقرأ المزيد
</h2>

<ul>
	<li>
		المقال السابق <a href="%D8%B1%D8%A7%D8%A8%D8%B7" rel="">إنشاء مكتبة محلية باستخدام Express: إضافة الكتب والمؤلفين</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-express-%D9%88%D8%A8%D9%8A%D8%A6%D8%A9-node-r2168/" rel="">مدخل إلى إطار عمل الويب Express وبيئة Node.js</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-node-%D9%85%D8%B9-express-r2169/" rel="">إعداد بيئة تطوير Node مع Express</a>
	</li>
	<li>
		ت<a href="https://wiki.hsoub.com/Node.js" rel="external">وثيق Node.js باللغة العربية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/express/" rel="">إطار عمل Express</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2232</guid><pubDate>Mon, 29 Jan 2024 12:04:01 +0000</pubDate></item><item><title>&#x625;&#x646;&#x634;&#x627;&#x621; &#x645;&#x643;&#x62A;&#x628;&#x629; &#x645;&#x62D;&#x644;&#x64A;&#x629; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; Express: &#x627;&#x633;&#x62A;&#x645;&#x627;&#x631;&#x627;&#x62A; &#x625;&#x636;&#x627;&#x641;&#x629; &#x627;&#x644;&#x643;&#x62A;&#x628; &#x648;&#x627;&#x644;&#x645;&#x624;&#x644;&#x641;&#x64A;&#x646;</title><link>https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-express-%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-%D8%A7%D9%84%D9%83%D8%AA%D8%A8-%D9%88%D8%A7%D9%84%D9%85%D8%A4%D9%84%D9%81%D9%8A%D9%86-r2231/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_01/--.png.1f45b9b28eef79269e6222f4c3cf8b91.png" /></p>
<p>
	يوضح هذا المقال كيفية تعريف صفحات لإنشاء كائنات المؤلف والكتاب ونسخ الكتب للتعرّف أكثر على كيفية التعامل مع الاستمارات forms في <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-express-%D9%88%D8%A8%D9%8A%D8%A6%D8%A9-node-r2168/" rel="">إطار عمل Express</a>.
</p>

<h2 id="">
	استمارة إنشاء مؤلف
</h2>

<p>
	سنوضّح فيما يلي كيفية تعريف صفحة لإنشاء كائنات المؤلف <code>Author</code>.
</p>

<h3 id="validationsanitization">
	استيراد توابع التحقق من صحة البيانات Validation وتطهيرها Sanitization
</h3>

<p>
	يجب طلب الدوال التي نريدها لاستخدام express-validator كما هو الحال في <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-express-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A9-form-%D9%84%D8%A3%D9%86%D9%88%D8%A7%D8%B9-%D8%A7%D9%84%D9%83%D8%AA%D8%A8-r2223/" rel="">استمارة نوع الكتاب</a>. افتح الملف "‎/controllers/authorController.js"، وأضِف السطر التالي في بداية الملف قبل دوال الوِجهة Route:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8472_6" style=""><span class="kwd">const</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> body</span><span class="pun">,</span><span class="pln"> validationResult </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">"express-validator"</span><span class="pun">);</span></pre>

<h3 id="get">
	متحكم وجهة Get
</h3>

<p>
	ابحث عن تابع المتحكم <code>author_create_get()‎</code> المُصدَّر وضع مكانه الشيفرة البرمجية التالية التي تؤدي إلى تقديم العرض author_form.pug وتمرير متغير العنوان <code>title</code>:
</p>

<pre class="ipsCode">// عرض استمارة إنشاء مؤلف‫ في طلب GET
exports.author_create_get = (req, res, next) =&gt; {
  res.render("author_form", { title: "Create Author" });
};
</pre>

<h3 id="post">
	متحكم وجهة Post
</h3>

<p>
	ابحث عن تابع المتحكم <code>author_create_post()‎</code> المُصدَّر وضع مكانه الشيفرة البرمجية التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8472_8" style=""><span class="com">// معالجة إ‫نشاء مؤلف Author في طلب POST</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">author_create_post </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
  </span><span class="com">// التحقق من صحة الحقول وتطهيرها</span><span class="pln">
  body</span><span class="pun">(</span><span class="str">"first_name"</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">trim</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">isLength</span><span class="pun">({</span><span class="pln"> min</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="pun">.</span><span class="pln">escape</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">withMessage</span><span class="pun">(</span><span class="str">"First name must be specified."</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">isAlphanumeric</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">withMessage</span><span class="pun">(</span><span class="str">"First name has non-alphanumeric characters."</span><span class="pun">),</span><span class="pln">
  body</span><span class="pun">(</span><span class="str">"family_name"</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">trim</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">isLength</span><span class="pun">({</span><span class="pln"> min</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="pun">.</span><span class="pln">escape</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">withMessage</span><span class="pun">(</span><span class="str">"Family name must be specified."</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">isAlphanumeric</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">withMessage</span><span class="pun">(</span><span class="str">"Family name has non-alphanumeric characters."</span><span class="pun">),</span><span class="pln">
  body</span><span class="pun">(</span><span class="str">"date_of_birth"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Invalid date of birth"</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">optional</span><span class="pun">({</span><span class="pln"> values</span><span class="pun">:</span><span class="pln"> </span><span class="str">"falsy"</span><span class="pln"> </span><span class="pun">})</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">isISO8601</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">toDate</span><span class="pun">(),</span><span class="pln">
  body</span><span class="pun">(</span><span class="str">"date_of_death"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Invalid date of death"</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">optional</span><span class="pun">({</span><span class="pln"> values</span><span class="pun">:</span><span class="pln"> </span><span class="str">"falsy"</span><span class="pln"> </span><span class="pun">})</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">isISO8601</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">toDate</span><span class="pun">(),</span><span class="pln">

  </span><span class="com">// طلب العملية بعد التحقق من صحة البيانات وتطهيرها</span><span class="pln">
  asyncHandler</span><span class="pun">(</span><span class="kwd">async</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="com">// استخراج أخطاء التحقق من صحة البيانات من الطلب</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> errors </span><span class="pun">=</span><span class="pln"> validationResult</span><span class="pun">(</span><span class="pln">req</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"> author </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Author</span><span class="pun">({</span><span class="pln">
      first_name</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">first_name</span><span class="pun">,</span><span class="pln">
      family_name</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">family_name</span><span class="pun">,</span><span class="pln">
      date_of_birth</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">date_of_birth</span><span class="pun">,</span><span class="pln">
      date_of_death</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">date_of_death</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">errors</span><span class="pun">.</span><span class="pln">isEmpty</span><span class="pun">())</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="com">// توجد أخطاء، لذا اعرض الاستمارة مرة أخرى مع قيم مُطهَّرة أو رسائل خطأ</span><span class="pln">
      res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">"author_form"</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="str">"Create Author"</span><span class="pun">,</span><span class="pln">
        author</span><span class="pun">:</span><span class="pln"> author</span><span class="pun">,</span><span class="pln">
        errors</span><span class="pun">:</span><span class="pln"> errors</span><span class="pun">.</span><span class="pln">array</span><span class="pun">(),</span><span class="pln">
      </span><span class="pun">});</span><span class="pln">
      </span><span class="kwd">return</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="com">// البيانات الواردة من الاستمارة صالحة</span><span class="pln">

      </span><span class="com">// احفظ المؤلف</span><span class="pln">
      </span><span class="kwd">await</span><span class="pln"> author</span><span class="pun">.</span><span class="pln">save</span><span class="pun">();</span><span class="pln">
      </span><span class="com">// أعِد التوجيه إلى سجل مؤلف جديد</span><span class="pln">
      res</span><span class="pun">.</span><span class="pln">redirect</span><span class="pun">(</span><span class="pln">author</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">
  </span><span class="pun">}),</span><span class="pln">
</span><span class="pun">];</span></pre>

<p>
	<strong>تحذير</strong>: لا تتحقق من صحة الأسماء باستخدام <code>isAlphanumeric()‎</code> كما فعلنا، نظرًا لوجود العديد من الأسماء التي تستخدم مجموعات محارف أخرى، ولكننا فعلنا ذلك لشرح كيفية استخدام أداة التحقق من صحة البيانات Validator، وكيفية ربطها بسلسلة تعاقبية مع أدوات التحقق الأخرى والإبلاغ عن الأخطاء.
</p>

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

<p>
	لا نتحقق مما إذا كان كائن المؤلف <code>Author</code> موجودًا مسبقًا قبل حفظه، وهذا يختلف عن معالج طلب <code>Genre</code> من النوع Post، إذ يمكننا ذلك بالرغم من أنه يمكن أن يكون لدينا عدة مؤلفين بالاسم نفسه.
</p>

<p>
	توضح شيفرة التحقق من صحة البيانات العديد من الميزات الجديدة التالية:
</p>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8472_10" style=""><span class="pun">[</span><span class="pln">
  </span><span class="com">// التحقق من صحة الحقول وتطهيرها</span><span class="pln">
  body</span><span class="pun">(</span><span class="str">"first_name"</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">trim</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">isLength</span><span class="pun">({</span><span class="pln"> min</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="pun">.</span><span class="pln">escape</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">withMessage</span><span class="pun">(</span><span class="str">"First name must be specified."</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">isAlphanumeric</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">withMessage</span><span class="pun">(</span><span class="str">"First name has non-alphanumeric characters."</span><span class="pun">),</span><span class="pln">
  </span><span class="com">// …</span><span class="pln">
</span><span class="pun">];</span></pre>

<p>
	ثانيًا، يمكننا استخدام الدالة <code>optional()‎</code> لإجراء عملية تحقق لاحقة فقط في حالة الإدخال في حقلٍ ما، مما يسمح بالتحقق من صحة الحقول الاختيارية، فمثلًا نتحقق فيما يلي من أن تاريخ الميلاد الاختياري هو تاريخ متوافق مع معيار ISO8601، إذ يعني تمرير كائن <code>{values: "falsy"‎}</code> أننا سنقبل إما سلسلة نصية فارغة أو <code>null</code> للقيمة الفارغة.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8472_12" style=""><span class="pun">[</span><span class="pln">
  body</span><span class="pun">(</span><span class="str">"date_of_birth"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Invalid date of birth"</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">optional</span><span class="pun">({</span><span class="pln"> values</span><span class="pun">:</span><span class="pln"> </span><span class="str">"falsy"</span><span class="pln"> </span><span class="pun">})</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">isISO8601</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">toDate</span><span class="pun">(),</span><span class="pln">
</span><span class="pun">];</span></pre>

<p>
	ثالثًا، تُستلَم المعاملات من الطلب بوصفها سلاسلًا نصية، ويمكننا استخدام <code>toDate()‎</code> أو <code>toBoolean()‎</code> لتغيير هذه الأنواع إلى <a href="https://academy.hsoub.com/programming/javascript/%D8%A3%D9%86%D9%88%D8%A7%D8%B9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-r672/" rel="">أنواع جافا سكريبت</a> المناسبة كما هو موضح في نهاية سلسلة أدوات التحقق سابقًا.
</p>

<h3 id="view">
	العرض View
</h3>

<p>
	أنشئ العرض "‎/views/author_form.pug" وضع فيه النص التالي:
</p>

<pre class="ipsCode">extends layout

block content
  h1=title

  form(method='POST' action='')
    div.form-group
      label(for='first_name') First Name:
      input#first_name.form-control(type='text' placeholder='First name' name='first_name' required='true' value=(undefined===author ? '' : author.first_name) )
      label(for='family_name') Family Name:
      input#family_name.form-control(type='text' placeholder='Family name' name='family_name' required='true' value=(undefined===author ? '' : author.family_name))
    div.form-group
      label(for='date_of_birth') Date of birth:
      input#date_of_birth.form-control(type='date' name='date_of_birth' value=(undefined===author ? '' : author.date_of_birth) )
    button.btn.btn-primary(type='submit') Submit
  if errors
    ul
      for error in errors
        li!= error.msg
</pre>

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

<p>
	<strong>ملاحظة</strong>: لا تدعم بعض المتصفحات حقل الإدخال <code>type="date"‎</code>، لذلك لن تحصل على عنصر واجهة مستخدم منتقي التاريخ أو العنصر البديل الافتراضي <code>dd/mm/yyyy</code>، ولكن ستحصل بدلًا من ذلك على حقل نص عادي فارغ. يتمثل أحد الحلول في إضافة السمة <code>placeholder='dd/mm/yyyy'‎</code> صراحةً، بحيث تظل تحصل على معلومات حول تنسيق النص المطلوب في المتصفحات ذات القدرات الأقل.
</p>

<h4 id="-1">
	التحدي: إضافة تاريخ الوفاة
</h4>

<p>
	يفتقد هذا القالب حقلًا لإدخال تاريخ الوفاة <code>date_of_death</code>، لذا أنشئ هذا الحقل باتباع النمط نفسه لمجموعة استمارة تاريخ الميلاد.
</p>

<h3 id="-2">
	كيف تبدو استمارة المؤلف؟
</h3>

<p>
	شغّل التطبيق، وافتح متصفحك على العنوان "http://localhost:3000/‎"، ثم حدّد رابط إنشاء مؤلف جديد Create new author. إذا جرى إعداد كل شيء بصورة صحيحة، فيجب أن يبدو موقعك كما يلي، ويجب حفظ القيمة بعد إدخالها وستُنقَل إلى صفحة تفاصيل المؤلف.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2024_01/01_locallibary_express_author_create_empty.png.5d448151c1977cd6f080f91c5534e77f.png" data-fileid="142156" data-fileext="png" rel=""><img alt="01 locallibary express author create empty" class="ipsImage ipsImage_thumbnailed" data-fileid="142156" data-unique="as53jnehh" src="https://academy.hsoub.com/uploads/monthly_2024_01/01_locallibary_express_author_create_empty.thumb.png.27afe57ba7ffebf049f6c115e32cf146.png"> </a>
</p>

<p>
	<strong>ملاحظة</strong>: إذا جرّبتَ تنسيقات إدخال مختلفة للتواريخ، فقد تجد أن التنسيق <code>yyyy-mm-dd</code> لا يتصرف بصورة صحيحة، لأن جافا سكريبت تتعامل مع سلاسل التاريخ بأنها تتضمن وقت 0 ساعة، ولكنها تتعامل أيضًا مع سلاسل التاريخ بهذا التنسيق (معيار ISO 8601) بوصفها تتضمن الوقت 0 ساعة بالتوقيت العالمي المنسَّق UTC بدلًا من التوقيت المحلي. إذا وقعت منطقتك الزمنية غرب توقيت UTC، فسيكون عرض التاريخ -لكونه محليًا- قبل يوم واحد من التاريخ الذي أدخلته، وهذه إحدى التعقيدات العديدة، مثل أسماء العائلات متعددة الكلمات والكتب متعددة المؤلفين التي لم نعالجها.
</p>

<h2 id="-3">
	استمارة إنشاء كتاب
</h2>

<p>
	سنوضح كيفية تعريف صفحة أو استمارة لإنشاء كائنات الكتاب <code>Book</code>، ويُعَد ذلك أكثر تعقيدًا قليلًا من صفحات المؤلف <code>Author</code> أو نوع الكتاب <code>Genre</code> المكافئة لها، لأننا نحتاج إلى الحصول على سجلات المؤلف <code>Author</code> ونوع الكتاب <code>Genre</code> المتوفرة وعرضها في استمارة الكتاب <code>Book</code>.
</p>

<h3 id="-4">
	استيراد توابع التحقق من صحة البيانات وتطهيرها
</h3>

<p>
	افتح الملف ‎/controllers/bookController.js، وأضِف السطر التالي في بداية الملف قبل دوال الوِجهة Route:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8472_14" style=""><span class="kwd">const</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> body</span><span class="pun">,</span><span class="pln"> validationResult </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">"express-validator"</span><span class="pun">);</span></pre>

<h3 id="get-1">
	متحكم وجهة Get
</h3>

<p>
	ابحث عن تابع المتحكم <code>book_create_get()‎</code> المُصدَّر وضع مكانه الشيفرة البرمجية التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8472_16" style=""><span class="com">// عرض استمارة إنشاء كتاب‫ في طلب GET</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">book_create_get </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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="com">// الحصول على جميع المؤلفين وأنواع الكتب التي يمكننا استخدامها للإضافة إلى الكتاب</span><span class="pln">
 </span><span class="kwd">const</span><span class="pln"> </span><span class="pun">[</span><span class="pln">allAuthors</span><span class="pun">,</span><span class="pln"> allGenres</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</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">
    </span><span class="typ">Author</span><span class="pun">.</span><span class="pln">find</span><span class="pun">().</span><span class="pln">sort</span><span class="pun">({</span><span class="pln"> family_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">exec</span><span class="pun">(),</span><span class="pln">
    </span><span class="typ">Genre</span><span class="pun">.</span><span class="pln">find</span><span class="pun">().</span><span class="pln">sort</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">exec</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">render</span><span class="pun">(</span><span class="str">"book_form"</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="str">"Create Book"</span><span class="pun">,</span><span class="pln">
    authors</span><span class="pun">:</span><span class="pln"> allAuthors</span><span class="pun">,</span><span class="pln">
    genres</span><span class="pun">:</span><span class="pln"> allGenres</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://wiki.hsoub.com/JavaScript/await" rel="external"><code>await</code></a> لانتظار نتيجة التابع <code>Promise.all()‎</code> للحصول على جميع كائنات المؤلف <code>Author</code> ونوع الكتاب <code>Genre</code> على التوازي، وهو الأسلوب نفسه الذي استخدمناه عند <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B1%D8%A7%D8%A8%D8%B9-%D8%B9%D8%B1%D8%B6-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%88%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-r2193/" rel="">عرض بيانات المكتبة</a>، ثم تُمرَّر إلى العرض book_form.pug بوصفها متغيرات بالاسم <code>authors</code> و <code>genres</code> مع عنوان <code>title</code> الصفحة.
</p>

<h3 id="post-1">
	متحكم وجهة Post
</h3>

<p>
	ابحث عن تابع المتحكم <code>book_create_post()‎</code> المُصدَّر وضع مكانه الشيفرة البرمجية التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8472_18" style=""><span class="com">// معالجة إ‫نشاء كتاب في طلب POST</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">book_create_post </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">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="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="typ">Array</span><span class="pun">.</span><span class="pln">isArray</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">genre</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">body</span><span class="pun">.</span><span class="pln">genre </span><span class="pun">=</span><span class="pln">
        </span><span class="kwd">typeof</span><span class="pln"> req</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">genre </span><span class="pun">===</span><span class="pln"> </span><span class="str">"undefined"</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">req</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">genre</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">

  </span><span class="com">// التحقق من صحة الحقول وتطهيرها</span><span class="pln">
  body</span><span class="pun">(</span><span class="str">"title"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Title must not be empty."</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">trim</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">isLength</span><span class="pun">({</span><span class="pln"> min</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="pun">.</span><span class="pln">escape</span><span class="pun">(),</span><span class="pln">
  body</span><span class="pun">(</span><span class="str">"author"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Author must not be empty."</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">trim</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">isLength</span><span class="pun">({</span><span class="pln"> min</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="pun">.</span><span class="pln">escape</span><span class="pun">(),</span><span class="pln">
  body</span><span class="pun">(</span><span class="str">"summary"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Summary must not be empty."</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">trim</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">isLength</span><span class="pun">({</span><span class="pln"> min</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="pun">.</span><span class="pln">escape</span><span class="pun">(),</span><span class="pln">
  body</span><span class="pun">(</span><span class="str">"isbn"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"ISBN must not be empty"</span><span class="pun">).</span><span class="pln">trim</span><span class="pun">().</span><span class="pln">isLength</span><span class="pun">({</span><span class="pln"> min</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">escape</span><span class="pun">(),</span><span class="pln">
  body</span><span class="pun">(</span><span class="str">"genre.*"</span><span class="pun">).</span><span class="pln">escape</span><span class="pun">(),</span><span class="pln">
  </span><span class="com">// طلب العملية بعد التحقق من صحة البيانات وتطهيرها</span><span class="pln">

  asyncHandler</span><span class="pun">(</span><span class="kwd">async</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="com">// استخراج أخطاء التحقق من صحة البيانات من الطلب</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> errors </span><span class="pun">=</span><span class="pln"> validationResult</span><span class="pun">(</span><span class="pln">req</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"> book </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Book</span><span class="pun">({</span><span class="pln">
      title</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">title</span><span class="pun">,</span><span class="pln">
      author</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">author</span><span class="pun">,</span><span class="pln">
      summary</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">summary</span><span class="pun">,</span><span class="pln">
      isbn</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">isbn</span><span class="pun">,</span><span class="pln">
      genre</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">genre</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">errors</span><span class="pun">.</span><span class="pln">isEmpty</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="com">// احصل على جميع المؤلفين وأنواع الكتب للاستمارة</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> </span><span class="pun">[</span><span class="pln">allAuthors</span><span class="pun">,</span><span class="pln"> allGenres</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</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">
        </span><span class="typ">Author</span><span class="pun">.</span><span class="pln">find</span><span class="pun">().</span><span class="pln">sort</span><span class="pun">({</span><span class="pln"> family_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">exec</span><span class="pun">(),</span><span class="pln">
        </span><span class="typ">Genre</span><span class="pun">.</span><span class="pln">find</span><span class="pun">().</span><span class="pln">sort</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">exec</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">for</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">const</span><span class="pln"> genre </span><span class="kwd">of</span><span class="pln"> allGenres</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">book</span><span class="pun">.</span><span class="pln">genre</span><span class="pun">.</span><span class="pln">includes</span><span class="pun">(</span><span class="pln">genre</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">
          genre</span><span class="pun">.</span><span class="pln">checked </span><span class="pun">=</span><span class="pln"> </span><span class="str">"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">
      res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">"book_form"</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="str">"Create Book"</span><span class="pun">,</span><span class="pln">
        authors</span><span class="pun">:</span><span class="pln"> allAuthors</span><span class="pun">,</span><span class="pln">
        genres</span><span class="pun">:</span><span class="pln"> allGenres</span><span class="pun">,</span><span class="pln">
        book</span><span class="pun">:</span><span class="pln"> book</span><span class="pun">,</span><span class="pln">
        errors</span><span class="pun">:</span><span class="pln"> errors</span><span class="pun">.</span><span class="pln">array</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="com">// البيانات الواردة من الاستمارة صالحة، لذا احفظ الكتاب</span><span class="pln">
      </span><span class="kwd">await</span><span class="pln"> book</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="pln">book</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">
  </span><span class="pun">}),</span><span class="pln">
</span><span class="pun">];</span></pre>

<p>
	تماثل بنية وسلوك هذه الشيفرة البرمجية تقريبًا دوال وجهة Post لاستمارات النوع <code>Genre</code> والكاتب <code>Author</code>، إذ نتحقق أولًا من صحة البيانات ونطهّرها؛ فإذا كانت البيانات غير صالحة، فسنعيد عرض الاستمارة مع البيانات التي أدخلها المستخدم في الأصل وقائمة برسائل الخطأ؛ وإذا كانت البيانات صالحة، فسنحفظ سجل الكتاب <code>Book</code> الجديد ونعيد توجيه المستخدم إلى صفحة تفاصيل الكتاب.
</p>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8472_20" style=""><span class="pun">[</span><span class="pln">
  </span><span class="com">// تحويل نوع الكتاب إلى مصفوفة</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="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="typ">Array</span><span class="pun">.</span><span class="pln">isArray</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">genre</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">body</span><span class="pun">.</span><span class="pln">genre </span><span class="pun">=</span><span class="pln">
        </span><span class="kwd">typeof</span><span class="pln"> req</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">genre </span><span class="pun">===</span><span class="pln"> </span><span class="str">"undefined"</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">req</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">genre</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">
  </span><span class="com">// …</span><span class="pln">
</span><span class="pun">];</span></pre>

<p>
	نستخدم بعد ذلك محرف بدل (<code>*</code>) في أداة التطهير للتحقق من صحة كل إدخال من مصفوفة أنواع الكتاب بصورة فردية، إذ توضح الشيفرة التالية كيفية ترجمة ذلك إلى "تطهير كل عنصر له المفتاح <code>genre</code>".
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8472_23" style=""><span class="pun">[</span><span class="pln">
  </span><span class="com">// …</span><span class="pln">
  body</span><span class="pun">(</span><span class="str">"genre.*"</span><span class="pun">).</span><span class="pln">escape</span><span class="pun">(),</span><span class="pln">
  </span><span class="com">// …</span><span class="pln">
</span><span class="pun">];</span></pre>

<p>
	الاختلاف الأخير فيما يتعلق بشيفرة معالجة الاستمارة الأخرى هو أنه نحتاج إلى تمرير جميع أنواع الكتب والمؤلفين الموجودين إلى الاستمارة. نكرر عبر جميع أنواع الكتب ونضيف المعامل <code>checked="true"‎</code> إلى تلك الأنواع التي كانت موجودة في بيانات Post (كما أعدنا الإنتاج في جزء الشيفرة التالي) لتمييز أنواع الكتب التي حدّدها المستخدم.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8472_25" style=""><span class="com">// تمييز أنواع الكتب المختارة بوصفها مُحدَّدة</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">const</span><span class="pln"> genre </span><span class="kwd">of</span><span class="pln"> results</span><span class="pun">.</span><span class="pln">genres</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">book</span><span class="pun">.</span><span class="pln">genre</span><span class="pun">.</span><span class="pln">includes</span><span class="pun">(</span><span class="pln">genre</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">// اختير نوع الكتاب الحالي، لذا ا‫ضبط الراية "checked"</span><span class="pln">
    genre</span><span class="pun">.</span><span class="pln">checked </span><span class="pun">=</span><span class="pln"> </span><span class="str">"true"</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<h3 id="-5">
	العرض
</h3>

<p>
	أنشئ العرض "‎/views/book_form.pug" وضع فيه النص التالي:
</p>

<pre class="ipsCode">extends layout

block content
  h1= title

  form(method='POST' action='')
    div.form-group
      label(for='title') Title:
      input#title.form-control(type='text', placeholder='Name of book' name='title' required='true' value=(undefined===book ? '' : book.title) )
    div.form-group
      label(for='author') Author:
      select#author.form-control(type='select', placeholder='Select author' name='author' required='true' )
        for author in authors
          if book
            option(value=author._id selected=(author._id.toString()===book.author._id.toString() ? 'selected' : false) ) #{author.name}
          else
            option(value=author._id) #{author.name}
    div.form-group
      label(for='summary') Summary:
      textarea#summary.form-control(type='textarea', placeholder='Summary' name='summary' required='true') #{undefined===book ? '' : book.summary}
    div.form-group
      label(for='isbn') ISBN:
      input#isbn.form-control(type='text', placeholder='ISBN13' name='isbn' value=(undefined===book ? '' : book.isbn) required='true')
    div.form-group
      label Genre:
      div
        for genre in genres
          div(style='display: inline; padding-right:10px;')
            input.checkbox-input(type='checkbox', name='genre', id=genre._id, value=genre._id, checked=genre.checked )
            label(for=genre._id) #{genre.name}
    button.btn.btn-primary(type='submit') Submit

  if errors
    ul
      for error in errors
        li!= error.msg
</pre>

<p>
	تماثل بنية وسلوك هذا العرض تقريبًا بنية وسلوك القالب genre_form.pug، ولكن تكمن الاختلافات الرئيسية في كيفية تقديم حقول نوع الاختيار: <code>Author</code> و <code>Genre</code>، إذ:
</p>

<ul>
	<li>
		تُعرَض مجموعة أنواع الكتب بوصفها مربعات اختيار، وتستخدم قيمة <code>checked</code> التي ضبطناها في المتحكم لتحديد ما إذا كان يجب تحديد المربع أم لا.
	</li>
	<li>
		تُعرَض مجموعة المؤلفين بوصفها قائمة منسدلة مع اختيار فردي مرتبة أبجديًا، فإذا حدّد المستخدم مسبقًا مؤلف كتابٍ ما عند إصلاح قيم الحقول غير الصالحة بعد إرسال الاستمارة الأولية أو عند تحديث تفاصيل الكتاب مثلًا، فسيُعاد اختيار المؤلف عند عرض الاستمارة. نحدد في هذه الحالة المؤلف المراد اختياره من خلال موازنة معرّف خيار المؤلف الحالي مع القيمة التي أدخلها المستخدم مسبقًا والمُمرَّرة عبر المتغير <code>book</code>.
	</li>
</ul>

<p>
	<strong>ملاحظة</strong>: إذا كان هناك خطأ في الاستمارة المُرسَلة، فسيكون معرّف مؤلف الكتاب الجديد ومعرّفات مؤلفي الكتب الموجودين مسبقًا من النوع <code>Schema.Types.ObjectId</code> عند إعادة تصيير الاستمارة، لذا يجب تحويلها إلى سلاسل نصية أولًا للموازنة بينها.
</p>

<h3 id="-6">
	كيف تبدو استمارة إنشاء كتاب؟
</h3>

<p>
	شغّل التطبيق، وافتح متصفحك على العنوان "http://localhost:3000/‎"، ثم حدد رابط إنشاء كتاب جديد Create new book. إذا جرى إعداد كل شيء بصورة صحيحة، فيجب أن يبدو موقعك كما يلي، ويجب حفظ الكتاب بعد إرسال كتاب صالح وستُنقَل إلى صفحة تفاصيل الكتاب:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2024_01/02_locallibary_express_book_create_empty.png.698cb835448450797330d944c454f25b.png" data-fileid="142157" data-fileext="png" rel=""><img alt="02 locallibary express book create empty" class="ipsImage ipsImage_thumbnailed" data-fileid="142157" data-unique="hxm6igewq" src="https://academy.hsoub.com/uploads/monthly_2024_01/02_locallibary_express_book_create_empty.thumb.png.4a14159df2e8c3be3f88a9d6ba71f299.png"> </a>
</p>

<h2 id="bookinstance">
	استمارة إنشاء نسخة كتاب BookInstance
</h2>

<p>
	سنوضح فيما يلي كيفية تعريف صفحة أو استمارة لإنشاء كائنات <code>BookInstance</code>، وتشبه هذه الاستمارة إلى حد كبير الاستمارة التي استخدمناها لإنشاء كائنات الكتاب <code>Book</code>.
</p>

<h3 id="-7">
	استيراد توابع التحقق من صحة البيانات وتطهيرها
</h3>

<p>
	افتح الملف "‎/controllers/bookinstanceController.js"، وضِف السطر التالي في بداية الملف:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8472_27" style=""><span class="kwd">const</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> body</span><span class="pun">,</span><span class="pln"> validationResult </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">"express-validator"</span><span class="pun">);</span></pre>

<h3 id="get-2">
	متحكم وجهة Get
</h3>

<p>
	اطلب الوحدة Book في الجزء العلوي من الملف، حيث تُعَد هذه الوحدة مطلوبة لأن كل نسخة كتاب <code>BookInstance</code> مرتبطة بكتاب <code>Book</code> معين.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8472_29" style=""><span class="kwd">const</span><span class="pln"> </span><span class="typ">Book</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"../models/book"</span><span class="pun">);</span></pre>

<p>
	ابحث عن تابع المتحكم <code>bookinstance_create_get()‎</code> المُصدَّر وضع مكانه الشيفرة البرمجية التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8472_31" style=""><span class="com">// عرض استمارة إنشاء نسخة كتاب‫ BookInstance في طلب GET</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">bookinstance_create_get </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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="kwd">const</span><span class="pln"> allBooks </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> </span><span class="typ">Book</span><span class="pun">.</span><span class="pln">find</span><span class="pun">({},</span><span class="pln"> </span><span class="str">"title"</span><span class="pun">).</span><span class="pln">sort</span><span class="pun">({</span><span class="pln"> title</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">exec</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">"bookinstance_form"</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="str">"Create BookInstance"</span><span class="pun">,</span><span class="pln">
    book_list</span><span class="pun">:</span><span class="pln"> allBooks</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	يحصل المتحكم على قائمة بجميع الكتب (<code>allBooks</code>) ويمرّرها عبر المتغير <code>book_list</code> إلى العرض <code>bookinstance_form.pug</code> مع المتغير <code>title</code>. لاحظ عدم تحديد أي كتاب عندما أظهرنا الاستمارة لأول مرة، وبالتالي لم يُمرّر المتغير <code>selected_book</code> إلى دالة التصيير <code>()render</code>، ونتيجةً لذلك سيكون للمتغير <code>selected_book</code> قيمة <code>undefined</code> في القالب.
</p>

<h3 id="post-2">
	متحكم وجهة Post
</h3>

<p>
	ابحث عن تابع المتحكم <code>bookinstance_create_post()‎</code> المُصدَّر وضع مكانه الشيفرة البرمجية التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8472_33" style=""><span class="com">// معالجة إ‫نشاء نسخة كتاب في طلب POST</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">bookinstance_create_post </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
  </span><span class="com">// التحقق من صحة الحقول وتطهيرها</span><span class="pln">
  body</span><span class="pun">(</span><span class="str">"book"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Book must be specified"</span><span class="pun">).</span><span class="pln">trim</span><span class="pun">().</span><span class="pln">isLength</span><span class="pun">({</span><span class="pln"> min</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">escape</span><span class="pun">(),</span><span class="pln">
  body</span><span class="pun">(</span><span class="str">"imprint"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Imprint must be specified"</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">trim</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">isLength</span><span class="pun">({</span><span class="pln"> min</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="pun">.</span><span class="pln">escape</span><span class="pun">(),</span><span class="pln">
  body</span><span class="pun">(</span><span class="str">"status"</span><span class="pun">).</span><span class="pln">escape</span><span class="pun">(),</span><span class="pln">
  body</span><span class="pun">(</span><span class="str">"due_back"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Invalid date"</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">optional</span><span class="pun">({</span><span class="pln"> values</span><span class="pun">:</span><span class="pln"> </span><span class="str">"falsy"</span><span class="pln"> </span><span class="pun">})</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">isISO8601</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">toDate</span><span class="pun">(),</span><span class="pln">

  </span><span class="com">// طلب العملية بعد التحقق من صحة البيانات وتطهيرها</span><span class="pln">
  asyncHandler</span><span class="pun">(</span><span class="kwd">async</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="com">// استخراج أخطاء التحقق من صحة البيانات من الطلب</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> errors </span><span class="pun">=</span><span class="pln"> validationResult</span><span class="pun">(</span><span class="pln">req</span><span class="pun">);</span><span class="pln">

    </span><span class="com">// إنشاء كائن‫ نسخة كتاب BookInstance مع بيانات مُهرَّبة ومحذوف منها المسافات</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> bookInstance </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">BookInstance</span><span class="pun">({</span><span class="pln">
      book</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">book</span><span class="pun">,</span><span class="pln">
      imprint</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">imprint</span><span class="pun">,</span><span class="pln">
      status</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">status</span><span class="pun">,</span><span class="pln">
      due_back</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">due_back</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">errors</span><span class="pun">.</span><span class="pln">isEmpty</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="com">// اعرض الاستمارة مرة أخرى مع قيم مُطهَّرة ورسائل خطأ</span><span class="pln">
      </span><span class="kwd">const</span><span class="pln"> allBooks </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> </span><span class="typ">Book</span><span class="pun">.</span><span class="pln">find</span><span class="pun">({},</span><span class="pln"> </span><span class="str">"title"</span><span class="pun">).</span><span class="pln">sort</span><span class="pun">({</span><span class="pln"> title</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">exec</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">"bookinstance_form"</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="str">"Create BookInstance"</span><span class="pun">,</span><span class="pln">
        book_list</span><span class="pun">:</span><span class="pln"> allBooks</span><span class="pun">,</span><span class="pln">
        selected_book</span><span class="pun">:</span><span class="pln"> bookInstance</span><span class="pun">.</span><span class="pln">book</span><span class="pun">.</span><span class="pln">_id</span><span class="pun">,</span><span class="pln">
        errors</span><span class="pun">:</span><span class="pln"> errors</span><span class="pun">.</span><span class="pln">array</span><span class="pun">(),</span><span class="pln">
        bookinstance</span><span class="pun">:</span><span class="pln"> bookInstance</span><span class="pun">,</span><span class="pln">
      </span><span class="pun">});</span><span class="pln">
      </span><span class="kwd">return</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="com">// البيانات الواردة من الاستمارة صالحة</span><span class="pln">
      </span><span class="kwd">await</span><span class="pln"> bookInstance</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="pln">bookInstance</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">
  </span><span class="pun">}),</span><span class="pln">
</span><span class="pun">];</span></pre>

<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="">الكائنات</a> الأخرى، إذ نتحقق أولًا من صحة البيانات ونطهرها؛ فإذا كانت البيانات غير صالحة، فسنعيد عرض الاستمارة مع البيانات التي أدخلها المستخدم في الأصل وقائمة برسائل الخطأ؛ وإذا كانت البيانات صالحة، فسنحفظ سجل نسخة الكتاب <code>BookInstance</code> الجديد ونعيد توجيه المستخدم إلى صفحة التفاصيل.
</p>

<h3 id="-8">
	العرض
</h3>

<p>
	أنشئ العرض "‎/views/bookinstance_form.pug" وضع فيه النص التالي:
</p>

<pre class="ipsCode">extends layout

block content
  h1=title

  form(method='POST' action='')
    div.form-group
      label(for='book') Book:
      select#book.form-control(type='select' placeholder='Select book' name='book' required='true')
        for book in book_list
          option(value=book._id, selected=(selected_book==book._id.toString() ? '' : false) ) #{book.title}

    div.form-group
      label(for='imprint') Imprint:
      input#imprint.form-control(type='text' placeholder='Publisher and date information' name='imprint' required='true' value=(undefined===bookinstance ? '' : bookinstance.imprint))
    div.form-group
      label(for='due_back') Date when book available:
      input#due_back.form-control(type='date' name='due_back' value=(undefined===bookinstance ? '' : bookinstance.due_back_yyyy_mm_dd))

    div.form-group
      label(for='status') Status:
      select#status.form-control(type='select' placeholder='Select status' name='status' required='true' )
        option(value='Maintenance' selected=(undefined===bookinstance || bookinstance.status!='Maintenance' ? false:'selected')) Maintenance
        option(value='Available' selected=(undefined===bookinstance || bookinstance.status!='Available' ? false:'selected')) Available
        option(value='Loaned' selected=(undefined===bookinstance || bookinstance.status!='Loaned' ? false:'selected')) Loaned
        option(value='Reserved' selected=(undefined===bookinstance || bookinstance.status!='Reserved' ? false:'selected')) Reserved

    button.btn.btn-primary(type='submit') Submit

  if errors
    ul
      for error in errors
        li!= error.msg
</pre>

<p>
	تماثل بنية وسلوك هذا العرض تقريبًا بنية وسلوك القالب book_form.pug، لذا لن نشرحها مرةً أخرى.
</p>

<p>
	<strong>ملاحظة</strong>: يجعل القالب السابق قيم الحالة Status ثابتة (في الصيانة Maintenance ومتوفر Available وغير ذلك)، ولا يتذكر القيم التي أدخلها المستخدم، فإذا رغبتَ في ذلك، ففكر في إعادة تقديم القائمة وتمرير بيانات الخيار من المتحكم وضبط القيمة المختارة عند إعادة عرض الاستمارة.
</p>

<h3 id="-9">
	كيف تبدو استمارة إنشاء نسخة كتاب؟
</h3>

<p>
	شغّل التطبيق، وافتح متصفحك على العنوان "http://localhost:3000/‎"، ثم حدد رابط إنشاء نسخة كتاب جديدة Create new book instance (copy)‎. إذا جرى إعداد كل شيء بصورة صحيحة، فيجب أن يبدو موقعك كما يلي، ويجب حفظ نسخة الكتاب بعد إرسال نسخة كتاب صالحة وستُنقَل إلى صفحة التفاصيل:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2024_01/03_locallibary_express_bookinstance_create_empty.png.5a222f5c7a8e632044e328835442c6d1.png" data-fileid="142158" data-fileext="png" rel=""><img alt="03 locallibary express bookinstance create empty" class="ipsImage ipsImage_thumbnailed" data-fileid="142158" data-unique="8xre2urgx" src="https://academy.hsoub.com/uploads/monthly_2024_01/03_locallibary_express_bookinstance_create_empty.thumb.png.aab1c665a203a9fbdfd2162af15d7931.png"> </a>
</p>

<p>
	ترجمة -وبتصرُّف- للمقالات <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Create_author_form" rel="external nofollow">Create Author form</a> و <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Create_book_form" rel="external nofollow">Create Book form</a> و <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Create_BookInstance_form" rel="external nofollow">Create BookInstance form</a>.
</p>

<h2 id="-10">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-express-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A9-form-%D9%84%D8%A3%D9%86%D9%88%D8%A7%D8%B9-%D8%A7%D9%84%D9%83%D8%AA%D8%A8-r2223/" rel="">إنشاء مكتبة محلية باستخدام Express: إنشاء استمارة Form لأنواع الكتب</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%B2%D9%84%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-%D9%88%D8%A7%D9%84%D8%A3%D8%AE%D8%B7%D8%A7%D8%A1-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1245/" rel="">الزلات البرمجية والأخطاء في جافاسكريبت</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/javascript/" rel="">تعلم لغة جافا سكريبت JavaScript</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-express-%D9%88%D8%A8%D9%8A%D8%A6%D8%A9-node-r2168/" rel="">مدخل إلى إطار عمل الويب Express وبيئة Node</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2231</guid><pubDate>Sun, 21 Jan 2024 12:02:01 +0000</pubDate></item><item><title>&#x625;&#x646;&#x634;&#x627;&#x621; &#x645;&#x643;&#x62A;&#x628;&#x629; &#x645;&#x62D;&#x644;&#x64A;&#x629; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; Express: &#x625;&#x646;&#x634;&#x627;&#x621; &#x627;&#x633;&#x62A;&#x645;&#x627;&#x631;&#x629; Form &#x644;&#x623;&#x646;&#x648;&#x627;&#x639; &#x627;&#x644;&#x643;&#x62A;&#x628;</title><link>https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-express-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A9-form-%D9%84%D8%A3%D9%86%D9%88%D8%A7%D8%B9-%D8%A7%D9%84%D9%83%D8%AA%D8%A8-r2223/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_01/----Express-----.png.82bd2df6f7d5052bd600e6cf341f3fed.png" /></p>
<p>
	يوضح هذا المقال كيفية تعريف صفحة لإنشاء كائنات النوع <code>Genre</code>، إذ يُعَد ذلك مكانًا جيدًا لتعلم التعامل مع الاستمارات Forms لأن  الاستمارة الخاصة بنوع الكتاب <code>Genre</code> تحتوي على حقل واحد فقط هو اسم النوع <code>name</code> وبدون اعتماديات، ويجب إعداد الوجهات Routes والمتحكمات Controllers والعروض Views لهذه الصفحة مثلها مثل أيّ صفحة أخرى.
</p>

<h2 id="validationsanitization">
	استيراد توابع التحقق من صحة البيانات Validation وتطهيرها Sanitization
</h2>

<p>
	يمكن استخدام express-validator في المتحكمات من خلال طلب require الدوال التي نريد استخدامها للتحقق من صحة حقول النماذج من الوحدة <code>'express-validator'</code>.
</p>

<p>
	افتح الملف "‎/controllers/genreController.js"، وأضف السطر التالي في بداية الملف قبل أيٍّ من دوال معالج الوجهة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1713_8" style=""><span class="kwd">const</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> body</span><span class="pun">,</span><span class="pln"> validationResult </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">"express-validator"</span><span class="pun">);</span></pre>

<p>
	<strong>ملاحظة</strong>: تسمح الصيغة السابقة باستخدام <code>body</code> و <code>validationResult</code> بوصفهما دوال وسيطة كما سترى في قسم طلب الوجهة من النوع Post، وهي تكافئ ما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1713_10" style=""><span class="kwd">const</span><span class="pln"> validator </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"express-validator"</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"> validator</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"> validationResult </span><span class="pun">=</span><span class="pln"> validator</span><span class="pun">.</span><span class="pln">validationResult</span><span class="pun">;</span></pre>

<h2 id="get">
	متحكم وجهة Get
</h2>

<p>
	ابحث عن تابع المتحكم <code>genre_create_get()‎</code> المُصدَّر وضع مكانه الشيفرة البرمجية التالية التي تؤدي إلى تقديم التصيير genre_form.pug وتمرير متغير العنوان:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1713_12" style=""><span class="com">// عرض استمارة إنشاء نوع ك‫تاب في طلب GET </span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">genre_create_get </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">
  res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">"genre_form"</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="str">"Create Genre"</span><span class="pln"> </span><span class="pun">});</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	لاحظ أن ذلك يؤدي إلى وضع دالة معالج وجهة Express "العادي" مكان المعالج غير المتزامن للعنصر البديل الذي أضفناه في <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%D8%A7%D9%84%D9%88%D8%AC%D9%87%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D9%85%D8%AA%D8%AD%D9%83%D9%85%D8%A7%D8%AA-r2184/" rel="">مقال الوجهات والمتحكمات</a>. لا حاجة للمغلّف <code>asyncHandler()‎</code> لهذه الوجهة، لأنه لا يحتوي على أيّ شيفرة برمجية يمكنها رمي استثناء.
</p>

<h2 id="post">
	متحكم وجهة Post
</h2>

<p>
	ابحث عن تابع المتحكم <code>genre_create_post()‎</code> المُصدَّر وضع مكانه الشيفرة البرمجية التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1713_14" style=""><span class="com">// معالجة إ‫نشاء نوع كتاب Genre في طلب POST</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">genre_create_post </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
  </span><span class="com">// التحقق من صحة حقل الاسم وتطهيره</span><span class="pln">
  body</span><span class="pun">(</span><span class="str">"name"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Genre name must contain at least 3 characters"</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">trim</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">isLength</span><span class="pun">({</span><span class="pln"> min</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3</span><span class="pln"> </span><span class="pun">})</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">escape</span><span class="pun">(),</span><span class="pln">

  </span><span class="com">// طلب العملية بعد التحقق من صحة البيانات وتطهيرها</span><span class="pln">
  asyncHandler</span><span class="pun">(</span><span class="kwd">async</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="com">// استخراج أخطاء التحقق من صحة البيانات من الطلب</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> errors </span><span class="pun">=</span><span class="pln"> validationResult</span><span class="pun">(</span><span class="pln">req</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"> genre </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Genre</span><span class="pun">({</span><span class="pln"> name</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">name </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">errors</span><span class="pun">.</span><span class="pln">isEmpty</span><span class="pun">())</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="com">// توجد أخطاء، لذا اعرض الاستمارة مرة أخرى مع قيم مُطهَّرة أو رسائل خطأ</span><span class="pln">
      res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">"genre_form"</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="str">"Create Genre"</span><span class="pun">,</span><span class="pln">
        genre</span><span class="pun">:</span><span class="pln"> genre</span><span class="pun">,</span><span class="pln">
        errors</span><span class="pun">:</span><span class="pln"> errors</span><span class="pun">.</span><span class="pln">array</span><span class="pun">(),</span><span class="pln">
      </span><span class="pun">});</span><span class="pln">
      </span><span class="kwd">return</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="com">// البيانات الواردة من الاستمارة صالحة</span><span class="pln">
      </span><span class="com">// تحقق مما إذا كان نوع الكتاب الذي يحمل الاسم نفسه موجود مسبقًا</span><span class="pln">
      </span><span class="kwd">const</span><span class="pln"> genreExists </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> </span><span class="typ">Genre</span><span class="pun">.</span><span class="pln">findOne</span><span class="pun">({</span><span class="pln"> name</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">name </span><span class="pun">}).</span><span class="pln">exec</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">genreExists</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="com">// نوع الكتاب موجود مسبقًا، لذا أعِد التوجيه إلى صفحة تفاصيل هذا النوع</span><span class="pln">
        res</span><span class="pun">.</span><span class="pln">redirect</span><span class="pun">(</span><span class="pln">genreExists</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"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">await</span><span class="pln"> genre</span><span class="pun">.</span><span class="pln">save</span><span class="pun">();</span><span class="pln">
        </span><span class="com">// حُفِظ نوع الكتاب الجديد، لذا أعِد التوجيه إلى صفحة تفاصيل نوع الكتاب</span><span class="pln">
        res</span><span class="pun">.</span><span class="pln">redirect</span><span class="pun">(</span><span class="pln">genre</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">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">}),</span><span class="pln">
</span><span class="pun">];</span></pre>

<p>
	أول شيء يجب ملاحظته هو أن المتحكم يحدّد مصفوفة من الدوال الوسيطة بدلًا من وجود دالة وسيطة واحدة مع الوسائط <code>(req, res, next)</code>، وتُمرَّر المصفوفة إلى دالة الموجِّه ويُستدعَى كل تابع حسب ترتيبه.
</p>

<p>
	<strong>ملاحظة</strong>: يُعَد هذا الأسلوب ضروريًا، لأن أدوات التحقق من صحة البيانات Validators هي دوال وسيطة.
</p>

<p>
	يعرِّف التابع الأول في المصفوفة أداة التحقق من صحة الجسم (<code>body()‎</code>) التي تتحقق من صحة الحقل وتطهّره، وتستخدم الدالة<code>trim()‎</code> لإزالة أي مسافة بيضاء لاحقة أو بادئة، وتتحقق من أن حقل الاسم name غير فارغ، ثم تستخدم الدالة <code>escape()‎</code> لإزالة أيّ محارف <a href="https://academy.hsoub.com/programming/html/" rel="">HTML</a> خطيرة.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1713_16" style=""><span class="pun">[</span><span class="pln">
  </span><span class="com">// تحقق من أن حقل الاسم غير فارغ</span><span class="pln">
  body</span><span class="pun">(</span><span class="str">"name"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Genre name must contain at least 3 characters"</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">trim</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">isLength</span><span class="pun">({</span><span class="pln"> min</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3</span><span class="pln"> </span><span class="pun">})</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">escape</span><span class="pun">(),</span><span class="pln">
  </span><span class="com">// …</span><span class="pln">
</span><span class="pun">];</span></pre>

<p>
	بعد تحديد أدوات التحقق من الصحة، ننشئ دالة وسيطة لاستخراج أيّ أخطاء في عملية التحقق من صحة البيانات، إذ نستخدم التابع <code>isEmpty()‎</code> للتحقق ما إذا كان هناك أيّ أخطاء في نتيجة التحقق من صحة البيانات، وإذا كان هناك أخطاء، فسنصيّر الاستمارة مرةً أخرى، ونمرّر كائن نوع الكتاب المُطهَّر ومصفوفة رسائل الخطأ (<code>errors.array()‎</code>).
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1713_18" style=""><span class="com">// طلب العملية بعد التحقق من صحة البيانات وتطهيرها</span><span class="pln">
asyncHandler</span><span class="pun">(</span><span class="kwd">async</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="com">// استخراج أخطاء التحقق من صحة البيانات من الطلب</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> errors </span><span class="pun">=</span><span class="pln"> validationResult</span><span class="pun">(</span><span class="pln">req</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"> genre </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Genre</span><span class="pun">({</span><span class="pln"> name</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">name </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">errors</span><span class="pun">.</span><span class="pln">isEmpty</span><span class="pun">())</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// توجد أخطاء، لذا اعرض الاستمارة مرة أخرى مع قيم مُطهَّرة أو رسائل خطأ</span><span class="pln">
    res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">"genre_form"</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="str">"Create Genre"</span><span class="pun">,</span><span class="pln">
      genre</span><span class="pun">:</span><span class="pln"> genre</span><span class="pun">,</span><span class="pln">
      errors</span><span class="pun">:</span><span class="pln"> errors</span><span class="pun">.</span><span class="pln">array</span><span class="pun">(),</span><span class="pln">
    </span><span class="pun">});</span><span class="pln">
    </span><span class="kwd">return</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="com">// البيانات الواردة من الاستمارة صالحة</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>
	إذا كانت بيانات اسم نوع الكتاب صالحة، فسنتحقق من وجود نوع كتاب <code>Genre</code> يحمل الاسم نفسه، لأننا لا نريد إنشاء نسخ مكررة. إذا كان الأمر كذلك، فسنعيد التوجيه إلى صفحة تفاصيل نوع الكتاب الموجود مسبقًا، وإن لم يكن الأمر كذلك، فسنحفظ نوع الكتاب <code>Genre</code> الجديد ونعيد التوجيه إلى صفحة تفاصيله. لاحظ أننا ننتظر باستخدام <a href="https://wiki.hsoub.com/JavaScript/await" rel="external"><code>await</code></a> نتيجة استعلام قاعدة البيانات باتباع النمط نفسه لمعالجات الوِجهة الأخرى.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1713_20" style=""><span class="com">// تحقق مما إذا كان نوع الكتاب الذي يحمل الاسم نفسه موجودًا مسبقًا</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> genreExists </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> </span><span class="typ">Genre</span><span class="pun">.</span><span class="pln">findOne</span><span class="pun">({</span><span class="pln"> name</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">name </span><span class="pun">}).</span><span class="pln">exec</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">genreExists</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="com">// نوع الكتاب موجود مسبقًا، لذا أعِد التوجيه إلى صفحة تفاصيله</span><span class="pln">
  res</span><span class="pun">.</span><span class="pln">redirect</span><span class="pun">(</span><span class="pln">genreExists</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"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">await</span><span class="pln"> genre</span><span class="pun">.</span><span class="pln">save</span><span class="pun">();</span><span class="pln">
  </span><span class="com">// حُفِظ نوع الكتاب الجديد، لذا أعِد التوجيه إلى صفحة تفاصيل نوع الكتاب</span><span class="pln">
  res</span><span class="pun">.</span><span class="pln">redirect</span><span class="pun">(</span><span class="pln">genre</span><span class="pun">.</span><span class="pln">url</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

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

<h2 id="view">
	العرض View
</h2>

<p>
	يُصيّر العرض نفسه في كل من متحكمات أو وِجهات <code>GET</code> و <code>POST</code> عندما ننشئ نوع كتاب <code>Genre</code> جديد (وسيُستخدَم لاحقًا عند تحديث نوع كتاب <code>Genre</code>) ، ولكن في حالة <code>GET</code> تكون الاستمارة فارغة ونمرر متغير العنوان فقط، بينما في حالة <code>POST</code> قد يكون المستخدم قد أدخل بيانات غير صالحة سابقًا ، لذا نمرّر في المتغير <code>genre</code> نسخة مُطهَّرة من البيانات المُدخَلة ونمرّر مصفوفة من رسائل الخطأ في المتغير <code>errors</code>. توضح الشيفرة التالية شيفرة المتحكم لتصيير القالب في كلتا الحالتين:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1713_22" style=""><span class="com">// ‫تصيير وِجهة GET</span><span class="pln">
res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">"genre_form"</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="str">"Create Genre"</span><span class="pln"> </span><span class="pun">});</span><span class="pln">

</span><span class="com">// تصيير‫ وِجهة POST</span><span class="pln">
res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">"genre_form"</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="str">"Create Genre"</span><span class="pun">,</span><span class="pln">
  genre</span><span class="pun">,</span><span class="pln">
  errors</span><span class="pun">:</span><span class="pln"> errors</span><span class="pun">.</span><span class="pln">array</span><span class="pun">(),</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	أنشئ العرض "‎/views/genre_form.pug" وضع فيه النص التالي:
</p>

<pre class="ipsCode">extends layout

block content

  h1 #{title}

  form(method='POST' action='')
    div.form-group
      label(for='name') Genre:
      input#name.form-control(type='text', placeholder='Fantasy, Poetry etc.' name='name' required='true' value=(undefined===genre ? '' : genre.name) )
    button.btn.btn-primary(type='submit') Submit

  if errors
    ul
      for error in errors
        li!= error.msg
</pre>

<pre class="ipsCode"> </pre>

<p>
	يُعَد أغلب هذا القالب مألوفًا من <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-template-%D9%81%D9%8A-express-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%A7%D9%84%D9%82%D8%A7%D9%84%D8%A8-%D8%A7%D9%84%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A-%D9%84%D9%85%D9%88%D9%82%D8%B9-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D9%85%D8%AB%D8%A7%D9%84%D9%8B%D8%A7-r2209/" rel="">مقال سابق</a>، إذ سنوسّع أولًا القالب الأساسي layout.pug ونعدّل الكتلة <code>block</code> التي اسمها 'content'، ثم لدينا عنوان مع المتغير <code>title</code> الذي مرّرناه من المتحكم عبر التابع <code>render()‎</code>. لدينا بعد ذلك شيفرة Pug الخاصة باستمارة HTML التي تستخدم <code>method="POST"‎</code> لإرسال البيانات إلى الخادم، وستُرسَل البيانات إلى <a href="https://academy.hsoub.com/programming/general/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%B9%D9%86%D9%88%D8%A7%D9%86-url-%D9%88%D8%A3%D9%86%D9%88%D8%A7%D8%B9%D9%87-r1435/" rel="">عنوان URL</a> نفسه الخاص بالصفحة لأن الإجراء <code>action</code> هو سلسلة نصية فارغة.
</p>

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

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

<p>
	<strong>ملاحظة</strong>: عرضنا طريقة واحدة فقط لتصيير الأخطاء، ويمكنك أيضًا الحصول على أسماء الحقول المتأثرة من متغير الخطأ، واستخدامها للتحكم في مكان عرض رسائل الخطأ، وما إذا أدرتَ تطبيق شيفرة <a href="https://academy.hsoub.com/programming/css/" rel="">CSS</a> مخصصة وغير ذلك.
</p>

<h2 id="">
	كيف تبدو استمارة نوع الكتاب؟
</h2>

<p>
	شغّل التطبيق، وافتح متصفحك على العنوان "http://localhost:3000/‎"، ثم حدّد رابط إنشاء نوع كتاب جديد Create new genre. إذا جرى إعداد كل شيء بصورة صحيحة، فيجب أن يبدو موقعك كما يلي، ويجب حفظ القيمة بعد إدخالها وستُنقَل إلى صفحة تفاصيل نوع الكتاب:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="141595" href="https://academy.hsoub.com/uploads/monthly_2024_01/01_locallibary_express_genre_create_empty.png.23be3e93bfb19585eb826f3a0c71cb5e.png" rel=""><img alt="01 locallibary express genre create empty" class="ipsImage ipsImage_thumbnailed" data-fileid="141595" data-unique="rqgf06t8n" src="https://academy.hsoub.com/uploads/monthly_2024_01/01_locallibary_express_genre_create_empty.thumb.png.9bd2cf510105a29383a4d376909ae73f.png"> </a>
</p>

<p>
	الخطأ الوحيد الذي نتحقق منه من طرف الخادم هو أن حقل نوع الكتاب يجب ألّا يكون فارغًا. توضح لقطة الشاشة التالية كيف ستبدو قائمة الأخطاء إن لم تقدّم نوع كتاب (المحدَّد باللون الأحمر):
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="141596" href="https://academy.hsoub.com/uploads/monthly_2024_01/02_locallibary_express_genre_create_error.png.a5900db5a2f3d766279fa73bbfc27e10.png" rel=""><img alt="02 locallibary express genre create error" class="ipsImage ipsImage_thumbnailed" data-fileid="141596" data-unique="j7jj9zbav" src="https://academy.hsoub.com/uploads/monthly_2024_01/02_locallibary_express_genre_create_error.png.a5900db5a2f3d766279fa73bbfc27e10.png"> </a>
</p>

<p>
	<strong>ملاحظة</strong>: تستخدم عملية التحقق من صحة البيانات التابع <code><a href="https://wiki.hsoub.com/JavaScript/String/trim" rel="external">trim()</a>‎</code> لضمان عدم قبول المسافة البيضاء بوصفها اسمًا لنوع الكتاب، ويمكننا أيضًا التحقق من أن الحقل ليس فارغًا من طرف العميل من خلال إضافة القيمة <code>required='true'‎</code> إلى تعريف الحقل في الاستمارة.
</p>

<pre class="ipsCode">input#name.form-control(type='text', placeholder='Fantasy, Poetry etc.' name='name' value=(undefined===genre ? '' : genre.name), required='true' )
</pre>

<p>
	ترجمة -وبتصرُّف- للمقال <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Create_genre_form" rel="external nofollow">Create genre form</a>.
</p>

<h2 id="-1">
	اقرأ المزيد
</h2>

<ul>
	<li>
		المقال السابق <a href="%D8%B1%D8%A7%D8%A8%D8%B7" rel="">إنشاء موقع مكتبة محلية باستخدام Express</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-forms-%D9%81%D9%8A-%D9%85%D8%AA%D8%B5%D9%81%D8%AD-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D9%88%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9%D9%87%D8%A7-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1193/" rel="">الاستمارات (forms) في متصفح الويب وكيفية التعامل معها في جافاسكربت</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%85%D9%86-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-forms-r2120/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/%D8%A5%D8%B1%D8%B3%D8%A7%D9%84-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%88%D8%A7%D8%B3%D8%AA%D9%84%D8%A7%D9%85%D9%87%D8%A7-%D8%B9%D8%A8%D8%B1-%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1294/" rel="">إرسال البيانات واستلامها عبر الشبكة في جافاسكريبت</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2223</guid><pubDate>Mon, 15 Jan 2024 12:09:01 +0000</pubDate></item><item><title>&#x625;&#x646;&#x634;&#x627;&#x621; &#x645;&#x648;&#x642;&#x639; &#x645;&#x643;&#x62A;&#x628;&#x629; &#x645;&#x62D;&#x644;&#x64A;&#x629; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; Express: &#x625;&#x646;&#x634;&#x627;&#x621; &#x635;&#x641;&#x62D;&#x627;&#x62A; &#x627;&#x644;&#x645;&#x648;&#x642;&#x639;</title><link>https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-express-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%B5%D9%81%D8%AD%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D9%88%D9%82%D8%B9-r2219/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_01/------Express.png.9f87205dc1fce65904c465e227981816.png" /></p>
<p>
	سنتعرف في هذا المقال على كيفية إنشاء الصفحة الرئيسية لموقع المكتبة المحلية LocalLibrary وصفحات قوائم الكتب والمؤلفين ونسخ الكتب وأنواعها، وسنوضح كيفية تنسيق التاريخ باستخدام المكتبة Luxon، كما سنتعرف على كيفية إنشاء صفحات تفاصيل الكتب وأنواعها ونسخها وتفاصيل المؤلفين.
</p>

<h2 id="">
	صفحة الموقع الرئيسية
</h2>

<p>
	ستكون الصفحة الأولى التي سننشئها هي الصفحة الرئيسية لموقع الويب، والتي يمكن الوصول إليها إما من جذر الموقع <code>/</code> أو من الدليل <code>catalog‎/</code>، مما سيؤدي إلى عرض بعض النصوص الثابتة التي تصف الموقع، مع الأعداد المحسوبة ديناميكيًا لأنواع السجلات المختلفة في قاعدة البيانات.
</p>

<p>
	أنشأنا مسبقًا وجهةً Route للصفحة الرئيسية، ولإكمال هذه الصفحة، يجب تحديث دالة المتحكم لجلب أعداد السجلات من قاعدة البيانات وإنشاء عرض (قالب) يمكننا استخدامه لعرض الصفحة.
</p>

<p>
	<strong>ملاحظة</strong>: سنستخدم مكتبة Mongoose للحصول على معلومات قاعدة البيانات، لذا لا بد من إعادة قراءة قسم البحث عن السجلات من <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%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-%D9%85%D8%B9-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-mongoose-r2171/" rel="">مقال استخدام قاعدة البيانات باستخدام مكتبة Mongoose</a>.
</p>

<h3 id="route">
	الوجهة Route
</h3>

<p>
	أنشأنا وِجهات صفحة الفهرس في مقال <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%D8%A7%D9%84%D9%88%D8%AC%D9%87%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D9%85%D8%AA%D8%AD%D9%83%D9%85%D8%A7%D8%AA-r2184/" rel="">الوجهات والمتحكمات</a>، إذ عرّفنا جميع دوال الوجهات في الملف "‎/routes/catalog.js":
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4640_14" style=""><span class="com">// الحصول على صفحة الدليل الرئيسية</span><span class="pln">
router</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"> book_controller</span><span class="pun">.</span><span class="pln">index</span><span class="pun">);</span><span class="pln"> </span><span class="com">//يُر‫بَط مع /catalog/ لأننا نستورد الوجهة مع البادئة ‎ /catalog</span></pre>

<p>
	تملك دالة متحكم الكتاب <code>index</code> المُمرَّرة بوصفها معاملًا (<code>book_controller.index</code>) تقديم بديل placeholder implementation مُعرَّف في الملف ‎/controllers/bookController.js:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4640_16" style=""><span class="pln">exports</span><span class="pun">.</span><span class="pln">index </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"NOT IMPLEMENTED: Site Home Page"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	تُعَد هذه الدالة هي دالة المتحكم التي نوسّعها للحصول على معلومات من نماذجنا ثم عرضها باستخدام عرض (قالب).
</p>

<h3 id="controller">
	المتحكم Controller
</h3>

<p>
	تحتاج دالة المتحكم <code>index</code> إلى جلب معلومات حول عدد سجلات الكتب <code>Book</code> ونسخ الكتب <code>BookInstance</code> (جميعها) ونسخ الكتب <code>BookInstance</code> (المتاحة) والمؤلفين <code>Author</code> وأنواع الكتب <code>Genre</code> الموجودة في قاعدة البيانات، وعرض هذه البيانات في قالب لإنشاء صفحة <a href="https://academy.hsoub.com/programming/html/" rel="">HTML</a>، ثم إعادتها في <a href="https://academy.hsoub.com/programming/general/%D8%B1%D9%85%D9%88%D8%B2-%D8%A7%D9%84%D8%A5%D8%AC%D8%A7%D8%A8%D8%A9-%D9%81%D9%8A-http-r75/" rel="">استجابة HTTP</a>.
</p>

<p>
	افتح الملف ‎/controllers/bookController.js، إذ سترى الدالة <code>index()‎</code> المستورَدة بالقرب من أعلى الملف.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4640_18" style=""><span class="kwd">const</span><span class="pln"> </span><span class="typ">Book</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"../models/book"</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> asyncHandler </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"express-async-handler"</span><span class="pun">);</span><span class="pln">

exports</span><span class="pun">.</span><span class="pln">index </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"NOT IMPLEMENTED: Site Home Page"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	ضع جزء الشيفرة التالي مكان الشيفرة السابقة، فأول شيء تفعله هذه الشيفرة هو استيراد (أو طلب <code>require()‎</code>) جميع النماذج Models، إذ نحتاج إلى ذلك لأننا سنستخدمها للحصول على عدد المستندات. تطلب الشيفرة أيضًا "express-async-handler" الذي يوفّر مغلِّفًا Wrapper <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%D8%A7%D9%84%D9%88%D8%AC%D9%87%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D9%85%D8%AA%D8%AD%D9%83%D9%85%D8%A7%D8%AA-r2184/" rel="">لالتقاط الاستثناءات التي تُرمَى في دوال معالج الوِجهة</a>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4640_20" style=""><span class="kwd">const</span><span class="pln"> </span><span class="typ">Book</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"../models/book"</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">Author</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"../models/author"</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">Genre</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"../models/genre"</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">BookInstance</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"../models/bookinstance"</span><span class="pun">);</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> asyncHandler </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"express-async-handler"</span><span class="pun">);</span><span class="pln">

exports</span><span class="pun">.</span><span class="pln">index </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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="com">// الحصول على تفاصيل عدد الكتب ونسخها والمؤلفين وأنواع الكتب (على ا‫لتوازي)</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    numBooks</span><span class="pun">,</span><span class="pln">
    numBookInstances</span><span class="pun">,</span><span class="pln">
    numAvailableBookInstances</span><span class="pun">,</span><span class="pln">
    numAuthors</span><span class="pun">,</span><span class="pln">
    numGenres</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">await</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">
    </span><span class="typ">Book</span><span class="pun">.</span><span class="pln">countDocuments</span><span class="pun">({}).</span><span class="pln">exec</span><span class="pun">(),</span><span class="pln">
    </span><span class="typ">BookInstance</span><span class="pun">.</span><span class="pln">countDocuments</span><span class="pun">({}).</span><span class="pln">exec</span><span class="pun">(),</span><span class="pln">
    </span><span class="typ">BookInstance</span><span class="pun">.</span><span class="pln">countDocuments</span><span class="pun">({</span><span class="pln"> status</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Available"</span><span class="pln"> </span><span class="pun">}).</span><span class="pln">exec</span><span class="pun">(),</span><span class="pln">
    </span><span class="typ">Author</span><span class="pun">.</span><span class="pln">countDocuments</span><span class="pun">({}).</span><span class="pln">exec</span><span class="pun">(),</span><span class="pln">
    </span><span class="typ">Genre</span><span class="pun">.</span><span class="pln">countDocuments</span><span class="pun">({}).</span><span class="pln">exec</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">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">
    title</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Local Library Home"</span><span class="pun">,</span><span class="pln">
    book_count</span><span class="pun">:</span><span class="pln"> numBooks</span><span class="pun">,</span><span class="pln">
    book_instance_count</span><span class="pun">:</span><span class="pln"> numBookInstances</span><span class="pun">,</span><span class="pln">
    book_instance_available_count</span><span class="pun">:</span><span class="pln"> numAvailableBookInstances</span><span class="pun">,</span><span class="pln">
    author_count</span><span class="pun">:</span><span class="pln"> numAuthors</span><span class="pun">,</span><span class="pln">
    genre_count</span><span class="pun">:</span><span class="pln"> numGenres</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	نستخدم التابع <code>countDocuments()‎</code> للحصول على عدد نسخ كل نموذج، ويُستدعَى هذا التابع في نموذج مع مجموعة اختيارية من الشروط لمطابقتها وإعادة كائن استعلام <code>Query</code>. يمكن تنفيذ الاستعلام من خلال استدعاء التابع <code>exec()‎</code>، والذي يعيد وعدًا <code>Promise</code> إما يجري الوفاء به مع نتيجة أو يُرفض إذا كان هناك خطأ في قاعدة البيانات.
</p>

<p>
	تُعَد الاستعلامات الخاصة بأعداد المستندات مستقلة عن بعضها بعضًا، لذا نستخدم التابع <code>Promise.all()‎</code> لتنفيذها على التوازي، ويعيد هذا التابع وعدًا جديدًا ننتظره باستخدام <code>await</code> حتى ينتهي، إذ يتوقف التنفيذ مؤقتًا في هذه الدالة عند <code>await</code>. يجري الوفاء بالوعد الذي يعيده <code>all()‎</code> عند اكتمال جميع الاستعلامات، ويستمر تنفيذ دالة معالج الوجهة، ويملأ هذا الوعد المصفوفةَ بنتائج استعلامات قاعدة البيانات.
</p>

<p>
	نستدعي بعد ذلك التابع <code>res.render()‎</code>، ونحدّد عرضًا (قالبًا) بالاسم "index" وكائنات تربط نتائج استعلامات قاعدة البيانات مع قالب العرض، إذ تتوفر البيانات على شكل أزواج مفتاح-قيمة key-value، ويمكن الوصول إليها في القالب باستخدام المفتاح.
</p>

<p>
	لاحظ أن الشيفرة البرمجية بسيطة جدًا لأنه يمكننا افتراض نجاح استعلامات قاعدة البيانات، وإذا فشلت أيٌّ من عمليات قاعدة البيانات، فسيكتشف <code>asyncHandler()‎</code> الاستثناء المُرمَى ويمرّره إلى معالج البرمجية الوسيطة التالية <code>next</code> في السلسلة.
</p>

<h3 id="view">
	العرض View
</h3>

<p>
	افتح العرض "‎/views/index.pug" وضع فيه المحتوى التالي بدلًا من محتواه الموجود مسبقًا:
</p>

<pre class="ipsCode">extends layout

block content
  h1= title
  p Welcome to #[em LocalLibrary], a very basic Express website developed as a tutorial example on the Mozilla Developer Network.

  h1 Dynamic content

  p The library has the following record counts:

  ul
    li #[strong Books:] !{book_count}
    li #[strong Copies:] !{book_instance_count}
    li #[strong Copies available:] !{book_instance_available_count}
    li #[strong Authors:] !{author_count}
    li #[strong Genres:] !{genre_count}
</pre>

<p>
	يُعَد العرض واضحًا، إذ نوسّع القالب الأساسي layout.pug من خلال تعديل الكتلة <code>block</code> التي اسمها 'content'. سيكون العنوان <code>h1</code> الأول هو النص المُهرَّب للمتغير <code>title</code> المُمرَّر إلى التابع <code>render()‎</code>، ولاحظ استخدام '<code>h1=‎</code>' بحيث يجري التعامل مع النص الذي يليه بوصفه تعبير <a href="https://academy.hsoub.com/programming/javascript/" rel="">جافا سكريبت</a>، ثم نضمّن فقرةً تعرّف بالمكتبة المحلية LocalLibrary.
</p>

<p>
	نسرد عدد النسخ من كل نموذج تحت عنوان المحتوى الديناميكي Dynamic content. لاحظ أن قيم القالب للبيانات هي المفاتيح المُحدَّدة عند استدعاء التابع <code>render()‎</code> في دالة معالج الوِجهة.
</p>

<p>
	<strong>ملاحظة</strong>: لم نهرّب قيم العد، إذ استخدمنا صيغة <code>{}!</code>، لأن قيم العد محسوبة، ولكن إذا وفّر المستخدمون النهائيون المعلومات، فسنهرّب المتغير لعرضه.
</p>

<h3 id="-1">
	كيف تبدو الصفحة الرئيسية؟
</h3>

<p>
	أنشأنا حتى الآن كل ما هو مطلوب لعرض صفحة الفهرس، لذا شغّل التطبيق وافتح متصفحك على العنوان "http://localhost:3000/‎". إذا جرى إعداد كل شيء بصورة صحيحة، فيجب أن يبدو موقعك كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="141446" href="https://academy.hsoub.com/uploads/monthly_2024_01/01_locallibary_express_home.png.9188c8c2615544c03a366769d2c4be7f.png" rel=""><img alt="01 locallibary express home" class="ipsImage ipsImage_thumbnailed" data-fileid="141446" data-unique="epocp2fvu" src="https://academy.hsoub.com/uploads/monthly_2024_01/01_locallibary_express_home.thumb.png.11e708ebe48733496840d621595b432f.png"> </a>
</p>

<p>
	<strong>ملاحظة</strong>: لن تتمكّن من استخدام روابط الشريط الجانبي حاليًا لأن <a href="https://academy.hsoub.com/programming/general/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%B9%D9%86%D9%88%D8%A7%D9%86-url-%D9%88%D8%A3%D9%86%D9%88%D8%A7%D8%B9%D9%87-r1435/" rel="">عناوين URL</a> والعروض والقوالب الخاصة بهذه الصفحات غير مُعرَّفة بعد، وإذا حاولت الوصول إليها، فستحصل على أخطاء مثل "NOT IMPLEMENTED: Book list" اعتمادًا على الرابط الذي تنقر عليه، إذ تُحدَّد هذه السلسلة النصية الحرفية -التي سنضع مكانها البيانات المناسبة- في المتحكمات المختلفة التي تكون موجودة ضمن الملف "controllers".
</p>

<h2 id="-2">
	صفحة قائمة الكتب
</h2>

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

<h3 id="-3">
	المتحكم
</h3>

<p>
	يجب أن تحصل دالة متحكم قائمة الكتب على قائمة بجميع كائنات الكتاب <code>Book</code> في قاعدة البيانات، ثم تفرزها وتمرّرها إلى القالب لعرضها.
</p>

<p>
	افتح الملف "‎/controllers/bookController.js"، وابحث عن تابع المتحكم <code>book_list()‎</code> المُصدَّر وضع مكانه الشيفرة البرمجية التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4640_22" style=""><span class="com">// عرض قائمة جميع الكتب</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">book_list </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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="kwd">const</span><span class="pln"> allBooks </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> </span><span class="typ">Book</span><span class="pun">.</span><span class="pln">find</span><span class="pun">({},</span><span class="pln"> </span><span class="str">"title author"</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">sort</span><span class="pun">({</span><span class="pln"> title</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="pun">.</span><span class="pln">populate</span><span class="pun">(</span><span class="str">"author"</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">exec</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">"book_list"</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="str">"Book List"</span><span class="pun">,</span><span class="pln"> book_list</span><span class="pun">:</span><span class="pln"> allBooks </span><span class="pun">});</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	يستدعي معالج الوجهة Route Handler الدالة <code>find()‎</code> للنموذج <code>Book</code>، ويختار إعادة العنوان <code>title</code> والمؤلف <code>author</code> فقط لأننا لسنا بحاجة إلى الحقول الأخرى (سيعيد أيضًا الحقل <code>‎_id</code> والحقول الافتراضية)، ويفرز النتائج حسب العنوان أبجديًا باستخدام التابع <code>sort()‎</code>. نستدعي أيضًا التابع <code>populate()‎</code> للنموذج <code>Book</code> مع تحديد الحقل <code>author</code>، مما سيؤدي إلى استبدال معرّف مؤلف الكتاب المُخزَّن بتفاصيل المؤلف الكاملة، ثم يُستدعَى التابع <code>exec()‎</code> في نهاية السلسلة التعاقبية daisy-chained لتنفيذ الاستعلام وإعادة الوعد.
</p>

<p>
	يستخدم معالج الوِجهة <code>await</code> لانتظار الوعد، مما يؤدي إلى إيقاف التنفيذ حتى استقراره. إذا جرى الوفاء بالوعد، فستُحفَظ نتائج الاستعلام في المتغير <code>allBooks</code> ويستمر المعالج في التنفيذ.
</p>

<p>
	يستدعي الجزء الأخير من معالج الوِجهة التابع <code>render()‎</code>، ويحدد القالب book_list (.pug)‎ ويمرر قيم <code>title</code> و <code>book_list</code> إلى القالب.
</p>

<h3 id="view-1">
	العرض View
</h3>

<p>
	أنشئ العرض "‎/views/book_list.pug" وضع فيه المحتوى التالي:
</p>

<pre class="ipsCode">extends layout

block content
  h1= title

  ul
    each book in book_list
      li
        a(href=book.url) #{book.title}
        |  (#{book.author.name})

    else
      li There are no books.
</pre>

<p>
	يوسّع العرض القالبَ الأساسي layout.pug ويعدّل الكتلة <code>block</code> التي اسمها 'content'، ويعرض العنوان <code>title</code> الذي مرّرناه من المتحكم (باستخدام التابع <code>render()‎</code>) ويتكرر عبر المتغير <code>book_list</code> باستخدام صيغة <code>each</code>-<code>in</code>-<code>else</code>.
</p>

<p>
	يُنشَأ عنصر قائمة لكل كتاب، إذ تعرض هذه القائمة عنوان الكتاب كرابط إلى صفحة تفاصيل الكتاب متبوعًا باسم المؤلف، وإذا لم يكن هناك كتب في <code>book_list</code>، فستُنفَّذ تعليمة <code>else</code>، ويعرض نص عدم وجود كتب "There are no books".
</p>

<p>
	<strong>ملاحظة</strong>: نستخدم <code>book.url</code> لتوفير رابط إلى سجل تفاصيل كل كتاب (قدّمنا هذه الوجهة، ولكن لم نقدّم الصفحة الخاصة بها بعد)، وهي خاصية افتراضية لنموذج الكتاب <code>Book</code> الذي يستخدم الحقل <code>‎_id</code> الخاص بنسخة النموذج لإنشاء مسار عنوان URL فريد.
</p>

<p>
	من المهم تعريف كل كتاب على سطرين، باستخدام الشريط العمودي <code>|</code> في السطر الثاني، ويُعَد هذا الأسلوب ضروريًا لأنه إذا كان اسم المؤلف موجودًا في السطر السابق، فسيكون جزءًا من الرابط التشعبي.
</p>

<h3 id="-4">
	كيف تبدو صفحة قائمة الكتب؟
</h3>

<p>
	شغّل التطبيق (راجع قسم اختبار الوجهات من مقال <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%D8%A7%D9%84%D9%88%D8%AC%D9%87%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D9%85%D8%AA%D8%AD%D9%83%D9%85%D8%A7%D8%AA-r2184/" rel="">الوجهات والمتحكمات</a> للاطلاع على الأوامر ذات الصلة) وافتح متصفحك على العنوان "http://localhost:3000/‎"، ثم حدّد رابط "جميع الكتب All books". إذا جرى إعداد كل شيء بصورة صحيحة، فيجب أن يبدو موقعك كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="141447" href="https://academy.hsoub.com/uploads/monthly_2024_01/02_new_book_list.png.46ad327db9f1a50c5f885fa71dc60fe4.png" rel=""><img alt="02 new book list" class="ipsImage ipsImage_thumbnailed" data-fileid="141447" data-unique="wwwp5i2gk" src="https://academy.hsoub.com/uploads/monthly_2024_01/02_new_book_list.png.46ad327db9f1a50c5f885fa71dc60fe4.png"> </a>
</p>

<h2 id="bookinstance">
	صفحة قائمة نسخ الكتب BookInstance
</h2>

<p>
	سنقدّم الآن قائمة بجميع نسخ الكتب <code>BookInstance</code> في المكتبة، إذ يجب أن تتضمن الصفحة عنوان الكتاب <code>Book</code> المرتبط بكل نسخة كتاب <code>BookInstance</code> (مرتبطة برابط إلى صفحة تفاصيلها) مع المعلومات الأخرى في نموذج <code>BookInstance</code>، بما في ذلك الحالة والناشر والمعرِّف الفريد لكل نسخة، ويجب ربط نص المعرّف الفريد برابط إلى صفحة تفاصيل نسخة الكتاب <code>BookInstance</code>.
</p>

<h3 id="-5">
	المتحكم
</h3>

<p>
	يجب أن يحصل متحكم قائمة <code>BookInstance</code> على قائمة بجميع نسخ الكتاب، ويجب ملء معلومات الكتاب المرتبطة بها، ثم تمرير القائمة إلى القالب لعرضها.
</p>

<p>
	افتح الملف "‎/controllers/bookinstanceController.js"، وابحث عن تابع المتحكم <code>bookinstance_list()‎</code> المُصدَّر وضع مكانه الشيفرة البرمجية التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4640_24" style=""><span class="com">// عرض قائمة جميع نسخ الكتب</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">bookinstance_list </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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="kwd">const</span><span class="pln"> allBookInstances </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> </span><span class="typ">BookInstance</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">"book"</span><span class="pun">).</span><span class="pln">exec</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">"bookinstance_list"</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="str">"Book Instance List"</span><span class="pun">,</span><span class="pln">
    bookinstance_list</span><span class="pun">:</span><span class="pln"> allBookInstances</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	يستدعي معالج الوجهة الدالةَ <code>find()‎</code> للنموذج <code>BookInstance</code>، ثم يتبعه في سلسلة تعاقبية استدعاءٌ للتابع <code>populate()‎</code> مع حقل الكتاب <code>book</code>، والذي سيضع مستند كتاب <code>Book</code> كامل مكان معرّف الكتاب المُخزَّن لكل <code>BookInstance</code>، ثم يُستدعَى التابع <code>exec()‎</code> في نهاية السلسلة التعاقبية لتنفيذ الاستعلام وإعادة الوعد.
</p>

<p>
	يستخدم معالج الوِجهة <code>await</code> لانتظار الوعد، مما يؤدي إلى إيقاف التنفيذ حتى استقراره. إذا جرى الوفاء بالوعد، فستُحفَظ نتائج الاستعلام في المتغير <code>allBookInstances</code> ويستمر المعالج في التنفيذ.
</p>

<p>
	يستدعي الجزء الأخير من الشيفرة البرمجية التابع <code>render()‎</code>، ويحدد القالب bookinstance_list (.pug)‎ ويمرر قيم <code>title</code> و <code>bookinstance_list</code> إلى القالب.
</p>

<h3 id="view-2">
	العرض View
</h3>

<p>
	أنشئ العرض "‎/views/bookinstance_list.pug" وضع فيه المحتوى التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4640_26" style=""><span class="pln">extends layout

block content
  h1</span><span class="pun">=</span><span class="pln"> title

  ul
    each val in bookinstance_list
      li
        a</span><span class="pun">(</span><span class="pln">href</span><span class="pun">=</span><span class="pln">val</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">val</span><span class="pun">.</span><span class="pln">book</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">val</span><span class="pun">.</span><span class="pln">imprint</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"> val</span><span class="pun">.</span><span class="pln">status</span><span class="pun">==</span><span class="str">'Available'</span><span class="pln">
          span</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">success </span><span class="pun">#{</span><span class="pln">val</span><span class="pun">.</span><span class="pln">status</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"> val</span><span class="pun">.</span><span class="pln">status</span><span class="pun">==</span><span class="str">'Maintenance'</span><span class="pln">
          span</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">danger </span><span class="pun">#{</span><span class="pln">val</span><span class="pun">.</span><span class="pln">status</span><span class="pun">}</span><span class="pln">
        </span><span class="kwd">else</span><span class="pln">
          span</span><span class="pun">.</span><span class="pln">text</span><span class="pun">-</span><span class="pln">warning </span><span class="pun">#{</span><span class="pln">val</span><span class="pun">.</span><span class="pln">status</span><span class="pun">}</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> val</span><span class="pun">.</span><span class="pln">status</span><span class="pun">!=</span><span class="str">'Available'</span><span class="pln">
          span  </span><span class="pun">(</span><span class="typ">Due</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">due_back</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">
      li </span><span class="typ">There</span><span class="pln"> are no book copies in </span><span class="kwd">this</span><span class="pln"> library</span><span class="pun">.</span></pre>

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

<p>
	قدّمنا ميزة جديدة، إذ يمكننا استخدام الصيغة النقطية بعد الوسم لإسناد صنف، لذلك سيُصرَّف <code>span.text-success</code> إلى <code>&lt;span class="text-success"‎&gt;</code>، ويمكن كتابته أيضًا في Pug بالشكل التالي:
</p>

<pre class="ipsCode">span(class="text-success")‎
</pre>

<h3 id="-6">
	كيف تبدو صفحة قائمة نسخ الكتب؟
</h3>

<p>
	شغّل التطبيق، وافتح متصفحك على العنوان "http://localhost:3000/‎" ثم حدد رابط جميع نسخ الكتاب All book-instances. إذا جرى إعداد كل شيء بصورة صحيحة، فيجب أن يبدو موقعك كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="141448" href="https://academy.hsoub.com/uploads/monthly_2024_01/03_locallibary_express_bookinstance_list.png.d104caaa3d3cf97abb83881c7177293b.png" rel=""><img alt="03 locallibary express bookinstance list" class="ipsImage ipsImage_thumbnailed" data-fileid="141448" data-unique="s8ezas2gu" src="https://academy.hsoub.com/uploads/monthly_2024_01/03_locallibary_express_bookinstance_list.thumb.png.8b092e029fa9441b41ec1cf091eabe89.png"> </a>
</p>

<h2 id="luxon">
	تنسيق التاريخ باستخدام مكتبة Luxon
</h2>

<p>
	لا يبدو العرض الافتراضي للتواريخ في نماذجنا جميلًا:
</p>

<pre class="ipsCode">Mon Apr 10 2020 15:49:58 GMT+1100 (AUS Eastern Daylight Time)
</pre>

<p>
	لذا سنوضح في هذا القسم كيفية تحديث صفحة قائمة نسخ الكتاب BookInstance List من القسم السابق من أجل عرض حقل <code>due_date</code> بتنسيق أكثر ملاءمة مثل: Apr 10th, 2023.
</p>

<p>
	الطريقة التي سنستخدمها هي إنشاء خاصية افتراضية في نموذج <code>BookInstance</code>، والتي تعيد التاريخ المُنسَّق. سنطبّق التنسيق الفعلي باستخدام مكتبة Luxon، وهي مكتبة قوية وحديثة وسهلة الاستخدام لتحليل التواريخ والتحقق من صحتها ومعالجتها وتنسيقها وجعلها تواريخًا محلية.
</p>

<p>
	<strong>ملاحظة</strong>: يمكن استخدام مكتبة Luxon لتنسيق السلاسل النصية مباشرةً في قوالب Pug، أو يمكننا تنسيق السلسلة النصية في عدد من الأماكن الأخرى. يتيح استخدام خاصية افتراضية الحصول على التاريخ المُنسَّق باستخدام الطريقة نفسها للحصول على تاريخ الاسترجاع <code>due_date</code> حاليًا.
</p>

<h3 id="luxon-1">
	تثبيت مكتبة Luxon
</h3>

<p>
	أدخل الأمر التالي في جذر المشروع:
</p>

<pre class="ipsCode">npm install luxon
</pre>

<h3 id="-7">
	إنشاء الخاصية الافتراضية
</h3>

<p>
	افتح الملف "‎./models/bookinstance.js"، ثم استورد مكتبة Luxon في أعلى هذا الملف كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4640_28" style=""><span class="kwd">const</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="typ">DateTime</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">"luxon"</span><span class="pun">);</span></pre>

<p>
	ضِف الخاصية الافتراضية <code>due_back_formatted</code> بعد خاصية URL مباشرةً.
</p>

<pre class="ipsCode">BookInstanceSchema.virtual("due_back_formatted").get(function () {
  return DateTime.fromJSDate(this.due_back).toLocaleString(DateTime.DATE_MED);
});
</pre>

<p>
	<strong>ملاحظة</strong>: يمكن لمكتبة Luxon استيراد السلاسل النصية بتنسيقات متعددة وتصديرها إلى كلٍّ من التنسيقات المُعرَّفة مسبقًا والتنسيقات الحرة free-form formats، إذ نستخدم في حالتنا التابع <code>fromJSDate()‎</code> لاستيراد سلسلة تاريخ جافا سكريبت و <code>toLocaleString()‎</code> لإخراج التاريخ بتنسيق DATE_MED باللغة الإنجليزية: Apr 10th, 2023. اطلع على <a href="https://github.com/moment/luxon/blob/master/docs/formatting.md#formatting" rel="external nofollow">توثيق مكتبة Luxon الخاص بالتنسيق</a> للحصول على معلومات حول التنسيقات الأخرى وجعل سلسلة التاريخ عالمية.
</p>

<h3 id="-8">
	تحديث العرض
</h3>

<p>
	افتح العرض "‎/views/bookinstance_list.pug" وضع <code>due_back_formatted</code> بدلًا من <code>due_back</code>.
</p>

<pre class="ipsCode">        if val.status != 'Available'
        //span  (Due: #{val.due_back} )
        span  (Due: #{val.due_back_formatted} )
</pre>

<p>
	إذا انتقلت إلى جميع نسخ الكتاب All book-instances في الشريط الجانبي، فيجب أن ترى أن جميع تواريخ الاسترجاع أجمل بكثير.
</p>

<h2 id="-9">
	صفحة قائمة المؤلفين
</h2>

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

<h3 id="-10">
	المتحكم
</h3>

<p>
	يجب أن تحصل دالة متحكم قائمة المؤلفين على قائمة بجميع نسخ المؤلف <code>Author</code>، ثم تمرّرها إلى القالب لعرضها. افتح الملف "‎/controllers/authorController.js"، ثم ابحث عن تابع المتحكم المُصدَّر <code>author_list()‎</code> بالقرب من أعلى الملف وضع مكانه الشيفرة البرمجية التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4640_31" style=""><span class="com">// عرض قائمة جميع المؤلفين</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">author_list </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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="kwd">const</span><span class="pln"> allAuthors </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> </span><span class="typ">Author</span><span class="pun">.</span><span class="pln">find</span><span class="pun">().</span><span class="pln">sort</span><span class="pun">({</span><span class="pln"> family_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">exec</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">"author_list"</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="str">"Author List"</span><span class="pun">,</span><span class="pln">
    author_list</span><span class="pun">:</span><span class="pln"> allAuthors</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	تتبع دالة متحكم الوجهة النمط نفسه المُتبَع في صفحات القوائم الأخرى، إذ تعرّف استعلامًا لنموذج المؤلف <code>Author</code> باستخدام دالة <code>find()‎</code> للحصول على جميع المؤلفين والتابع <code>sort()‎</code> لفرزهم حسب <code>family_name</code> بترتيب أبجدي، ثم يُستدعَى التابع <code>exec()‎</code> في نهاية السلسلة التعاقبية لتنفيذ الاستعلام وإعادة الوعد الذي يمكن أن تنتظره الدالة باستخدام <code>await</code>.
</p>

<p>
	يعرض معالج الوجهة قالب author_list(.pug)‎ بعد الوفاء بالوعد، ويمرر عنوان <code>title</code> الصفحة وقائمة المؤلفين (<code>allAuthors</code>) باستخدام مفاتيح القالب.
</p>

<h3 id="-11">
	العرض
</h3>

<p>
	أنشئ العرض "‎/views/author_list.pug" وضع المحتوى التالي بدل المحتوى الموجود مسبقًا:
</p>

<pre class="ipsCode">extends layout

block content
  h1= title

  ul
    each author in author_list
      li
        a(href=author.url) #{author.name}
        |  (#{author.date_of_birth} - #{author.date_of_death})

    else
      li There are no authors.
</pre>

<p>
	شغّل التطبيق وافتح متصفحك على العنوان "http://localhost:3000/‎"، ثم حدد رابط جميع المؤلفين "All authors". إذا جرى إعداد كل شيء بصورة صحيحة، فيجب أن تبدو الصفحة كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="141449" href="https://academy.hsoub.com/uploads/monthly_2024_01/04_locallibary_express_author_list.png.7c960223ed44eb32cea53b1e49c4c750.png" rel=""><img alt="04 locallibary express author list" class="ipsImage ipsImage_thumbnailed" data-fileid="141449" data-unique="oaqr2t05b" src="https://academy.hsoub.com/uploads/monthly_2024_01/04_locallibary_express_author_list.thumb.png.f5c97f4851248802837513c6be5e3327.png"> </a>
</p>

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

<pre class="ipsCode">return this.date_of_birth ? DateTime.fromJSDate(this.date_of_birth).toLocaleString(DateTime.DATE_MED) : '';
</pre>

<h2 id="genre">
	صفحة قائمة أنواع الكتب Genre - التحدي
</h2>

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

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="141450" href="https://academy.hsoub.com/uploads/monthly_2024_01/05_locallibary_express_genre_list.png.83469eba05759d15a5502e064daad98e.png" rel=""><img alt="05 locallibary express genre list" class="ipsImage ipsImage_thumbnailed" data-fileid="141450" data-unique="rsmvsoxek" src="https://academy.hsoub.com/uploads/monthly_2024_01/05_locallibary_express_genre_list.png.83469eba05759d15a5502e064daad98e.png"> </a>
</p>

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

<ol>
	<li>
		يجب تعديل التابع <code>genre_list()‎</code> في الملف "‎/controllers/genreController.js".
	</li>
	<li>
		يماثل تقديم هذا التابع تقريبًا متحكم التابع <code>author_list()‎</code>، ثم يجب فرز النتائج حسب الاسم بترتيب تصاعدي.
	</li>
	<li>
		يجب تسمية القالب المراد عرضه بالاسم genre_list.pug.
	</li>
	<li>
		يجب أن يمرر القالب المراد عرضه متغيرات العنوان <code>title</code> (الذي هو 'Genre List') و <code>genre_list</code> (قائمة أنواع الكتب المُعادة من دالة رد النداء <code>Genre.find()‎</code>).
	</li>
	<li>
		يجب أن يتطابق العرض مع لقطة الشاشة أو المتطلبات السابقة، إذ يجب أن يكون له بنية أو تنسيق مشابه جدًا لعرض قائمة المؤلفين باستثناء أن أنواع الكتب لا تحتوي على تواريخ.
	</li>
</ol>

<h2 id="genre-1">
	صفحة تفاصيل أنواع الكتب Genre
</h2>

<p>
	يجب أن تعرض صفحة تفاصيل أنواع الكتب المعلومات الخاصة بنسخة نوع كتاب معينة باستخدام قيمة الحقل <code>‎_id</code> المُولَّد تلقائيًا بوصفه المعرِّف. يُرمَّز معرّف سجل نوع الكتاب المطلوب في نهاية عنوان URL ويُستخرَج تلقائيًا بناءً على تعريف الوجهة (‎/genre/:id)، ثم يجري الوصول إليه ضمن المتحكم باستخدام معاملات الطلب: <code>req.params.id</code>.
</p>

<p>
	يجب أن تعرض الصفحة اسم نوع الكتاب وقائمة بجميع الكتب في هذا النوع مع روابط إلى صفحة تفاصيل كل كتاب.
</p>

<h3 id="-12">
	المتحكم
</h3>

<p>
	افتح الملف "‎/controllers/genreController.js" واطلب وحدة الكتاب <code>Book</code> في بداية الملف، إذ يجب أن يطلب الملف باستخدام الدالة <code>require()‎</code> الوحدة <code>Genre</code> و "express-async-handler".
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4640_33" style=""><span class="kwd">const</span><span class="pln"> </span><span class="typ">Book</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"../models/book"</span><span class="pun">);</span></pre>

<p>
	ابحث عن تابع المتحكم <code>genre_detail()‎</code> المُصدَّر وضع مكانه الشيفرة البرمجية التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4640_35" style=""><span class="com">// عرض صفحة التفاصيل لنوع كتاب معين</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">genre_detail </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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="com">// الحصول على تفاصيل نوع الكتاب وجميع الكتب المرتبطة به (على‫ التوازي)</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> </span><span class="pun">[</span><span class="pln">genre</span><span class="pun">,</span><span class="pln"> booksInGenre</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</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">
    </span><span class="typ">Genre</span><span class="pun">.</span><span class="pln">findById</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">exec</span><span class="pun">(),</span><span class="pln">
    </span><span class="typ">Book</span><span class="pun">.</span><span class="pln">find</span><span class="pun">({</span><span class="pln"> genre</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="str">"title summary"</span><span class="pun">).</span><span class="pln">exec</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">genre </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">
    </span><span class="kwd">const</span><span class="pln"> err </span><span class="pun">=</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">"Genre not found"</span><span class="pun">);</span><span class="pln">
    err</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">
    </span><span class="kwd">return</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">

  res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">"genre_detail"</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="str">"Genre Detail"</span><span class="pun">,</span><span class="pln">
    genre</span><span class="pun">:</span><span class="pln"> genre</span><span class="pun">,</span><span class="pln">
    genre_books</span><span class="pun">:</span><span class="pln"> booksInGenre</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	نستخدم التابع <code>Genre.findById()‎</code> أولًا للحصول على معلومات نوع كتاب لمعرّف معين، والتابع <code>Book.find()‎</code> للحصول على جميع سجلات الكتب التي لها نفس معرّف نوع الكتاب المرتبط بها. لا يعتمد هذان الطلبان على بعضهما، لذا نستخدم التابع <code>Promise.all()‎</code> لتشغيل استعلامات قاعدة البيانات على التوازي، إذ وضّحنا هذا الأسلوب لتشغيل الاستعلامات على التوازي عندما أنشأنا [الصفحة الرئيسية](رابط المقال السابق)).
</p>

<p>
	ننتظر باستخدام <code>await</code> الوعد المُعاد، ثم نتحقق من النتائج عندما يستقر؛ فإذا لم يكن نوع الكتاب موجودًا في قاعدة البيانات (أي يمكن أن يكون محذوفًا)، فسيعيد التابع <code>findById()‎</code> بنجاح بدون نتائج، ونريد في هذه الحالة عرض صفحة عدم وجوده "not found"، لذلك ننشئ كائن خطأ <code>Error</code> ونمرّره إلى دالة البرمجية الوسيطة التالية <code>next</code> في السلسلة.
</p>

<p>
	<strong>ملاحظة</strong>: تنتقل الأخطاء -المُمرَّرة إلى دالة البرمجية الوسيطة التالية <code>next</code>- إلى شيفرة معالجة الأخطاء التي جرى إعدادها عندما أنشأنا <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2170/" rel="">التطبيق الهيكلي</a> (اطلع على قسم معالجة الأخطاء لمزيد من المعلومات).
</p>

<p>
	إذا عُثِر على نوع الكتاب <code>genre</code>، فسنستدعي التابع <code>render()‎</code> لتقديم العرض، إذ يكون قالب العرض هو genre_detail (.pug)‎. تُمرَّر قيم <code>title</code> و <code>genre</code> و <code>booksInGenre</code> إلى القالب باستخدام المفاتيح المقابلة (<code>title</code> و <code>genre</code> و <code>genre_books</code>).
</p>

<h3 id="-13">
	العرض
</h3>

<p>
	أنشئ العرض "‎/views/genre_detail.pug" وضع فيه المحتوى التالي:
</p>

<pre class="ipsCode">extends layout

block content

  h1 Genre: #{genre.name}

  div(style='margin-left:20px;margin-top:20px')

    h4 Books

    dl
      each book in genre_books
        dt
          a(href=book.url) #{book.title}
        dd #{book.summary}

      else
        p This genre has no books
</pre>

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

<h3 id="-14">
	كيف تبدو صفحة تفاصيل نوع الكتاب؟
</h3>

<p>
	شغّل التطبيق وافتح متصفحك على العنوان "http://localhost:3000/‎"، ثم حدد رابط جميع أنواع الكتب "All genres"، ثم حدّد أحد هذه الأنواع مثل النوع "Fantasy". إذا جرى إعداد كل شيء بصورة صحيحة، فيجب أن تبدو صفحتك كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="141451" href="https://academy.hsoub.com/uploads/monthly_2024_01/06_locallibary_express_genre_detail.png.b2dd4e6562ac8f0a43a1e4ff34ef13ea.png" rel=""><img alt="06 locallibary express genre detail" class="ipsImage ipsImage_thumbnailed" data-fileid="141451" data-unique="1g8umaaj7" src="https://academy.hsoub.com/uploads/monthly_2024_01/06_locallibary_express_genre_detail.thumb.png.42460ebfbb36ab4b6ffdc0a97b3a7605.png"> </a>
</p>

<p>
	<strong>ملاحظة</strong>: يمكن أن تحصل على خطأ مشابه لما يلي:
</p>

<pre class="ipsCode">Cast to ObjectId failed for value " 59347139895ea23f9430ecbb" at path "_id" for model "Genre"
</pre>

<p>
	وهو خطأ Mongoose سببه عدم تحويل <code>req.params.id</code> أو أي معرّف ID آخر إلى <code>()mongoose.Types.ObjectId</code>، ويعود السبب الأكثر شيوعًا لذلك إلى اختلاف الرقم المعرّف عن المعرّف الحقيقي، ويمكن حل هذه المشكلة من خلال استخدام <code>()Mongoose.prototype.isValidObjectId</code> لفحص المعرّف إذا كان صالحًا.
</p>

<h2 id="-15">
	صفحة تفاصيل الكتاب
</h2>

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

<h3 id="-16">
	المتحكم
</h3>

<p>
	افتح الملف "‎/controllers/bookController.js"، وابحث عن تابع المتحكم <code>book_detail()‎</code> المُصدَّر وضع مكانه الشيفرة البرمجية التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4640_37" style=""><span class="com">// عرض صفحة تفاصيل كتاب معين</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">book_detail </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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="com">// الحصول على تفاصيل الكتب ونسخ الكتب لكتاب معين</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> </span><span class="pun">[</span><span class="pln">book</span><span class="pun">,</span><span class="pln"> bookInstances</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</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">
    </span><span class="typ">Book</span><span class="pun">.</span><span class="pln">findById</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">populate</span><span class="pun">(</span><span class="str">"author"</span><span class="pun">).</span><span class="pln">populate</span><span class="pun">(</span><span class="str">"genre"</span><span class="pun">).</span><span class="pln">exec</span><span class="pun">(),</span><span class="pln">
    </span><span class="typ">BookInstance</span><span class="pun">.</span><span class="pln">find</span><span class="pun">({</span><span class="pln"> book</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">exec</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">book </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">
    </span><span class="kwd">const</span><span class="pln"> err </span><span class="pun">=</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">"Book not found"</span><span class="pun">);</span><span class="pln">
    err</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">
    </span><span class="kwd">return</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">

  res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">"book_detail"</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"> book</span><span class="pun">.</span><span class="pln">title</span><span class="pun">,</span><span class="pln">
    book</span><span class="pun">:</span><span class="pln"> book</span><span class="pun">,</span><span class="pln">
    book_instances</span><span class="pun">:</span><span class="pln"> bookInstances</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>: لا نحتاج إلى طلب أيّ وحدات إضافية حاليًا، إذ استوردنا مسبقًا الاعتماديات عندما قدّمنا متحكم الصفحة الرئيسية.
</p>

<p>
	استخدمنا الأسلوب نفسه الموضح في صفحة تفاصيل نوع الكتاب، إذ تستخدم دالةُ متحكم الوجهة التابعَ <code>Promise.all()‎</code> للاستعلام عن الكتاب <code>Book</code> المُحدَّد والنسخ المرتبطة به <code>BookInstance</code> على التوازي. إذا لم يُعثَر على كتاب مطابق، فسيُعاد كائن خطأ مع الخطأ "404‎: Not Found"، وإذا عُثِر على الكتاب، فستُعرَض معلومات قاعدة البيانات المسترجَعة باستخدام قالب "book_detail".
</p>

<p>
	نمرر <code>results.book.title</code> أثناء عرض صفحة الويب، نظرًا لاستخدام المفتاح "title" لإعطاء اسم لصفحة الويب كما هو مُعرَّف في ترويسة القالب "layout.pug".
</p>

<h3 id="-17">
	العرض
</h3>

<p>
	أنشئ العرض "‎/views/book_detail.pug" وضِف إليه المحتوى التالي:
</p>

<pre class="ipsCode">extends layout

block content
  h1 Title: #{book.title}

  p #[strong Author:]
    a(href=book.author.url) #{book.author.name}
  p #[strong Summary:] #{book.summary}
  p #[strong ISBN:] #{book.isbn}
  p #[strong Genre:]
    each val, index in book.genre
      a(href=val.url) #{val.name}
      if index &lt; book.genre.length - 1
        |,

  div(style='margin-left:20px;margin-top:20px')
    h4 Copies

    each val in book_instances
      hr
      if val.status=='Available'
        p.text-success #{val.status}
      else if val.status=='Maintenance'
        p.text-danger #{val.status}
      else
        p.text-warning #{val.status}
      p #[strong Imprint:] #{val.imprint}
      if val.status!='Available'
        p #[strong Due back:] #{val.due_back}
      p #[strong Id:]
        a(href=val.url) #{val._id}

    else
      p There are no copies of this book in the library.
</pre>

<p>
	وضّحنا كل شيء تقريبًا في هذا القالب سابقًا، فلا حاجة للإعادة.
</p>

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

<pre class="ipsCode"> p #[strong Genre:]
    each val, index in book.genre
      a(href=val.url) #{val.name}
      if index &lt; book.genre.length - 1
        |,
</pre>

<h3 id="-18">
	كيف تبدو صفحة تفاصيل الكتاب؟
</h3>

<p>
	شغّل التطبيق وافتح متصفحك على العنوان "http://localhost:3000/‎"، ثم حدد رابط جميع الكتب All books، وحدد أحد هذه الكتب. إذا جرى إعداد كل شيء بصورة صحيحة، فيجب أن تبدو صفحتك كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="141452" href="https://academy.hsoub.com/uploads/monthly_2024_01/07_locallibary_express_book_detail.png.7713b7626d252d4122af80aa2a91421d.png" rel=""><img alt="07 locallibary express book detail" class="ipsImage ipsImage_thumbnailed" data-fileid="141452" data-unique="o3azmewje" src="https://academy.hsoub.com/uploads/monthly_2024_01/07_locallibary_express_book_detail.thumb.png.cf3b8b22a798ab93131926d09cb66a1a.png"> </a>
</p>

<h2 id="-19">
	صفحة تفاصيل المؤلف
</h2>

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

<h3 id="-20">
	المتحكم
</h3>

<p>
	افتح الملف "‎/controllers/authorController.js"، ثم ضِف السطر التالي إلى بداية الملف لطلب -باستخدام الدالة <code>require()‎</code>- الوحدة <code>Book</code> التي تحتاجها صفحة تفاصيل المؤلف، ويجب أن تكون الوحدات الأخرى مثل "express-async-handler" موجودة مسبقًا.
</p>

<pre class="ipsCode">const Book = require("../models/book");
</pre>

<p>
	ابحث عن تابع المتحكم <code>author_detail()‎</code> المُصدَّر وضع مكانه الشيفرة البرمجية التالية:
</p>

<pre class="ipsCode">// عرض صفحة تفاصيل مؤلف مُحدَّد
exports.author_detail = asyncHandler(async (req, res, next) =&gt; {
  // الحصول على تفاصيل المؤلف وجميع كتبه (على التوا‫زي)
  const [author, allBooksByAuthor] = await Promise.all([
    Author.findById(req.params.id).exec(),
    Book.find({ author: req.params.id }, "title summary").exec(),
  ]);

  if (author === null) {
    // لا توجد نتائج
    const err = new Error("Author not found");
    err.status = 404;
    return next(err);
  }

  res.render("author_detail", {
    title: "Author Detail",
    author: author,
    author_books: allBooksByAuthor,
  });
});
</pre>

<p>
	يماثل هذا الأسلوب المتبع تمامًا الأسلوب الموضح في صفحة تفاصيل نوع الكتاب، إذ تستخدم دالةُ متحكم الوجهة التابعَ <code>Promise.all()‎</code> للاستعلام عن المؤلف <code>Author</code> ونسخ الكتب <code>Book</code> المرتبطة بها على التوازي. إذا لم يُعثَر على مؤلف مطابق، فسيُرسَل كائن خطأ إلى برمجية Express الوسيطة لمعالجة الأخطاء، وإذا عُثِر على المؤلف، فستُعرَض معلومات قاعدة البيانات المُسترجَعة باستخدام قالب تفاصيل المؤلف "author_detail".
</p>

<h3 id="-21">
	العرض
</h3>

<p>
	أنشئ العرض "‎/views/author_detail.pug" وضع فيه المحتوى التالي:
</p>

<pre class="ipsCode">extends layout

block content

  h1 Author: #{author.name}
  p #{author.date_of_birth} - #{author.date_of_death}

  div(style='margin-left:20px;margin-top:20px')

    h4 Books

    dl
      each book in author_books
        dt
          a(href=book.url) #{book.title}
        dd #{book.summary}

      else
        p This author has no books.
</pre>

<p>
	وضّحنا كل شيء تقريبًا في هذا القالب سابقًا، فلا حاجة للإعادة.
</p>

<h3 id="-22">
	كيف تبدو صفحة تفاصيل المؤلف؟
</h3>

<p>
	شغّل التطبيق وافتح متصفحك على العنوان "http://localhost:3000/‎"، ثم حدد رابط جميع المؤلفين All Authors، ثم حدد أحد المؤلفين. إذا جرى إعداد كل شيء بصورة صحيحة، فيجب أن يبدو موقعك كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="141453" href="https://academy.hsoub.com/uploads/monthly_2024_01/08_locallibary_express_author_detail.png.6a008dd18c9a2f47db884c6113df96f0.png" rel=""><img alt="08 locallibary express author detail" class="ipsImage ipsImage_thumbnailed" data-fileid="141453" data-unique="vr6ofxsay" src="https://academy.hsoub.com/uploads/monthly_2024_01/08_locallibary_express_author_detail.thumb.png.0947cca7b3267fb7969ab77a7e44ca31.png"> </a>
</p>

<p>
	<strong>ملاحظة</strong>: لا تبدو تواريخ عمر المؤلف جميلة، لذا سنحسّنها في التحدي الأخير من هذا المقال.
</p>

<h2 id="bookinstance-1">
	صفحة تفاصيل نسخ الكتب BookInstance
</h2>

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

<h3 id="-23">
	المتحكم
</h3>

<p>
	افتح الملف "‎/controllers/bookinstanceController.js"، ثم ابحث عن تابع المتحكم <code>bookinstance_detail()‎</code> المُصدَّر وضع مكانه الشيفرة البرمجية التالية:
</p>

<pre class="ipsCode">// عرض صفحة تفاصيل نسخة كتاب معينة
exports.bookinstance_detail = asyncHandler(async (req, res, next) =&gt; {
  const bookInstance = await BookInstance.findById(req.params.id)
    .populate("book")
    .exec();

  if (bookInstance === null) {
    // لا توجد نتائج
    const err = new Error("Book copy not found");
    err.status = 404;
    return next(err);
  }

  res.render("bookinstance_detail", {
    title: "Book:",
    bookinstance: bookInstance,
  });
});
</pre>

<p>
	يشابه هذا التقديم ما استخدمناه لصفحات تفاصيل النماذج الأخرى، إذ تستدعي دالة متحكم الوجهة التابع <code>BookInstance.findById()‎</code> مع معرّف نسخة كتاب معين مُستخرَج من عنوان URL (باستخدام الوجهة Route)، ويمكن الوصول إليه في المتحكم باستخدام معاملات الطلب: <code>req.params.id</code>، ثم تستدعي التابع <code>populate()‎</code> للحصول على تفاصيل الكتاب <code>Book</code> المرتبط به. إذا لم يُعثَر على نسخة كتاب <code>BookInstance</code> مطابقة، فسيُرسَل خطأ إلى برمجية Express الوسيطة، وإلّا فستُعرَض البيانات المُعادة باستخدام طريقة العرض bookinstance_detail.pug.
</p>

<h3 id="-24">
	العرض
</h3>

<p>
	أنشئ العرض "‎/views/bookinstance_detail.pug" وضع فيه المحتوى التالي:
</p>

<pre class="ipsCode">extends layout

block content

  h1 ID: #{bookinstance._id}

  p #[strong Title:]
    a(href=bookinstance.book.url) #{bookinstance.book.title}
  p #[strong Imprint:] #{bookinstance.imprint}

  p #[strong Status:]
    if bookinstance.status=='Available'
      span.text-success #{bookinstance.status}
    else if bookinstance.status=='Maintenance'
      span.text-danger #{bookinstance.status}
    else
      span.text-warning #{bookinstance.status}

  if bookinstance.status!='Available'
    p #[strong Due back:] #{bookinstance.due_back}
</pre>

<p>
	وضّحنا كل شيء تقريبًا في هذا القالب سابقًا، فلا حاجة للإعادة.
</p>

<h3 id="-25">
	كيف تبدو صفحة تفاصيل نسخ الكتاب؟
</h3>

<p>
	شغّل التطبيق وافتح متصفحك على العنوان "http://localhost:3000/‎"، ثم حدّد رابط جميع نسخ الكتب All book-instances، وحدّد أحد هذه العناصر. إذا جرى إعداد كل شيء بصورة صحيحة، فيجب أن تبدو موقعك كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="141454" href="https://academy.hsoub.com/uploads/monthly_2024_01/09_locallibary_express_bookinstance_detail.png.53727df798e49366133a624f29ad8666.png" rel=""><img alt="09 locallibary express bookinstance detail" class="ipsImage ipsImage_thumbnailed" data-fileid="141454" data-unique="o6r3w11de" src="https://academy.hsoub.com/uploads/monthly_2024_01/09_locallibary_express_bookinstance_detail.thumb.png.b67f81d7b6adceb1f120c08fc66900ae.png"> </a>
</p>

<h2 id="-26">
	تحدى نفسك
</h2>

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

<pre class="ipsCode">Tue Oct 06 2020 15:49:58 GMT+1100 (AUS Eastern Daylight Time)‎
</pre>

<p>
	لذا يتمثل التحدي في هذا المقال في تحسين مظهر عرض التاريخ لمعلومات عمر المؤلف <code>Author</code> (تاريخ الوفاة والميلاد) ولصفحات تفاصيل نسخ الكتب لاستخدام التنسيق Oct 6th, 2016.
</p>

<p>
	<strong>ملاحظة</strong>: يمكنك استخدام نفس الأسلوب الذي استخدمناه لقائمة نسخ الكتب في <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B1%D8%A7%D8%A8%D8%B9-%D8%B9%D8%B1%D8%B6-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%88%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-r2193/" rel="">المقال السابق</a> من خلال إضافة خاصية العمر Lifespan الافتراضية إلى نموذج المؤلف <code>Author</code> واستخدام مكتبة Luxon لتنسيق سلاسل التاريخ.
</p>

<p>
	يجب عليك اتباع الخطوات التالية لإكمال هذا التحدي:
</p>

<ol>
	<li>
		ضع المتغير <code>due_back_formatted</code> مكان <code>due_back</code> في صفحة تفاصيل نسخ الكتاب.
	</li>
	<li>
		حدّث نموذج المؤلف <code>Author</code> لإضافة خاصية العمر الافتراضية، إذ يجب أن يبدو مثل: date_of_birth - date_of_death، ويكون لكلا القيمتين تنسيق التاريخ <code>BookInstance.due_back_formatted</code>.
	</li>
	<li>
		استخدم <code>Author.lifespan</code> في جميع العروض حيث تستخدم حاليًا <code>date_of_birth</code> و <code>date_of_death</code> صراحةً.
	</li>
</ol>

<p>
	ترجمة -وبتصرُّف- للمقالات <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Home_page" rel="external nofollow">Home page</a> و <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Book_list_page" rel="external nofollow">Book list page</a> و <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_list_page" rel="external nofollow">BookInstance list page</a> و <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Date_formatting_using_moment" rel="external nofollow">Date formatting using luxon</a> و <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Author_list_page" rel="external nofollow">Author list page and Genre list page challenge</a> و <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Template_primer" rel="external nofollow">Genre detail page</a> و <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Book_detail_page" rel="external nofollow">Book detail page</a> و <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Author_detail_page" rel="external nofollow">Author detail page</a> و <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_detail_page_and_challenge" rel="external nofollow">BookInstance detail page and challenge</a>.
</p>

<h2 id="-27">
	اقرأ المزيد
</h2>

<ul>
	<li>
		المقال السابق <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-template-%D9%81%D9%8A-express-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%A7%D9%84%D9%82%D8%A7%D9%84%D8%A8-%D8%A7%D9%84%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A-%D9%84%D9%85%D9%88%D9%82%D8%B9-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D9%85%D8%AB%D8%A7%D9%84%D9%8B%D8%A7-r2209/" rel="">مقدمة إلى القوالب Template في Express: إنشاء القالب الأساسي لموقع مكتبة محلية مثالًا</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2170/" rel="">تطبيق عملي لتعلم Express - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2090/" rel="">تطبيق عملي لتعلم إطار عمل جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية</a>.
	</li>
</ul>
]]></description><guid isPermaLink="false">2219</guid><pubDate>Tue, 09 Jan 2024 12:01:00 +0000</pubDate></item><item><title>&#x62F;&#x644;&#x64A;&#x644;&#x643; &#x644;&#x631;&#x628;&#x637; &#x648;&#x627;&#x62C;&#x647;&#x629; OpenAI API &#x645;&#x639; Node.js</title><link>https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AF%D9%84%D9%8A%D9%84%D9%83-%D9%84%D8%B1%D8%A8%D8%B7-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-openai-api-%D9%85%D8%B9-nodejs-r2233/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_01/--ChatGPT-API--Node.png.23a44215e36b9b878faf80542e7f9b4c.png" /></p>
<p>
	في عالمنا المتغير بسرعة، يعد الذكاء الاصطناعي أحد أكثر التكنولوجيات انتشارًا وتأثيرًا في حياتنا. فالذكاء الاصطناعي يُعدّ من أكثر التطورات المثيرة والمهمة في عصرنا، إذ يوفر فرصًا جديدة لتطوير حياتنا وأعمالنا بطرق لم نكن نتخيلها من قبل.
</p>

<p>
	ومن بين <a href="https://academy.hsoub.com/programming/artificial-intelligence/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D8%B0%D9%83%D8%A7%D8%A1-%D8%A7%D9%84%D8%A7%D8%B5%D8%B7%D9%86%D8%A7%D8%B9%D9%8A/" rel="">تطبيقات الذكاء الاصطناعي</a>، تأتي العديد من التطبيقات المميزة، مثل التعرف على الصوت والصور والترجمة الفورية، وحتى الروبوتات التي تستطيع العمل بدلاً من الإنسان في بعض المهام. فالذكاء الاصطناعي يوفر العديد من المزايا والفرص لتحسين حياتنا وأعمالنا بشكل كبير.
</p>

<p>
	وبالنظر إلى الذكاء الاصطناعي في نطاق الشركات، فهو يتيح لها الفرصة للتحسين من كفاءة عملياتها وتقليل الأخطاء، إلى جانب توفير تحليلات وإحصاءات دقيقة عن البيانات المخزنة والمعالجة. ومن بين أفضل التطبيقات على الذكاء الاصطناعي هي تطبيقات معالجة اللغات الطبيعية و التي انبثقت منها أدوات مثل <a href="https://academy.hsoub.com/apps/general/%D8%AA%D8%AF%D8%B1%D9%8A%D8%A8-%D8%A8%D9%88%D8%AA-%D8%A7%D9%84%D9%85%D8%AD%D8%A7%D8%AF%D8%AB%D8%A9-chatgpt-%D9%88%D8%AA%D8%B9%D9%84%D9%8A%D9%85%D9%87-%D9%83%D9%8A%D9%81-%D9%8A%D8%AA%D8%AD%D8%AF%D8%AB-%D9%88%D9%8A%D8%AA%D8%B9%D9%84%D9%85-r897/" rel="">ChatGPT</a> التي تتيح للمستخدمين التفاعل مع النظام والحصول على إجابات دقيقة وفعالة عن الأسئلة المختلفة التي تتعلق بالمواضيع المختلفة.
</p>

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

<h2 id="openaiapi">
	ما هي OpenAI <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr>
</h2>

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

<p>
	يعد OpenAI <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> من أكثر الواجهات البرمجية شيوعًا في مجال الذكاء الاصطناعي، حيث يوفر للمطورين الوصول إلى النماذج الأكثر تطورًا للتعلم الآلي والتي تدعم العديد من التطبيقات والأدوات. فباستخدام هذه <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%A7-%D9%87%D9%8A-%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-%D9%84%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-api%D8%9F-r1512/" rel="">الواجهة البرمجية</a>، يمكن للمطورين إنشاء تطبيقات متطورة تعتمد على الذكاء الاصطناعي في مجالات مختلفة مثل التسويق والتعليم والصحة والإدارة والمالية وغيرها.
</p>

<p>
	ومن بين أهم التطبيقات التي يمكن استخدام OpenAI <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> فيها هي التحدث مع الآلة والتعرف على الصور والنصوص والتحكم في الأجهزة الذكية. كما يمكن استخدامها في مجالات مختلفة مثل الروبوتات والألعاب والتصميم والترجمة والعديد من التطبيقات الأخرى.
</p>

<p>
	وبفضل تقنياتها المتطورة، تمكّن OpenAI <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> المستخدمين من الوصول إلى أدوات ونماذج <a href="https://academy.hsoub.com/programming/artificial-intelligence/%D8%AA%D8%B9%D9%84%D9%85-%D8%A7%D9%84%D8%A2%D9%84%D8%A9/" rel="">التعلم الآلي</a> الأكثر تطورًا، مما يجعلها خيارًا جيدًا للمطورين الذين يبحثون عن الحلول الفعالة لتطوير التطبيقات التي تعتمد على الذكاء الاصطناعي.
</p>

<p>
	نتعلم في هذا المقال كيفية الاستفادة من واجهة OpenAI <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> والاتصال بخدمة ChatGPT واستعمالها في تطبيق عملي بسيط، ويمكنك أنت بعد ذلك استعمالها في أي تطبيق تريده.
</p>

<h2 id="">
	فكرة المشروع ومتطلباته
</h2>

<p>
	في هذا المقال، سنستكشف كيفية إنشاء مساعد شخصي باستخدام ChatGPT <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> بطريقة فعالة ومبتكرة، وكيفية تخصيص تجربة المستخدم لتلبية احتياجاتهم الفردية، سنتحدث أيضًا عن أهمية هذه التقنية في عالمنا الرقمي الحديث، وكيف يمكن أن يكون بناء مساعد شخصي باستخدام ChatGPT <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> استثمارًا قيماً و لكن قبل الشروع في التنفيذ علينا أن نلقي نظرة خاطفة على التقنيات المستخدمة لربط ChatGpt برمجيًا باستخدام Node.js.
</p>

<p>
	المتطلبات المسبقة لبناء المشروع:
</p>

<ul>
	<li>
		تثبيت بيئة Nodejs: لمن لا يعرفها، هي بيئة تشغيل JavaScript خارج المتصفح.
	</li>
	<li>
		إطار العمل Express.js: لتطوير تطبيقات الويب باستخدام Node.js، وارجع إلى مقال <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-node-%D9%85%D8%B9-express-r2169/" rel="">إعداد بيئة تطوير Node مع Express</a> من أجل تثبيته مع Node.js.
	</li>
	<li>
		واجهة OpenAI <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr>: توفر خدمات وأدوات للمطورين لإنشاء تطبيقات وأنظمة تعتمد على تقنيات الذكاء الاصطناعي. وتشمل خدمات OpenAI <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> العديد من الأدوات والتقنيات مثل GPT-3 وDALL-E ومنصة الاستكشاف للتحليلات اللغوية وتطبيقات الروبوتات والعديد من الخدمات الأخرى.
	</li>
</ul>

<p>
	الآن سنقوم بكتابة الكود البرمجي خطوة بخطوة، بدءًا من إعداد بيانات الواجهة <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> وصولاً إلى بناء بوت دردشة يمكنه فهم استفسارات المستخدمين والرد عليها بلغة طبيعية.
</p>

<h2 id="-1">
	تهيئة المشروع
</h2>

<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="">الطرفية terminal</a> ونطلق عليه اسم خاص بالمشروع:
</p>

<pre class="ipsCode">$ mkdir chatgpt-api-bot
$ cd chatgpt-api-bot
</pre>

<p>
	ننشئ ملف package.json عبر الأمر التالي:
</p>

<pre class="ipsCode">$ npm init -y
</pre>

<p>
	نضيف الحزم التالية:
</p>

<ul>
	<li>
		openai: توفر مكتبة OpenAI Node.js وصولاً إلى واجهة OpenAI <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> من تطبيقات Node.js
	</li>
	<li>
		readline-sync: توفر قراءة متزامنة إدخالات المستخدم من سطر الأوامر
	</li>
	<li>
		Dotenv: تسمح لنا بادارة المتغيرات في الملف ‎.env داخل بيئة المشروع
	</li>
	<li>
		إطار العمل Expressjs
	</li>
	<li>
		body-parser: تسمح لك بمعالجة الطلبات بتنسيقات مختلفة مثل JSON و XML.
	</li>
</ul>

<pre class="ipsCode" id="ips_uid_387_6">$ npm i openai@4.20.0 readline-sync@1.4.10 dotenv@16.3.1 express@4.18.2 body-parser
</pre>

<h2 id="apiopenai">
	إنشاء مفتاح api من OpenAI
</h2>

<p>
	لكي نتمكن من استخدام واجهة OpenAI <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> داخل تطبيق Node.js، نحتاج أولاً إلى إنشاء مفتاح <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> من لوحة تحكم OpenAI.
</p>

<p>
	لإنشاء المفتاح، نتحتاج إلى إنشاء حساب مستخدم على <a href="https://openai.com" ipsnoembed="true" rel="external nofollow">https://openai.com</a> والوصول إلى قسم مفاتيح واجهة برمجة التطبيقات في لوحة تحكم OpenAI ثم إنشاء مفتاح جديد.
</p>

<p>
	هذا المفتاح سري ويجب ألا يتم مشاركته مع أي شخص آخر، سنحتاج إلى استخدام هذا المفتاح لاحقًا عند تنفيذ برنامج Node.js للوصول إلى OpenAI <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr>.
</p>

<p>
	نضيف هذا المفتاح إلى متغير البيئة في المشروع الخاص بنا، حيث ننشئ ملفًا جديدًا:
</p>

<pre class="ipsCode">touch .env
</pre>

<p>
	نضيف في هذا الملف المتغير <code>OPENAI_API_KEY</code> ونسند له قيمة المفتاح api الذي نسخناه للتو:
</p>

<pre class="ipsCode">OPENAI_API_KEY="YOUR OPEN AI API KEY"
</pre>

<h2 id="backend">
	بناء الواجهة الخلفية backEnd الخاص بالمشروع
</h2>

<p>
	ننشئ ملف المشروع و ليكن اسمه index.js:
</p>

<pre class="ipsCode">$ touch index.js
</pre>

<p>
	داخل ملف index.js نبدأ في كتابة الكود ونقوم أولا باستدعاء الحزم التي قمنا بتحميلها داخل المشروع:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_671_8" 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"> </span><span class="typ">OpenAI</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"openai"</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></pre>

<ul>
	<li>
		<code>Configuration</code> و <code>OpenAIApi</code> هي كائنات من حزمة OpenAI، والتي تستخدم للاتصال بواجهة OpenAI <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> واستخدامها
	</li>
	<li>
		<code>dotenv</code> لتحميل متغيرات بيئة المشروع من ملف ‎.env
	</li>
</ul>

<p>
	ننشئ بعد ذلك خادم المشروع عبر Express:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_671_10" style=""><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"> 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"> is 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>

<h3 id="openaiapi-1">
	بناء كود الربط بين openAI api والتطبيق
</h3>

<p>
	نضبط مكتبة <code>openai</code> بتمرير قيمة المفتاح الموجود في ملف بيئة المشروع:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_671_12" style=""><span class="kwd">const</span><span class="pln"> openai </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">OpenAI</span><span class="pun">({</span><span class="pln">
  apiKey</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">OPENAI_API_KEY</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_671_14" style=""><span class="kwd">const</span><span class="pln"> history </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[];</span></pre>

<p>
	ننشئ مصفوفة للرسائل ثم نستخدم حلقة <code>for</code> لتكرار سجل المحادثة وإنشاء قائمة بالرسائل بالتنسيق المناسب لبوت الدردشة ChatGPT الخاص بـ OpenAI. و إعطاء كل رسالة دور role (إما "مستخدم" أو "مساعد ") والمحتوى (نص الرسالة)، ولمزيد من التفصيل يمكن الإطلاع على صفحة <a href="https://platform.openai.com/docs/guides/chat/introduction" rel="external nofollow">Text generation models</a> من openAI.
</p>

<p>
	نضيف بعد ذلك مدخلات المستخدم <code>user_input</code> ودوره الحالي إلى نهاية مصفوفة الرسائل:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_671_16" style=""><span class="kwd">const</span><span class="pln"> messages </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="kwd">const</span><span class="pln"> </span><span class="pun">[</span><span class="pln">input_text</span><span class="pun">,</span><span class="pln"> completion_text</span><span class="pun">]</span><span class="pln"> </span><span class="kwd">of</span><span class="pln"> history</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  messages</span><span class="pun">.</span><span class="pln">push</span><span class="pun">({</span><span class="pln"> role</span><span class="pun">:</span><span class="pln"> </span><span class="str">"user"</span><span class="pun">,</span><span class="pln"> content</span><span class="pun">:</span><span class="pln"> input_text </span><span class="pun">});</span><span class="pln">
  messages</span><span class="pun">.</span><span class="pln">push</span><span class="pun">({</span><span class="pln"> role</span><span class="pun">:</span><span class="pln"> </span><span class="str">"assistant"</span><span class="pun">,</span><span class="pln"> content</span><span class="pun">:</span><span class="pln"> completion_text </span><span class="pun">});</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
messages</span><span class="pun">.</span><span class="pln">push</span><span class="pun">({</span><span class="pln"> role</span><span class="pun">:</span><span class="pln"> </span><span class="str">"user"</span><span class="pun">,</span><span class="pln"> content</span><span class="pun">:</span><span class="pln"> user_input </span><span class="pun">});</span></pre>

<p>
	الآن نستخدم <code>openai.createChatCompletion</code> لتوليد رد من OpenAI <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> وندخل معامل <a href="https://platform.openai.com/docs/models" rel="external nofollow">نموذج الذكاء الاصطناعي</a> الخاص بتوليد الرد وهو نموذج<br>
	gpt-3.5-turbo:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_671_19" style=""><span class="kwd">const</span><span class="pln"> completion </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> openai</span><span class="pun">.</span><span class="pln">createChatCompletion</span><span class="pun">({</span><span class="pln">
 model</span><span class="pun">:</span><span class="pln"> </span><span class="str">"gpt-3.5-turbo"</span><span class="pun">,</span><span class="pln">
 messages</span><span class="pun">:</span><span class="pln"> messages</span><span class="pun">,</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	يوجد أكثر من معامل ويمكنك زيارة صفحة <a href="https://platform.openai.com/docs/models" rel="external nofollow">models</a> لمعرفة المعاملات المتوفرة واختلافها ومعامل الرسائل <code>messages</code> أيضًا.
</p>

<p>
	نستخدم بعد ذلك التابع <code>createChatCompletion</code> لتوليد الاجابة من خلال الواجهة openAi <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr>، ويتم استخراج الاستجابة التي تم إنشاؤها من <a href="https://academy.hsoub.com/programming/artificial-intelligence/%D8%A7%D9%84%D8%B0%D9%83%D8%A7%D8%A1-%D8%A7%D9%84%D8%A7%D8%B5%D8%B7%D9%86%D8%A7%D8%B9%D9%8A/" rel="">نموذج ذكاء اصطناعي AI</a> من الكائن <code>completion</code> ونخزنها في المتغير <code>completion_text</code>، ثم نطبع الاجابة في الطرفية.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_671_25" style=""><span class="kwd">const</span><span class="pln"> completion_text </span><span class="pun">=</span><span class="pln"> completion</span><span class="pun">.</span><span class="pln">choices</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">message</span><span class="pun">.</span><span class="pln">content</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">completion_text</span><span class="pun">);</span></pre>

<p>
	ندخل البيانات التالية إلى مصفوفة سجل المحادثة: النص الحالي الذي أدخله المستخدم <code>user_input</code> والرد الذي تم إنشاؤه من قبل نموذج الذكاء الاصطناعي <code>completion_text</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_671_23" style=""><span class="pln">history</span><span class="pun">.</span><span class="pln">push</span><span class="pun">([</span><span class="pln">user_input</span><span class="pun">,</span><span class="pln"> completion_text</span><span class="pun">]);</span></pre>

<p>
	ثم نعالج الأخطاء التي يمكن أن تحدث أثناء تنفيذ الطلب بطباعة كود الخطأ ومعلومات عن الخطأ عن طريق استخدام <a 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=""><code>try/catch</code></a> في JavaScript:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_671_21" style=""><span class="kwd">try</span><span class="pun">{</span><span class="pln">
</span><span class="com">// code here</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">if</span><span class="pln"> </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"> </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="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><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="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="pun">}</span></pre>

<h3 id="crud">
	ما هي عمليات CRUD؟
</h3>

<p>
	عمليات CRUD هي اختصار للعمليات الأربعة الأساسية التي يتم استخدامها في إدارة البيانات وهي الإنشاء Create والقراءة Read والتحديث Update والحذف Delete، ويتم استخدام هذه العمليات في العادة في تطوير <a href="https://academy.hsoub.com/apps/web/" rel="">تطبيقات الويب</a> والبرمجيات المختلفة التي تتعامل مع قواعد البيانات.
</p>

<p>
	ويوفر إطار العمل Express وظائف مختلفة لتسهيل عمليات الإنشاء والقراءة والتحديث والحذف والتي تتماشى مع عمليات CRUD. على سبيل المثال، يوفر Express وظائف لمعالجة <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%B7%D9%84%D8%A8%D8%A7%D8%AA-http-%D9%81%D9%8A-nodejs-r1868/" rel="">طلبات http</a> مثل:
</p>

<ul>
	<li>
		<code>app.post()‎</code> للإنشاء
	</li>
	<li>
		<code>app.get()‎</code> للقراءة
	</li>
	<li>
		<code>app.put()‎</code> للتحديث
	</li>
	<li>
		<code>app.delete()‎</code> للحذف
	</li>
</ul>

<p>
	ويمكن استخدام هذه الوظائف لتنفيذ عمليات CRUD بسهولة في تطبيقات Express، مما يساعد في إدارة البيانات بشكل فعال.
</p>

<h3 id="-2">
	بناء نقاط الوصول الخاصة بالمشروع
</h3>

<p>
	سنستخدم في مشروعنا عمليتين فقط لإدارة البيانات وهما عملية الإنشاء وعملية القراءة حاليًا. نقوم بقراءة ملف index.html الذي سننشئه لاحقًا لعرض الواجهة الأمامية للتطبيق:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_387_13" style=""><span class="com">// Serve the index.html file</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">sendFile</span><span class="pun">(</span><span class="pln">__dirname </span><span class="pun">+</span><span class="pln"> </span><span class="str">'/index.html'</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	لمعالجة الرسائل القادمة من المستخدم نستخدم <code>app.post</code> ونقوم بكتابة كود الربط بين واجهة openAi APi والتطبيق داخل الدالة ونضيف الاستجابة اللي سوف تعود لنا من openAi APi:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_671_28" style=""><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">'/message'</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">async</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">
  </span><span class="kwd">const</span><span class="pln"> message </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">message</span><span class="pun">;</span><span class="pln">

  </span><span class="com">// If there is a previous message, include it in the prompt</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> user_input </span><span class="pun">=</span><span class="pln"> history</span><span class="pun">.</span><span class="pln">length </span><span class="pun">&gt;</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"> </span><span class="pun">المحادثة:</span><span class="pln">\n$</span><span class="pun">{</span><span class="pln">history</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="str">'\n'</span><span class="pun">)}</span><span class="pln">\n</span><span class="pun">أنت:</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">message</span><span class="pun">}</span><span class="pln">\n</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">message</span><span class="pun">}</span><span class="pln">\n</span><span class="pun">`;</span><span class="pln">

  </span><span class="kwd">const</span><span class="pln"> messages </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="kwd">const</span><span class="pln"> </span><span class="pun">[</span><span class="pln">input_text</span><span class="pun">,</span><span class="pln"> completion_text</span><span class="pun">]</span><span class="pln"> </span><span class="kwd">of</span><span class="pln"> history</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    messages</span><span class="pun">.</span><span class="pln">push</span><span class="pun">({</span><span class="pln"> role</span><span class="pun">:</span><span class="pln"> </span><span class="str">"user"</span><span class="pun">,</span><span class="pln"> content</span><span class="pun">:</span><span class="pln"> input_text </span><span class="pun">});</span><span class="pln">
    messages</span><span class="pun">.</span><span class="pln">push</span><span class="pun">({</span><span class="pln"> role</span><span class="pun">:</span><span class="pln"> </span><span class="str">"assistant"</span><span class="pun">,</span><span class="pln"> content</span><span class="pun">:</span><span class="pln"> completion_text </span><span class="pun">});</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  messages</span><span class="pun">.</span><span class="pln">push</span><span class="pun">({</span><span class="pln"> role</span><span class="pun">:</span><span class="pln"> </span><span class="str">"user"</span><span class="pun">,</span><span class="pln"> content</span><span class="pun">:</span><span class="pln"> user_input </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"> completion </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> openai</span><span class="pun">.</span><span class="pln">chat</span><span class="pun">.</span><span class="pln">completions</span><span class="pun">.</span><span class="pln">create</span><span class="pun">({</span><span class="pln">
      model</span><span class="pun">:</span><span class="pln"> </span><span class="str">"gpt-3.5-turbo"</span><span class="pun">,</span><span class="pln">
      messages</span><span class="pun">:</span><span class="pln"> messages</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"> completion_text </span><span class="pun">=</span><span class="pln"> completion</span><span class="pun">.</span><span class="pln">choices</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">message</span><span class="pun">.</span><span class="pln">content</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">completion_text</span><span class="pun">);</span><span class="pln">

    history</span><span class="pun">.</span><span class="pln">push</span><span class="pun">([</span><span class="pln">user_input</span><span class="pun">,</span><span class="pln"> completion_text</span><span class="pun">]);</span><span class="pln">
    res</span><span class="pun">.</span><span class="pln">json</span><span class="pun">({</span><span class="pln"> message</span><span class="pun">:</span><span class="pln"> completion_text </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">if</span><span class="pln"> </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"> </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="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">
      res</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">json</span><span class="pun">({</span><span class="pln"> error</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Something went wrong'</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">
      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">message</span><span class="pun">);</span><span class="pln">
      res</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">json</span><span class="pun">({</span><span class="pln"> error</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Something went wrong'</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>
	لاحظ أننا قمنا بتخزين الرسائل القادمة من طلب http المرسل من المستخدم داخل المتغير <code>message</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_671_30" style=""><span class="pln">message </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">message</span><span class="pun">;</span><span class="pln"> </span></pre>

<p>
	وأعدنا الاستجابة التي نحصل عليها عبر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_387_17" style=""><span class="pln">res</span><span class="pun">.</span><span class="pln">json</span><span class="pun">({</span><span class="pln"> message</span><span class="pun">:</span><span class="pln"> completion_text </span><span class="pun">});</span></pre>

<p>
	انتهينا الآن من بناء الواجهة الخلفية للتطبيق وهي جاهزة للعمل، وكخطوة إضافية تُطلب عادة في اختبار الواجهات الخلفية، يمكنك تجربة نقاط الوصول عبر تطبيق postman أو Insomnia والتأكد من أن كل شيء يعمل بشكل صحيح.
</p>

<h2 id="-3">
	بناء الواجهة التفاعلية للتطبيق
</h2>

<p>
	سنبدأ الآن بناء الواجهة الأمامية للتطبيق وهي <a href="https://academy.hsoub.com/programming/html/html-%D9%88-css-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86-%D9%83%D9%8A%D9%81-%D8%AA%D8%B5%D9%85%D9%85-%D8%A3%D9%88%D9%84-%D8%B5%D9%81%D8%AD%D8%A9-%D9%88%D9%8A%D8%A8-%D9%84%D9%83-r242/" rel="">صفحة HTML</a> بسيطة تمكن من التفاعل مع خدمة ChatGPT ورؤية النتيجة مباشرةً.
</p>

<h3 id="-4">
	إنشاء الصفحة الرئيسية
</h3>

<p>
	ننشئ ملف index.html الذي يمثل واجهة التطبيق الذي نعمل على بنائه:
</p>

<pre class="ipsCode">$ touch index.html
</pre>

<p>
	نضيف كود HTML الأساسي ونضع ضمن وسم title عنوان الصفحة ولتكن دردش مع البوت chatbot:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_387_19" 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="pln"> </span><span class="atn">dir</span><span class="pun">=</span><span class="atv">"rtl"</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;title&gt;</span><span class="pln">دردش مع البوت chatbot</span><span class="tag">&lt;/title&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;/body&gt;</span><span class="pln">
</span><span class="tag">&lt;/html&gt;</span></pre>

<p>
	الآن نكتب كود الصفحة داخل وسم <code>body</code>:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_387_21" style=""><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">"container"</span><span class="tag">&gt;</span><span class="pln">
      </span><span class="tag">&lt;h1&gt;</span><span class="pln">دردش مع البوت chatbot</span><span class="tag">&lt;/h1&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">"conversation"</span><span class="tag">&gt;&lt;/div&gt;</span><span class="pln">
      </span><span class="tag">&lt;form&gt;</span><span class="pln">
        </span><span class="tag">&lt;input</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">id</span><span class="pun">=</span><span class="atv">"message"</span><span class="pln"> </span><span class="atn">placeholder</span><span class="pun">=</span><span class="atv">"اكتب رسالتك هنا"</span><span class="pln"> </span><span class="tag">/&gt;</span><span class="pln">
        </span><span class="tag">&lt;button</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></pre>

<p>
	هنا قمنا بإضافة عنوان يظهر بأعلى الصفحة واستمارة <code>form</code> تحوي قسمًا لإدخال استفسارات المستخدم وزر الإرسال، ثم نعرض المحادثة داخل العنصر <code>&lt;div id="conversation"&gt;&lt;/div&gt;</code>.
</p>

<h3 id="-5">
	تنسيق الصفحة الرئيسية
</h3>

<p>
	نقوم بوضع الكود التالي ضمن وسم <code>&lt;head&gt;</code> وهو تنسيق جاهز بسيط أضفناه للصفحة ويمكنك تعديله أو كتابة التنسيق الخاص بك:
</p>

<pre class="ipsCode prettyprint lang-css prettyprinted" id="ips_uid_387_23" style=""><span class="pln">    </span><span class="pun">&lt;</span><span class="pln">style</span><span class="pun">&gt;</span><span class="pln">
      body </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">font-family</span><span class="pun">:</span><span class="pln"> Arial</span><span class="pun">,</span><span class="pln"> sans-serif</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">margin</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">padding</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">container </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">padding</span><span class="pun">:</span><span class="pln"> </span><span class="lit">8rem</span><span class="pln"> </span><span class="lit">16rem</span><span class="pun">;</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">

      h1 </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">text-align</span><span class="pun">:</span><span class="pln"> center</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">margin</span><span class="pun">:</span><span class="pln"> </span><span class="lit">2rem</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">

      form </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">display</span><span class="pun">:</span><span class="pln"> flex</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">justify-content</span><span class="pun">:</span><span class="pln"> space-between</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">align-items</span><span class="pun">:</span><span class="pln"> center</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">margin</span><span class="pun">:</span><span class="pln"> </span><span class="lit">2rem</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">

      input</span><span class="pun">[</span><span class="pln">type</span><span class="pun">=</span><span class="str">"text"</span><span class="pun">]</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">height</span><span class="pun">:</span><span class="pln"> </span><span class="lit">2rem</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">flex</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">padding</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1rem</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">font-size</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1.2rem</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">border-radius</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0.5rem</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">border</span><span class="pun">:</span><span class="pln"> none</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">box-shadow</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0px</span><span class="pln"> </span><span class="lit">0px</span><span class="pln"> </span><span class="lit">5px</span><span class="pln"> rgba</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0.2</span><span class="pun">);</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">

      button</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="kwd">height</span><span class="pun">:</span><span class="pln"> </span><span class="lit">4rem</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">padding</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> </span><span class="lit">1.2rem</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">margin-right</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1rem</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">font-size</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1.2rem</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">border-radius</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0.5rem</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">border</span><span class="pun">:</span><span class="pln"> none</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">background-color</span><span class="pun">:</span><span class="pln"> </span><span class="lit">#007bff</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">color</span><span class="pun">:</span><span class="pln"> white</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">cursor</span><span class="pun">:</span><span class="pln"> pointer</span><span class="pun">;</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">

      </span><span class="pun">#</span><span class="pln">conversation </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">margin</span><span class="pun">:</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">

      p </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">margin</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0.5rem</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">user </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">color</span><span class="pun">:</span><span class="pln"> </span><span class="lit">#007bff</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">font-weight</span><span class="pun">:</span><span class="pln"> bold</span><span class="pun">;</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">

      </span><span class="pun">.</span><span class="pln">chatbot </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">color</span><span class="pun">:</span><span class="pln"> </span><span class="lit">#333</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">font-weight</span><span class="pun">:</span><span class="pln"> bold</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">width</span><span class="pun">:</span><span class="pln"> </span><span class="lit">50%</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">style</span><span class="pun">&gt;</span></pre>

<p>
	الصفحة بسيطة وستظهر بالشكل التالي:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2024_01/264480924_.PNG.624a3e42d7a8dea16ca514079ffafe7f.PNG" data-fileid="142824" data-fileext="PNG" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="142824" data-ratio="39.63" data-unique="m9gd1w533" style="width: 800px; height: auto;" width="900" alt="الواجهة الأمامية.PNG" src="https://academy.hsoub.com/uploads/monthly_2024_01/.thumb.PNG.34b8fc51f8145ff4473e4a24b7ea9209.PNG"></a>
</p>

<p>
	سنعمل بعدها على تركيب أجزاء المشروع مع بعضها بربط الواجهة الأمامية مع الخلفية وتجربة خصائصه.
</p>

<h3 id="-6">
	ربط الواجهة الأمامية مع الواجهة الخلفية
</h3>

<p>
	الآن نكتب الكود الخاص بربط الواجهة الخلفية مع الواجهة التفاعلية الأمامية ونضيفه ضمن وسم <code>body</code>:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_387_25" style=""><span class="pln"> </span><span class="tag">&lt;script&gt;</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> conversationElem </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">'conversation'</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> messageInput </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">'message'</span><span class="pun">);</span><span class="pln">

    </span><span class="com">// Send message to chatbot on form submit</span><span class="pln">
    document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">'form'</span><span class="pun">).</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">'submit'</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">async</span><span class="pln"> </span><span class="pun">(</span><span class="pln">event</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</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">
      </span><span class="kwd">const</span><span class="pln"> message </span><span class="pun">=</span><span class="pln"> messageInput</span><span class="pun">.</span><span class="pln">value</span><span class="pun">;</span><span class="pln">
      messageInput</span><span class="pun">.</span><span class="pln">value </span><span class="pun">=</span><span class="pln"> </span><span class="str">''</span><span class="pun">;</span><span class="pln">

      </span><span class="com">// Send message to server and wait for response</span><span class="pln">
      </span><span class="kwd">const</span><span class="pln"> response </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> fetch</span><span class="pun">(</span><span class="str">'/message'</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="pln"> </span><span class="str">'Content-Type'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'application/json'</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"> message </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">res </span><span class="pun">=&gt;</span><span class="pln"> res</span><span class="pun">.</span><span class="pln">json</span><span class="pun">());</span><span class="pln">

      </span><span class="com">// Add user message and chatbot response to conversation</span><span class="pln">
      conversationElem</span><span class="pun">.</span><span class="pln">innerHTML </span><span class="pun">+=</span><span class="pln"> </span><span class="pun">`&lt;</span><span class="pln">p </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"user"</span><span class="pun">&gt;أنت:</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">message</span><span class="pun">}&lt;/</span><span class="pln">p</span><span class="pun">&gt;`;</span><span class="pln">
      conversationElem</span><span class="pun">.</span><span class="pln">innerHTML </span><span class="pun">+=</span><span class="pln"> </span><span class="pun">`&lt;</span><span class="pln">p </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"chatbot"</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">message</span><span class="pun">}&lt;/</span><span class="pln">p</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">response</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="tag">&lt;/script&gt;</span></pre>

<p>
	قمنا بتعريف متغيرين الأول هو <code>conversationElem</code> لقسم المحادثة والثاني <code>messageInput</code> لمدخلات المستخدم ثم قمنا باختيار النموذج الذي انشأناه في ملف index.html لمعالجة البيانات المدخلة وإرسالها إلى الواجهة الخلفية، ونستخدم لذلك الغرض دالة <code>fetch</code> من خلال عملية <code>post</code> التي قمنا بشرحها سابقًا.
</p>

<p>
	ثم نقوم باضافة رسالة المستخدم واستجابة بوت المحادثة المرسلة من الواجهة الخلفية للتطبيق من خلال استخدام الدالة <code>innerHTML</code> ونضيف العناصر ضمن وسم النص <code>&lt;p&gt;</code> ونكون بذلك قد انتهينا من المشروع النموذجي.
</p>

<h3 id="-7">
	الكود النهائي للمشروع
</h3>

<p>
	ملف index.js:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_387_27" 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"> </span><span class="typ">OpenAI</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"openai"</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="kwd">const</span><span class="pln"> openai </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">OpenAI</span><span class="pun">({</span><span class="pln">
  apiKey</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">OPENAI_API_KEY</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"> 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"> 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="com">// Array to store previous messages</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> history </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[];</span><span class="pln">

</span><span class="com">// Middleware to parse JSON in the request body</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">

</span><span class="com">// Serve the index.html file</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">sendFile</span><span class="pun">(</span><span class="pln">__dirname </span><span class="pun">+</span><span class="pln"> </span><span class="str">"/index.html"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="com">// Handle incoming messages</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">"/message"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">async</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">
  </span><span class="kwd">const</span><span class="pln"> message </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">message</span><span class="pun">;</span><span class="pln">

  </span><span class="com">// If there is a previous message, include it in the prompt</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> user_input </span><span class="pun">=</span><span class="pln">
    history</span><span class="pun">.</span><span class="pln">length </span><span class="pun">&gt;</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"> </span><span class="pun">المحادثة:</span><span class="pln">\n$</span><span class="pun">{</span><span class="pln">history</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="str">"\n"</span><span class="pun">)}</span><span class="pln">\n</span><span class="pun">أنت:</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">message</span><span class="pun">}</span><span class="pln">\n</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">message</span><span class="pun">}</span><span class="pln">\n</span><span class="pun">`;</span><span class="pln">

  </span><span class="kwd">const</span><span class="pln"> messages </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="kwd">const</span><span class="pln"> </span><span class="pun">[</span><span class="pln">input_text</span><span class="pun">,</span><span class="pln"> completion_text</span><span class="pun">]</span><span class="pln"> </span><span class="kwd">of</span><span class="pln"> history</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    messages</span><span class="pun">.</span><span class="pln">push</span><span class="pun">({</span><span class="pln"> role</span><span class="pun">:</span><span class="pln"> </span><span class="str">"user"</span><span class="pun">,</span><span class="pln"> content</span><span class="pun">:</span><span class="pln"> input_text </span><span class="pun">});</span><span class="pln">
    messages</span><span class="pun">.</span><span class="pln">push</span><span class="pun">({</span><span class="pln"> role</span><span class="pun">:</span><span class="pln"> </span><span class="str">"assistant"</span><span class="pun">,</span><span class="pln"> content</span><span class="pun">:</span><span class="pln"> completion_text </span><span class="pun">});</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  messages</span><span class="pun">.</span><span class="pln">push</span><span class="pun">({</span><span class="pln"> role</span><span class="pun">:</span><span class="pln"> </span><span class="str">"user"</span><span class="pun">,</span><span class="pln"> content</span><span class="pun">:</span><span class="pln"> user_input </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"> completion </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> openai</span><span class="pun">.</span><span class="pln">chat</span><span class="pun">.</span><span class="pln">completions</span><span class="pun">.</span><span class="pln">create</span><span class="pun">({</span><span class="pln">
      model</span><span class="pun">:</span><span class="pln"> </span><span class="str">"gpt-3.5-turbo"</span><span class="pun">,</span><span class="pln">
      messages</span><span class="pun">:</span><span class="pln"> messages</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"> completion_text </span><span class="pun">=</span><span class="pln"> completion</span><span class="pun">.</span><span class="pln">data</span><span class="pun">.</span><span class="pln">choices</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">message</span><span class="pun">.</span><span class="pln">content</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">completion_text</span><span class="pun">);</span><span class="pln">

    history</span><span class="pun">.</span><span class="pln">push</span><span class="pun">([</span><span class="pln">user_input</span><span class="pun">,</span><span class="pln"> completion_text</span><span class="pun">]);</span><span class="pln">
    res</span><span class="pun">.</span><span class="pln">json</span><span class="pun">({</span><span class="pln"> message</span><span class="pun">:</span><span class="pln"> completion_text </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">if</span><span class="pln"> </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"> </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="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">
      res</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">json</span><span class="pun">({</span><span class="pln"> error</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Something went wrong"</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">
      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">message</span><span class="pun">);</span><span class="pln">
      res</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">json</span><span class="pun">({</span><span class="pln"> error</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Something went wrong"</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="com">// Start the server</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"> is 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>
	ملف index.html:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_387_29" 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="pln"> </span><span class="atn">dir</span><span class="pun">=</span><span class="atv">"rtl"</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;style&gt;</span><span class="pln">
      body </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">font-family</span><span class="pun">:</span><span class="pln"> Arial</span><span class="pun">,</span><span class="pln"> sans-serif</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">margin</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">padding</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">container </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">padding</span><span class="pun">:</span><span class="pln"> </span><span class="lit">8rem</span><span class="pln"> </span><span class="lit">16rem</span><span class="pun">;</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">

      h1 </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">text-align</span><span class="pun">:</span><span class="pln"> center</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">margin</span><span class="pun">:</span><span class="pln"> </span><span class="lit">2rem</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">

      form </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">display</span><span class="pun">:</span><span class="pln"> flex</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">justify-content</span><span class="pun">:</span><span class="pln"> space-between</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">align-items</span><span class="pun">:</span><span class="pln"> center</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">margin</span><span class="pun">:</span><span class="pln"> </span><span class="lit">2rem</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">

      input</span><span class="pun">[</span><span class="pln">type</span><span class="pun">=</span><span class="str">"text"</span><span class="pun">]</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">height</span><span class="pun">:</span><span class="pln"> </span><span class="lit">2rem</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">flex</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">padding</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1rem</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">font-size</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1.2rem</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">border-radius</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0.5rem</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">border</span><span class="pun">:</span><span class="pln"> none</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">box-shadow</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0px</span><span class="pln"> </span><span class="lit">0px</span><span class="pln"> </span><span class="lit">5px</span><span class="pln"> rgba</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0.2</span><span class="pun">);</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">

      button</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="kwd">height</span><span class="pun">:</span><span class="pln"> </span><span class="lit">4rem</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">padding</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> </span><span class="lit">1.2rem</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">margin-right</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1rem</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">font-size</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1.2rem</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">border-radius</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0.5rem</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">border</span><span class="pun">:</span><span class="pln"> none</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">background-color</span><span class="pun">:</span><span class="pln"> </span><span class="lit">#007bff</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">color</span><span class="pun">:</span><span class="pln"> white</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">cursor</span><span class="pun">:</span><span class="pln"> pointer</span><span class="pun">;</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">

      </span><span class="pun">#</span><span class="pln">conversation </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">margin</span><span class="pun">:</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">

      p </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">margin</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0.5rem</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">user </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">color</span><span class="pun">:</span><span class="pln"> </span><span class="lit">#007bff</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">font-weight</span><span class="pun">:</span><span class="pln"> bold</span><span class="pun">;</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">

      </span><span class="pun">.</span><span class="pln">chatbot </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">color</span><span class="pun">:</span><span class="pln"> </span><span class="lit">#333</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">font-weight</span><span class="pun">:</span><span class="pln"> bold</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">width</span><span class="pun">:</span><span class="pln"> </span><span class="lit">50%</span><span class="pun">;</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
    </span><span class="tag">&lt;/style&gt;</span><span class="pln">

    </span><span class="tag">&lt;title&gt;</span><span class="pln">دردش مع البوت chatbot</span><span class="tag">&lt;/title&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">"container"</span><span class="tag">&gt;</span><span class="pln">
      </span><span class="tag">&lt;h1&gt;</span><span class="pln">دردش مع البوت chatbot</span><span class="tag">&lt;/h1&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">"conversation"</span><span class="tag">&gt;&lt;/div&gt;</span><span class="pln">
      </span><span class="tag">&lt;form&gt;</span><span class="pln">
        </span><span class="tag">&lt;input</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">id</span><span class="pun">=</span><span class="atv">"message"</span><span class="pln"> </span><span class="atn">placeholder</span><span class="pun">=</span><span class="atv">"اكتب رسالتك هنا"</span><span class="pln"> </span><span class="tag">/&gt;</span><span class="pln">
        </span><span class="tag">&lt;button</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;script&gt;</span><span class="pln">
      </span><span class="kwd">const</span><span class="pln"> conversationElem </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">"conversation"</span><span class="pun">);</span><span class="pln">
      </span><span class="kwd">const</span><span class="pln"> messageInput </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">"message"</span><span class="pun">);</span><span class="pln">

      </span><span class="com">// Send message to chatbot on form submit</span><span class="pln">
      document
        </span><span class="pun">.</span><span class="pln">querySelector</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">addEventListener</span><span class="pun">(</span><span class="str">"submit"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">async</span><span class="pln"> </span><span class="pun">(</span><span class="pln">event</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</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">
          </span><span class="kwd">const</span><span class="pln"> message </span><span class="pun">=</span><span class="pln"> messageInput</span><span class="pun">.</span><span class="pln">value</span><span class="pun">;</span><span class="pln">
          messageInput</span><span class="pun">.</span><span class="pln">value </span><span class="pun">=</span><span class="pln"> </span><span class="str">""</span><span class="pun">;</span><span class="pln">

          </span><span class="com">// Send message to server and wait for response</span><span class="pln">
          </span><span class="kwd">const</span><span class="pln"> response </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> fetch</span><span class="pun">(</span><span class="str">"/message"</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="pln"> </span><span class="str">"Content-Type"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"application/json"</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"> message </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">res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> res</span><span class="pun">.</span><span class="pln">json</span><span class="pun">());</span><span class="pln">

          </span><span class="com">// Add user message and chatbot response to conversation</span><span class="pln">
          conversationElem</span><span class="pun">.</span><span class="pln">innerHTML </span><span class="pun">+=</span><span class="pln"> </span><span class="pun">`&lt;</span><span class="pln">p </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"user"</span><span class="pun">&gt;أنت:</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">message</span><span class="pun">}&lt;/</span><span class="pln">p</span><span class="pun">&gt;`;</span><span class="pln">
          conversationElem</span><span class="pun">.</span><span class="pln">innerHTML </span><span class="pun">+=</span><span class="pln"> </span><span class="pun">`&lt;</span><span class="pln">p </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"chatbot"</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">message</span><span class="pun">}&lt;/</span><span class="pln">p</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">response</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="tag">&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>

<h2 id="-8">
	تجربة التطبيق
</h2>

<p>
	ندخل الأمر التالي بالطرفية لتشغيل التطبيق:
</p>

<pre class="ipsCode">$ node index.js
</pre>

<p>
	ثم نذهب للمتصفح ونقوم بإدخال العنوان التالي localhost:3000 لتظهر لنا واجهة التطبيق، ندخل أي سؤال وليكن كتابة قصة قصيرة وننتظر الرد من التطبيق كما في الصورة التالية:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2024_01/2112260782_.png.2db5da417832d5a261d96b667cc21e53.png" data-fileid="142825" data-fileext="png" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="142825" data-ratio="31.25" data-unique="uq57vvgth" style="width: 800px; height: auto;" width="900" alt="تنفيذ التطبيق.png" src="https://academy.hsoub.com/uploads/monthly_2024_01/.thumb.png.8859b5b2daca02e865845df05373db23.png"></a>
</p>

<p>
	يمكنك تحميل كود التطبيق من هنا <a class="ipsAttachLink" data-fileid="142826" href="https://academy.hsoub.com/applications/core/interface/file/attachment.php?id=142826&amp;key=207274d4f67f741011eae1a8392a853c" data-fileext="zip" rel="">chatgpt-api-bot.zip</a>
</p>

<h2 id="-9">
	تطويرات يمكن إضافتها للمشروع
</h2>

<p>
	يمكننا تطوير المشروع السابق بالعديد من الطرق، ومنها:
</p>

<ol>
	<li>
		إضافة ميزات لبوت الدردشة: يمكنك إضافة ميزات جديدة مثل إضافة قائمة من الخيارات للمستخدم للاختيار من بينها مثل إضافة طريقة رد البوت هل هي بطريقة جدية أو بطريقة مضحكة أو بسخرية …إلخ، أو تمكين البوت من إجراء بعض العمليات الرياضية أو توفير إجابات علمية أو حتى إنشاء بوت لتعلم شيء جديد مثل تعلم البرمجة أو تعلم الإنجليزية وهكذا.
	</li>
	<li>
		تحسين واجهة المستخدم: يمكنك تحسين واجهة المستخدم لتبدو أكثر جاذبية وسهولة في الاستخدام، ويمكنك استخدام <a href="https://academy.hsoub.com/programming/css/%D8%A3%D9%81%D8%B6%D9%84-25-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%AA%D8%AD%D8%B1%D9%8A%D9%83-%D9%81%D9%8A-css-r657/" rel="">مكتبات CSS</a> المختلفة لتحسين التصميم والإضاءة على المكونات المهمة في الصفحة.
	</li>
	<li>
		تخزين المحادثات: يمكنك إضافة القدرة على تخزين المحادثات السابقة بين المستخدم والشات بوت، وعرضها للمستخدم عند الضرورة.
	</li>
	<li>
		إضافة تحليلات: يمكنك إضافة تحليلات للمحادثات، مثل عدد المحادثات الناجحة والمحادثات التي تم إنهاؤها بشكل مبكر التي قام المستخدم باغلاقها، وعرضها بشكل رسومي لمساعدتك في تحسين أداء البوت.
	</li>
	<li>
		استخدام مكتبة <a href="https://academy.hsoub.com/programming/javascript/react/%D8%A8%D8%AF%D8%A1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-react-r1569/" rel="">React</a> أو <a href="https://academy.hsoub.com/programming/javascript/vuejs/%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-vuejs-r1664/" rel="">Vue</a> لتصميم واجهة أمامية تفاعلية أكثر وأداء أفضل.
	</li>
	<li>
		ربط البوت مع خدمات openAI الأخرى المختلفة مثل استعمال رده في توليد صورة من خدمة DALL·E
	</li>
</ol>

<p>
	هذه بعض الأفكار التي يمكن تطبيقها لتطوير مشروع الدردشة ويمكنك استخدامها كنقطة انطلاق لمشاريعك المستقبلية، ويمكنك أن تطلع على <a href="https://platform.openai.com/examples" rel="external nofollow">صفحة الأمثلة</a> من openAI التي تحوي على عشرات الأمثلة والتطبيقات التي يمكن الاستلهام منها والبناء عليها.
</p>

<h2 id="-10">
	الخلاصة
</h2>

<p>
	بهذا نكون قد انتهينا من هذا المقال الذي شرحنا فيه كيفية ربط واجهة برمجة تطبيقات OpenAI <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> عبر خدمة ChatGPT مع Node.js لإنشاء بوت دردشة بسيط. وباستخدام هذه التقنيات يمكن للمطورين إضافة قيمة كبيرة لتطبيقاتهم على الويب، وتوفير تجربة مميزة للمستخدمين، وكما رأينا، فإن استخدام OpenAI's ChatGPT في Node.js ليس صعبًا بقدر ما يبدو، ويمكن لأي مطور أن يبدأ في تجربتها والاستفادة منها.
</p>

<p>
	ومن المؤكد أننا سنشهد مزيدًا من التطورات والتحسينات في مجال الذكاء الاصطناعي والبوتات الذكية في المستقبل، ولذلك يجب أن نستعد لمواكبة هذه التحولات والتطورات بالاستمرار في تعلم وتحسين مهاراتنا في هذا المجال، فلا شك أنه بالعمل الجاد والاستمرار في الابتكار، يمكن للجميع أن يحققوا نجاحات كبيرة في هذا المجال الواعد.
</p>

<h2 id="-11">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/apps/web/%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-chatgpt-api-%D9%84%D8%AA%D8%AD%D8%B3%D9%8A%D9%86-%D8%AE%D8%AF%D9%85%D8%A7%D8%AA%D9%83-%D8%B9%D8%A8%D8%B1-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%B1%D9%86%D8%AA-r909/" rel="">دليل استخدام ChatGPT <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> لتحسين خدماتك عبر الإنترنت</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/php/%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%88%D8%B5%D9%81%D8%A9-%D9%84%D8%A7%D9%82%D8%AA%D8%B1%D8%A7%D8%AD-%D8%A7%D9%84%D9%88%D8%AC%D8%A8%D8%A7%D8%AA-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-chatgpt-%D9%88-dall-e-%D9%81%D9%8A-php-r2005/" rel="">تطوير تطبيق 'وصفة' لاقتراح الوجبات باستخدام ChatGPT و DALL-E في PHP</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%84%D9%85%D9%86%D9%8A-%D9%84%D8%A7%D8%B3%D8%AA%D9%83%D8%B4%D8%A7%D9%81-%D8%A7%D9%84%D9%85%D9%88%D8%A7%D8%B6%D9%8A%D8%B9-%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%D9%8A%D8%A8%D8%AA-%D9%88-chatgpt-r2031/" rel="">تطوير تطبيق 'علمني' لاستكشاف المواضيع باستخدام جافا سكريبت و ChatGPT</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2233</guid><pubDate>Sun, 07 Jan 2024 12:00:00 +0000</pubDate></item><item><title>&#x645;&#x642;&#x62F;&#x645;&#x629; &#x625;&#x644;&#x649; &#x627;&#x644;&#x642;&#x648;&#x627;&#x644;&#x628; Template &#x641;&#x64A; Express: &#x625;&#x646;&#x634;&#x627;&#x621; &#x627;&#x644;&#x642;&#x627;&#x644;&#x628; &#x627;&#x644;&#x623;&#x633;&#x627;&#x633;&#x64A; &#x644;&#x645;&#x648;&#x642;&#x639; &#x645;&#x643;&#x62A;&#x628;&#x629; &#x645;&#x62D;&#x644;&#x64A;&#x629; &#x645;&#x62B;&#x627;&#x644;&#x64B;&#x627;</title><link>https://academy.hsoub.com/programming/javascript/nodejs/express/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-template-%D9%81%D9%8A-express-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%A7%D9%84%D9%82%D8%A7%D9%84%D8%A8-%D8%A7%D9%84%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A-%D9%84%D9%85%D9%88%D9%82%D8%B9-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D9%85%D8%AB%D8%A7%D9%84%D9%8B%D8%A7-r2209/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_12/---Template--Express-------.png.a283cf2cf5c1c442edced48970b1c92a.png" /></p>
<p>
	القالب Template هو ملف نصي يحدّد بنية أو تخطيط ملف الخرج مع استخدام العناصر البديلة لتمثيل مكان إدراج البيانات عند عرض القالب، ويُشار إلى القوالب في <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-express-%D9%88%D8%A8%D9%8A%D8%A6%D8%A9-node-r2168/" rel="">إطار عمل Express</a> بوصفها عروضًا Views.
</p>

<h2 id="express">
	خيارات قوالب Express
</h2>

<p>
	يمكن استخدام إطار عمل Express مع العديد من محرّكات عرض القوالب المختلفة، إذ سنستخدم في هذا المقال Pug (المعروفة سابقًا باسم Jade) لقوالبنا، والتي تُعَد لغة قوالب Node الأكثر شيوعًا، وتصف نفسها بأنها صيغة نظيفة وحساسة للمسافات لكتابة شيفرة <a href="https://academy.hsoub.com/programming/html/" rel="">HTML</a> وهي متأثرة كثيرًا بلغة <a href="https://haml.info/" rel="external nofollow">Haml</a>.
</p>

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

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

<h2 id="">
	ضبط القوالب
</h2>

<p>
	ضبطنا موقع المكتبة المحلية LocalLibrary لاستخدام Pug عندما أنشأنا <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2170/" rel="">موقع الويب الهيكلي</a>، إذ يجب أن تُضمَّن وحدة pug بوصفها اعتمادية في ملف package.json الخاص بموقع الويب، ويجب أن ترى إعدادات الضبط التالية في الملف app.js؛ إذ تخبرنا هذه الإعدادات أننا نستخدم Pug بوصفها محرّك عرض، وأن إطار عمل Express يجب أن يبحث عن القوالب في المجلد الفرعي "‎/views".
</p>

<pre class="ipsCode">// إعداد محرك العروض
app.set("views", path.join(__dirname, "views"));
app.set("view engine", "pug");
</pre>

<p>
	إذا بحثتَ في مجلد العروض views، فسترى ملفات "‎.pug" للعروض الافتراضية الخاصة بالمشروع، ويتضمن ذلك عرض الصفحة الرئيسية index.pug والقالب الأساسي layout.pug الذي يجب أن نضع مكانه محتوانا.
</p>

<pre class="ipsCode" id="ips_uid_4280_21">/express-locallibrary-tutorial  //the project root
  /views
    error.pug
    index.pug
    layout.pug</pre>

<h2 id="-1">
	صيغة القوالب
</h2>

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

<pre class="ipsCode" id="ips_uid_1068_8">doctype html
html(lang="en")
  head
    title= title
    script(type='text/javascript').
  body
    h1= title

    p This is a line with #[em some emphasis] and #[strong strong text] markup.
    p This line has un-escaped data: !{'&lt;em&gt; is emphasized&lt;/em&gt;'} and escaped data: #{'&lt;em&gt; is not emphasized&lt;/em&gt;'}.
      | This line follows on.
    p= 'Evaluated and &lt;em&gt;escaped expression&lt;/em&gt;:' + title

    &lt;!-- مباشرةً HTML يمكنك إضافة تعليقات--&gt;
    // يمكنك ‫إضافة تعليقات جافاسكربت ذات سطر واحد وتُنشَأ في تعليقات HTML
    //- ‫يضمن تقديم تعليق جافاسكربت مؤلف من سطر واحد مع "-//" عدم عرض التعليق بوصفه شيفرة HTML

    p A line with a link
      a(href='/catalog/authors') Some link text
      |  and some extra text.

    #container.col
      if title
        p A variable named "title" exists.
      else
        p A variable named "title" does not exist.
      p.
        Pug is a terse and simple template language with a
        strong focus on performance and powerful features.

    h2 Generate a list

    ul
      each val in [1, 2, 3, 4, 5]
        li= val</pre>

<p>
	تُعرَّف سمات العنصر بين أقواس بعد العنصر المرتبط بها، وتُعرَّف السمات ضمن الأقواس في قوائم مفصولٌ بينها بفواصل أو بمسافات بيضاء لأزواج أسماء السمات وقيمها مثل:
</p>

<pre class="ipsCode" id="ips_uid_1068_10">script(type='text/javascript'), link(rel='stylesheet', href='/stylesheets/style.css')</pre>

<p>
	أو
</p>

<pre class="ipsCode" id="ips_uid_1068_12">meta(name='viewport' content='width=device-width initial-scale=1')</pre>

<p>
	جرى تهريب escaped قيم جميع السمات مثل تحويل محارف مثل المحرف "<code>&lt;</code>" إلى ما يكافئها من شيفرة HTML مثل "<code>‎&amp;gt;‎</code>" لمنع هجمات حقن شيفرة <a href="https://academy.hsoub.com/programming/javascript/" rel="">جافا سكريبت</a> أو هجمات السكريبتات العابرة للمواقع cross-site scripting attacks - أو اختصارًا XSS.
</p>

<p>
	إذا كان الوسم متبوعًا بعلامة مساواة، فسيُتعامَل مع النص التالي بوصفه تعبير جافا سكريبت، فمثلًا سيكون محتوى الوسم <code>h1</code> في السطر الأول متغير <code>title</code> (إما مُعرَّفًا في الملف أو مُمرَّرًا إلى القالب من إطار عمل Express). يكون محتوى الفقرة في السطر الثاني سلسلة نصية متعاقبة مع المتغير <code>title</code>. يكون السلوك الافتراضي في كلتا الحالتين هو تهريب السطر.
</p>

<pre class="ipsCode" id="ips_uid_1068_14">h1= title
p= 'Evaluated and &lt;em&gt;escaped expression&lt;/em&gt;:' + title</pre>

<p>
	إذا لم يكن هناك رمز مساواة بعد الوسم، فسيُتعامَل مع المحتوى بوصفه نصًا عاديًا، إذ يمكنك ضمن النص العادي إدخال البيانات التي جرى تهريبها والتي لم يجرِ تهريبها باستخدام الصيغتين <code>{}#</code> و <code>{}!</code> على التوالي، ويمكنك أيضًا إضافة شيفرة HTML خام ضمن النص العادي.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1068_16" style=""><span class="pln">p This is a line with #[em some emphasis] and #[strong strong text] markup.
p This line has an un-escaped string: !{'</span><span class="tag">&lt;em&gt;</span><span class="pln"> is emphasized</span><span class="tag">&lt;/em&gt;</span><span class="pln">'}, an escaped string: #{'</span><span class="tag">&lt;em&gt;</span><span class="pln"> is not emphasized</span><span class="tag">&lt;/em&gt;</span><span class="pln">'}, and escaped variables: #{title}.</span></pre>

<p>
	<strong>ملاحظة</strong>: سترغب دائمًا في تهريب البيانات من المستخدمين باستخدام صيغة <code>{}#</code>، وقد تُعرَض البيانات التي يمكن الوثوق بها، مثل أعداد السجلات المُولَّدة وما إلى ذلك دون تهريب القيم.
</p>

<p>
	يمكنك استخدام محرف الشريط العمودي ('|') في بداية السطر للإشارة إلى النص العادي، فمثلًا سيُعرَض النص الإضافي التالي على سطر الرابط نفسه، ولكنه لن يمثل رابطًا:
</p>

<pre class="ipsCode" id="ips_uid_1068_18">a(href='http://someurl/') Link text
| Plain text</pre>

<p>
	تسمح Pug بإجراء عمليات شرطية باستخدام <code>if</code> و <code>else</code> و <code>else if</code> و <code>unless</code> مثل:
</p>

<pre class="ipsCode" id="ips_uid_1068_20">if title
  p A variable named "title" exists
else
  p A variable named "title" does not exist</pre>

<p>
	يمكنك أيضًا إجراء عمليات الحلقة أو التكرار باستخدام صيغة <code>each-in</code> أو <code>while</code>، إذ أنشأنا في جزء الشيفرة البرمجية التالي حلقةً تكرارية تمر عبر عناصر مصفوفة لعرض قائمة متغيرات. لاحظ استخدام ''li=‎' لتقييم "val" بوصفه متغيرًا، ويمكن أيضًا تمرير القيمة التي تكررها إلى القالب بوصفها متغيرًا.
</p>

<pre class="ipsCode" id="ips_uid_1068_22">ul
  each val in [1, 2, 3, 4, 5]
    li= val</pre>

<p>
	تدعم الصيغة أيضًا التعليقات (التي يمكن عرضها في الخرج أو عدم عرضها وفق ما تريده)، والمخاليط Mixins لإنشاء كتل من الشيفرة البرمجية قابلة لإعادة الاستخدام، وتعليمات الحالة، والعديد من الميزات الأخرى. اطلع على <a href="https://pugjs.org/api/getting-started.html" rel="external nofollow">توثيق Pug</a> لمزيد من المعلومات.
</p>

<h2 id="-2">
	توسيع القوالب
</h2>

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

<p>
	يبدو مثلًا القالب الأساسي layout.pug الذي أنشأناه في <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2170/" rel="">المشروع الهيكلي</a> كما يلي:
</p>

<pre class="ipsCode" id="ips_uid_1068_27">doctype html
html
  head
    title= title
    link(rel='stylesheet', href='/stylesheets/style.css')
  body
    block content</pre>

<p>
	يُستخدَم الوسم <code>block</code> لتمييز أقسام المحتوى التي يمكن استبدالها في قالب مشتق، فإن لم يُعاد تعريف الكتلة، فسيُستخدَم تقديمها في الصنف الأساسي.
</p>

<p>
	يوضح القالب index.pug الافتراضي -الذي أنشأناه لمشروعنا الهيكلي- كيفية تعديل القالب الأساسي، إذ يحدّد الوسم <code>extends</code> القالب الأساسي المراد استخدامه، ثم نستخدم <code>block section_name</code> للإشارة إلى المحتوى الجديد للقسم الذي سنعدّله.
</p>

<pre class="ipsCode" id="ips_uid_1068_30">extends layout

block content
  h1= title
  p Welcome to #{title}</pre>

<h2 id="locallibrary">
	القالب الأساسي لموقع المكتبة المحلية LocalLibrary
</h2>

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

<p>
	افتح القالب ‎/views/layout.pug وضع الشيفرة التالية مكان المحتوى الموجود مسبقًا:
</p>

<pre class="ipsCode" id="ips_uid_1068_32">doctype html
html(lang='en')
  head
    title= title
    meta(charset='utf-8')
    meta(name='viewport', content='width=device-width, initial-scale=1')
    link(rel="stylesheet", href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css", integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N", crossorigin="anonymous")
    script(src="https://code.jquery.com/jquery-3.5.1.slim.min.js", integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj", crossorigin="anonymous")
    script(src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.min.js", integrity="sha384-+sLIOodYLS7CIrQpBjl+C7nPvqq+FbNUBDunl/OZv93DB7Ln/533i8e/mZXLi/P+", crossorigin="anonymous")
    link(rel='stylesheet', href='/stylesheets/style.css')
  body
    div(class='container-fluid')
      div(class='row')
        div(class='col-sm-2')
          block sidebar
            ul(class='sidebar-nav')
              li
                a(href='/catalog') Home
              li
                a(href='/catalog/books') All books
              li
                a(href='/catalog/authors') All authors
              li
                a(href='/catalog/genres') All genres
              li
                a(href='/catalog/bookinstances') All book-instances
              li
                hr
              li
                a(href='/catalog/author/create') Create new author
              li
                a(href='/catalog/genre/create') Create new genre
              li
                a(href='/catalog/book/create') Create new book
              li
                a(href='/catalog/bookinstance/create') Create new book instance (copy)

        div(class='col-sm-10')
          block content</pre>

<p>
	يتضمن القالب شيفرة جافا سكريبت و <a href="https://academy.hsoub.com/programming/css/" rel="">CSS</a> من <a href="https://academy.hsoub.com/programming/css/bootstrap/" rel="">بوتستراب Bootstrap</a> لتحسين تخطيط وعرض صفحة HTML؛ إذ يُعَد استخدام بوتستراب أو أي إطار عمل ويب آخر من طرف العميل طريقةً سريعة لإنشاء صفحة جذابة يمكن أن تتناسب مع أحجام المتصفحات المختلفة، ويتيح أيضًا التعامل مع عرض الصفحة دون الحاجة إلى الدخول في التفاصيل، إذ نريد فقط التركيز على الشيفرة البرمجية من طرف الخادم حاليًا.
</p>

<p>
	<strong>ملاحظة</strong>: تكون السكربتات مُحمَّلة على أصول مختلطة، لذا يجب السماح صراحةً بتحميل هذه الملفات لاحقًا عندما نضيف برمجية وسيطة للأمان. اطلع على فقرة استخدام حزمة Helmet للحماية من الثغرات المعروفة من مقال <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AE%D8%A7%D9%85%D8%B3-%D8%A7%D9%84%D9%86%D8%B4%D8%B1-%D9%81%D9%8A-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%A7%D8%AC-r2202/" rel="">النشر في بيئة الإنتاج</a> لمزيد من المعلومات.
</p>

<p>
	يجب أن يكون التخطيط واضحًا إلى حدٍ ما إذا قرأت القسم السابق. لاحظ استخدام كتلة <code>block content</code> بوصفها عنصرًا بديلًا للمكان الذي سيوضع فيه محتوى صفحاتنا.
</p>

<p>
	يشير القالب الأساسي أيضًا إلى ملف CSS محلي (style.css) الذي يوفر بعض التنسيق الإضافي. افتح الملف "‎/public/stylesheets/style.css" وضع شيفرة CSS التالية مكان محتواه:
</p>

<pre class="ipsCode prettyprint lang-css prettyprinted" id="ips_uid_4280_8" style=""><span class="pun">.</span><span class="pln">sidebar-nav </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">margin-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="kwd">padding</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">list-style</span><span class="pun">:</span><span class="pln"> none</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	لدينا الآن قالب أساسي لإنشاء صفحات مع شريط جانبي، وسنستخدمه في المقالات التالية لتعريف الصفحات الفردية.
</p>

<p>
	ترجمة -وبتصرُّف- للمقالين <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Template_primer" rel="external nofollow">Template primer</a> و <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/LocalLibrary_base_template" rel="external nofollow">LocalLibrary base template</a>.
</p>

<h2 id="-3">
	اقرأ المزيد
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AE%D8%A7%D9%85%D8%B3-%D8%A7%D9%84%D9%86%D8%B4%D8%B1-%D9%81%D9%8A-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%A7%D8%AC-r2202/" rel="">تطبيق عملي لتعلم Express - الجزء الخامس: النشر في بيئة الإنتاج</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r1737/" rel="">كيفية استخدام القوالب في تطبيقات فلاسك Flask</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-express-%D9%88%D8%A8%D9%8A%D8%A6%D8%A9-node-r2168/" rel="">مدخل إلى إطار عمل الويب Express وبيئة Node</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D8%AF%D9%88%D9%91%D9%86%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-nodejs-%D9%88-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-r19/" rel="">مدخل إلى express إنشاء مدوّنة باستخدام Node.js و Express (الجزء الأول)</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2209</guid><pubDate>Tue, 02 Jan 2024 12:02:05 +0000</pubDate></item><item><title>&#x62A;&#x637;&#x628;&#x64A;&#x642; &#x639;&#x645;&#x644;&#x64A; &#x644;&#x62A;&#x639;&#x644;&#x645; Express - &#x627;&#x644;&#x62C;&#x632;&#x621; &#x627;&#x644;&#x62E;&#x627;&#x645;&#x633;: &#x627;&#x644;&#x646;&#x634;&#x631; &#x641;&#x64A; &#x628;&#x64A;&#x626;&#x629; &#x627;&#x644;&#x625;&#x646;&#x62A;&#x627;&#x62C;</title><link>https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AE%D8%A7%D9%85%D8%B3-%D8%A7%D9%84%D9%86%D8%B4%D8%B1-%D9%81%D9%8A-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%A7%D8%AC-r2202/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_12/---.png.0749086c731f66323d59d5bb29f3ccba.png" /></p>
<p>
	أنشأتَ واختبرتَ موقع <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2170/" rel="">المكتبة المحلية LocalLibrary</a>، ويجب الآن تثبيته على خادم ويب عام ليصل إليه موظفو المكتبة وأعضاؤها عبر <a href="https://academy.hsoub.com/devops/networking/%D8%A2%D9%84%D9%8A%D8%A9-%D8%B9%D9%85%D9%84-%D8%B4%D8%A8%D9%83%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%B1%D9%86%D8%AA-r571/" rel="">الإنترنت</a>. يقدّم هذا المقال نظرة عامة حول كيفية البحث عن مضيف لنشر موقعك، وما عليك فعله لتجهيز موقعك لمرحلة الإنتاج.
</p>

<ul>
	<li>
		<strong>المتطلبات الأساسية</strong>: إكمال المقالات السابقة من هذه السلسلة بما في ذلك مقال <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B1%D8%A7%D8%A8%D8%B9-%D8%B9%D8%B1%D8%B6-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%88%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-r2193/" rel="">عرض بيانات المكتبة والعمل مع الاستمارات</a>.
	</li>
	<li>
		<strong>الهدف</strong>: معرفة مكان وكيفية نشر تطبيق Express في بيئة الإنتاج.
	</li>
</ul>

<p>
	يجب استضافة موقعك بعد الانتهاء منه (أو الانتهاء منه "بما يكفي" لبدء الاختبار العام) في مكان أعم ويمكن الوصول إليه من أجهزة أخرى غير حاسوبك الشخصي الخاص بالتطوير.
</p>

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

<ul>
	<li>
		اختيار بيئة لاستضافة تطبيق Express.
	</li>
	<li>
		إجراء بعض التغييرات على إعدادات مشروعك.
	</li>
	<li>
		إعداد بنية تحتية على مستوى الإنتاج لتخديم موقع الويب.
	</li>
</ul>

<p>
	يقدّم هذا المقال بعض الإرشادات حول الخيارات المتاحة لاختيار موقع استضافة، ونظرة ًعامة مختصرة على ما يجب تطبيقه لتجهيز تطبيق Express لبيئة الإنتاج، ويقدم مثالًا عمليًا لكيفية تثبيت موقع المكتبة المحلية LocalLibrary على خدمة الاستضافة السحابية <a href="https://railway.app/" rel="external nofollow">Railway</a>.
</p>

<h2 id="">
	ما هي بيئة الإنتاج؟
</h2>

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

<ul>
	<li>
		عتاد الحاسوب الذي يعمل عليه الموقع.
	</li>
	<li>
		<a href="https://academy.hsoub.com/apps/operating-systems/%D9%86%D8%B8%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84/" rel="">نظام التشغيل</a>، مثل لينكس أو ويندوز.
	</li>
	<li>
		وقت التشغيل الخاص بلغة البرمجة ومكتبات <a href="https://academy.hsoub.com/programming/general/%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-framework/" rel="">أطر العمل</a> التي كُتِب عليها موقعك.
	</li>
	<li>
		خادم الويب المُستخدَم لتخديم الصفحات والمحتويات الأخرى، مثل خادم إنجن إكس <a href="https://academy.hsoub.com/devops/servers/web/nginx/" rel="">Nginx</a> وأباتشي <a href="https://academy.hsoub.com/devops/servers/web/apache/" rel="">Apache</a>.
	</li>
	<li>
		البنية التحتية لخادم الويب وتتضمن <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">خادم الويب</a> والوكيل العكسي Reverse Proxy وموازن الحِمل Load Balancer وغير ذلك.
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/servers/databases/%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-database/" rel="">قواعد البيانات</a> التي يعتمد عليها موقعك.
	</li>
</ul>

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

<p>
	يشار إلى هذا النوع من عتاد المعالجة والتشبيك الذي يمكن الوصول إليه عن بُعد باسم البنية التحتية كخدمة Infrastructure as a Service -أو IaaS اختصارًا. يوفّر العديد من بائعي خدمة IaaS خيارات التثبيت المُسبَق لنظام تشغيل معين، والذي يجب تثبيت مكونات بيئتك الإنتاجية الأخرى عليه، ويسمح لك البائعون الآخرون باختيار بيئات كاملة الميزات، ويمكن أن يتضمن ذلك إعداد بيئة Node الكاملة.
</p>

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

<p>
	يدعم مزوّدو الاستضافة الآخرون إطار عمل Express بوصفه جزءًا من استضافة المنصة كخدمة Platform as a Service -أو PaaS اختصارًا، فلا داعي للقلق في هذا النوع من الاستضافة بشأن معظم أجزاء بيئتك الإنتاجية (الخوادم وموازنو الحِمل load balancers وغيرها) لأن المنصة المضيفة تهتم بهذه الأشياء نيابةً عنك، مما يجعل النشر سهلًا جدًا، لأنك تحتاج فقط إلى التركيز على تطبيق الويب وليس على بنية الخادم التحتية الأخرى.
</p>

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

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

<h2 id="-1">
	اختيار مزود الاستضافة
</h2>

<p>
	يوجد العديد من مزوّدات الاستضافة المعروفة التي تدعم بنشاط أو تعمل بصورة جيدة مع بيئة Node و Express، ويوفّر هؤلاء البائعون أنواعًا مختلفة من البيئات (IaaS و PaaS) ومستويات مختلفة من موارد المعالجة والشبكات بأسعار مختلفة.
</p>

<p>
	<strong>ملاحظة</strong>: هناك الكثير من حلول الاستضافة، ويمكن أن تتغير خدماتها وأسعارها بمرور الوقت. سنقدّم بعض الخيارات، ولكن يجدر بك التحقق من هذه الخيارات وغيرها قبل اختيار مزود الاستضافة.
</p>

<p>
	إليك بعض الأشياء التي يجب مراعاتها عند اختيار المضيف:
</p>

<ul>
	<li>
		مدى انشغال موقعك المُحتمَل وتكلفة البيانات وموارد المعالجة المطلوبة لتلبية هذا المطلب.
	</li>
	<li>
		مستوى الدعم للتوسّع أفقيًا (إضافة المزيد من الأجهزة) وعموديًا (الترقية إلى أجهزة أقوى) وتكاليف ذلك.
	</li>
	<li>
		مكان مراكز بيانات المزوّد، أي المكان الذي يكون الوصول إليه أسرع.
	</li>
	<li>
		أداء وقت التشغيل ووقت التعطل السابقَين للمضيف.
	</li>
	<li>
		الأدوات المتوفرة لإدارة الموقع، لمعرفة إذا كانت سهلة الاستخدام وآمنة، مثل استخدام <a href="https://academy.hsoub.com/devops/linux/%D9%83%D9%8A%D9%81-%D8%AA%D8%B3%D8%AA%D8%AE%D8%AF%D9%90%D9%85-sftp-%D9%84%D9%86%D9%82%D9%84-%D8%A7%D9%84%D9%85%D9%84%D9%81%D9%91%D8%A7%D8%AA-%D8%A8%D8%A3%D9%85%D8%A7%D9%86-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%88%D9%85-%D8%A8%D8%B9%D9%8A%D8%AF-r30/" rel="">بروتوكول SFTP</a> أو استخدام بروتوكول FTP.
	</li>
	<li>
		أطر العمل المبنية مسبقًا لمراقبة خادمك.
	</li>
	<li>
		القيود المعروفة، إذ سيوقِف بعض المضيفين عمدًا خدمات معينة مثل البريد الإلكتروني، ويقدّم البعض الآخر فقط عددًا معينًا من ساعات "النشاط" في بعض مستويات الأسعار، أو يقدّم فقط قدرًا صغيرًا من التخزين.
	</li>
	<li>
		الفوائد الإضافية، إذ ستقدّم بعض المزوّدات أسماء نطاقات مجانية ودعمًا لشهادات <abbr title="Secure Socket Layer | طبقة المنافذ الآمنة"><abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">SSL</abbr></abbr> التي يمكن أن يتعيّن عليك دفع ثمنها.
	</li>
	<li>
		معرفة ما إذا كان المستوى "المجاني" الذي تعتمد عليه تنتهي صلاحيته بمرور الوقت، وما إذا كانت تكلفة التهجير Migrating إلى مستوًى أغلى تعني أنه كان من الأفضل استخدام بعض الخدمات الأخرى من البداية.
	</li>
</ul>

<p>
	هناك عددٌ قليل جدًا من المواقع التي توفر بيئات معالجة "مجانية" مخصصة للتقييم والاختبار في البداية، وتكون عادةً بيئات مُقيَّدة أو محدودة الموارد إلى حدٍ ما، ويجب أن تدرك أنه يمكن أن تنتهي صلاحيتها بعد فترة أولية أو يكون لديها قيود أخرى، ولكنها رائعة لاختبار المواقع ذات حركة المرور المنخفضة في بيئة مُستضافة، ويمكن أن توفر تهجيرًا سهلًا للدفع مقابل المزيد من الموارد عندما يصبح موقعك أكثر انشغالًا. تشمل الخيارات الشائعة في هذه الفئة <a href="https://railway.app/" rel="external nofollow">Railway</a> و <a href="https://academy.hsoub.com/programming/python/flask/%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-flask-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-pythonanywhere-r423/" rel="">Python Anywhere</a> و <a href="https://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/billing-free-tier.html" rel="external nofollow">Amazon Web Services</a> و <a href="https://azure.microsoft.com/en-us/pricing/details/app-service/windows/" rel="external nofollow">Microsoft Azure</a> وغير ذلك.
</p>

<p>
	يقدّم معظم المزوّدين مستوًى أساسيًا مخصصًا لمواقع الإنتاج الصغيرة، والذي يوفّر مستويات أكثر فائدة من قدرة المعالجة وقيودًا أقل. يُعَد <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="">Heroku</a> و <a href="https://www.digitalocean.com/" rel="external nofollow">Digital Ocean</a> و <a href="https://www.pythonanywhere.com/" rel="external nofollow">Python Anywhere</a> أمثلة على مزودي الاستضافة المشهورة التي لديها مستوى معالجة أساسي غير مكلف نسبيًا (في نطاق يتراوح بين 5 و 10 دولارات أمريكية شهريًا).
</p>

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

<h2 id="-2">
	تجهيز موقعك للنشر
</h2>

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

<p>
	<strong>ملاحظة</strong>: هناك نصائح مفيدة أخرى في توثيق Express، لذا اطلع على أفضل ممارسات عملية الإنتاج: <a href="https://expressjs.com/en/advanced/best-practice-performance.html" rel="external nofollow">الأداء والموثوقية</a> و<a href="https://expressjs.com/en/advanced/best-practice-security.html" rel="external nofollow">الأمان</a>، و<a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D8%AF%D9%88%D9%86%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-6-%D8%A7%D8%B9%D8%AA%D8%A8%D8%A7%D8%B1%D8%A7%D8%AA-%D9%86%D8%B4%D8%B1-%D9%85%D8%B4%D8%A7%D8%B1%D9%8A%D8%B9-nodejs-%D9%88express-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r24/" rel="">اعتبارات نشر مشاريع Node.js وExpress على الويب</a>.
</p>

<h3 id="-3">
	ضبط قاعدة البيانات
</h3>

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

<p>
	إذا كان مزود الاستضافة الخاص بك داعمًا لمتغيرات البيئة من خلال واجهة الويب، ستكون إحدى الطرق لتحقيق ما سبق هي جعل الخادم يحصل على <a href="https://academy.hsoub.com/programming/general/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%B9%D9%86%D9%88%D8%A7%D9%86-url-%D9%88%D8%A3%D9%86%D9%88%D8%A7%D8%B9%D9%87-r1435/" rel="">عنوان URL</a> لقاعدة البيانات من متغير البيئة، لذلك سنعدّل موقع المكتبة المحلية LocalLibrary للحصول على معرّف URI لقاعدة البيانات من بيئة نظام التشغيل (إذا كان معرّفًا)، وإلّا فسنستخدم قاعدة بيانات التطوير الخاصة بنا.
</p>

<p>
	افتح الملف app.js وابحث عن السطر الذي يضبط متغير اتصال قاعدة بيانات MongoDB، والذي سيبدو كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5973_9" style=""><span class="kwd">const</span><span class="pln"> mongoDB </span><span class="pun">=</span><span class="pln">
  </span><span class="str">"mongodb+srv://your_user_name:your_password@cluster0.lz91hw2.mongodb.net/local_library?retryWrites=true&amp;w=majority"</span><span class="pun">;</span></pre>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5973_11" style=""><span class="com">// إعداد‫ اتصال mongoose</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> dev_db_url </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">
mongoose</span><span class="pun">.</span><span class="kwd">set</span><span class="pun">(</span><span class="str">"strictQuery"</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"> dev_db_url </span><span class="pun">=</span><span class="pln">
  </span><span class="str">"mongodb+srv://your_user_name:your_password@cluster0.lz91hw2.mongodb.net/local_library?retryWrites=true&amp;w=majority"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> mongoDB </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="pun">||</span><span class="pln"> dev_db_url</span><span class="pun">;</span><span class="pln">

main</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"> 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">async</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">await</span><span class="pln"> mongoose</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="pln">mongoDB</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<h3 id="node_envproduction">
	ضبط متغير البيئة NODE_ENV على القيمة production
</h3>

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

<p>
	<strong>ملاحظة</strong>: يُعَد ذلك تغييرًا تجريه في إعداد بيئتك بدلًا من إجرائه في تطبيقك، ولكنه جدير بالذكر حاليًا، إذ سنوضح كيفية ضبطه لمثال الاستضافة الآتي.
</p>

<h3 id="-4">
	التسجيل المناسب
</h3>

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

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5973_13" style=""><span class="kwd">const</span><span class="pln"> debug </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"debug"</span><span class="pun">)(</span><span class="str">"author"</span><span class="pun">);</span><span class="pln">

</span><span class="com">// عرض استمارة‫ تحديث المؤلف باستخدام GET</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">author_update_get </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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="kwd">const</span><span class="pln"> author </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> </span><span class="typ">Author</span><span class="pun">.</span><span class="pln">findById</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">exec</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">author </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">
    debug</span><span class="pun">(`</span><span class="pln">id not found on update</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">params</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"> err </span><span class="pun">=</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">"Author not found"</span><span class="pun">);</span><span class="pln">
    err</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">
    </span><span class="kwd">return</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">

  res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">"author_form"</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="str">"Update Author"</span><span class="pun">,</span><span class="pln"> author</span><span class="pun">:</span><span class="pln"> author </span><span class="pun">});</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	يمكنك بعد ذلك تفعيل مجموعة معينة من السجلات من خلال تحديدها بوصفها قائمة مفصول بين عناصرها بفواصل في متغير بيئة <code>DEBUG</code>، ويمكنك ضبط المتغيرات لعرض سجلات المؤلف والكتاب كما هو موضح فيما يلي (المحارف البديلة Wildcards مدعومة أيضًا):
</p>

<pre class="ipsCode"># في نظام ويندوز
set DEBUG=author,book

# في نظام لينكس
export DEBUG="author,book"
</pre>

<p>
	<strong>ملاحظة</strong>: يمكن أن تحل استدعاءات <code>debug</code> محل التسجيل الذي ربما طبّقته مسبقًا باستخدام <code>console.log()‎</code> أو <code>console.error()‎</code>، لذا ضع التسجيل باستخدام وحدة debug مكان استدعاءات <code>console.log()‎</code> في شيفرتك البرمجية، وشغّل التسجيل وأوقفه في بيئة التطوير من خلال ضبط المتغير <code>DEBUG</code> ولاحظ تأثير ذلك على التسجيل.
</p>

<p>
	إذا كنت بحاجة إلى تسجيل نشاط موقع الويب، يمكنك استخدام مكتبة تسجيل مثل Winston أو Bunyan. اطلع على أفضل ممارسات عملية الإنتاج: <a href="https://expressjs.com/en/advanced/best-practice-performance.html" rel="external nofollow">الأداء والموثوقية</a> لمزيد من المعلومات.
</p>

<h3 id="gzipdeflate">
	استخدم ضغط gzip أو deflate للاستجابة
</h3>

<p>
	تضغط خوادم الويب غالبًا استجابة <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>
	يمكنك إضافة ذلك إلى موقعك باستخدام برمجية الضغط الوسيطة <a href="https://www.npmjs.com/package/compression" rel="external nofollow">compression</a>، لذا ثبّتها في جذر مشروعك من خلال تشغيل الأمر التالي:
</p>

<pre class="ipsCode">npm install compression
</pre>

<p>
	افتح الملف "‎./app.js" واطلب مكتبة الضغط. ضِف مكتبة الضغط إلى سلسلة البرمجيات الوسيطة باستخدام التابع <code>use()‎</code> الذي يجب أن يظهر قبل أي وجهات Routes تريد ضغطها (جميع الوجهات في حالتنا):
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5973_16" style=""><span class="kwd">const</span><span class="pln"> catalogRouter </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"./routes/catalog"</span><span class="pun">);</span><span class="pln"> </span><span class="com">// استيراد الوجهات ‫لمنطقة الدليل "catalog" من موقعك</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> compression </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"compression"</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="pln">use</span><span class="pun">(</span><span class="pln">compression</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">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="pln">use</span><span class="pun">(</span><span class="str">"/"</span><span class="pun">,</span><span class="pln"> indexRouter</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">"/users"</span><span class="pun">,</span><span class="pln"> usersRouter</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">"/catalog"</span><span class="pun">,</span><span class="pln"> catalogRouter</span><span class="pun">);</span><span class="pln"> </span><span class="com">// إضافة وجهات‫ الدليل catalog إلى سلسلة البرمجيات الوسيطة</span><span class="pln">

</span><span class="com">// …</span></pre>

<p>
	<strong>ملاحظة</strong>: لن تَستخدمَ هذه البرمجيات الوسيطة لموقع الويب الذي يمتلك حركة مرور عالية في بيئة الإنتاج، لذا يمكنك استخدام وكيل عكسي مثل Nginx بدلًا من ذلك.
</p>

<h3 id="helmet">
	استخدام حزمة Helmet للحماية من الثغرات المعروفة
</h3>

<p>
	تُعَد Helmet حزمة برمجيات وسيطة يمكنها ضبط ترويسات HTTP المناسبة التي تساعد في حماية تطبيقك من ثغرات الويب المعروفة. اطّلع على <a href="https://helmetjs.github.io/" rel="external nofollow">توثيقها</a> للحصول على مزيد من المعلومات حول الترويسات التي تضبطها والثغرات الأمنية التي تحميك منها.
</p>

<p>
	ثبّتها في جذر مشروعك من خلال تشغيل الأمر التالي:
</p>

<pre class="ipsCode">npm install helmet
</pre>

<p>
	افتح الملف "‎./app.js" واطلب مكتبة helmet، ثم ضِف الوحدة إلى سلسلة البرمجيات الوسيطة باستخدام التابع <code>use()‎</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5973_18" style=""><span class="kwd">const</span><span class="pln"> compression </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"compression"</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> helmet </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"helmet"</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">// إض‫افة حزمة helmet إلى سلسلة البرمجيات الوسيطة</span><span class="pln">
</span><span class="com">// ضبط‫ ترويسات CSP للسماح بتخديم Bootstrap و Jquery</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">
  helmet</span><span class="pun">.</span><span class="pln">contentSecurityPolicy</span><span class="pun">({</span><span class="pln">
    directives</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="str">"script-src"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="str">"'self'"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"code.jquery.com"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"cdn.jsdelivr.net"</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="com">// …</span></pre>

<p>
	يمكن أن نضيف التابع <code>app.use(helmet());‎</code> لإضافة مجموعة فرعية من الترويسات المتعلقة بالأمان والتي تكون منطقية لمعظم المواقع، ولكننا نضمّن بعض سكربتات <a href="https://academy.hsoub.com/programming/css/bootstrap/" rel="">بوتستراب bootstrap</a> و <a href="https://academy.hsoub.com/programming/javascript/jquery/" rel="">jQuery</a> في قالب موقع المكتبة المحلية LocalLibrary الأساسي، مما يؤدي إلى انتهاك سياسة أمان المحتوى Content Security Policy -أو CSP اختصارًا- الافتراضية الخاصة بحزمة helmet، والتي لا تسمح بتحميل السكربتات العابرة للمواقع. يمكن السماح بتحميل هذه السكربتات من خلال تعديل ضبط helmet بحيث تُضبَط موجّهات CSP للسماح بتحميل السكربت من النطاقات المُشار إليها، ويمكنك بالنسبة لخادمك إضافة أو تعطيل ترويسات محددة حسب الحاجة باتباع <a href="https://www.npmjs.com/package/helmet" rel="external nofollow">الإرشادات الخاصة باستخدام helmet</a>.
</p>

<h3 id="api">
	إضافة معدل محدود إلى وجهات واجهة برمجة التطبيقات <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr>
</h3>

<p>
	تُعَد Express-rate-limit حزمة برمجية وسيطة يمكن استخدامها للحد من الطلبات المتكررة <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 | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr></a> والنقاط النهائية، فهناك العديد من الأسباب التي يمكن أن تؤدي إلى زيادة الطلبات على موقعك مثل هجمات حجب الخدمة أو هجمات القوة الغاشمة أو حتى مجرد عميل أو سكربت لا يتصرف كما هو متوقع، ويمكن أن تُحاسَب على حركة المرور الإضافية بغض النظر عن مشاكل الأداء التي يمكن أن تنشأ عن كثرة الطلبات التي تتسبب في إبطاء خادمك. يمكن استخدام حزمة Express-rate-limit للحد من عدد الطلبات التي يمكن إجراؤها على وجهة معينة أو مجموعة من الوِجهات.
</p>

<p>
	يمكنك الاطلاع على مقال <a href="https://academy.hsoub.com/devops/networking/%D8%A7%D9%84%D9%87%D8%AC%D9%85%D8%A7%D8%AA-%D8%A7%D9%84%D8%A3%D9%85%D9%86%D9%8A%D8%A9-security-attacks-%D9%81%D9%8A-%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A7%D8%AA-%D8%A7%D9%84%D8%AD%D8%A7%D8%B3%D9%88%D8%A8%D9%8A%D8%A9-r540/" rel="">الهجمات الأمنية Security Attacks في الشبكات الحاسوبية</a> على أكاديمية حسوب لمزيد من المعلومات حول أنواع الهجمات الأمنية.
</p>

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

<pre class="ipsCode">npm install express-rate-limit
</pre>

<p>
	افتح الملف "‎./app.js" واطلب مكتبة express-rate-limit كما يلي، ثم ضِف الوحدة إلى سلسلة البرمجيات الوسيطة باستخدام التابع <code>use()‎</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5973_20" style=""><span class="kwd">const</span><span class="pln"> compression </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"compression"</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> helmet </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"helmet"</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="com">// إعداد محدِّد المعدل إلى عشرين طلبًا في الدقيقة كحد أعلى</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">RateLimit</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"express-rate-limit"</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> limiter </span><span class="pun">=</span><span class="pln"> </span><span class="typ">RateLimit</span><span class="pun">({</span><span class="pln">
  windowMs</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">60</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="com">// ‫1 دقيقة</span><span class="pln">
  max</span><span class="pun">:</span><span class="pln"> </span><span class="lit">20</span><span class="pun">,</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="pln">limiter</span><span class="pun">);</span><span class="pln">

</span><span class="com">// …</span></pre>

<p>
	<strong>ملاحظة</strong>:
</p>

<ul>
	<li>
		يحدّد الأمر السابق جميع الطلبات لتكون 20 طلبًا في الدقيقة، ولكن يمكنك تغييره حسب الحاجة.
	</li>
	<li>
		يمكن أيضًا استخدام خدمات خارجية مثل <a href="https://www.cloudflare.com/" rel="external nofollow">Cloudflare</a> إذا كنت بحاجة إلى مزيد من الحماية المتقدمة ضد هجمات حجب الخدمة أو أنواع أخرى من الهجمات.
	</li>
</ul>

<h2 id="locallibraryrailway">
	تطبيق عملي: تثبيت موقع المكتبة المحلية LocalLibrary على منصة Railway
</h2>

<p>
	يقدّم هذا القسم شرحًا عمليًا لكيفية تثبيت موقع المكتبة المحلية LocalLibrary على منصة Railway.
</p>

<h3 id="railway">
	سبب استخدام Railway
</h3>

<p>
	اخترنا استخدام منصة Railway لعدة أسباب هي:
</p>

<ul>
	<li>
		<p>
			تمتلك Railway مستوًى مجانيًا <a href="https://docs.railway.app/reference/plans#starter-plan" rel="external nofollow">لخطة بداية</a> مع بعض القيود، إذ من المهم أن تكون بأسعار مقبولة لجميع المطورين.
		</p>
	</li>
	<li>
		<p>
			تهتم Railway بمعظم البنية التحتية، فلا حاجة للقلق بشأن الخوادم وموازني الحِمل والوكلاء العكسيين وغير ذلك، مما يجعل البدء أسهل بكثير.
		</p>
	</li>
	<li>
		<p>
			تركّز Railway على تجربة المطور للتطوير والنشر، مما يؤدي إلى وجود منحنى تعليمي أسرع وأسلس من العديد من البدائل الأخرى.
		</p>
	</li>
	<li>
		<p>
			تُعَد المهارات والمفاهيم التي ستتعلمها عند استخدام Railway قابلة للتحويل، إذ تمتلك Railway بعض الميزات الجديدة الممتازة، ولكن تستخدم خدماتُ الاستضافة الشائعة الأخرى العديدَ من الأفكار والأساليب نفسها.
		</p>
	</li>
	<li>
		<p>
			لا تؤثر قيود الخدمات والخطط على استخدامنا لمنصة Railway في مثالنا، فمثلًا:
		</p>

		<ul>
			<li>
				تقدّم خطة البداية 500 ساعة فقط من وقت النشر المستمر كل شهر و5 دولارات من الرصيد الذي يُستهلَك بناءً على الاستخدام، ويُعاد ضبط الساعات والرصيد ويجب إعادة نشر المشاريع في نهاية كل شهر. تعني هذه القيود أنه يمكنك تشغيل هذا المثال بصورة مستمرة لمدة 21 يومًا تقريبًا، ويُعَد ذلك أكثر من كافٍ للتطوير والاختبار، ولكن لن تتمكّن من استخدام هذه الخطة لموقع حقيقي للإنتاج.
			</li>
			<li>
				تحتوي بيئة خطة البداية على 512 ميجابايت فقط من الذاكرة RAM و1 جيجابايت من ذاكرة التخزين، وهذا أكثر من كافٍ لمثالنا.
			</li>
			<li>
				لا توجد سوى منطقة واحدة مدعومة وهي الولايات المتحدة الأمريكية حاليًا، إذ يمكن أن تكون الخدمة خارج هذه المنطقة أبطأ أو تحظرها القوانين المحلية.
			</li>
			<li>
				يمكن العثور على قيود أخرى في <a href="https://docs.railway.app/reference/plans#starter-plan" rel="external nofollow">توثيق خطط Railway للدفع</a>.
			</li>
		</ul>
	</li>
	<li>
		<p>
			تبدو الخدمة موثوقة جدًا، وإذا كانت مناسبة لك، فإن الأسعار يمكن التنبؤ بها، ويكون توسيع تطبيقك سهلًا جدًا.
		</p>
	</li>
	<li>
		<p>
			تعَد Railway مناسبة لاستضافة مثالنا، ولكن يجب أن تأخذ الوقت الكافي لتحديد ما إذا كانت مناسبة لموقعك.
		</p>
	</li>
</ul>

<h3 id="railway-1">
	كيفية عمل Railway
</h3>

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

<p>
	تجعل Railway هذا الأمر سهلًا، إذ يمكنها التعرف تلقائيًا على العديد من أطر عمل وبيئات تطبيقات الويب المختلفة وتثبيتها بناءً على استخدامها "للمصطلحات الشائعة"، فمثلًا تتعرّف Railway على تطبيقات Node لأن لديها ملف package.json، ويمكنها تحديد مدير الحزم المُستخدَم للبناء من ملف "القفل Lock". إذا احتوى التطبيق على ملف package-lock.json مثلًا، فستعرف Railway أنها ستستخدم <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</a> لتثبيت الحزم، وإذا وجدت yarn.lock فستعرف أنها ستستخدم yarn. ستبحث Railway -بعد تثبيت جميع الاعتماديات- عن سكربتات بالاسم "build" و "start" في ملف الحزمة، وستستخدمها لبناء وتشغيل الشيفرة البرمجية.
</p>

<p>
	<strong>ملاحظة</strong>: تستخدم Railway حزمة Nixpacks للتعرف على العديد من أطر عمل تطبيقات الويب المكتوبة بلغات برمجة مختلفة. لا حاجة إلى معرفة أيّ شيء آخر لهذا المقال، ولكن يمكنك معرفة المزيد حول خيارات نشر تطبيقات Node في <a href="https://nixpacks.com/docs/providers/node" rel="external nofollow">توثيق Nixpacks</a>.
</p>

<p>
	يمكن للتطبيق بعد تشغيله ضبط نفسه باستخدام المعلومات المقدمة في <a href="https://docs.railway.app/develop/variables" rel="external nofollow">متغيرات البيئة</a>، فمثلًا يجب أن يحصل التطبيق الذي يستخدم قاعدة بيانات على العنوان باستخدام متغير، ويمكن أن تستضيف Railway خدمة قاعدة البيانات نفسها أو على أي مزوّد آخر.
</p>

<p>
	يتفاعل المطورون مع Railway من خلال موقع Railway وباستخدام أداة <a href="https://docs.railway.app/develop/cli" rel="external nofollow">واجهة سطر أوامر CLI</a> خاصة، إذ تسمح لك واجهة CLI بربط مستودع غيت هب GitHub محلي بمشروع Railway، ورفع المستودع من الفرع المحلي إلى الموقع المباشر، وفحص سجلات العملية الجارية، وإعداد متغيرات الضبط والحصول عليها وغير ذلك. من أهم الميزات أنه يمكنك استخدام واجهة CLI لتشغيل مشروعك المحلي مع متغيرات البيئة نفسها للمشروع المباشر.
</p>

<p>
	يجب وضع تطبيق Express الخاص بنا في مستودع <a href="https://academy.hsoub.com/programming/workflow/git/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%A3%D9%88%D9%84-%D9%85%D8%B3%D8%AA%D9%88%D8%AF%D8%B9-%D9%84%D9%83-%D9%85%D9%86-%D8%AE%D9%84%D8%A7%D9%84-%D8%AC%D9%8A%D8%AA-git-r1594/" rel="">غيت git</a> وإجراء بعض التغييرات الطفيفة ليعمل تطبيقنا على Railway، ثم يمكننا إعداد حساب على Railway وتثبيت موقعنا وقاعدة البيانات وتجربة عميل Railway.
</p>

<p>
	وهذا هو كل ما تحتاجه من معلومات في البداية.
</p>

<h3 id="github">
	إنشاء مستودع للتطبيق على غيت هب GitHub
</h3>

<p>
	تتكامل Railway تكاملًا وثيقًا مع غيت هب ونظام التحكم في إصدارات الشيفرة المصدرية git، ويمكنك ضبطها لنشر التحديثات تلقائيًا عند إجراء تغييرات إلى مستودع أو فرع معين على غيت هب، أو يمكنك دفع فرع الشيفرة المحلية الحالي مباشرةً إلى النشر الخاص بمنصة Railway باستخدام واجهة CLI.
</p>

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

<p>
	هناك العديد من الطرق للعمل مع git، ومن أسهلها إعداد حساب على غيت هب أولًا وإنشاء المستودع عليه، ثم المزامنة معه محليًا كما يلي:
</p>

<ol>
	<li>
		انتقل إلى موقع <a href="https://github.com/" rel="external nofollow">GitHub</a> الرسمي وأنشئ حسابًا عليه.
	</li>
	<li>
		انقر على ارتباط + في شريط الأدوات العلوي وحدّد خيار "مستودع جديد New repository" بعد تسجيل الدخول.
	</li>
	<li>
		املأ جميع الحقول في هذه الاستمارة، إذ يمكن أن تكون هذه الحقول غير إلزامية، ولكن يُوصَى بها بشدة.
		<ul>
			<li>
				أدخل اسم المستودع الجديد والوصف، فمثلًا يمكنك استخدام الاسم "express-locallibrary-tutorial" والوصف<br>
				"Local Library website written in Express (Node)‎" (موقع المكتبة المحلية المكتوب باستخدام Express).
			</li>
			<li>
				اختر الخيار Node في قائمة الاختيار Add .gitignore.
			</li>
			<li>
				اختر الترخيص المفضل لديك في قائمة الاختيار Add license.
			</li>
			<li>
				تحقق من تهيئة المستودع باستخدام README.
			</li>
		</ul>
	</li>
</ol>

<p>
	<strong>تحذير</strong>: سيجعل الوصول الافتراضي العام "Public" جميع الشيفرة البرمجية المصدرية -بما في ذلك اسم المستخدم وكلمة المرور لقاعدة البيانات- مرئيةً لأي شخص على الإنترنت، لذا تأكد من أن الشيفرة البرمجية المصدرية لا تقرأ اعتماديات إلّا من متغيرات البيئة ولا تحتوي على أيّ اعتماديات ثابتة، وإلّا حدد الخيار خاص "Private" للسماح فقط لأشخاص محدَّدين برؤية الشيفرة المصدرية.
</p>

<ol start="4">
	<li>
		اضغط على إنشاء مستودع Create repository.
	</li>
	<li>
		انقر فوق الزر الأخضر نسخ Clone أو تنزيل Download في صفحة مستودعك الجديد.
	</li>
	<li>
		انسخ قيمة URL من حقل النص الموجود في مربع الحوار الذي يظهر، فإذا استخدمت اسم المستودع "express-locallibrary-tutorial"، فيجب أن يكون عنوان URL مثل العنوان: <code><a href="https://github.com/&lt;your_git_user_id&gt;/express-locallibrary-tutorial.git" ipsnoembed="false" rel="external nofollow">https://github.com/&lt;your_git_user_id&gt;/express-locallibrary-tutorial.git</a></code>.
	</li>
</ol>

<p>
	انتهينا من إنشاء المستودع، ويجب الآن نسخه على حاسوبك المحلي باتباع الخطوات التالية:
</p>

<p>
	أولًا، ثبّت git على حاسوبك المحلي، إذ يمكنك العثور على <a href="https://git-scm.com/downloads" rel="external nofollow">نسخ لأنظمة مختلفة</a>.
</p>

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

<pre class="ipsCode">git clone https://github.com/&lt;your_git_user_id&gt;/express-locallibrary-tutorial.git
</pre>

<p>
	سيؤدي هذا الأمر إلى إنشاء المستودع في المجلد الحالي.
</p>

<p>
	ثالثًا، انتقل إلى مجلد المستودع باستخدام الأمر التالي:
</p>

<pre class="ipsCode">cd express-locallibrary-tutorial
</pre>

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

<p>
	أولًا، انسخ تطبيق Express إلى هذا المجلد باستثناء المجلد "‎/node_modules" الذي يحتوي على ملفات الاعتماديات التي يجب جلبها من مدير حزم npm حسب الحاجة.
</p>

<p>
	ثانيًا، افتح موجه الأوامر أو الطرفية واستخدم الأمر <code>add</code> لإضافة جميع الملفات إلى git كما يلي:
</p>

<pre class="ipsCode">git add -A
</pre>

<p>
	ثالثًا، استخدم الأمر <code>status</code> للتحقق من صحة جميع الملفات التي تريد تثبيتها <code>commit</code>، إذ نريد تضمين الملفات المصدرية، وليس الملفات الثنائية والمؤقتة وما إلى ذلك، إذ يجب أن تبدو كما يلي:
</p>

<pre class="ipsCode">&gt; git status
</pre>

<p>
	ويكون الخرج الناتج بعد تنفيذ هذا الأمر على النحو التالي:
</p>

<pre class="ipsCode">On branch main
Your branch is up-to-date with 'origin/main'.
Changes to be committed:
  (use "git reset HEAD &lt;file&gt;..." to unstage)

        new file:   …
</pre>

<p>
	رابعًا، ثبّت باستخدام <code>commit</code> الملفات في مستودعك المحلي عندما تكون راضيًا عن النتيجة، إذ يكافئ ذلك الإقرار بالتغييرات وجعلها جزءًا رسميًا من المستودع المحلي.
</p>

<pre class="ipsCode">git commit -m "First version of application moved into GitHub"
</pre>

<p>
	خامسًا، لم يتغيّر المستودع البعيد حتى الآن، لذا يجب مزامنة (باستخدام الأمر <code>push</code>) مستودعك المحلي مع مستودع GitHub البعيد باستخدام الأمر التالي:
</p>

<pre class="ipsCode">git push origin main
</pre>

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

<p>
	<strong>ملاحظة</strong>: يمكنك الآن إنشاء نسخة احتياطية من شيفرة مشروعك البرمجية، إذ يمكن أن تكون بعض التغييرات التي سنجريها في الأقسام التالية مفيدة للنشر (أو للتطوير) على أي خدمة استضافة، ويمكن أن تكون بعض التغييرات الأخرى غير مفيدة. أفضل طريقة لذلك هي استخدام git لإدارة الإصدارات، إذ يمكنك باستخدامه الرجوع إلى إصدار سابق معين، ويمكنك الاحتفاظ به في فرع منفصل عن التغييرات الإنتاجية واختيار أيّ تغييرات للتنقل بين فروع الإنتاج والتطوير. يستحق تعلم غيت Git الجهد المبذول، لذا اطلع على مجموعة مقالات <a href="https://academy.hsoub.com/programming/workflow/git/" rel="">Git</a> في أكاديمية حسوب. يُعَد نسخ ملفاتك في موقع آخر الطريقة الأسهل، ولكن يمكنك استخدام أيّ طريقة تتناسب مع معرفتك لنظام git.
</p>

<h3 id="railway-2">
	تحديث التطبيق ليعمل على Railway
</h3>

<p>
	سنشرح في هذا القسم التغييرات التي يجب إجراؤها على تطبيق المكتبة المحلية LocalLibrary ليعمل على Railway، ولكن لن تمنعك هذه التغييرات من استخدام الاختبار وسير العمل المحلي الذي تعلمناه مسبقًا.
</p>

<h4 id="node">
	ضبط نسخة Node
</h4>

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

<pre class="ipsCode">&gt;node --version
v16.17.1
</pre>

<p>
	افتح الملف package.json وأضِف المعلومات التالية في قسم engines &gt; node (مع استخدام رقم النسخة في نظامك):
</p>

<pre class="ipsCode">{
  "name": "express-locallibrary-tutorial",
  "version": "0.0.0",
  "engines": {
    "node": "&gt;=16.17.1"
  },
  "private": true,
  // …
</pre>

<p>
	لاحظ أن هناك طرقًا أخرى لتوفير نسخة Node على Railway، ولكننا نستخدم الملف package.json لأنه الأسلوب الذي تدعمه العديد من الخدمات على نطاق واسع. لاحظ أيضًا أن Railway لن تستخدم بالضرورة نسخة Node الدقيقة التي تحددها، إذ ستستخدم نسخةً لها رقم النسخة الرئيسي نفسه.
</p>

<h4 id="-5">
	الحصول على الاعتماديات وإعادة الاختبار
</h4>

<p>
	لنختبر الموقع مرةً أخرى ونتأكد من أنه لم يتأثر بأيٍّ من التغييرات التي أجريناها.
</p>

<p>
	أولًا، يجب جلب الاعتماديات (تذكّر أننا لم ننسخ المجلد node_modules في شجرة git)، إذ يمكنك ذلك من خلال تشغيل الأمر التالي في طرفيتك ضمن جذر المشروع:
</p>

<pre class="ipsCode">npm install
</pre>

<p>
	شغّل الموقع (اطّلع على فقرة اختبار الوجهات في مقال <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%D8%A7%D9%84%D9%88%D8%AC%D9%87%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D9%85%D8%AA%D8%AD%D9%83%D9%85%D8%A7%D8%AA-r2184/" rel="">الوجهات والمتحكمات</a> لمعرفة الأوامر ذات الصلة) وتحقق من أن الموقع لا يزال يتصرف كما هو متوقع.
</p>

<h4 id="github-1">
	حفظ التغييرات على غيت هب GitHub
</h4>

<p>
	احفظ الآن جميع التغييرات التي أجريتها على غيت هب، وأدخِل الأوامر التالية في الطرفية أثناء وجودك في مستودعك:
</p>

<pre class="ipsCode">git add -A
git commit -m "Added files and changes required for deployment"
git push origin main
</pre>

<p>
	يجب أن نكون جاهزين الآن لبدء نشر موقع المكتبة المحلية LocalLibrary على Railway.
</p>

<h3 id="railway-3">
	الحصول على حساب على Railway
</h3>

<p>
	يجب أولًا إنشاء حساب لبدء استخدام Railway باتباع الخطوات التالية:
</p>

<ul>
	<li>
		اذهب إلى <a href="https://railway.app/" rel="external nofollow">موقع Railway الرسمي</a> وانقر على ارتباط تسجيل الدخول Login في شريط الأدوات العلوي.
	</li>
	<li>
		اختر GitHub في النافذة المنبثقة لتسجيل الدخول باستخدام اعتماديات GitHub الخاصة بك.
	</li>
	<li>
		يمكن أن تحتاج بعد ذلك إلى الانتقال إلى بريدك الإلكتروني والتحقق من حسابك.
	</li>
	<li>
		ستسجل بعد ذلك الدخول إلى <a href="https://railway.app/dashboard" rel="external nofollow">لوحة تحكم Railway</a>.
	</li>
</ul>

<h3 id="railwaygithub">
	النشر على Railway من GitHub
</h3>

<p>
	يجب الآن إعداد Railway لنشر موقع مكتبتنا من GitHub، لذا اختر أولًا خيار لوحة التحكم Dashboard من القائمة العلوية للموقع، ثم حدّد زر مشروع جديد New Project:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="140205" href="https://academy.hsoub.com/uploads/monthly_2023_12/01_railway_new_project_button.png.4f4ca06572912c8daf70121ea9105fcb.png" rel=""><img alt="01 railway new project button" class="ipsImage ipsImage_thumbnailed" data-fileid="140205" data-unique="1m3zp8jby" src="https://academy.hsoub.com/uploads/monthly_2023_12/01_railway_new_project_button.png.4f4ca06572912c8daf70121ea9105fcb.png"> </a>
</p>

<p>
	ستعرض Railway قائمةً بالخيارات الخاصة بالمشروع الجديد، بما في ذلك خيار نشر مشروع من قالب أنشأته لأول مرة في حسابك على GitHub، وعددًا من قواعد البيانات، لذا حدد خيار النشر Deploy from GitHub repo.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="140206" href="https://academy.hsoub.com/uploads/monthly_2023_12/02_railway_new_project_button_deploy_github_repo.png.a2b877f277475caef33d0a03bcd9bb2a.png" rel=""><img alt="02 railway new project button deploy github repo" class="ipsImage ipsImage_thumbnailed" data-fileid="140206" data-unique="5ydo9pc3b" src="https://academy.hsoub.com/uploads/monthly_2023_12/02_railway_new_project_button_deploy_github_repo.png.a2b877f277475caef33d0a03bcd9bb2a.png"> </a>
</p>

<p>
	ستُعرَض جميع المشاريع في مستودعات GitHub التي شاركتها مع Railway أثناء عملية الإعداد، ولكنك ستختار مستودع GitHub الخاص بموقع المكتبة المحلية: <code>‎&lt;user-name&gt;/express-locallibrary-tutorial</code>.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="140207" href="https://academy.hsoub.com/uploads/monthly_2023_12/03_railway_new_project_button_deploy_github_selectrepo.png.ff2f840a62d8b170923c744712f410f9.png" rel=""><img alt="03 railway new project button deploy github selectrepo" class="ipsImage ipsImage_thumbnailed" data-fileid="140207" data-unique="c3jl3bb1i" src="https://academy.hsoub.com/uploads/monthly_2023_12/03_railway_new_project_button_deploy_github_selectrepo.png.ff2f840a62d8b170923c744712f410f9.png"> </a>
</p>

<p>
	أكّد النشر من خلال تحديد خيار النشر حالًا Deploy Now.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="140208" href="https://academy.hsoub.com/uploads/monthly_2023_12/04_railway_new_project_deploy_confirm.png.7193153c961b8d9283b30bec8c48d63f.png" rel=""><img alt="04 railway new project deploy confirm" class="ipsImage ipsImage_thumbnailed" data-fileid="140208" data-unique="r1ncr14pc" src="https://academy.hsoub.com/uploads/monthly_2023_12/04_railway_new_project_deploy_confirm.png.7193153c961b8d9283b30bec8c48d63f.png"> </a>
</p>

<p>
	ستحمّل Railway بعد ذلك مشروعك وتنشره، مع عرض التقدم في نافذة عمليات النشر Deployments، ثم سترى شيئًا يشبه ما يلي عند اكتمال النشر بنجاح:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="140209" href="https://academy.hsoub.com/uploads/monthly_2023_12/05_railway_project_deploy.png.a1d7ed5dda62ee98dc2a8b18195345cc.png" rel=""><img alt="05 railway project deploy" class="ipsImage ipsImage_thumbnailed" data-fileid="140209" data-unique="p8t6ws8x7" src="https://academy.hsoub.com/uploads/monthly_2023_12/05_railway_project_deploy.thumb.png.3a54590ef33869bf25f971cd71efd967.png"> </a>
</p>

<p>
	اختر الآن نافذة الإعدادات Settings، ثم انتقل إلى الأسفل إلى قسم النطاقات Domains، واضغط على زر توليد نطاق Generate Domain.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="140210" href="https://academy.hsoub.com/uploads/monthly_2023_12/06_railway_project_generate_domain.png.fb2ef8625df220d66a7a72ed9280fbdc.png" rel=""><img alt="06 railway project generate domain" class="ipsImage ipsImage_thumbnailed" data-fileid="140210" data-unique="npheorw2c" src="https://academy.hsoub.com/uploads/monthly_2023_12/06_railway_project_generate_domain.png.fb2ef8625df220d66a7a72ed9280fbdc.png"> </a>
</p>

<p>
	سيؤدي ذلك إلى نشر الموقع ووضع النطاق في مكان الزر كما هو موضح فيما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="140211" href="https://academy.hsoub.com/uploads/monthly_2023_12/07_railway_project_domain.png.c36f7501151e3ec99047a0ef3bad15cf.png" rel=""><img alt="07 railway project domain" class="ipsImage ipsImage_thumbnailed" data-fileid="140211" data-unique="bk1qkhs9d" src="https://academy.hsoub.com/uploads/monthly_2023_12/07_railway_project_domain.png.c36f7501151e3ec99047a0ef3bad15cf.png"> </a>
</p>

<p>
	حدّد عنوان URL للنطاق لفتح تطبيق المكتبة. لاحظ فتح موقع المكتبة المحلية باستخدام بيانات التطوير الخاصة بك لأننا لم نحدّد قاعدة بيانات الإنتاج.
</p>

<h3 id="mongodb">
	تجهيز وتوصيل قاعدة بيانات MongoDb
</h3>

<p>
	لننشئ الآن قاعدة بيانات MongoDB الخاصة بالإنتاج لاستخدامها بدلًا من استخدام بيانات التطوير، إذ سننشئ قاعدة البيانات بوصفها جزءًا من مشروع تطبيق Railway، بالرغم من أنه لا يوجد ما يمنعك من إنشائها في مشروعها المنفصل أو استخدام قاعدة بيانات MongoDB Atlas لبيانات الإنتاج، كما فعلت مع قاعدة بيانات التطوير.
</p>

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

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="140212" href="https://academy.hsoub.com/uploads/monthly_2023_12/08_railway_project_open_no_database.png.d1276c16360e01179e7f6539e89afd97.png" rel=""><img alt="08 railway project open no database" class="ipsImage ipsImage_thumbnailed" data-fileid="140212" data-unique="siejj7pp8" src="https://academy.hsoub.com/uploads/monthly_2023_12/08_railway_project_open_no_database.thumb.png.0ab92a882715291e240406b02e8fb3d8.png"> </a>
</p>

<p>
	حدد قاعدة البيانات Database عندما يُطلَب منك تحديد نوع الخدمة لإضافتها.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="140213" href="https://academy.hsoub.com/uploads/monthly_2023_12/09_railway_database_add.png.f914ec967937f17c8fe0b3d3e4fe44ea.png" rel=""><img alt="09 railway database add" class="ipsImage ipsImage_thumbnailed" data-fileid="140213" data-unique="2dphu4sfn" src="https://academy.hsoub.com/uploads/monthly_2023_12/09_railway_database_add.png.f914ec967937f17c8fe0b3d3e4fe44ea.png"> </a>
</p>

<p>
	حدّد بعد ذلك خيار الإضافة Add MongoDB لبدء إضافة قاعدة البيانات.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="140214" href="https://academy.hsoub.com/uploads/monthly_2023_12/10_railway_database_select_type.png.53ce7c7c37f86d4a6de599c5f7a80ce7.png" rel=""><img alt="10 railway database select type" class="ipsImage ipsImage_thumbnailed" data-fileid="140214" data-unique="caayxnqef" src="https://academy.hsoub.com/uploads/monthly_2023_12/10_railway_database_select_type.png.53ce7c7c37f86d4a6de599c5f7a80ce7.png"> </a>
</p>

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

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="140215" href="https://academy.hsoub.com/uploads/monthly_2023_12/11_railway_project_two_services.png.1792ffdc78d682401ad8e047a973e1d7.png" rel=""><img alt="11 railway project two services" class="ipsImage ipsImage_thumbnailed" data-fileid="140215" data-unique="1z934og6v" src="https://academy.hsoub.com/uploads/monthly_2023_12/11_railway_project_two_services.thumb.png.4e9471469445265e67b7aed1d3bf8626.png"> </a>
</p>

<p>
	حدّد خدمة MongoDB لعرض معلومات حول قاعدة البيانات، ثم افتح نافذة "الاتصال Connect" وانسخ "عنوان URL لاتصال Mongo"، وهو عنوان قاعدة البيانات.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="140216" href="https://academy.hsoub.com/uploads/monthly_2023_12/12_railway_mongodb_connect.png.aa593c885c766344466d2a4e148d9b8c.png" rel=""><img alt="12 railway mongodb connect" class="ipsImage ipsImage_thumbnailed" data-fileid="140216" data-unique="sx04bos52" src="https://academy.hsoub.com/uploads/monthly_2023_12/12_railway_mongodb_connect.thumb.png.d133fa00d7573001b185591dcba48aa7.png"> </a>
</p>

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

<p>
	أدخِل اسم المتغير <code>MONGODB_URI</code> وعنوان URL للاتصال الذي نسخته لقاعدة البيانات، فالمتغير <code>MONGODB_URI</code> هو اسم متغير البيئة الذي ضبطنا التطبيق منه لقراءة عنوان قاعدة البيانات، وسيظهر لديك ما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="140217" href="https://academy.hsoub.com/uploads/monthly_2023_12/13_railway_variables_database_url.png.1e5f417faf372eea4849fb191854caad.png" rel=""><img alt="13 railway variables database url" class="ipsImage ipsImage_thumbnailed" data-fileid="140217" data-unique="qilayb45u" src="https://academy.hsoub.com/uploads/monthly_2023_12/13_railway_variables_database_url.thumb.png.7fe66c92780a093e98410ce28e0a07ec.png"> </a>
</p>

<p>
	حدّد بعد ذلك زر الإضافة Add لإضافة المتغير.
</p>

<p>
	تعيد Railway تشغيل تطبيقك عندما تحدّث المتغيرات. إذا فحصتَ الصفحة الرئيسية الآن، فيجب أن تظهِر قيمًا صفرية لأعداد الكائنات، إذ تعني التغييرات السابقة أننا نستخدم الآن قاعدة بيانات جديدة (فارغة).
</p>

<h3 id="-6">
	متغيرات الضبط الأخرى
</h3>

<p>
	تذكّر من القسم السابق أننا بحاجة إلى ضبط <code>NODE_ENV</code> على قيمة الإنتاج 'production' لتحسين الأداء وإنشاء رسائل خطأ أقل تفصيلًا، ويمكننا فعل ذلك في الشاشة نفسها التي ضبطنا منها المتغير <code>MONGODB_URI</code>.
</p>

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

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="140218" href="https://academy.hsoub.com/uploads/monthly_2023_12/14_railway_variables_new.png.9895959c84d16e240ebc72f651c8e617.png" rel=""><img alt="14 railway variables new" class="ipsImage ipsImage_thumbnailed" data-fileid="140218" data-unique="zgre6benp" src="https://academy.hsoub.com/uploads/monthly_2023_12/14_railway_variables_new.thumb.png.3f79d3efac844eb9cec4bf100934626b.png"> </a>
</p>

<p>
	أدخل <code>NODE_ENV</code> بوصفه اسم المتغير الجديد و <code>production</code> بوصفه اسم البيئة، ثم اضغط على زر الإضافة Add.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="140219" href="https://academy.hsoub.com/uploads/monthly_2023_12/15_railway_variables_new_node_env.png.f2ade1286d02b2fd4dcaba84d2075310.png" rel=""><img alt="15 railway variables new node env" class="ipsImage ipsImage_thumbnailed" data-fileid="140219" data-unique="8x8cd1lvg" src="https://academy.hsoub.com/uploads/monthly_2023_12/15_railway_variables_new_node_env.png.f2ade1286d02b2fd4dcaba84d2075310.png"> </a>
</p>

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

<p>
	<strong>ملاحظة</strong>: إذا أدرتَ إضافة بعض البيانات للاختبار، فيمكنك استخدام السكربت <code>populatedb</code> مع عنوان URL لقاعدة بيانات MongoDB الخاصة بالإنتاج كما ناقشنا في مقال <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%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-%D9%85%D8%B9-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-mongoose-r2171/" rel="">استخدام قاعدة البيانات باستخدام مكتبة Mongoose</a>.
</p>

<h3 id="-7">
	تثبيت العميل
</h3>

<p>
	نزّل وثبّت عميل Railway لنظام تشغيلك المحلي باتباع <a href="https://docs.railway.app/develop/cli" rel="external nofollow">التعليمات</a> الواردة في توثيق Railway.
</p>

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

<p>
	يمكنك الحصول على قائمة بجميع الأوامر الممكنة من خلال إدخال الأمر التالي في الطرفية:
</p>

<pre class="ipsCode">railway help
</pre>

<h3 id="debugging">
	تنقيح الأخطاء Debugging
</h3>

<p>
	يوفّر عميل Railway الأمر <code>logs</code> لإظهار السجلات الأخيرة (يتوفر سجل كامل على الموقع لكل مشروع):
</p>

<pre class="ipsCode">railway logs
</pre>

<h2 id="-8">
	الخلاصة
</h2>

<p>
	وصلنا إلى نهاية مقالنا حول إعداد تطبيقات Express في بيئة الإنتاج، وكذلك إلى نهاية سلسلة مقالات إطار عمل Express، لذا نأمل أنها كانت مفيدة لك، ولا تنسَ أنه يمكنك التحقق من النسخة الكاملة من <a href="https://github.com/mdn/express-locallibrary-tutorial" rel="external nofollow">الشيفرة المصدرية على GitHub</a>.
</p>

<p>
	ترجمة -وبتصرُّف- للمقال <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/deployment" rel="external nofollow">Express Tutorial Part 7: Deploying to production</a>.
</p>

<h1 id="-9">
	اقرأ المزيد
</h1>

<ul>
	<li>
		المقال السابق <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B1%D8%A7%D8%A8%D8%B9-%D8%B9%D8%B1%D8%B6-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%88%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-r2193/" rel="">تطبيق عملي لتعلم Express - الجزء الرابع: عرض بيانات المكتبة والعمل مع الاستمارات</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/deployment/%D9%85%D8%B1%D8%AD%D9%84%D8%A9-%D9%86%D8%B4%D8%B1-%D8%A7%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%81%D9%8A-%D8%B9%D9%85%D9%84%D9%8A%D8%A9-%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r589/" rel="">مرحلة نشر التطبيق في عملية تطوير الويب</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-10-%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%81%D9%8A-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%A7%D8%AC-r2122/" rel="">تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج</a>.
	</li>
</ul>
]]></description><guid isPermaLink="false">2202</guid><pubDate>Sat, 16 Dec 2023 11:00:00 +0000</pubDate></item><item><title>&#x62A;&#x637;&#x628;&#x64A;&#x642; &#x639;&#x645;&#x644;&#x64A; &#x644;&#x62A;&#x639;&#x644;&#x645; Express -&#x627;&#x644;&#x62C;&#x632;&#x621; &#x627;&#x644;&#x631;&#x627;&#x628;&#x639;: &#x639;&#x631;&#x636; &#x628;&#x64A;&#x627;&#x646;&#x627;&#x62A; &#x627;&#x644;&#x645;&#x643;&#x62A;&#x628;&#x629; &#x648;&#x627;&#x644;&#x639;&#x645;&#x644; &#x645;&#x639; &#x627;&#x644;&#x627;&#x633;&#x62A;&#x645;&#x627;&#x631;&#x627;&#x62A;</title><link>https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B1%D8%A7%D8%A8%D8%B9-%D8%B9%D8%B1%D8%B6-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%88%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-r2193/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_12/-----.png.477b5e1f9ea8070a671953a73080da5f.png" /></p>
<p>
	أصبحنا الآن جاهزين لإضافة الصفحات التي تعرض كتب <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2170/" rel="">موقع المكتبة المحلية LocalLibrary</a> وبيانات أخرى، إذ ستتضمن هذه الصفحات صفحةً رئيسية توضح عدد السجلات لكل نوع نموذج Model وعددًا من صفحات القائمة والصفحات التفصيلية لجميع النماذج، وبالتالي سنكتسب خبرةً عملية في الحصول على السجلات من قاعدة البيانات واستخدام القوالب. سنشرح أيضًا في هذا المقال كيفية العمل مع استمارات HTML في إطار عمل Express باستخدام لغة القوالب Pug، إذ سنناقش كيفية كتابة استمارات لإنشاء المستندات وتحديثها وحذفها من قاعدة بيانات الموقع.
</p>

<ul>
	<li>
		<strong>المتطلبات الأساسية</strong>: إكمال المقالات السابقة من هذه السلسلة بما في ذلك مقال <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%D8%A7%D9%84%D9%88%D8%AC%D9%87%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D9%85%D8%AA%D8%AD%D9%83%D9%85%D8%A7%D8%AA-r2184/" rel="">الوِجهات Routes والمتحكمات Controllers</a>.
	</li>
	<li>
		<strong>الهدف</strong>: فهم كيفية إجراء عمليات قاعدة البيانات غير المتزامنة باستخدام <code>async</code>/<code>await</code>، واستخدام لغة القوالب Pug، والحصول على البيانات من <a href="https://academy.hsoub.com/programming/general/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%B9%D9%86%D9%88%D8%A7%D9%86-url-%D9%88%D8%A3%D9%86%D9%88%D8%A7%D8%B9%D9%87-r1435/" rel="">عنوان URL</a> في دوال المتحكمات، وكتابة الاستمارات للحصول على البيانات من المستخدمين وتحديث قاعدة البيانات باستخدام هذه البيانات.
	</li>
</ul>

<h2 id="">
	عرض بيانات المكتبة
</h2>

<p>
	عرّفنا في المقالات السابقة <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%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-%D9%85%D8%B9-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-mongoose-r2171/" rel="">نماذجَ Mongoose</a> التي يمكننا استخدامها للتفاعل مع قاعدة بيانات وأنشأنا بعض سجلات المكتبة الأولية، ثم أنشأنا جميع <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%D8%A7%D9%84%D9%88%D8%AC%D9%87%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D9%85%D8%AA%D8%AD%D9%83%D9%85%D8%A7%D8%AA-r2184/" rel="">الوِجهات Routes</a> اللازمة لموقع المكتبة المحلية LocalLibrary، ولكن مع دوال متحكمات وهمية dummy، وهي دوال متحكمات هيكلية تعيد فقط رسالة "not implemented" عند الوصول إلى الصفحة.
</p>

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

<p>
	سنبدأ بتقديم موضوعات عامة أو أولية توضح كيفية إدارة العمليات غير المتزامنة في دوال المتحكمات وكيفية كتابة القوالب باستخدام مكتبة القوالب Pug، ثم سنوفّر عمليات تقديم لكل صفحة من صفحاتنا الرئيسية المُعَدّة للقراءة فقط مع شرح موجز لأي ميزات خاصة أو جديدة تستخدمها، وبالتالي يجب أن يكون لديك فهم جيد وشامل لكيفية عمل الوجهات و<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="">الدوال غير المتزامنة</a> والعروض Views والنماذج عمليًا.
</p>

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

<ol>
	<li>
		مقدمة إلى القوالب Template، وإنشاء القالب الأساسي لموقع مكتبة محلية مثالًا.
	</li>
	<li>
		إنشاء الصفحة الرئيسية وصفحات القوائم لموقع المكتبة المحلية التي تتضمن:
		<ul>
			<li>
				صفحة قائمة الكتب.
			</li>
			<li>
				صفحة قائمة نسخ الكتب BookInstance.
			</li>
			<li>
				تنسيق التاريخ باستخدام مكتبة Luxon.
			</li>
			<li>
				صفحة قائمة المؤلفين وتحدي صفحة قائمة أنواع الكتب.
			</li>
		</ul>
	</li>
	<li>
		إنشاء صفحات التفاصيل لموقع المكتبة المحلية التي تتضمن:
		<ul>
			<li>
				صفحة تفاصيل نوع الكتاب.
			</li>
			<li>
				صفحة تفاصيل الكتاب.
			</li>
			<li>
				صفحة تفاصيل المؤلف.
			</li>
			<li>
				صفحة تفاصيل نسخ الكتاب والتحدي.
			</li>
		</ul>
	</li>
</ol>

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

<p>
	سننشئ الآن استمارات <a href="https://academy.hsoub.com/programming/html/" rel="">HTML</a> وشيفرة معالجة الاستمارات لبدء تعديل البيانات التي يخزنها الموقع.
</p>

<h2 id="-1">
	العمل مع الاستمارات
</h2>

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

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

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

<h3 id="html">
	استمارات HTML
</h3>

<p>
	اطّلع أولًا على نظرة عامة موجزة على استمارات HTML. ليكن لدينا استمارة HTML البسيطة التالية مع حقل نص واحد لإدخال اسم "الفريق" والتسمية Label المرتبطة به:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="139832" href="https://academy.hsoub.com/uploads/monthly_2023_12/01_form_example_name_field.png.3bc32e784d507cb592c8b16977b85012.png" rel=""><img alt="01 form example name field" class="ipsImage ipsImage_thumbnailed" data-fileid="139832" data-unique="pcnnxjumj" src="https://academy.hsoub.com/uploads/monthly_2023_12/01_form_example_name_field.png.3bc32e784d507cb592c8b16977b85012.png"> </a>
</p>

<p>
	تُعرَّف الاستمارات في لغة HTML بوصفها مجموعة من العناصر ضمن وسوم <code>&lt;form&gt;…&lt;/form&gt;</code> التي تحتوي على عنصر إدخال <code>input</code> واحد على الأقل من النوع <code>type="submit"‎</code>.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1317_6" style=""><span class="tag">&lt;form</span><span class="pln"> </span><span class="atn">action</span><span class="pun">=</span><span class="atv">"/team_name_url/"</span><span class="pln"> </span><span class="atn">method</span><span class="pun">=</span><span class="atv">"post"</span><span class="tag">&gt;</span><span class="pln">
  </span><span class="tag">&lt;label</span><span class="pln"> </span><span class="atn">for</span><span class="pun">=</span><span class="atv">"team_name"</span><span class="tag">&gt;</span><span class="pln">Enter name: </span><span class="tag">&lt;/label&gt;</span><span class="pln">
  </span><span class="tag">&lt;input</span><span class="pln">
    </span><span class="atn">id</span><span class="pun">=</span><span class="atv">"team_name"</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">name</span><span class="pun">=</span><span class="atv">"name_field"</span><span class="pln">
    </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"Default name for team."</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">type</span><span class="pun">=</span><span class="atv">"submit"</span><span class="pln"> </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"OK"</span><span class="pln"> </span><span class="tag">/&gt;</span><span class="pln">
</span><span class="tag">&lt;/form&gt;</span></pre>

<p>
	ضمّنا في المثال السابق حقلًا نصيًا واحدًا فقط لإدخال اسم الفريق، ولكن يمكن أن تحتوي الاستمارة على أيّ عدد من عناصر الإدخال الأخرى والتسميات Labels المرتبطة بها. تعرّف السمة <code>type</code> الخاصة بالحقل نوع عنصر واجهة المستخدم الذي سيُعرَض، وتُستخدَم سمات الاسم <code>name</code> والمعرّف <code>id</code> الخاصة بالحقل لتعريف الحقل في شيفرة <a href="https://academy.hsoub.com/programming/javascript/" rel="">جافا سكريبت Javascript</a> أو <a href="https://academy.hsoub.com/programming/css/" rel="">CSS</a> أو <a href="https://academy.hsoub.com/programming/html/" rel="">HTML</a>، بينما تعرّف السمة <code>value</code> القيمة الأولية للحقل عند عرضه لأول مرة. تُحدَّد تسمية الفريق المطابقة باستخدام الوسم <code>label</code> (مثل التسمية "Enter name" السابقة)، مع حقل <code>for</code> الذي يحتوي على قيمة معرّف <code>id</code> لحقل الإدخال <code>input</code> المرتبط به.
</p>

<p>
	يُعرَض حقل الإدخال <code>submit</code> بوصفه زرًا افتراضيًا، إذ يمكن للمستخدم الضغط عليه لرفع البيانات التي تحتويها عناصر الإدخال الأخرى إلى الخادم، وهي اسم الفريق <code>team_name</code> فقط في مثالنا. تعرّف سمات الاستمارة تابع HTTP وهو <code>method</code> المستخدَم لإرسال البيانات وهدف البيانات على الخادم (السمة <code>action</code>) كما يلي:
</p>

<ul>
	<li>
		<code>action</code>: هو المورد أو عنوان URL، إذ ستُرسَل البيانات للمعالجة عند إرسال الاستمارة. إن لم تُضبَط هذه السمة، أو ضُبِطت بوصفها سلسلة نصية فارغة، فستُرسَل الاستمارة إلى عنوان URL للصفحة الحالية.
	</li>
	<li>
		<code>method</code>: تابع HTTP المُستخدَم لإرسال البيانات: إما <code>POST</code> أو <code>GET</code>.
		<ul>
			<li>
				يجب دائمًا استخدام تابع <code>POST</code> إذا أدّت البيانات إلى تغييرٍ في قاعدة بيانات الخادم، لأنه يسمح بمقاومة أكبر لهجمات طلبات التزوير عبر المواقع.
			</li>
			<li>
				يجب استخدام تابع <code>GET</code> فقط للاستمارات التي لا تغير بيانات المستخدم (مثل استمارة البحث)، ويوصَى به عندما تريد أن تكون قادرًا على وضع إشارة مرجعية على عنوان URL أو مشاركته.
			</li>
		</ul>
	</li>
</ul>

<h3 id="-2">
	عملية معالجة الاستمارة
</h3>

<p>
	تستخدم معالجة الاستمارة الأساليب نفسها التي تعلمناها لعرض معلومات <a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D9%84%D9%86%D9%85%D8%A7%D8%B0%D8%AC-models-%D9%88%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D8%B9%D9%84%D8%A7%D9%85-%D8%B9%D9%86-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%81%D9%8A-django-r405/" rel="">النماذج Models</a>، إذ ترسِل الوِجهة طلبنا إلى دالة متحكم تطبّق أيّ إجراءات قاعدة بيانات مطلوبة بما في ذلك قراءة البيانات من النماذج، ثم تولّد صفحة HTML وتعيدها، ولكن ما يعقّد الأمور هو أن الخادم يحتاج أيضًا إلى أن يكون قادرًا على معالجة البيانات التي يقدّمها المستخدم، وإعادة عرض الاستمارة مع معلومات الخطأ عند وجود مشاكل.
</p>

<p>
	يوضّح المخطط التالي عملية معالجة طلبات الاستمارة، بدءًا من طلب الصفحة التي تحتوي على استمارة (كما هو موضّح باللون الأخضر):
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="139831" href="https://academy.hsoub.com/uploads/monthly_2023_12/02_web_server_form_handling.png.ce52eccc0b5bdce3a1afb2209a81f2b8.png" rel=""><img alt="02 web server form handling" class="ipsImage ipsImage_thumbnailed" data-fileid="139831" data-unique="svazbcobz" src="https://academy.hsoub.com/uploads/monthly_2023_12/02_web_server_form_handling.thumb.png.7b00778734c9f68a867e14767bc12121.png"> </a>
</p>

<p>
	الأشياء الرئيسية التي يجب أن تطبقها شيفرة معالجة الاستمارة هي:
</p>

<ol>
	<li>
		عرض الاستمارة الافتراضية في المرة الأولى التي يطلبها المستخدم.
		<ul>
			<li>
				يمكن أن تحتوي الاستمارة على حقول فارغة (إذا أنشأتَ سجلًا جديدًا مثلًا)، أو يمكن ملؤها مسبقًا بالقيم الأولية (إذا غيّرتَ سجلًا أو كان لديك قيم أولية افتراضية مفيدة مثلًا).
			</li>
		</ul>
	</li>
	<li>
		تلقي البيانات التي يرسلها المستخدم في طلب <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> من النوع <code>POST</code> مثلًا.
	</li>
	<li>
		التحقق من صحة البيانات وتطهيرها.
	</li>
	<li>
		إذا كان هناك بيانات غير صالحة، فأعِد عرض الاستمارة مع أيّ قيم يملأها المستخدم ورسائل خطأ للحقول التي تحتوي على مشاكل.
	</li>
	<li>
		إذا كانت جميع البيانات صالحة، فطبّق الإجراءات المطلوبة، مثل حفظ البيانات في قاعدة البيانات وإرسال إشعار بالبريد الإلكتروني وإعادة نتيجة البحث وتحميل ملف وإلخ.
	</li>
	<li>
		إعادة توجيه المستخدم إلى صفحة أخرى بعد اكتمال جميع الإجراءات.
	</li>
</ol>

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

<p>
	لا يوفّر إطار عمل Express أيّ دعم لعمليات معالجة الاستمارة، ولكن يمكنه استخدام البرمجيات الوسيطة لمعالجة معاملات <code>POST</code> و <code>GET</code> من الاستمارة وللتحقق من صحة أو تطهير قيمها.
</p>

<h3 id="-3">
	التحقق من صحة البيانات وتطهيرها
</h3>

<p>
	يجب التحقق من صحة البيانات الواردة من النموذج وتطهيرها قبل تخزينها كما يلي:
</p>

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

<p>
	سنستخدم في هذا المقال الوحدة <a href="https://www.npmjs.com/package/express-validator" rel="external nofollow">express-validator</a> الشائعة لإجراء كلٍّ من التحقق من صحة بيانات الاستمارة وتطهيرها، إذ يمكننا ثثبيتها من خلال تشغيل الأمر التالي في جذر المشروع:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1317_10" style=""><span class="pln">npm install express</span><span class="pun">-</span><span class="pln">validator</span></pre>

<p>
	<strong>ملاحظة</strong>: يوفر دليل وحدة <a href="https://express-validator.github.io/docs/#basic-guide" rel="external nofollow">express-validator</a> على غيت هب GitHub نظرة عامة جيدة على واجهة برمجة التطبيقات، لذا نوصيك بقراءته للحصول على فكرة عن جميع إمكانياتها بما في ذلك استخدام <a href="https://express-validator.github.io/docs/guides/schema-validation" rel="external nofollow">التحقق من صحة المخطط Schema Validation</a> و<a href="https://express-validator.github.io/docs/guides/customizing#custom-validators-and-sanitizers" rel="external nofollow">إنشاء أدوات تحقق مخصصة</a>، إذ سنشرح فيما يلي فقط جزءًا مفيدًا لموقع المكتبة المحلية.
</p>

<p>
	يمكن استخدام أداة التحقق من صحة البيانات Validator في المتحكمات من خلال تحديد الدوال التي نريد استيرادها من وحدة express-validator كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1317_8" style=""><span class="kwd">const</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> body</span><span class="pun">,</span><span class="pln"> validationResult </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">"express-validator"</span><span class="pun">);</span></pre>

<p>
	هناك العديد من الدوال المتاحة، مما يسمح بفحص وتطهير البيانات الواردة من معاملات الطلب وجسمه وترويساته و<a href="https://academy.hsoub.com/programming/javascript/%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D8%AA%D8%B9%D8%B1%D9%8A%D9%81-%D8%A7%D9%84%D8%A7%D8%B1%D8%AA%D8%A8%D8%A7%D8%B7-%D9%88%D8%B6%D8%A8%D8%B7%D9%87%D8%A7-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1330/" rel="">ملفات تعريف الارتباط cookies</a> وغير ذلك أو جميعها معًا في وقت واحد، إذ سنستخدم في هذا المقال <code>body</code> و <code>validationResult</code> (كما هو مطلوب سابقًا).
</p>

<p>
	تُعرَّف الدوال على النحو التالي:
</p>

<p>
	أولًا، <code>body([fields, message])‎</code> التي تحدّد مجموعة من الحقول في متن الطلب (معامل <code>POST</code>) للتحقق من صحة البيانات و/أو تطهيرها مع رسالة خطأ اختيارية يمكن عرضها إذا فشلت الاختبارات. تتمثل معايير التحقق من صحة البيانات وتطهيرها بسلسلة متعاقبة في تابع <code>body()‎</code>، فمثلًا يحدّد السطر التالي أولًا أننا نتحقق من حقل "الاسم name" وأن خطأ التحقق من صحة البيانات سيضبط رسالة الخطأ "اسم فارغ Empty name"، ثم نستدعي تابع التطهير <code>trim()‎</code> لإزالة المسافة من بداية السلسلة ونهايتها، ثم <code>isLength()‎</code> للتحقق من أن السلسلة النصية الناتجة ليست فارغة. أخيرًا، نستدعي <code>escape()‎</code> لإزالة محارف HTML من المتغير الذي يمكن استخدامه في هجمات كتابة سكربتات جافا سكربت العابرة للمواقع.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1317_12" style=""><span class="pun">[</span><span class="pln">
  </span><span class="com">// …</span><span class="pln">
  body</span><span class="pun">(</span><span class="str">"name"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Empty name"</span><span class="pun">).</span><span class="pln">trim</span><span class="pun">().</span><span class="pln">isLength</span><span class="pun">({</span><span class="pln"> min</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">escape</span><span class="pun">(),</span><span class="pln">
  </span><span class="com">// …</span><span class="pln">
</span><span class="pun">];</span></pre>

<p>
	يتحقق الاختبار التالي من أن حقل العمر age هو تاريخ صالح ويستخدم <code>optional()‎</code> لتحديد أن السلاسل النصية الخالية والفارغة لن تفشل في التحقق من صحة البيانات.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1317_14" style=""><span class="pun">[</span><span class="pln">
  </span><span class="com">// …</span><span class="pln">
  body</span><span class="pun">(</span><span class="str">"age"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Invalid age"</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">optional</span><span class="pun">({</span><span class="pln"> values</span><span class="pun">:</span><span class="pln"> </span><span class="str">"falsy"</span><span class="pln"> </span><span class="pun">})</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">isISO8601</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">toDate</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_1317_16" style=""><span class="pun">[</span><span class="pln">
  </span><span class="com">// …</span><span class="pln">
  body</span><span class="pun">(</span><span class="str">"name"</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">trim</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">isLength</span><span class="pun">({</span><span class="pln"> min</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="pun">.</span><span class="pln">withMessage</span><span class="pun">(</span><span class="str">"Name empty."</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">isAlpha</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">withMessage</span><span class="pun">(</span><span class="str">"Name must be alphabet letters."</span><span class="pun">),</span><span class="pln">
  </span><span class="com">// …</span><span class="pln">
</span><span class="pun">];</span></pre>

<p>
	ثانيًا، الدالة <code>validationResult(req)‎</code> التي تشغّل عملية التحقق من صحة البيانات، وتتيح الأخطاء بصيغة كائن النتيجة <code>validation</code>، وتستدعَى في دالة رد نداء منفصلة كما هو موضح فيما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1317_18" style=""><span class="pln">asyncHandler</span><span class="pun">(</span><span class="kwd">async</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="com">// استخراج رسائل التحقق من صحة البيانات من الطلب</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> errors </span><span class="pun">=</span><span class="pln"> validationResult</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">errors</span><span class="pun">.</span><span class="pln">isEmpty</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="com">// يمكن إعادة رسائل الخطأ في مص‫فوفة باستخدام errors.array()‎</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="com">// البيانات الواردة من الاستمارة صالحة</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	نستخدم التابع <code>isEmpty()‎</code> الخاص بنتيجة التحقق من صحة البيانات للتحقق مما إذا كان هناك أخطاء، والتابع <code>array()‎</code> للحصول على مجموعة رسائل الخطأ (اطلع على قسم <a href="https://express-validator.github.io/docs/guides/getting-started/#handling-validation-errors" rel="external nofollow">معالجة التحقق من صحة البيانات</a> للحصول على مزيد من المعلومات).
</p>

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

<h3 id="-4">
	تصميم الاستمارة
</h3>

<p>
	ترتبط أو تعتمد العديد من النماذج في المكتبة على بعضها بعضًا، إذ يتطلب الكتاب <code>Book</code> مؤلفًا <code>Author</code> ويمكن أن يكون له نوع <code>Genre</code> واحد أو أكثر، مما يؤدي إلى التساؤل حول كيفية التعامل مع الحالة التي يرغب فيها المستخدم في:
</p>

<ul>
	<li>
		إنشاء كائن عندما لا تكون الكائنات المرتبطة به موجودةً بعد، مثل كتاب لم يُعرَّف كائن المؤلف فيه.
	</li>
	<li>
		حذف كائن لا يزال كائن آخر يستخدمه مثل حذف كائن النوع <code>Genre</code> الذي لا يزال كائن الكتاب <code>Book</code> يستخدمه.
	</li>
</ul>

<p>
	سنبسّط العمل في هذا المشروع بالقول إن الاستمارة يمكنها فقط:
</p>

<ul>
	<li>
		إنشاء كائن يستخدم كائنات موجودة فعليًا، لذلك يجب على المستخدمين إنشاء النسخ المطلوبة من المؤلف <code>Author</code> ونوع الكتاب <code>Genre</code> قبل محاولة إنشاء أي كائنات <code>Book</code>.
	</li>
	<li>
		حذف كائن إن لم تشِر إليه كائنات أخرى، فمثلًا لن تتمكّن من حذف كتاب <code>Book</code> حتى حذف جميع كائنات <code>BookInstance</code> المرتبطة به.
	</li>
</ul>

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

<h3 id="routes">
	الوجهات Routes
</h3>

<p>
	سنحتاج إلى وجهتين لهما نمط عنوان URL نفسه لتقديم شيفرة معالجة الاستمارة، إذ تُستخدَم الوجهة الأولى <code>GET</code> لعرض استمارة فارغة جديدة لإنشاء الكائن، وتُستخدَم الوجهة الثانية (<code>POST</code>) للتحقق من صحة البيانات التي أدخلها المستخدم، ثم حفظ المعلومات وإعادة التوجيه إلى صفحة التفاصيل (إذا كانت البيانات صالحة) أو إعادة عرض الاستمارة مع وجود أخطاء (إذا كانت البيانات غير صالحة).
</p>

<p>
	أنشأنا الوجهات مسبقًا لجميع صفحات إنشاء النموذج في الملف "‎/routes/catalog.js" مثل وجهات نوع الكتب التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1317_20" style=""><span class="com">// طل‫ب GET لإنشاء نوع كتاب Genre، إذ يجب أن يأتي قبل الوجهة التي تعرض نوع الكتاب (تستخدم المعرّف id)</span><span class="pln">
router</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/genre/create"</span><span class="pun">,</span><span class="pln"> genre_controller</span><span class="pun">.</span><span class="pln">genre_create_get</span><span class="pun">);</span><span class="pln">

</span><span class="com">// ط‫لب POST لإنشاء نوع كتاب</span><span class="pln">
router</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">"/genre/create"</span><span class="pun">,</span><span class="pln"> genre_controller</span><span class="pun">.</span><span class="pln">genre_create_post</span><span class="pun">);</span></pre>

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

<ol>
	<li>
		إنشاء استمارة نوع الكتاب Genre: تعريف صفحة لإنشاء كائنات <code>Genre</code>.
	</li>
	<li>
		إنشاء استمارة المؤلف Author واستمارة الكتاب Book واستمارة نسخة الكتاب BookInstance: تعريف صفحة لإنشاء كائنات <code>Author</code> وتعريف صفحة أو استمارة لإنشاء كائنات <code>Book</code> وتعريف صفحة أو استمارة لإنشاء كائنات <code>BookInstance</code>.
	</li>
	<li>
		حذف استمارة المؤلف Author وتحديث استمارة الكتاب Book: تعريف صفحة لحذف كائنات <code>Author</code> وتعريف صفحة لتحديث كائنات <code>Book</code>.
	</li>
</ol>

<h3 id="-5">
	تحدى نفسك
</h3>

<p>
	طبّق صفحات الحذف لنماذج <code>Book</code> و <code>BookInstance</code> و <code>Genre</code> واربطها بصفحات التفاصيل المتعلقة بها باستخدام الطريقة نفسها لصفحة حذف المؤلف، ويجب أن تتبع الصفحات أسلوب التصميم نفسه بحيث:
</p>

<ul>
	<li>
		إذا كان هناك مراجع إلى كائن من كائنات أخرى، فيجب عرض هذه الكائنات الأخرى مع ملاحظة أنه لا يمكن حذف هذا السجل حتى حذف الكائنات.
	</li>
	<li>
		إن لم يكن هناك مراجع أخرى إلى الكائن، فيجب أن يطالب العرض بحذفه، وإذا ضغط المستخدم على زر الحذف، فيجب حذف السجل.
	</li>
</ul>

<p>
	إليك بعض النصائح لتطبيقها:
</p>

<ul>
	<li>
		يشبه حذفُ الكائن <code>Genre</code> حذفَ الكائن <code>Author</code> تمامًا، فكلا الكائنين يعتمد عليهما الكائن <code>Book</code>، لذلك لا يمكنك حذف الكائن إلّا عند حذف الكتب المرتبطة به في كلتا الحالتين.
	</li>
	<li>
		يحدث الشيء نفسه عند حذف الكائن <code>Book</code> أيضًا، إذ يجب التحقق أولًا من عدم وجود كائنات <code>BookInstance</code> مرتبطة به.
	</li>
	<li>
		يُعَد حذف الكائن <code>BookInstance</code> أسهل ما في الأمر لأنه لا توجد كائنات معتمدة عليه، إذ يمكنك في هذه الحالة العثور على السجل وحذفه.
	</li>
</ul>

<p>
	طبّق صفحات التحديث لنماذج <code>BookInstance</code> و <code>Author</code> و <code>Genre</code>، واربطها بصفحات التفاصيل المرتبطة بها بالطريقة نفسها لصفحة تحديث الكتاب.
</p>

<p>
	واتبع أيضًا النصائح التالية:
</p>

<ul>
	<li>
		تُعَد صفحة تحديث الكتاب التي طبّقناها للتو هي الأصعب، ويمكن استخدام الأنماط نفسها لصفحات التحديث للكائنات الأخرى.
	</li>
	<li>
		يُعَد حقل تاريخ وفاة المؤلف <code>Author</code> وتاريخ ميلاده وحقل تاريخ استرجاع نسخة الكتاب <code>BookInstance</code> تنسيقًا خاطئًا لإدخاله في حقل إدخال التاريخ في الاستمارة، إذ يتطلب بياناتٍ في الاستمارة بالتنسيق: "YYYY-MM-DD". أسهل طريقة للتغلب على ذلك هي تعريف خاصية افتراضية جديدة للتواريخ التي تنسّق التواريخ بصورة مناسبة، ثم استخدام هذا الحقل في قوالب العرض المرتبطة به.
	</li>
	<li>
		إذا واجهتك مشكلة، فهناك أمثلة <a href="https://github.com/mdn/express-locallibrary-tutorial" rel="external nofollow">لصفحات التحديث</a> يمكنك الاطلاع عليها.
	</li>
</ul>

<h2 id="-6">
	الخلاصة
</h2>

<p>
	توفّر حزم Express و node والحزم الخارجية <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</a> كل ما تحتاجه لإضافة استمارات إلى موقع الويب، إذ تعلمت في هذا المقال كيفية إنشاء استمارات باستخدام Pug والتحقق من صحة بيانات الإدخال وتطهيرها باستخدام express-validator وإضافة السجلات وحذفها وتعديلها في قاعدة البيانات، ويجب أن تفهم الآن كيفية إضافة الاستمارات الأساسية وشيفرة معالجة الاستمارات إلى مواقع Node الخاصة بك.
</p>

<p>
	ترجمة -وبتصرُّف- للمقالين <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data" rel="external nofollow">Express Tutorial Part 5: Displaying library data</a> و <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/forms" rel="external nofollow">Express Tutorial Part 6: Working with forms</a>.
</p>

<h1 id="-7">
	اقرأ أيضًا
</h1>

<ul>
	<li>
		المقال السابق <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%D8%A7%D9%84%D9%88%D8%AC%D9%87%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D9%85%D8%AA%D8%AD%D9%83%D9%85%D8%A7%D8%AA-r2184/" rel="">تطبيق عملي لتعلم Express - الجزء الثالث: الوجهات Routes والمتحكمات Controllers</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-forms-%D9%81%D9%8A-%D9%85%D8%AA%D8%B5%D9%81%D8%AD-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D9%88%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9%D9%87%D8%A7-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1193/" rel="">الاستمارات (forms) في متصفح الويب وكيفية التعامل معها في جافاسكربت</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/%D8%A5%D8%B1%D8%B3%D8%A7%D9%84-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-form-submit-%D9%88%D9%85%D8%B9%D8%A7%D9%84%D8%AC%D8%AA%D9%87%D8%A7-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1196/" rel="">اإرسال الاستمارات (form submit) ومعالجتها في جافاسكربت</a>.
	</li>
</ul>
]]></description><guid isPermaLink="false">2193</guid><pubDate>Sun, 10 Dec 2023 16:00:00 +0000</pubDate></item><item><title>&#x62A;&#x637;&#x628;&#x64A;&#x642; &#x639;&#x645;&#x644;&#x64A; &#x644;&#x62A;&#x639;&#x644;&#x645; Express - &#x627;&#x644;&#x62C;&#x632;&#x621; &#x627;&#x644;&#x62B;&#x627;&#x644;&#x62B;: &#x627;&#x644;&#x648;&#x62C;&#x647;&#x627;&#x62A; &#x648;&#x627;&#x644;&#x645;&#x62A;&#x62D;&#x643;&#x645;&#x627;&#x62A;</title><link>https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%D8%A7%D9%84%D9%88%D8%AC%D9%87%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D9%85%D8%AA%D8%AD%D9%83%D9%85%D8%A7%D8%AA-r2184/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_12/-.png.c1d605f85fcff27d895f5ca3859a92a4.png" /></p>
<p>
	سنُعِدّ في هذا المقال الوجهات Routes (شيفرة معالجة عناوين URL، ويسميها البعض بالموجهات) باستخدام دوال معالجة وهمية dummy لجميع النقاط النهائية للموارد التي سنحتاجها في <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2170/" rel="">موقع المكتبة المحلية LocalLibrary</a>. سيكون لدينا في النهاية بنية معيارية لشيفرة معالجة الوجهات، والتي يمكننا توسيعها باستخدام دوال معالجة حقيقية في المقالات اللاحقة، وسيصبح لدينا أيضًا فهم جيد لكيفية إنشاء وجهات معيارية باستخدام إطار عمل Express.
</p>

<ul>
	<li>
		<strong>المتطلبات الأساسية</strong>: الاطلاع على مقال <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-express-%D9%88%D8%A8%D9%8A%D8%A6%D8%A9-node-r2168/" rel=""> مدخل إلى إطار عمل الويب Express</a>، وإكمال المقالات السابقة من هذه السلسلة بما في ذلك مقال <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%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-%D9%85%D8%B9-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-mongoose-r2171/" rel="">استخدام قاعدة البيانات</a>.
	</li>
	<li>
		<strong>الهدف</strong>: فهم كيفية إنشاء وجهات بسيطة وإعداد جميع النقاط النهائية لعناوين URL.
	</li>
</ul>

<p>
	تعرّفنا في <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%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-%D9%85%D8%B9-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-mongoose-r2171/" rel="">المقال السابق</a> على نماذج Mongoose للتفاعل مع قاعدة البيانات، واستخدمنا سكربتًا مستقلًا لإنشاء بعض سجلات المكتبة الأولية، ويمكننا الآن كتابة الشيفرة البرمجية لتقديم تلك المعلومات للمستخدمين. يجب أولًا تحديد المعلومات التي نريد عرضها في صفحاتنا، ثم تعريف <a href="https://academy.hsoub.com/programming/general/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%B9%D9%86%D9%88%D8%A7%D9%86-url-%D9%88%D8%A3%D9%86%D9%88%D8%A7%D8%B9%D9%87-r1435/" rel="">عناوين URL</a> المناسبة لإعادة تلك الموارد، ثم يجب إنشاء الوجهات (معالجات عناوين URL) والعروض Views (القوالب Templates) لعرض تلك الصفحات.
</p>

<p>
	يوضح المخطط البياني الآتي التدفق الرئيسي للبيانات والأشياء التي يجب تقديمها عند التعامل مع <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%B7%D9%84%D8%A8%D8%A7%D8%AA-http-%D9%81%D9%8A-nodejs-r1868/" rel="">طلب أو استجابة HTTP</a>، ويُوضّح العروض والوجهات والمتحكّمات التي تمثل الدوال التي تفصل الشيفرة البرمجية لتوجيه الطلبات من هذه الشيفرة التي تعالج الطلبات فعليًا.
</p>

<p>
	أنشأنا النماذج مسبقًا، فالأشياء الرئيسية التي يجب إنشاؤها هي:
</p>

<ul>
	<li>
		"الوجهات Routes" لتوجيه الطلبات المدعومة وأيّ معلومات مُشفَّرة في عناوين URL للطلب إلى دوال المتحكم المناسبة.
	</li>
	<li>
		دوال المتحكم للحصول على البيانات المطلوبة من النماذج وإنشاء صفحة <a href="https://academy.hsoub.com/programming/html/" rel="">HTML</a> تعرض البيانات وإعادتها إلى المستخدم لعرضها في المتصفح.
	</li>
	<li>
		العروض (القوالب) التي تستخدمها المتحكمات لتقديم البيانات.
	</li>
</ul>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="139337" href="https://academy.hsoub.com/uploads/monthly_2023_12/01_mvc_express.png.88ae853cf0bed4c10058826bbda0a72b.png" rel=""><img alt="01 mvc express" class="ipsImage ipsImage_thumbnailed" data-fileid="139337" data-ratio="57.50" data-unique="0f6dzfvk6" style="width: 600px; height: auto;" width="600" src="https://academy.hsoub.com/uploads/monthly_2023_12/01_mvc_express.png.88ae853cf0bed4c10058826bbda0a72b.png"> </a>
</p>

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

<p>
	يوفّر القسم الأول التالي نظرةً عامةً موجزة حول كيفية استخدام برمجية Express الوسيطة <a href="https://expressjs.com/en/4x/api.html#router" rel="external nofollow">Router</a>، ثم سنستخدم ذلك في الأقسام اللاحقة عند إعداد وجهات موقع المكتبة المحلية LocalLibrary.
</p>

<h2 id="">
	مقدمة إلى الوجهات
</h2>

<p>
	الوجهة هي قسم من شيفرة Express التي تربط بين فعل HTTP، مثل <code>GET</code> و <code>POST</code> و <code>PUT</code> و <code>DELETE</code> وإلخ مع مسار أو نمط URL ودالة تُستدعى لمعالجة هذا النمط.
</p>

<p>
	هناك عدة طرق لإنشاء الوجهات، وسنستخدم في هذا المقال البرمجية الوسيطة <a href="https://expressjs.com/en/guide/routing.html#express-router" rel="external nofollow"><code>express.Router</code></a> لأنها تسمح بتجميع معالجات الوجهات لجزء معين من الموقع مع بعضها البعض والوصول إليها باستخدام بادئة prefix وجهة مشتركة. سنحتفظ بجميع الوجهات المتعلقة بالمكتبة في وحدة "دليل catalog"، وإذا أضفنا وجهات لمعالجة حسابات المستخدمين أو لدوال أخرى، فيمكننا الاحتفاظ بها مُجمَّعةً بصورة منفصلة.
</p>

<p>
	<strong>ملاحظة</strong>: ناقشنا سابقًا وجهات تطبيق Express باختصار في <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-express-%D9%88%D8%A8%D9%8A%D8%A6%D8%A9-node-r2168/" rel="">مقال سابق</a>. يشبه استخدام كائن الموجّه Router إلى حدٍ كبير تعريف الوجهات مباشرةً في كائن تطبيق Express باستثناء تقديم دعم أفضل للتقسيم إلى وحدات Modularization (كما سننافش في القسم التالي).
</p>

<p>
	يوفر الجزء المتبقي من هذا القسم نظرةً عامة حول كيفية استخدام الكائن <code>Router</code> لتعريف الوجهات.
</p>

<h3 id="-1">
	تعريف واستخدام وحدات وجهة منفصلة
</h3>

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

<p>
	أولًا، ننشئ وجهات لميزة الويكي wiki في وحدة بالاسم "wiki.js"، إذ تستورد هذه الشيفرة البرمجية كائن تطبيق Express، وتستخدمه للحصول على كائن <code>Router</code>، ثم تضيف بعض الوجهات إليه باستخدام التابع <code>get()‎</code>، ثم تصدّر الوحدةُ الكائنَ <code>Router</code>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5559_9" style=""><span class="com">// ‫wiki.js: ‫وحدة وجهة ويكي Wiki</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"> router </span><span class="pun">=</span><span class="pln"> 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">
router</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="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"> </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">"Wiki home page"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="com">// ‫وجهة صفحة About</span><span class="pln">
router</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/about"</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">req</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"About this wiki"</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"> router</span><span class="pun">;</span></pre>

<p>
	<strong>ملاحظة</strong>: عرّفنا في الشيفرة السابقة دوال رد النداء Callbacks الخاصة بمعالج الوجهة مباشرةً في دوال وجهة، وسنعرّف في موقع المكتبة المحلية LocalLibrary دوال رد النداء هذه في متحكم وحدة منفصل.
</p>

<p>
	يمكن استخدام وحدة وجهة في ملف تطبيقنا الرئيسي من خلال طلب وحدة الوجهة (wiki.js) باستخدام الدالة <code>require()‎</code>، ثم استدعاء الدالة <code>use()‎</code> في تطبيق Express لإضافة كائن <code>Router</code> إلى مسار معالجة البرمجية الوسيطة مع تحديد مسار URL الخاص بالويكي "wiki".
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5559_11" style=""><span class="kwd">const</span><span class="pln"> wiki </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"./wiki.js"</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">"/wiki"</span><span class="pun">,</span><span class="pln"> wiki</span><span class="pun">);</span></pre>

<p>
	ويمكن بعد ذلك الوصول إلى الوجهتين المُعرَّفتين في وحدة وجهة wiki من <code>/wiki/</code> و <code>/wiki/about/</code>.
</p>

<h3 id="-2">
	دوال الوجهة
</h3>

<p>
	تعرّف الوحدة السابقة بعضًا من دوال الوجهة، إذ تُعرَّف الوجهة "about" (التي سنعيد إنتاجها فيما يلي) باستخدام التابع <code>Router.get()‎</code>، والذي يستجيب فقط لطلبات HTTP من النوع GET. الوسيط الأول لهذا التابع هو مسار URL والوسيط الثاني هو دالة رد نداء تُستدعَى عند تلقي طلب HTTP من النوع GET مع المسار.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5559_13" style=""><span class="pln">router</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/about"</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">req</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"About this wiki"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	تأخذ دالة رد النداء ثلاثة وسائط تُسمَّى عادة <code>req</code> و <code>res</code> و <code>next</code>، والتي ستحتوي على كائن طلب HTTP واستجابة HTTP والدالة التالية next في سلسلة البرمجيات الوسيطة.
</p>

<p>
	<strong>ملاحظة</strong>: تُعَد دوال Router برمجيات Express وسيطة، مما يعني أنها يجب إما إكمال (أو الاستجابة) للطلب أو استدعاء الدالة <code>next</code> في السلسلة، إذ نكمل في حالتنا الطلب باستخدام التابع <code>send()‎</code>، لذلك لا يُستخدَم الوسيط <code>next</code> ونختار عدم تحديده.
</p>

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

<p>
	تستدعي دالة رد النداء في مثالنا التابع <code>send()‎</code> في الاستجابة لإعادة السلسلة النصية "About this wiki" عندما نتلقى طلب GET مع المسار ("<code>‎/about</code>"). هناك عدد من توابع الاستجابة الأخرى لإنهاء دورة الطلب/الاستجابة، فمثلًا يمكنك استدعاء التابع <code>res.json()‎</code> لإرسال استجابة <a href="https://academy.hsoub.com/programming/javascript/%D8%AA%D8%B9%D9%84%D9%85-json-r604/" rel="">JSON</a> أو التابع <code>res.sendFile()‎</code> لإرسال ملف. يُعَد التابع <code>render()‎</code> تابع الاستجابة الذي سنستخدمه غالبًا أثناء بناء المكتبة، والذي ينشئ ويعيد ملفات HTML باستخدام القوالب والبيانات (سنتحدث عن ذلك في مقالٍ لاحق).
</p>

<h3 id="http">
	أفعال HTTP
</h3>

<p>
	تستخدم أمثلة الوجهات السابقة التابع <code>Router.get()‎</code> للاستجابة على طلبات HTTP من النوع GET مع مسار معين.
</p>

<p>
	يوفّر الكائن <code>Router</code> أيضًا توابع وجهة لجميع أفعال HTTP الأخرى، والتي تُستخدَم غالبًا بنفس الطريقة تمامًا ومن هذه التوابع: <code>post()‎</code> و <code>put()‎</code> و <code>delete()‎</code> و <code>options()‎</code> و <code>trace()‎</code> و <code>copy()‎</code> و <code>lock()‎</code> و <code>mkcol()‎</code> و <code>move()‎</code> و <code>purge()‎</code> و <code>propfind()‎</code> و <code>proppatch()‎</code> و <code>unlock()‎</code> و <code>report()‎</code> و <code>mkactivity()‎</code> و <code>checkout()‎</code> و <code>merge()‎</code> و <code>m-search()‎</code> و <code>notify()‎</code> و <code>subscribe()‎</code> و <code>unsubscribe()‎</code> و <code>patch()‎</code> و <code>search()‎</code> و <code>connect()‎</code>.
</p>

<p>
	تتصرف الشيفرة البرمجية التالية مثلًا مثل وجهة <code>‎/about</code> السابقة، ولكنها تستجيب فقط لطلبات HTTP من النوع <code>POST</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5559_15" style=""><span class="pln">router</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">"/about"</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">"About this wiki"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span></pre>

<h3 id="-3">
	مسارات الوجهة
</h3>

<p>
	تعرِّف مساراتُ الوجهة النقاطَ النهائية التي يمكن إجراء الطلبات عندها، فالأمثلة التي رأيناها حتى الآن كانت مجرد سلاسل نصية، واُستخدِمت كما هو مكتوب تمامًا مثل: '/' و '‎/about' و '‎/book' و '‎/any-random.path'.
</p>

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

<ul>
	<li>
		<code>?</code>: يجب أن تحتوي النقطة النهائية endpoint على 0 أو 1 من المحرف السابق (أو المجموعة)، فمثلًا سيتطابق مسار الوجهة <code>'‎/ab?cd'</code> مع النقاط النهائية <code>acd</code> أو <code>abcd</code>.
	</li>
	<li>
		<code>+</code>: يجب أن تحتوي النقطة النهائية على واحد أو أكثر من المحرف السابق (أو المجموعة)، فمثلًا سيتطابق مسار الوجهة <code>'‎/ab+cd'</code> مع النقاط النهائية <code>abcd</code> و <code>abbcd</code> و <code>abbbcd</code> وإلخ.
	</li>
	<li>
		<code>*</code>: يمكن أن تحتوي النقطة النهائية على سلسلة نصية عشوائية في مكان المحرف <code>*</code>، فمثلًا سيتطابق مسار الوجهة <code>'‎/ab*cd'</code> مع النقاط النهائية <code>abcd</code> و <code>abXcd</code> و <code>abSOMErandomTEXTcd</code> وإلخ.
	</li>
	<li>
		<code>()</code>: تجميع تطابق مجموعة من المحارف لإجراء عملية أخرى عليها، فمثلًا سينفّذ <code>'‎/ab(cd)?e'</code> تطابق <code>?</code> على المجموعة <code>(cd)</code>، إذ سيتطابق مع <code>abe</code> و <code>abcde</code>.
	</li>
</ul>

<p>
	يمكن أن تكون مسارات الوجهة أيضًا <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D8%A8%D9%8A%D8%B1-%D8%A7%D9%84%D9%86%D9%85%D8%B7%D9%8A%D8%A9-regular-expressions-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1278/" rel="">تعابير نمطية Regular Expressions</a> في <a href="https://academy.hsoub.com/programming/javascript/" rel="">لغة جافا سكريبت JavaScript</a>، فمثلًا سيتطابق مسار الوجهة التالي مع <code>catfish</code> و <code>dogfish</code>، ولكن لن يتطابق مع <code>catflap</code> و <code>catfishhead</code> وما إلى ذلك. لاحظ أن مسار التعبير النمطي يستخدم صياغة التعابير النمطية، وهي ليست سلسلة نصية بين علامات اقتباس كما في الحالات السابقة.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5559_17" style=""><span class="pln">app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">/.*fish$/</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">req</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="com">// …</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	<strong>ملاحظة</strong>: ستستخدم معظم الوجهات في موقع المكتبة المحلية LocalLibrary سلاسلًا نصية ولن تستخدم تعابيرًا نمطية، وسنستخدم أيضًا معاملات الوجهات كما سنناقش في القسم التالي.
</p>

<h3 id="-4">
	معاملات الوجهة
</h3>

<p>
	تُسمَّى معاملات الوجهة بأجزاء Segments عنوان URL المُستخدَمة لالتقاط القيم في مواضع محددة من عنوان URL، إذ تُسبَق هذه المقاطع بنقطتين ثم الاسم (مثل <code>‎/:your_parameter_name/‎</code>). تُخزَّن القيم المُلتقَطة في كائن <code>req.params</code> من خلال استخدام أسماء المعاملات بوصفها مفاتيحًا، مثل <code>req.params.your_parameter_name</code>.
</p>

<p>
	ليكن لدينا مثلًا عنوان URL مُشفَّر يحتوي على معلومات حول المستخدمين والكتب: <code><a href="http://localhost:3000/users/34/books/8989" ipsnoembed="true" rel="external nofollow">http://localhost:3000/users/34/books/8989</a></code>، إذ يمكننا استخراج هذه المعلومات كما هو موضح فيما يلي باستخدام معاملات مسار <code>userId</code> و <code>bookId</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5559_19" style=""><span class="pln">app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/users/:userId/books/:bookId"</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">
  </span><span class="com">// ‫الوصول إلى userId باستخدام req.params.userId</span><span class="pln">
  </span><span class="com">// الوصول إلى‫ bookId باستخدام req.params.bookId</span><span class="pln">
  res</span><span class="pun">.</span><span class="pln">send</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">
</span><span class="pun">});</span></pre>

<p>
	يجب أن تتكون أسماء معاملات الوجهة من "محارف كلمات"، أي A-Z و a-z و0-9 و _.
</p>

<p>
	<strong>ملاحظة</strong>: ستجري مطابقة عنوان "‎/book/create" مع وجهة مثل الوجهة <code>‎/book/:bookId</code>، والتي ستستخرج قيمة "bookId" الخاصة بالإنشاء '<code>create</code>'. ستُستخدَم الوجهة الأولى التي تطابق عنوان URL الوارد، لذلك إذا أردتَ معالجة عناوين <code>‎/book/create</code> بصورة منفصلة، فيجب تعريف معالج الوجهة قبل الوجهة <code>‎/book/:bookId</code>.
</p>

<p>
	هذا كل ما تحتاجه لبدء استخدام الوجهات، ولكن يمكنك العثور على مزيد من المعلومات في مقال <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="">كيفية تحديد الوجهات وأنواع طلبات HTTP</a> وفي توثيق Express: <a href="https://expressjs.com/en/starter/basic-routing.html" rel="external nofollow">التوجيه الأساسي</a> و<a href="https://expressjs.com/en/guide/routing.html" rel="external nofollow">دليل التوجيه</a>. توضح الأقسام التالية كيفية إعداد الوجهات والمتحكمات لموقع المكتبة المحلية.
</p>

<h3 id="-5">
	معالجة الأخطاء في دوال الوجهة
</h3>

<p>
	تحتوي جميع دوال الوجهة الموضحة سابقًا على وسائط <code>req</code> و <code>res</code> التي تمثل الطلب والاستجابة على التوالي. تُستدعَى دوال الوجهة أيضًا مع وسيط ثالث هو <code>next</code>، والذي يمكن استخدامه لتمرير الأخطاء إلى سلسلة برمجيات Express الوسيطة. توضح الشيفرة التالية كيفية إنجاز ذلك باستخدام مثال استعلام قاعدة البيانات الذي يأخذ دالة رد نداء ويعيد إما خطأ <code>err</code> أو بعض النتائج. تُستدعَى <code>next</code> مع خطأ <code>err</code> بوصفه قيمة معاملها الأول عند إعادة خطأ <code>err</code>، وسينتقل هذا الخطأ في النهاية إلى شيفرة معالجة الأخطاء العامة، وتُعاد البيانات المطلوبة ثم تُستخدَم في الاستجابة في حالة النجاح.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5559_21" style=""><span class="pln">router</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/about"</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">About</span><span class="pun">.</span><span class="pln">find</span><span class="pun">({}).</span><span class="pln">exec</span><span class="pun">((</span><span class="pln">err</span><span class="pun">,</span><span class="pln"> queryResults</span><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="pun">{</span><span class="pln">
        </span><span class="kwd">return</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="com">// اعرض شيئًا ما في حالة النجاح</span><span class="pln">
      res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">"about_view"</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="str">"About"</span><span class="pun">,</span><span class="pln"> list</span><span class="pun">:</span><span class="pln"> queryResults </span><span class="pun">});</span><span class="pln">
    </span><span class="pun">});</span><span class="pln">
</span><span class="pun">});</span></pre>

<h3 id="-6">
	معالجة الاستثناءات في دوال الوجهة
</h3>

<p>
	يوضح القسم السابق كيف يتوقع إطار عمل Express أن تعيد دوال الوجهة أخطاءً، إذ صُمِّم إطار العمل للاستخدام مع الدوال غير المتزامنة التي تأخذ دالة رد نداء (مع خطأ وووسيط النتيجة)، والتي تُستدعَى عند اكتمال العملية. يُعَد ذلك مشكلة لأننا سنجري لاحقًا استعلامات قاعدة بيانات Mongoose التي تستخدم واجهات برمجة تطبيقات مستندة إلى <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="">الوعود promise</a>، والتي يمكن أن تؤدي إلى رمي استثناءات في دوال الوجهة بدلًا من إعادة الأخطاء في دالة رد النداء.
</p>

<p>
	يمكن أن يعالج إطار العمل الاستثناءات بصورة صحيحة من خلال اكتشافها ثم توجيهها بوصفها أخطاءً كما هو موضح في القسم السابق.
</p>

<p>
	<strong>ملاحظة</strong>: من المتوقع أن يعالج Express 5 التجريبي استثناءات جافا سكريبت بطريقة أصيلة.
</p>

<p>
	ليكن لدينا المثال البسيط من القسم السابق مع وجود <code>About.find().exec()‎</code> بوصفه استعلام قاعدة بيانات يعيد وعدًا، إذ يمكن كتابة دالة الوجهة ضمن كتلة <a href="https://wiki.hsoub.com/JavaScript/try%E2%80%A6catch" rel="external"><code>try...catch</code></a> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5559_23" style=""><span class="pln">exports</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/about"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">async</span><span class="pln"> </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">
  </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"> successfulResult </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> </span><span class="typ">About</span><span class="pun">.</span><span class="pln">find</span><span class="pun">({}).</span><span class="pln">exec</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">"about_view"</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="str">"About"</span><span class="pun">,</span><span class="pln"> list</span><span class="pun">:</span><span class="pln"> successfulResult </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"> 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>
	يُعَد ذلك كمًا كبيرًا جدًا من الشيفرة البرمجية المتداولة لإضافتها إلى كل دالة، لذا سنستخدم بدلًا من ذلك وحدة <a href="https://www.npmjs.com/package/express-async-handler" rel="external nofollow">express-async-handler</a> التي تعرّف دالة تغليف تخفي كتلة <code>try...catch</code> والشيفرة البرمجية لتوجيه الخطأ، وبالتالي أصبح المثال نفسه الآن بسيطًا جدًا، لأننا نحتاج فقط إلى كتابة شيفرة برمجية للحالة التي نفترض فيها النجاح:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5559_25" style=""><span class="com">// استيراد الوحدة</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> asyncHandler </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"express-async-handler"</span><span class="pun">);</span><span class="pln">

exports</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="pln">
  </span><span class="str">"/about"</span><span class="pun">,</span><span class="pln">
  asyncHandler</span><span class="pun">(</span><span class="kwd">async</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="kwd">const</span><span class="pln"> successfulResult </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> </span><span class="typ">About</span><span class="pun">.</span><span class="pln">find</span><span class="pun">({}).</span><span class="pln">exec</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">"about_view"</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="str">"About"</span><span class="pun">,</span><span class="pln"> list</span><span class="pun">:</span><span class="pln"> successfulResult </span><span class="pun">});</span><span class="pln">
  </span><span class="pun">}),</span><span class="pln">
</span><span class="pun">);</span></pre>

<h2 id="-7">
	الوجهات اللازمة لموقع المكتبة المحلية
</h2>

<p>
	سنعرض فيما يلي عناوين URL التي سنحتاجها لصفحاتنا، إذ نضع اسم كل نموذج من نماذجنا (الكتاب ونسخة الكتاب ونوع الكتاب والمؤلف) مكان الكائن object، والكائنات objects هي جمع كائن، والمعرّف id هو نسخة فريدة من الحقل (<code>‎_id</code>) تُعطَى لكل نسخة من نموذج Mongoose افتراضيًا.
</p>

<ul>
	<li>
		<code>catalog/‏‎</code>: الصفحة الرئيسية أو صفحة الفهرس.
	</li>
	<li>
		<code>catalog/&lt;objects&gt;/‏‎</code>: قائمة بجميع الكتب أو نسخها أو أنواعها أو المؤلفين، مثل <code>/catalog/books/</code> و <code>/catalog/genres/</code> وغير ذلك.
	</li>
	<li>
		<code>catalog/&lt;object&gt;/&lt;id&gt;‎</code>: صفحة التفاصيل لكتاب أو نسخة أو نوع أو مؤلف معين مع قيمة الحقل <code>‎_id</code> المُحدَّدة، مثل <code>‎/catalog/book/584493c1f4887f06c0e67d37</code>.
	</li>
	<li>
		<code>catalog/&lt;object&gt;/create</code>: استمارة إنشاء كتاب أو نسخة أو نوع أو مؤلف جديد، مثل <code>‎/catalog/book/create</code>.
	</li>
	<li>
		<code>catalog/&lt;object&gt;/&lt;id&gt;/update</code>: استمارة لتحديث كتاب أو نسخة أو نوع أو مؤلف معين مع قيمة الحقل <code>‎_id</code> المُحدَّدة، مثل <code>‎/catalog/book/584493c1f4887f06c0e67d37/update</code>.
	</li>
	<li>
		<code>catalog/&lt;object&gt;/&lt;id&gt;/delete</code>: استمارة لحذف كتاب أو نسخة أو نوع أو مؤلف معين مع قيمة الحقل <code>‎_id</code> المُحدَّدة، مثل <code>‎/catalog/book/584493c1f4887f06c0e67d37/delete</code>.
	</li>
</ul>

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

<p>
	تُستخدَم عناوين URL الأخرى للعمل على نسخةٍ من مستند أو نموذج معين، وتشفّر هوية العنصر في عنوان URL (كما هو موضّح في العنصر <code>&lt;id&gt;</code> سابقًا). سنستخدم معاملات المسار لاستخراج المعلومات المشفرة وتمريرها إلى معالج الوجهة، وسنستخدمها في مقال لاحق لتحديد المعلومات التي نحصل عليها من قاعدة البيانات ديناميكيًا. نحتاج فقط إلى وجهة واحدة لكل مورد من نوع معين من خلال تشفير المعلومات في عنوان URL، مثل استخدام وجهة واحدة للتعامل مع عرض عنصر كتاب واحد.
</p>

<p>
	<strong>ملاحظة</strong>: يسمح إطار عمل Express بإنشاء عناوين URL الخاصة بك بالطريقة التي تريدها، إذ يمكنك تشفير المعلومات في متن عنوان URL أو استخدام معاملات URL من النوع <code>GET</code> (مثل <code>‎/book/?id=6</code>). يجب أن تظل عناوين URL نظيفة ومنطقية وقابلة للقراءة بغض النظر عن الطريقة التي تستخدمها.
</p>

<p>
	سننشئ فيما يلي دوال رد نداء معالجة الوجهة وشيفرة الوجهة البرمجية لجميع عناوين URL السابقة.
</p>

<h2 id="-8">
	إنشاء دوال رد النداء لمعالجة الوجهة
</h2>

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

<p>
	ابدأ بإنشاء مجلدٍ للمتحكمات في جذر المشروع <code>‎/controllers</code>، ثم أنشئ ملفات أو وحدات منفصلة للمتحكمات للتعامل مع كل نموذج كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5559_27" style=""><span class="str">/express-locallibrary-tutorial  /</span><span class="pun">/</span><span class="pln">the project root
  </span><span class="pun">/</span><span class="pln">controllers
    authorController</span><span class="pun">.</span><span class="pln">js
    bookController</span><span class="pun">.</span><span class="pln">js
    bookinstanceController</span><span class="pun">.</span><span class="pln">js
    genreController</span><span class="pun">.</span><span class="pln">js</span></pre>

<p>
	ستستخدم المتحكمات الوحدة <code>express-async-handler</code>، لذا ثبّتها في المكتبة باستخدام <code>npm</code> قبل المتابعة كما يلي:
</p>

<pre class="ipsCode">npm install --save express-async-handler
</pre>

<h3 id="author">
	متحكم المؤلف Author
</h3>

<p>
	افتح الملف "‎/controllers/authorController.js" واكتب فيه الشيفرة البرمجية التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5559_29" style=""><span class="kwd">const</span><span class="pln"> </span><span class="typ">Author</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"../models/author"</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> asyncHandler </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"express-async-handler"</span><span class="pun">);</span><span class="pln">

</span><span class="com">// عرض قائمة بجميع المؤلفين</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">author_list </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"NOT IMPLEMENTED: Author list"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="com">// عرض صفحة التفاصيل لمؤلف معين</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">author_detail </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(`</span><span class="pln">NOT IMPLEMENTED</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Author</span><span class="pln"> detail</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">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="com">// عرض استمارة إنشاء م‫ؤلف باستخدام GET</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">author_create_get </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"NOT IMPLEMENTED: Author create GET"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="com">// معالجة إنشاء مؤلف باستخدام‫ POST</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">author_create_post </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"NOT IMPLEMENTED: Author create POST"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="com">// عرض است‫مارة حذف المؤلف باستخدام GET</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">author_delete_get </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"NOT IMPLEMENTED: Author delete GET"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="com">// معالجة ‫حذف المؤلف باستخدام POST</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">author_delete_post </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"NOT IMPLEMENTED: Author delete POST"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="com">// عرض استمارة تحد‫يث المؤلف باستخدام GET</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">author_update_get </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"NOT IMPLEMENTED: Author update GET"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="com">// معالجة تحديث‫ المؤلف باستخدام POST</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">author_update_post </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"NOT IMPLEMENTED: Author update POST"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	تطلب الوحدة أولًا نموذج المؤلف <code>Author</code> الذي سنستخدمه لاحقًا للوصول إلى بياناتنا وتحديثها، والمغلِّف <code>asyncHandler</code> الذي سنستخدمه لالتقاط الاستثناءات من دوال معالج الوجهة، ثم تصدِّر دوالًا لكل عنوان من عناوين URL التي نرغب في معالجتها. لاحظ أن عمليات الإنشاء والتحديث والحذف تستخدم <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-forms-%D9%81%D9%8A-%D9%85%D8%AA%D8%B5%D9%81%D8%AD-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D9%88%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9%D9%87%D8%A7-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1193/" rel="">الاستمارات Forms</a>، وبالتالي تملك توابعًا إضافية لمعالجة طلبات الاستمارة من النوع Post، وسنناقش هذه التوابع في مقال لاحق.
</p>

<p>
	تستخدم جميع الدوال الدالة المُغلِّفة Wrapper السابقة في معالجة الاستثناءات في دوال الوجهة مع وسائط للطلب والاستجابة والدالة التالية next، وتستجيب الدوال بسلسلة نصية تشير إلى أن الصفحة المرتبطة لم تُنشَأ بعد. إذا كان من المتوقع أن تتلقى دالة المتحكم معاملات المسار، فستكون هذه المعاملات هي الخرج الموجود في سلسلة الرسالة النصية (لاحظ <code>req.params.id</code>).
</p>

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

<h4 id="bookinstance">
	متحكم نسخة الكتاب BookInstance
</h4>

<p>
	افتح الملف ‎/controllers/bookinstanceController.js وانسخ الشيفرة البرمجية التالية التي تتبع نمطًا مطابقًا لوحدة المتحكم <code>Author</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5559_31" style=""><span class="kwd">const</span><span class="pln"> </span><span class="typ">BookInstance</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"../models/bookinstance"</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> asyncHandler </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"express-async-handler"</span><span class="pun">);</span><span class="pln">

</span><span class="com">// ‫عرض قائمة بجميع نسخ الكتب BookInstance</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">bookinstance_list </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"NOT IMPLEMENTED: BookInstance list"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="com">// ‫عرض صفحة تفاصيل نسخة كتاب BookInstance معينة</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">bookinstance_detail </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(`</span><span class="pln">NOT IMPLEMENTED</span><span class="pun">:</span><span class="pln"> </span><span class="typ">BookInstance</span><span class="pln"> detail</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">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="com">// ‫عرض استمارة إنشاء نسخة الكتاب BookInstance باستخدام GET</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">bookinstance_create_get </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"NOT IMPLEMENTED: BookInstance create GET"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="com">// ‫معالجة إنشاء نسخة كتاب BookInstance باستخدام POST</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">bookinstance_create_post </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"NOT IMPLEMENTED: BookInstance create POST"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="com">// ع‫رض استمارة حذف نسخة كتاب BookInstance باستخدام GET</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">bookinstance_delete_get </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"NOT IMPLEMENTED: BookInstance delete GET"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="com">// معالجة حذ‫ف نسخة كتاب BookInstance باستخدام POST</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">bookinstance_delete_post </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"NOT IMPLEMENTED: BookInstance delete POST"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="com">// عرض‫ استمارة تحديث نسخة كتاب BookInstance باستخدام GET</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">bookinstance_update_get </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"NOT IMPLEMENTED: BookInstance update GET"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="com">// معالجة‫ تحديث نسخة الكتاب باستخدام POST</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">bookinstance_update_post </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"NOT IMPLEMENTED: BookInstance update POST"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span></pre>

<h4 id="genre">
	متحكم نوع الكتاب Genre
</h4>

<p>
	افتح الملف ‎/controllers/genreController.js والصق فيه النص التالي الذي يتبع نمطًا مطابقًا لملفات <code>Author</code> و <code>BookInstance</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5559_33" style=""><span class="kwd">const</span><span class="pln"> </span><span class="typ">Genre</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"../models/genre"</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> asyncHandler </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"express-async-handler"</span><span class="pun">);</span><span class="pln">

</span><span class="com">// عرض ق‫ائمة بجميع أنواع الكتب Genre</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">genre_list </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"NOT IMPLEMENTED: Genre list"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="com">// عرض صفحة ت‫فاصيل نوع كتاب Genre معين</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">genre_detail </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(`</span><span class="pln">NOT IMPLEMENTED</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Genre</span><span class="pln"> detail</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">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="com">// عرض استم‫ارة إنشاء نوع الكتاب Genre باستخدام GET</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">genre_create_get </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"NOT IMPLEMENTED: Genre create GET"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="com">// معالجة إنشاء ‫نوع كتاب Genre باستخدام POST</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">genre_create_post </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"NOT IMPLEMENTED: Genre create POST"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="com">// عرض اس‫تمارة حذف نوع كتاب Genre باستخدام GET</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">genre_delete_get </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"NOT IMPLEMENTED: Genre delete GET"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="com">// معال‫جة حذف نوع كتاب Genre باستخدام POST</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">genre_delete_post </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"NOT IMPLEMENTED: Genre delete POST"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="com">// عرض استمارة تحد‫يث نوع كتاب Genre باستخدام GET</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">genre_update_get </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"NOT IMPLEMENTED: Genre update GET"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="com">// معالجة ‫تحديث نوع كتاب Genre باستخدام POST</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">genre_update_post </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"NOT IMPLEMENTED: Genre update POST"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span></pre>

<h4 id="book">
	متحكم الكتاب Book
</h4>

<p>
	افتح الملف ‎/controllers/bookController.js وانسخ الشيفرة البرمجية التالية، إذ يتبع هذا الملف النمط المتبع نفسه في وحدات المتحكمات الأخرى، ولكنه يحتوي أيضًا على الدالة <code>index()‎</code> لعرض صفحة الترحيب بالموقع:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5559_35" style=""><span class="kwd">const</span><span class="pln"> </span><span class="typ">Book</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"../models/book"</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> asyncHandler </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"express-async-handler"</span><span class="pun">);</span><span class="pln">

exports</span><span class="pun">.</span><span class="pln">index </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"NOT IMPLEMENTED: Site Home Page"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="com">// عرض قائمة بجميع الكتب</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">book_list </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"NOT IMPLEMENTED: Book list"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="com">// عرض صفحة تفاصيل كتاب معين</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">book_detail </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(`</span><span class="pln">NOT IMPLEMENTED</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Book</span><span class="pln"> detail</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">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="com">// عرض استما‫رة إنشاء كتاب باستخدام GET</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">book_create_get </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"NOT IMPLEMENTED: Book create GET"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="com">// معال‫جة إنشاء كتاب باستخدام POST</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">book_create_post </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"NOT IMPLEMENTED: Book create POST"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="com">// عرض است‫مارة حذف كتاب باستخدام GET</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">book_delete_get </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"NOT IMPLEMENTED: Book delete GET"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="com">// معالج‫ة حذف كتاب باستخدام POST</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">book_delete_post </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"NOT IMPLEMENTED: Book delete POST"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="com">// عرض استمارة تحدي‫ث كتاب باستخدام GET</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">book_update_get </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"NOT IMPLEMENTED: Book update GET"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="com">// معالجة تحديث كت‫اب باستخدام POST</span><span class="pln">
exports</span><span class="pun">.</span><span class="pln">book_update_post </span><span class="pun">=</span><span class="pln"> asyncHandler</span><span class="pun">(</span><span class="kwd">async</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"NOT IMPLEMENTED: Book update POST"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span></pre>

<h2 id="-9">
	إنشاء دليل وحدات الوجهة
</h2>

<p>
	سننشئ الآن وجهات لجميع عناوين URL التي يحتاجها موقع المكتبة المحلية LocalLibrary، والتي ستستدعي دوال المتحكمات التي عرّفناها في الأقسام السابقة.
</p>

<p>
	تحتوي البنية الهيكلية للموقع مسبقًا على المجلد "‎./routes" الذي يحتوي على وجهات لصفحة الفهرس index والمستخدمين users. أنشئ ملف وجهة آخر هو catalog.js ضمن هذا المجلد كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5559_37" style=""><span class="str">/express-locallibrary-tutorial /</span><span class="pun">/</span><span class="pln">the project root
  </span><span class="pun">/</span><span class="pln">routes
    index</span><span class="pun">.</span><span class="pln">js
    users</span><span class="pun">.</span><span class="pln">js
    catalog</span><span class="pun">.</span><span class="pln">js</span></pre>

<p>
	افتح الملف ‎/routes/catalog.js وانسخ الشيفرة البرمجية التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5559_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"> router </span><span class="pun">=</span><span class="pln"> 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">
</span><span class="kwd">const</span><span class="pln"> book_controller </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"../controllers/bookController"</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> author_controller </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"../controllers/authorController"</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> genre_controller </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"../controllers/genreController"</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> book_instance_controller </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"../controllers/bookinstanceController"</span><span class="pun">);</span><span class="pln">

</span><span class="com">/// وجهات الكتاب ///</span><span class="pln">

</span><span class="com">// الحصول على صفحة الدليل الرئيسية</span><span class="pln">
router</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"> book_controller</span><span class="pun">.</span><span class="pln">index</span><span class="pun">);</span><span class="pln">

</span><span class="com">// ط‫لب GET لإنشاء كتاب. يجب أن يأتي هذا الطلب قبل الوجهات Routes التي تعرض الكتاب (يستخدم المعرّف id)</span><span class="pln">
router</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/book/create"</span><span class="pun">,</span><span class="pln"> book_controller</span><span class="pun">.</span><span class="pln">book_create_get</span><span class="pun">);</span><span class="pln">

</span><span class="com">// طل‫ب POST لإنشاء كتاب</span><span class="pln">
router</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">"/book/create"</span><span class="pun">,</span><span class="pln"> book_controller</span><span class="pun">.</span><span class="pln">book_create_post</span><span class="pun">);</span><span class="pln">

</span><span class="com">// طل‫ب GET لحذف كتاب</span><span class="pln">
router</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/book/:id/delete"</span><span class="pun">,</span><span class="pln"> book_controller</span><span class="pun">.</span><span class="pln">book_delete_get</span><span class="pun">);</span><span class="pln">

</span><span class="com">// ط‫لب POST لحذف كتاب</span><span class="pln">
router</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">"/book/:id/delete"</span><span class="pun">,</span><span class="pln"> book_controller</span><span class="pun">.</span><span class="pln">book_delete_post</span><span class="pun">);</span><span class="pln">

</span><span class="com">// ‫طلب GET لتحديث كتاب</span><span class="pln">
router</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/book/:id/update"</span><span class="pun">,</span><span class="pln"> book_controller</span><span class="pun">.</span><span class="pln">book_update_get</span><span class="pun">);</span><span class="pln">

</span><span class="com">// ط‫لب POST لتحديث كتاب</span><span class="pln">
router</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">"/book/:id/update"</span><span class="pun">,</span><span class="pln"> book_controller</span><span class="pun">.</span><span class="pln">book_update_post</span><span class="pun">);</span><span class="pln">

</span><span class="com">// ط‫لب GET لكتاب واحد</span><span class="pln">
router</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/book/:id"</span><span class="pun">,</span><span class="pln"> book_controller</span><span class="pun">.</span><span class="pln">book_detail</span><span class="pun">);</span><span class="pln">

</span><span class="com">// ‫طلب GET لقائمة جميع عناصر الكتب</span><span class="pln">
router</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/books"</span><span class="pun">,</span><span class="pln"> book_controller</span><span class="pun">.</span><span class="pln">book_list</span><span class="pun">);</span><span class="pln">

</span><span class="com">/// وجهات المؤلف ///</span><span class="pln">

</span><span class="com">// ط‫لب GET لإنشاء مؤلف Author. يجب أن يأتي هذا الطلب قبل وجهة المعرّف (مثل عرض مؤلف)</span><span class="pln">
router</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/author/create"</span><span class="pun">,</span><span class="pln"> author_controller</span><span class="pun">.</span><span class="pln">author_create_get</span><span class="pun">);</span><span class="pln">

</span><span class="com">// ط‫لب POST لإنشاء مؤلف</span><span class="pln">
router</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">"/author/create"</span><span class="pun">,</span><span class="pln"> author_controller</span><span class="pun">.</span><span class="pln">author_create_post</span><span class="pun">);</span><span class="pln">

</span><span class="com">// ط‫لب GET لحذف مؤلف</span><span class="pln">
router</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/author/:id/delete"</span><span class="pun">,</span><span class="pln"> author_controller</span><span class="pun">.</span><span class="pln">author_delete_get</span><span class="pun">);</span><span class="pln">

</span><span class="com">// طل‫ب POST لحذف مؤلف</span><span class="pln">
router</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">"/author/:id/delete"</span><span class="pun">,</span><span class="pln"> author_controller</span><span class="pun">.</span><span class="pln">author_delete_post</span><span class="pun">);</span><span class="pln">

</span><span class="com">// ط‫لب GET لتحديث مؤلف</span><span class="pln">
router</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/author/:id/update"</span><span class="pun">,</span><span class="pln"> author_controller</span><span class="pun">.</span><span class="pln">author_update_get</span><span class="pun">);</span><span class="pln">

</span><span class="com">// ط‫لب POST لتحديث مؤلف</span><span class="pln">
router</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">"/author/:id/update"</span><span class="pun">,</span><span class="pln"> author_controller</span><span class="pun">.</span><span class="pln">author_update_post</span><span class="pun">);</span><span class="pln">

</span><span class="com">// ط‫لب GET لمؤلف واحد</span><span class="pln">
router</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/author/:id"</span><span class="pun">,</span><span class="pln"> author_controller</span><span class="pun">.</span><span class="pln">author_detail</span><span class="pun">);</span><span class="pln">

</span><span class="com">// ط‫لب GET لقائمة جميع المؤلفين</span><span class="pln">
router</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/authors"</span><span class="pun">,</span><span class="pln"> author_controller</span><span class="pun">.</span><span class="pln">author_list</span><span class="pun">);</span><span class="pln">

</span><span class="pun">‏</span><span class="com">/// وجهات‫ نوع الكتاب GENRE ///</span><span class="pln">

</span><span class="com">// طل‫ب GET لإنشاء نوع كتاب. يجب أن يأتي هذا الطلب قبل الوجهة التي تعرض نوع الكتاب (يستخدم المعرّف)</span><span class="pln">
router</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/genre/create"</span><span class="pun">,</span><span class="pln"> genre_controller</span><span class="pun">.</span><span class="pln">genre_create_get</span><span class="pun">);</span><span class="pln">

</span><span class="com">// طل‫ب POST لإنشاء نوع كتاب</span><span class="pln">
router</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">"/genre/create"</span><span class="pun">,</span><span class="pln"> genre_controller</span><span class="pun">.</span><span class="pln">genre_create_post</span><span class="pun">);</span><span class="pln">

</span><span class="com">// طل‫ب GET لحذف نوع كتاب</span><span class="pln">
router</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/genre/:id/delete"</span><span class="pun">,</span><span class="pln"> genre_controller</span><span class="pun">.</span><span class="pln">genre_delete_get</span><span class="pun">);</span><span class="pln">

</span><span class="com">// ط‫لب POST لحذف نوع كتاب</span><span class="pln">
router</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">"/genre/:id/delete"</span><span class="pun">,</span><span class="pln"> genre_controller</span><span class="pun">.</span><span class="pln">genre_delete_post</span><span class="pun">);</span><span class="pln">

</span><span class="com">// ط‫لب GET لتحديث نوع كتاب</span><span class="pln">
router</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/genre/:id/update"</span><span class="pun">,</span><span class="pln"> genre_controller</span><span class="pun">.</span><span class="pln">genre_update_get</span><span class="pun">);</span><span class="pln">

</span><span class="com">// طل‫ب POST لتحديث نوع كتاب</span><span class="pln">
router</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">"/genre/:id/update"</span><span class="pun">,</span><span class="pln"> genre_controller</span><span class="pun">.</span><span class="pln">genre_update_post</span><span class="pun">);</span><span class="pln">

</span><span class="com">// ‫طلب GET لنوع كتاب واحد</span><span class="pln">
router</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/genre/:id"</span><span class="pun">,</span><span class="pln"> genre_controller</span><span class="pun">.</span><span class="pln">genre_detail</span><span class="pun">);</span><span class="pln">

</span><span class="com">// طل‫ب GET لقائمة جميع أنواع الكتب</span><span class="pln">
router</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/genres"</span><span class="pun">,</span><span class="pln"> genre_controller</span><span class="pun">.</span><span class="pln">genre_list</span><span class="pun">);</span><span class="pln">

</span><span class="pun">‏</span><span class="com">/// طر‫ق نسخ الكتب BOOKINSTANCE ///</span><span class="pln">

</span><span class="com">// طل‫ب GET لإنشاء نسخة كتاب BookInstance. يجب أن يأتي هذا الطلب قبل الوجهة التي تعرض نسخة الكتاب (يستخدم المعرّف)</span><span class="pln">
router</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="pln">
  </span><span class="str">"/bookinstance/create"</span><span class="pun">,</span><span class="pln">
  book_instance_controller</span><span class="pun">.</span><span class="pln">bookinstance_create_get</span><span class="pun">,</span><span class="pln">
</span><span class="pun">);</span><span class="pln">

</span><span class="com">// ط‫لب POST لإنشاء نسخة كتاب</span><span class="pln">
router</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="pln">
  </span><span class="str">"/bookinstance/create"</span><span class="pun">,</span><span class="pln">
  book_instance_controller</span><span class="pun">.</span><span class="pln">bookinstance_create_post</span><span class="pun">,</span><span class="pln">
</span><span class="pun">);</span><span class="pln">

</span><span class="com">// ط‫لب GET لحذف نسخة كتاب</span><span class="pln">
router</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="pln">
  </span><span class="str">"/bookinstance/:id/delete"</span><span class="pun">,</span><span class="pln">
  book_instance_controller</span><span class="pun">.</span><span class="pln">bookinstance_delete_get</span><span class="pun">,</span><span class="pln">
</span><span class="pun">);</span><span class="pln">

</span><span class="com">// ط‫لب POST لحذف نسخة كتاب</span><span class="pln">
router</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="pln">
  </span><span class="str">"/bookinstance/:id/delete"</span><span class="pun">,</span><span class="pln">
  book_instance_controller</span><span class="pun">.</span><span class="pln">bookinstance_delete_post</span><span class="pun">,</span><span class="pln">
</span><span class="pun">);</span><span class="pln">

</span><span class="com">// ط‫لب GET لتحديث نسخة الكتاب</span><span class="pln">
router</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="pln">
  </span><span class="str">"/bookinstance/:id/update"</span><span class="pun">,</span><span class="pln">
  book_instance_controller</span><span class="pun">.</span><span class="pln">bookinstance_update_get</span><span class="pun">,</span><span class="pln">
</span><span class="pun">);</span><span class="pln">

</span><span class="com">// ط‫لب POST لتحديث نسخة كتاب</span><span class="pln">
router</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="pln">
  </span><span class="str">"/bookinstance/:id/update"</span><span class="pun">,</span><span class="pln">
  book_instance_controller</span><span class="pun">.</span><span class="pln">bookinstance_update_post</span><span class="pun">,</span><span class="pln">
</span><span class="pun">);</span><span class="pln">

</span><span class="com">// طل‫ب GET لنسخة كتاب واحدة</span><span class="pln">
router</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/bookinstance/:id"</span><span class="pun">,</span><span class="pln"> book_instance_controller</span><span class="pun">.</span><span class="pln">bookinstance_detail</span><span class="pun">);</span><span class="pln">

</span><span class="com">// ط‫لب GET لقائمة جميع نسخ الكتاب</span><span class="pln">
router</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/bookinstances"</span><span class="pun">,</span><span class="pln"> book_instance_controller</span><span class="pun">.</span><span class="pln">bookinstance_list</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"> router</span><span class="pun">;</span></pre>

<p>
	تطلب الوحدة كائن Express ثم تستخدمه لإنشاء كائن <code>Router</code>، إذ تُضبَط جميع الوجهات في هذا الكائن الذي يُصدَّر لاحقًا. تُعرّف الوجهات إما باستخدام تابع <code>‎.get()‎</code> أو تابع <code>‎.post()‎</code> للكائن <code>Router</code>، وتُعرّف جميع المسارات باستخدام سلاسل نصية، إذ لا نستخدم أنماط سلاسل نصية أو تعابيرًا نمطية. تستخدم الوجهات التي تعمل على بعض الموارد المحددة (مثل الكتاب) معاملاتِ المسار للحصول على معرّف الكائن من عنوان URL، وتُستورَد جميع دوال المعالجة من وحدات المتحكمات التي أنشأناها في القسم السابق.
</p>

<h3 id="-10">
	تحديث وحدة وجهة صفحة الفهرس
</h3>

<p>
	ضبطنا جميع وجهاتنا الجديدة، ولكن لا يزال لدينا الوجهة إلى الصفحة الأصلية، ولكن لنعيد التوجيه إلى صفحة الفهرس الجديدة التي أنشأناها في المسار<code> '‎/catalog' </code>بدلًا من ذلك.
</p>

<p>
	افتح الملف ‎/routes/index.js وضع الدالة التالية بدلًا من الوجهة الحالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5559_43" style=""><span class="com">// الحصول على الصفحة الرئيسية</span><span class="pln">
router</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="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"> </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">"/catalog"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	<strong>ملاحظة</strong>: استخدمنا تابع الاستجابة <code>redirect()‎</code> الذي يؤدي إلى إعادة التوجيه إلى الصفحة المحددة، مع إرسال رمز حالة HTTP الذي هو "302 Found" افتراضيًا، ويمكنك تغيير رمز الحالة المُعاد إن لزم الأمر وتوفير مسارات مطلقة أو نسبية. يمكنك الاطلاع على مقال <a href="https://academy.hsoub.com/programming/general/%D8%B1%D9%85%D9%88%D8%B2-%D8%A7%D9%84%D8%A5%D8%AC%D8%A7%D8%A8%D8%A9-%D9%81%D9%8A-http-r75/" rel="">رموز الإجابة في HTTP</a> والمقال <a href="https://academy.hsoub.com/devops/servers/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D9%83%D8%B4%D8%A7%D9%81-%D9%88%D8%A5%D8%B5%D9%84%D8%A7%D8%AD-%D8%B1%D9%85%D9%88%D8%B2-%D8%A3%D8%AE%D8%B7%D8%A7%D8%A1-http-%D8%A7%D9%84%D8%B4%D8%A7%D8%A6%D8%B9%D8%A9-r116/" rel="">كيفية استكشاف وإصلاح رموز أخطاء HTTP الشائعة</a>على أكاديمية حسوب لمزيدٍ من المعلومات حول رموز حالة HTTP.
</p>

<h3 id="appjs">
	تحديث الملف app.js
</h3>

<p>
	تتمثل الخطوة الأخيرة في إضافة الوجهات إلى سلسلة البرمجيات الوسيطة في الملف app.js، لذا افتح هذا الملف، واطلب وجهة الدليل بعد الوجهات الأخرى من خلال إضافة السطر الثالث التالي بعد السطرين الآخرين الموجودين مسبقًا في الملف:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5559_45" style=""><span class="kwd">var</span><span class="pln"> indexRouter </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"./routes/index"</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">var</span><span class="pln"> usersRouter </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"./routes/users"</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> catalogRouter </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"./routes/catalog"</span><span class="pun">);</span><span class="pln"> </span><span class="com">// استيراد الوجهات لمنطقة "الدليل" في الموقع</span></pre>

<p>
	ضِف بعد ذلك وجهة الدليل catalog إلى مكدس البرمجيات الوسيطة بعد الوجهات الأخرى من خلال إضافة السطر الثالث التالي بعد السطرين الآخرين الموجودين مسبقًا في الملف:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5559_47" style=""><span class="pln">app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="str">"/"</span><span class="pun">,</span><span class="pln"> indexRouter</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">"/users"</span><span class="pun">,</span><span class="pln"> usersRouter</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">"/catalog"</span><span class="pun">,</span><span class="pln"> catalogRouter</span><span class="pun">);</span><span class="pln"> </span><span class="com">// إضافة وجهات الدليل إلى سلسلة البرمجيات الوسيطة</span></pre>

<p>
	<strong>ملاحظة</strong>: أضفنا وحدة دليلنا في المسار <code>'‎/catalog'</code> الذي سيكون البادئة لجميع المسارات المحدَّدة في وحدة الدليل، فمثلًا سيكون عنوان URL هو <code>/catalog/books/</code> للوصول إلى قائمة الكتب.
</p>

<p>
	يجب أن يكون لدينا الآن مسارات ودوال هيكلية مُفعَّلة لجميع عناوين URL التي سندعمها في موقع المكتبة المحلية LocalLibrary.
</p>

<h3 id="-11">
	اختبار الوجهات
</h3>

<p>
	أولًا، ابدأ تشغيل موقع الويب بالطريقة المعتادة، فالطريقة الافتراضية في أنظمة التشغيل هي:
</p>

<pre class="ipsCode">// نظام ويندوز
SET DEBUG=express-locallibrary-tutorial:* &amp; npm start

// ماك أو لينكس
DEBUG=express-locallibrary-tutorial:* npm start
</pre>

<p>
	ولكن إذا سبق لك إعداد nodemon، فيمكنك بدلًا من ذلك استخدام ما يلي:
</p>

<pre class="ipsCode">npm run serverstart
</pre>

<p>
	انتقل بعد ذلك إلى عدد من عناوين URL الخاصة بموقع المكتبة المحلية LocalLibrary وتحقق من عدم ظهور صفحة خطأ (HTTP 404). إليك مجموعة صغيرة من عناوين URL:
</p>

<ul>
	<li>
		<code><a href="http://localhost:3000/" ipsnoembed="true" rel="external nofollow">http://localhost:3000/</a></code>
	</li>
	<li>
		<code><a href="http://localhost:3000/catalog" ipsnoembed="true" rel="external nofollow">http://localhost:3000/catalog</a></code>
	</li>
	<li>
		<code><a href="http://localhost:3000/catalog/books" ipsnoembed="true" rel="external nofollow">http://localhost:3000/catalog/books</a></code>
	</li>
	<li>
		<code><a href="http://localhost:3000/catalog/bookinstances/" ipsnoembed="true" rel="external nofollow">http://localhost:3000/catalog/bookinstances/</a></code>
	</li>
	<li>
		<code><a href="http://localhost:3000/catalog/authors/" ipsnoembed="true" rel="external nofollow">http://localhost:3000/catalog/authors/</a></code>
	</li>
	<li>
		<code><a href="http://localhost:3000/catalog/genres/" ipsnoembed="true" rel="external nofollow">http://localhost:3000/catalog/genres/</a></code>
	</li>
	<li>
		<code><a href="http://localhost:3000/catalog/book/5846437593935e2f8c2aa226" ipsnoembed="true" rel="external nofollow">http://localhost:3000/catalog/book/5846437593935e2f8c2aa226</a></code>
	</li>
	<li>
		<code><a href="http://localhost:3000/catalog/book/create" ipsnoembed="true" rel="external nofollow">http://localhost:3000/catalog/book/create</a></code>
	</li>
</ul>

<h2 id="-12">
	الخلاصة
</h2>

<p>
	أنشأنا جميع الوجهات الخاصة بموقعنا، مع دوال المتحكمات الوهمية التي يمكننا ملؤها بتقديم كامل في مقالات لاحقة، وتعلمنا الكثير من المعلومات الأساسية حول وجهات Express والتعامل مع الاستثناءات وبعض الأساليب لبناء الوجهات والمتحكمات.
</p>

<p>
	سننشئ في المقال التالي صفحة ترحيب مناسبة للموقع باستخدام العروض (القوالب) والمعلومات المخزنة في نماذجنا، وسننشئ استمارات HTML وشيفرة معالجة الاستمارات لبدء تعديل البيانات التي يخزنها الموقع.
</p>

<p>
	ترجمة -وبتصرُّف- للمقال <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/routes" rel="external nofollow">Express Tutorial Part 4: Routes and controllers</a>.
</p>

<h1 id="-13">
	اقرأ أيضًا
</h1>

<ul>
	<li>
		المقال السابق <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%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-%D9%85%D8%B9-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-mongoose-r2171/" rel="">تطبيق عملي لتعلم Express - الجزء الثاني: استخدام قاعدة البيانات باستخدام مكتبة Mongoose</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AE%D8%A7%D8%AF%D9%85-%D9%88%D9%8A%D8%A8-%D9%81%D9%8A-nodejs-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%88%D8%AD%D8%AF%D8%A9-http-r1745/" rel="">إنشاء خادم ويب في Node.js باستخدام الوحدة HTTP</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/ruby/rails/%D8%A7%D9%84%D9%85%D8%AA%D8%AD%D9%83%D9%91%D9%85%D8%A7%D8%AA-controllers-%D9%88%D8%A7%D9%84%D8%B9%D8%B1%D9%88%D8%B6-views-%D9%81%D9%8A-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-rails-r530/" rel="">المتحكّمات Controllers والعروض Views في إطار العمل Rails</a>.
	</li>
</ul>
]]></description><guid isPermaLink="false">2184</guid><pubDate>Sun, 03 Dec 2023 13:09:00 +0000</pubDate></item><item><title>&#x62A;&#x637;&#x628;&#x64A;&#x642; &#x639;&#x645;&#x644;&#x64A; &#x644;&#x62A;&#x639;&#x644;&#x645; Express - &#x627;&#x644;&#x62C;&#x632;&#x621; &#x627;&#x644;&#x62B;&#x627;&#x646;&#x64A;: &#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x642;&#x627;&#x639;&#x62F;&#x629; &#x627;&#x644;&#x628;&#x64A;&#x627;&#x646;&#x627;&#x62A; &#x645;&#x639; &#x645;&#x643;&#x62A;&#x628;&#x629; Mongoose</title><link>https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%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-%D9%85%D8%B9-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-mongoose-r2171/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_11/---(--Mongoose).png.927a970da3619cf5a4afa1af55ebf253.png" /></p>
<p>
	يقدّم هذا المقال مقدمة موجزة عن قواعد البيانات وكيفية استخدامها مع تطبيقات Node/Express، ثم يوضّح كيفية استخدام مكتبة <a href="https://mongoosejs.com/" rel="external nofollow">Mongoose</a> لتوفير الوصول إلى قاعدة بيانات <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2170/" rel="">موقع المكتبة المحلية LocalLibrary</a>، ويشرح كيفية التصريح عن مخطط الكائنات object schema والنماذج Models، وأنواع الحقول الرئيسية والتحقق الأساسي من صحة البيانات. يعرض أيضًا بإيجاز بعض الطرق الرئيسية التي يمكنك من خلالها الوصول إلى بيانات النموذج.
</p>

<ul>
	<li>
		<strong>المتطلبات الأساسية</strong>: الاطلاع على مقال <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2170/" rel=""> إنشاء موقع ويب هيكلي لمكتبة محلية</a>.
	</li>
	<li>
		<strong>الهدف</strong>: أن تكون قادرًا على تصميم وإنشاء نماذجك باستخدام مكتبة Mongoose.
	</li>
</ul>

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

<p>
	يمكن لتطبيقات Express استخدام العديد من قواعد البيانات المختلفة، وهناك العديد من الأساليب التي يمكنك استخدامها لإجراء عمليات الإنشاء والقراءة والتحديث والحذف -أو CRUD اختصارًا. يقدم هذا المقال نظرةً عامةً موجزة عن بعض الخيارات المتاحة ثم ينتقل ليشرح الآليات المختارة بالتفصيل.
</p>
<iframe allowfullscreen="" data-controller="core.front.core.autosizeiframe" data-embedauthorid="3889" data-embedcontent="" src="https://academy.hsoub.com/files/26-%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/?do=embed" style="margin: auto;"></iframe>

<h2 id="">
	قواعد البيانات الممكن استخدامها
</h2>

<p>
	يمكن لتطبيقات Express استخدام أيّ قاعدة بيانات تدعمها بيئة Node، إذ لا يحدد إطارعمل Express أيّ سلوك أو متطلبات إضافية محددة لإدارة قاعدة البيانات، وهناك العديد من الخيارات الشائعة بما في ذلك قواعد بيانات <a href="https://academy.hsoub.com/devops/servers/databases/postgresql/" rel="">PostgreSQL</a> و <a href="https://academy.hsoub.com/devops/servers/databases/mysql/" rel="">MySQL</a> و <a href="https://academy.hsoub.com/devops/servers/databases/redis/" rel="">Redis</a> و <a href="https://academy.hsoub.com/devops/servers/databases/%D9%83%D9%8A%D9%81-%D9%88%D9%85%D8%AA%D9%89-%D9%86%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-sqlite-r111/" rel="">SQLite</a> و <a href="https://academy.hsoub.com/devops/servers/databases/mongodb/" rel="">MongoDB</a> وغير ذلك.
</p>

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

<p>
	اطلع على <a href="https://expressjs.com/en/guide/database-integration.html" rel="external nofollow">تكامل قاعدة البيانات</a> في توثيق Express لمزيد من المعلومات حول هذه الخيارات.
</p>

<h2 id="-1">
	أفضل طريقة للتفاعل مع قاعدة البيانات
</h2>

<p>
	هناك طريقتان شائعتان للتفاعل مع قاعدة البيانات، هما:
</p>

<ul>
	<li>
		استخدام لغة الاستعلام الأصيلة لقواعد البيانات مثل لغة SQL.
	</li>
	<li>
		استخدام نموذج بيانات الكائن Object Data Model -أو اختصارًا ODM- أو نموذج الكائنات العلاقي Object Relational Model -أو ORM اختصارًا. يمثل نموذج ODM/ORM بيانات موقع الويب بوصفها <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="">كائنات جافا سكريبت</a>، والتي تُربَط بعد ذلك بقاعدة البيانات الأساسية، إذ ترتبط بعض نماذج ORM بقاعدة بيانات معينة، بينما يوفر بعضها الآخر واجهة خلفية لا تعتمد على قاعدة البيانات.
	</li>
</ul>

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

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

<p>
	<strong>ملاحظة</strong>: يؤدي استخدام نماذج ODM/ORM إلى انخفاض تكاليف التطوير والصيانة، إذ يجب أن تفكر كثيرًا في استخدام نموذج ODM إلّا إذا كنت على دراية بلغة الاستعلام الأصيلة أو إذا كان الأداء أمرًا بالغ الأهمية.
</p>

<h3 id="ormodm">
	نموذج ORM/ODM الذي يجب أن استخدامه
</h3>

<p>
	هناك العديد من حلول ODM/ORM المتاحة على موقع مدير الحزم npm (اطلع على <a href="https://www.npmjs.com/search?q=keywords:odm" rel="external nofollow">odm</a> و <a href="https://www.npmjs.com/search?q=keywords:orm" rel="external nofollow">orm</a> للتعرف على بعض من هذه الحلول).
</p>

<p>
	إليك بعضًا من هذه الحلول الشائعة:
</p>

<ul>
	<li>
		Mongoose: هي أداة نمذجة كائنات قاعدة بيانات MongoDB المُصمَّمة للعمل في بيئة غير متزامنة.
	</li>
	<li>
		Waterline: هو نموذج ORM المُستخرَج من إطار عمل الويب Sails القائم على إطار عمل Express. يوفر واجهة برمجة تطبيقات مُوحَّدة للوصول إلى العديد من قواعد البيانات المختلفة، بما في ذلك Redis و MySQL و LDAP و MongoDB و Postgres.
	</li>
	<li>
		Bookshelf: يتميز بواجهات رد النداء Callback التقليدية القائمة على الوعود Promise، مما يوفر دعمًا لمعامَلات قاعدة البيانات وتحميل العلاقات النشط أو النشط المتداخل والارتباطات متعددة الأشكال ودعم علاقات واحد إلى واحد one-to-one وواحد إلى متعدد one-to-many ومتعدد إلى متعدد many-to-many، ويعمل مع قواعد بيانات PostgreSQL و MySQL و SQLite3. يمكنك الاطلاع على مقال <a href="https://academy.hsoub.com/programming/sql/%D8%A7%D9%84%D8%B9%D9%84%D8%A7%D9%82%D8%A7%D8%AA-%D8%A8%D9%8A%D9%86-%D8%A7%D9%84%D8%AC%D8%AF%D8%A7%D9%88%D9%84-%D9%81%D9%8A-sql-r590/" rel="">العلاقات بين الجداول في SQL</a> على أكاديمية حسوب لمزيدٍ من المعلومات حول العلاقات بين الجداول.
	</li>
	<li>
		Objection: يسهّل قدر الإمكان استخدام قوة لغة SQL الكاملة ومحرك قاعدة البيانات الأساسي، ويدعم SQLite3 و Postgres و MySQL.
	</li>
	<li>
		Sequelize: هو نموذج ORM مبني على الوعود لكلٍّ من Node.js و io.js، ويدعم الأنواع المختلفة من لغات PostgreSQL و MySQL و MariaDB و SQLite و MSSQL ويتميز بدعمٍ قوي للمعامَلات transaction والعلاقات وتكرار عمليات القراءة وغير ذلك.
	</li>
	<li>
		Node ORM2: هو مدير علاقات الكائنات الخاص ببيئة NodeJS، ويدعم MySQL و SQLite و Progress، مما يساعد على العمل مع قاعدة البيانات باستخدام أسلوب موجَّه بالكائنات.
	</li>
	<li>
		GraphQL: لغة استعلام أساسية لواجهات برمجة التطبيقات <a href="https://academy.hsoub.com/programming/general/%D8%B4%D8%B1%D8%AD-%D9%81%D9%84%D8%B3%D9%81%D8%A9-restful-%D8%AA%D8%B9%D9%84%D9%85-%D9%83%D9%8A%D9%81-%D8%AA%D8%A8%D9%86%D9%8A-%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-rest-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r635/" rel="">restful <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr></a>، وتحظى لغة GraphQL بشعبية كبيرة ولديها ميزات متاحة لقراءة البيانات من قواعد البيانات.
	</li>
</ul>

<p>
	يجب مراعاة كل من الميزات المتوفرة ونشاط المجتمع (التنزيلات والمساهمات وتقارير الأخطاء وجودة التوثيق وغير ذلك) عند اختيار الحل المناسب، وتُعَد مكتبة Mongoose أكثر نماذج ODM شيوعًا، وهو خيار جيد عند استخدام MongoDB لقاعدة بياناتك.
</p>

<h2 id="mongoosemongodb">
	استخدام مكتبة Mongoose وقاعدة بيانات MongoDB لموقع المكتبة المحلية
</h2>

<p>
	سنستخدم في مثالنا مكتبة Mongoose بوصفها نموذج ODM للوصول إلى بيانات مكتبتنا، إذ تتصرف هذه المكتبة بمثابة واجهة أمامية لقاعدة بيانات MongoDB، وهي قاعدة بيانات NoSQL مفتوحة المصدر تستخدم نموذج بيانات موجَّهًا بالمستندات، إذ تشبه مجموعة المستندات في قاعدة بيانات MongoDB جدولًا من الصفوف في قاعدة بيانات علاقية.
</p>

<p>
	تحظى هذه المجموعة من نموذج ODM وقاعدة البيانات بشعبية كبيرة في مجتمع Node، ويرجع ذلك جزئيًا إلى أن تخزين المستندات ونظام الاستعلام يشبه إلى حد كبير <a href="https://academy.hsoub.com/programming/javascript/%D8%AA%D8%B9%D9%84%D9%85-json-r604/" rel="">JSON</a>، وبالتالي فهو مألوف لمطوري جافا سكريبت.
</p>

<p>
	<strong>ملاحظة</strong>: لست بحاجة إلى معرفة قاعدة بيانات MongoDB لاستخدام مكتبة Mongoose، بالرغم من أن أجزاءً من توثيق Mongoose أسهل في الاستخدام والفهم إذا كنت على دراية بقاعدة بيانات MongoDB.
</p>

<p>
	سنوضح فيما يلي كيفية تعريف والوصول إلى مخطط ونماذج Mongoose لمثال موقع ويب المكتبة المحلية LocalLibrary.
</p>

<h2 id="-2">
	تصميم نماذج موقع المكتبة المحلية
</h2>

<p>
	يفضَّل أخذ بضع دقائق للتفكير في البيانات التي يجب تخزينها والعلاقات بين الكائنات قبل البدء في كتابة شيفرة النماذج، إذ نعلم أننا بحاجةٍ إلى تخزين معلومات حول الكتب (العنوان والملخص والمؤلف ونوع الكتاب ورقم ISBN)، وقد يكون لدينا نسخٌ متعددة متاحة (مع معرّفات فريدة عامة وحالات توفرها وغير ذلك)، ويمكن أن نحتاج إلى تخزين مزيدٍ من المعلومات حول المؤلف أكثر من مجرد اسمه، ويمكن أن يكون هناك عدة مؤلفين لهم الاسم نفسه أو أسماء متشابهة. نريد أن نكون قادرين على فرز المعلومات بناءً على عنوان الكتاب والمؤلف ونوع الكتاب Genre وفئته Category.
</p>

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

<p>
	يجب الآن التفكير في العلاقات بين النماذج والحقول بعد تحديدها، إذ يوضح <a href="https://academy.hsoub.com/programming/workflow/%D9%85%D8%AE%D8%B7%D8%B7%D8%A7%D8%AA-%D8%A7%D9%84%D9%81%D8%A6%D8%A7%D8%AA-class-diagram-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D9%86%D9%85%D8%B0%D8%AC%D8%A9-%D8%A7%D9%84%D9%85%D9%88%D8%AD%D8%AF%D8%A9-uml-r307/" rel="">مخطط ارتباط باستخدام لغة UML</a> الآتي النماذج التي سنعرّفها في حالتنا (على شكل صناديق)، إذ أنشأنا نماذجًا للكتاب (تفاصيل الكتاب العامة)، ونسخة الكتاب (حالة نسخ الكتاب الحقيقية المحدَّدة المتاحة في النظام)، والمؤلف، وقررنا أيضًا أن يكون لدينا نموذج لنوع الكتاب بحيث يمكن إنشاء القيم ديناميكيًا. لم ننشئ نموذجًا لحالة نسخة الكتاب <code>BookInstance:status</code>، إذ سنجعل القيم المقبولة ثابتة لأننا لا نتوقع تغييرها. يمكنك رؤية اسم النموذج وأسماء الحقول وأنواعها والتوابع وأنواع الإعادة الخاصة بها في كل صندوق.
</p>

<p>
	يوضح المخطط البياني الآتي أيضًا العلاقات بين النماذج، بما في ذلك درجة تعدّدها Multiplicities، وهي الأعداد الموجودة على المخطط والتي توضح عدد أو الحد الأقصى والحد الأدنى لكل نموذج الذي يمكن أن يكون موجودًا في العلاقة، فمثلًا يوضّح الخط المتصل بين الصناديق أن الكتاب Book والنوع Genre مرتبطان، وتوضح الأعداد القريبة من نموذج الكتاب <code>Book</code> أنه يجب يكون للنوع <code>Genre</code> صفر أو أكثر من الكتب <code>Book</code> (بقدر ما تريد)، بينما توضح الأعداد الموجودة على الطرف الآخر من الخط بجوار نموذج النوع <code>Genre</code> أن الكتاب يمكن أن يكون له صفر أو أكثر من الأنواع <code>Genre</code> المتعلقة به.
</p>

<p>
	ملاحظة: يُفضَّل غالبًا أن يكون لديك الحقل الذي يعرّف العلاقة بين المستندات/النماذج في نموذج واحد فقط كما سنوضّح لاحقًا، ولا يزال بإمكانك العثور على العلاقة العكسية من خلال البحث عن <code>‎_id</code> المرتبط بها في النموذج الآخر. اخترنا فيما يلي تعريف العلاقة بين<code>Book</code>/<code>Genre</code> و <code>Book</code>/<code>Author</code> في مخطط Schema الكتاب <code>Book</code>، والعلاقة بين <code>Book</code>/<code>BookInstance</code> في مخطط نسخة الكتاب <code>BookInstance</code>، إذ كان هذا الاختيار عشوائيًا إلى حدٍ ما، وكان من الممكن أيضًا أن يكون أحد الحقول موجودًا في المخطط الآخر.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="138647" href="https://academy.hsoub.com/uploads/monthly_2023_11/01_library_website_-_mongoose_express.png.da5e22663884a0fccf76e63aca4e206a.png" rel=""><img alt="01_library_website_-_mongoose_express.png" class="ipsImage ipsImage_thumbnailed" data-fileid="138647" data-ratio="84.03" data-unique="ndoncn72e" width="714" src="https://academy.hsoub.com/uploads/monthly_2023_11/01_library_website_-_mongoose_express.thumb.png.d2a6e46860b42b60938299a32db5c586.png"></a>
</p>

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

<h3 id="-3">
	واجهات برمجة تطبيقات قاعدة البيانات غير المتزامنة
</h3>

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

<p>
	تحتوي <a href="https://academy.hsoub.com/programming/javascript/" rel="">لغة جافا سكريبت Javascript</a> على عدد من الآليات لدعم السلوك غير المتزامن، إذ اعتمدت كثيرًا سابقًا على تمرير دوال رد النداء إلى توابع غير متزامنة لمعالجة حالات النجاح والخطأ، وحلّت <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> محل <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> إلى حد كبير في لغة جافا سكربت الحديثة. الوعود هي كائنات يعيدها (مباشرةً) تابع غير متزامن يمثل حالتها المستقبلية، ويستقر كائن الوعد عند اكتمال العملية، ويحقق كائنًا يمثل نتيجة العملية أو الخطأ.
</p>

<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="">كيفية استخدام الوعود</a> للحصول على نظرة عامة عالية المستوى على كلا الأسلوبين. سنستخدم في هذا المقال <a href="https://wiki.hsoub.com/JavaScript/await" rel="external"><code>await</code></a> لانتظار اكتمال الوعد في <a href="https://wiki.hsoub.com/JavaScript/Async_Function_Expression" rel="external"><code>async function</code></a>، لأن هذا الأسلوب يؤدي إلى الحصول على شيفرة برمجية غير متزامنة مفهومة وأكثر قابلية للقراءة.
</p>

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

<p>
	يمكنك أن ترى كيفية عمل هذه الطريقة في المثال الآتي، إذ تُعَد <code>myFunction()‎</code> دالة غير متزامنة تُستدعَى ضمن كتلة <a href="https://wiki.hsoub.com/JavaScript/try%E2%80%A6catch" rel="external"><code>try...catch</code></a>. يُوقََف تنفيذ الشيفرة البرمجية مؤقتًا في التابع <code>methodThatReturnsPromise()‎</code> عند تشغيل الدالة <code>myFunction()‎</code> حتى تحقيق الوعد، وعندها يستمر تنفيذ الشيفرة البرمجية حتى الوصول إلى التابع <code>aFunctionThatReturnsPromise()‎</code> وينتظر مرةً أخرى. تُشغَّل الشيفرة البرمجية الموجودة في كتلة <code>catch</code> عند رمي خطأ في الدالة غير المتزامنة، إذ سيحدث ذلك عند رفض الوعد الذي يعيده أيّ من هذين التابعين.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2931_9" style=""><span class="kwd">async</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> myFunction </span><span class="pun">{</span><span class="pln">
  </span><span class="com">// ...</span><span class="pln">
  </span><span class="kwd">await</span><span class="pln"> someObject</span><span class="pun">.</span><span class="pln">methodThatReturnsPromise</span><span class="pun">();</span><span class="pln">
  </span><span class="com">// ...</span><span class="pln">
  </span><span class="kwd">await</span><span class="pln"> aFunctionThatReturnsPromise</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">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="com">// ...</span><span class="pln">
  myFunction</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">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">
 </span><span class="com">// شيفرة معالجة الخطأ</span><span class="pln">
</span><span class="pun">}</span></pre>

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

<p>
	توضح الشيفرة البرمجية التالية كيفية عمل ذلك، إذ لدينا أولًا دالتان تعيدان وعودًا، إذ ننتظرهما <code>await</code> حتى يكتملا باستخدام الوعد الذي يعيده التابع <code>Promise.all()‎</code>. يعيد <code>await</code> بمجرد أن تكتمل كلتا الدالتين وتُملَأ مصفوفة النتائج، ثم يستمر تنفيذ الدالة حتى الوصول إلى تابع <code>await</code> التالي، وتنتظر حتى استقرار الوعد الذي تعيده الدالة <code>anotherFunctionThatReturnsPromise()‎</code>. يمكنك استدعاء الدالة <code>myFunction()‎</code> في كتلة <code>try...catch</code> لالتقاط الأخطاء.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2931_11" style=""><span class="kwd">async</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> myFunction </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="pun">[</span><span class="pln">resultFunction1</span><span class="pun">,</span><span class="pln"> resultFunction2</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</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">
     functionThatReturnsPromise1</span><span class="pun">(),</span><span class="pln">
     functionThatReturnsPromise2</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">await</span><span class="pln"> anotherFunctionThatReturnsPromise</span><span class="pun">(</span><span class="pln">resultFunction1</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تسمح الوعود مع <code>await</code>/<code>async</code> بالتحكم المرن والمنطقي بالتنفيذ غير المتزامن.
</p>

<h2 id="mongoose">
	مقدمة إلى مكتبة Mongoose
</h2>

<p>
	يقدم هذا القسم نظرةً عامة حول كيفية توصيل مكتبة Mongoose بقاعدة بيانات MongoDB، وكيفية تعريف المخططات والنماذج، وكيفية تطبيق الاستعلامات الأساسية.
</p>

<h3 id="mongoosemongodb-1">
	تثبيت Mongoose و MongoDB
</h3>

<p>
	تُثبَّت مكتبة Mongoose في مشروعك (في الملف package.json) مثل أي اعتمادية أخرى باستخدام <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</a>، إذ يمكن تثبيتها باستخدم الأمر التالي في مجلد مشروعك:
</p>

<pre class="ipsCode">npm install mongoose
</pre>

<p>
	يضيف تثبيت مكتبة Mongoose جميع اعتمادياتها بما في ذلك مشغِّل قاعدة بيانات MongoDB، لكنه لا يؤدي إلى تثبيت MongoDB. إذا أدرتَ تثبيت خادم MongoDB، فيمكنك <a href="https://www.mongodb.com/try/download/community" rel="external nofollow">تنزيل المثبِّتات</a> لأنظمة تشغيل مختلفة وتثبيتها محليًا، ويمكنك استخدام نسخ من MongoDB المستندة إلى السحابة.
</p>

<p>
	<strong>ملاحظة</strong>: سنستخدم في هذا المقال قاعدة البيانات المستندة إلى السحابة MongoDB Atlas بوصفها طبقة خدمة مجانية لتوفير قاعدة البيانات، وهي مناسبة لبيئة التطوير والتعلم لأنها تجعل نظام تشغيل التثبيت مستقلًا، وتُعَد قاعدة البيانات التي تمثل خدمة Database-as-a-service أيضًا إحدى الطرق التي يمكن أن تستخدمها لقاعدة بيانات بيئة الإنتاج.
</p>

<h3 id="mongodb">
	الاتصال بقاعدة بيانات MongoDB
</h3>

<p>
	تتطلب مكتبة Mongoose اتصالًا بقاعدة بيانات MongoDB، وذلك باستخدام الدالة <code>require()‎</code> والاتصال بقاعدة بيانات مستضافة محليًا باستخدام <code>mongoose.connect()‎</code> كما يلي، ولكن سنتّصل بدلًا من ذلك في هذا المقال بقاعدة بيانات مستضافة عبر الإنترنت:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2931_13" style=""><span class="com">// استيراد وحدة‫ mongoose</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">// اضبط‫ `strictQuery: false` للاشتراك العام في الترشيح وفق الخاصيات غير المُدرَجة في المخطط</span><span class="pln">
</span><span class="com">// لأن هذا الخيار ‫يزيل تحذيرات Mongoose 7 الأولية.</span><span class="pln">
</span><span class="com">// اطّلع على‫ https://mongoosejs.com/docs/migrating_to_6.html#strictquery-is-removed-and-replaced-by-strict</span><span class="pln">
mongoose</span><span class="pun">.</span><span class="kwd">set</span><span class="pun">(</span><span class="str">"strictQuery"</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">// ‫حدّد عنوان URL لقاعدة البيانات للاتصال به</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> mongoDB </span><span class="pun">=</span><span class="pln"> </span><span class="str">"mongodb://127.0.0.1/my_database"</span><span class="pun">;</span><span class="pln">

</span><span class="com">// انتظر حتى الاتصال بقاعدة البيانات، مع تسجيل خطأ إذا كانت هناك مشكلة</span><span class="pln">
main</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"> 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">async</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">await</span><span class="pln"> mongoose</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="pln">mongoDB</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	<strong>ملاحظة</strong>: ننتظر <code>await</code> الوعد الذي يعيده التابع <code>connect()‎</code> ضمن دالة مُصرَّح عنها باستخدام <code>async function</code> كما ناقشنا سابقًا في قسم واجهات برمجة التطبيقات لقاعدة البيانات غير المتزامنة من هذا المقال. نستخدم المعالج <code>catch()‎</code> الخاص بالوعد لمعالجة الأخطاء عند محاولة الاتصال، ولكن يمكن أيضًا استدعاء <code>main()‎</code> ضمن كتلة <code>try...catch</code>.
</p>

<p>
	يمكنك الحصول على كائن <code>Connection</code> الافتراضي باستخدام <code>mongoose.connection</code>، وإذا كنت بحاجة إلى إنشاء اتصالات إضافية، فيمكنك استخدام التابع <code>mongoose.createConnection()‎</code> الذي يأخذ صيغة معرّف URI نفسه الخاص بقاعدة البيانات (مع المضيف وقاعدة البيانات والمنفذ والخيارات وإلخ) الذي يستخدمه التابع <code>connect()‎</code> ويعيد كائن <code>Connection</code>. لاحظ أن <code>createConnection()‎</code> يعيد مباشرةً، وبالتالي إذا كنت بحاجة إلى الانتظار حتى إنشاء الاتصال، فيمكنك استدعاؤه مع <code>asPromise()‎</code> لإعادة وعد (<code>mongoose.createConnection(mongoDB).asPromise()‎</code>).
</p>

<h3 id="-4">
	تعريف وإنشاء النماذج
</h3>

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

<p>
	تُصرَّف بعد ذلك واجهات <code>Schema</code> إلى نماذج باستخدام التابع <code>mongoose.model()‎</code>، ثم يمكنك استخدام النموذج للعثور على كائنات من نوعٍ محدَّد وإنشائها وتحديثها وحذفها.
</p>

<p>
	<strong>ملاحظة</strong>: يُربَط كل نموذج بمجموعة من المستندات في قاعدة بيانات MongoDB، إذ ستحتوي المستندات على أنواع الحقول/المخططات Schema المحددة في نموذج <code>Schema</code>.
</p>

<h4 id="-5">
	تعريف المخططات
</h4>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2931_15" style=""><span class="com">// طلب مكتبة‫ Mongoose</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">
</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">Schema</span><span class="pln"> </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="pln">

</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">SomeModelSchema</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Schema</span><span class="pun">({</span><span class="pln">
  a_string</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</span><span class="pun">,</span><span class="pln">
  a_date</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">,</span><span class="pln">
</span><span class="pun">});</span></pre>

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

<h4 id="-6">
	إنشاء نموذج
</h4>

<p>
	تُنشَأ النماذج من المخططات باستخدام التابع <code>mongoose.model()‎</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2931_17" style=""><span class="com">// تعريف مخطط</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">Schema</span><span class="pln"> </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="pln">

</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">SomeModelSchema</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Schema</span><span class="pun">({</span><span class="pln">
  a_string</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</span><span class="pun">,</span><span class="pln">
  a_date</span><span class="pun">:</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="com">// تصريف النموذج من المخطط</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">SomeModel</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">"SomeModel"</span><span class="pun">,</span><span class="pln"> </span><span class="typ">SomeModelSchema</span><span class="pun">);</span></pre>

<p>
	الوسيط الأول هو الاسم المفرد للمجموعة التي ستُنشَأ لنموذجك، إذ ستنشِئ مكتبة Mongoose مجموعة قاعدة البيانات للنموذج <code>SomeModel</code> السابق، والوسيط الثاني هو المخطط الذي تريد استخدامه في إنشاء النموذج.
</p>

<p>
	<strong>ملاحظة</strong>: يمكنك استخدام أصناف Classes نموذجك بعد تعريفها لإنشاء سجلات أو تحديثها أو حذفها، ويمكنك تشغيل الاستعلامات للحصول على جميع السجلات أو مجموعات فرعية معينة من السجلات. سنوضح كيفية تحقيق ذلك لاحقًا في قسم استخدام النماذج وعندما ننشئ العروض Views.
</p>

<h4 id="-7">
	أنواع المخططات والحقول
</h4>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2931_19" style=""><span class="kwd">const</span><span class="pln"> schema </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Schema</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">
  binary</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Buffer</span><span class="pun">,</span><span class="pln">
  living</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Boolean</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"> type</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">default</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">
  age</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">Number</span><span class="pun">,</span><span class="pln"> min</span><span class="pun">:</span><span class="pln"> </span><span class="lit">18</span><span class="pun">,</span><span class="pln"> max</span><span class="pun">:</span><span class="pln"> </span><span class="lit">65</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">
  mixed</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Schema</span><span class="pun">.</span><span class="typ">Types</span><span class="pun">.</span><span class="typ">Mixed</span><span class="pun">,</span><span class="pln">
  _someId</span><span class="pun">:</span><span class="pln"> </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">
  array</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[],</span><span class="pln">
  ofString</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="typ">String</span><span class="pun">],</span><span class="pln"> </span><span class="com">// يمكنك أيضًا الحصول على مصفوفة لكل نوع من الأنواع الأخرى</span><span class="pln">
  nested</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> stuff</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"> lowercase</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln"> trim</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></pre>

<p>
	لا تحتاج معظم أنواع المخططات SchemaTypes (الواصفات الموجودة بعد "type:‎" أو بعد أسماء الحقول) شرحًا، ولكن هناك بعض الاستثناءات وهي:
</p>

<ul>
	<li>
		<code>ObjectId</code>: يمثل نسخًا محدّدة لنموذجٍ في قاعدة البيانات، فمثلًا يمكن أن يستخدم الكتاب هذا النوع لتمثيل كائن مؤلفه، وسيحتوي هذا النوع على معرّف فريد (<code>‎_id</code>) للكائن، ويمكننا استخدام التابع <code>populate()‎</code> لسحب المعلومات عند الحاجة.
	</li>
	<li>
		<code>Mixed</code>: نوع مخطط عشوائي.
	</li>
	<li>
		<code>[]</code>: مصفوفة من العناصر، إذ يمكنك إجراء عمليات مصفوفات جافاسكربت على هذه النماذج (الدفع والسحب إلغاء الإزاحة وإلخ). توضح الأمثلة السابقة مصفوفةً من الكائنات بدون نوع محدد ومصفوفة من كائنات <code>String</code>، ولكن يمكن أن يكون لديك مصفوفة من أيّ نوع من الكائنات.
	</li>
</ul>

<p>
	توضّح الشيفرة البرمجية أيضًا طريقتين للتصريح عن الحقل هما:
</p>

<ul>
	<li>
		اسم الحقل ونوعه مثل زوج قيمة-مفتاح (كما هو الحال مع اسم الحقول <code>name</code> و <code>binary</code> و <code>living</code> مثلًا).
	</li>
	<li>
		اسم الحقل متبوعًا بكائن يحدد النوع <code>type</code> وأيّ خيارات أخرى للحقل، إذ تتضمن هذه الخيارات ما يلي:
		<ul>
			<li>
				قيم افتراضية.
			</li>
			<li>
				أدوات التحقق المبنية مسبقًا، مثل القيم العليا أو الدنيا، ودوال التحقق من صحة البيانات المُخصَّصة.
			</li>
			<li>
				ما إذا كان الحقل مطلوبًا.
			</li>
			<li>
				ما إذا كان يجب ضبط حقول <code>String</code> تلقائيًا بأحرف صغيرة أو كبيرة أو حذف المسافات في بداية ونهاية السلسلة النصية، مثل:
			</li>
		</ul>
	</li>
</ul>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2931_21" style=""><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"> lowercase</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln"> trim</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln"> </span><span class="pun">}</span></pre>

<p>
	اطّلع على <a href="https://mongoosejs.com/docs/schematypes.html" rel="external nofollow">أنواع المخططات</a> في توثيق Mongoose لمزيد من المعلومات حول الخيارات.
</p>

<h4 id="-8">
	التحقق من صحة البيانات
</h4>

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

<p>
	تتضمن أدوات التحقق من صحة البيانات المبنية مسبقًا ما يلي:
</p>

<ul>
	<li>
		تحتوي جميع أنواع المخططات على أداة التحقق <a href="https://mongoosejs.com/docs/api/schematype.html#SchemaType.prototype.required()" rel="external nofollow">required</a> المبنية مسبقًا التي تُستخدم لتحديد ما إذا كان يجب توفير الحقل لحفظ مستندٍ ما.
	</li>
	<li>
		تحتوي الأعداد Numbers على أدوات تحقق من صحة الحد الأدنى min والحد الأعلى max.
	</li>
	<li>
		تحتوي السلاسل النصية Strings على أدوات التحقق التالية:
		<ul>
			<li>
				enum: تحدد مجموعة القيم المسموح بها للحقل.
			</li>
			<li>
				match: تحدد التعبير النمطي Regular Expression الذي يجب أن تتطابق معه السلسلة النصية.
			</li>
			<li>
				الطول الأقصى maxLength والطول الأدنى minLength للسلسلة النصية.
			</li>
		</ul>
	</li>
</ul>

<p>
	يوضح المثال التالي -المأخوذ من توثيق Mongoose- كيفية تحديد بعض أنواع أدوات التحقق من صحة البيانات ورسائل الخطأ:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2931_23" style=""><span class="kwd">const</span><span class="pln"> breakfastSchema </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Schema</span><span class="pun">({</span><span class="pln">
  eggs</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">Number</span><span class="pun">,</span><span class="pln">
    min</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="lit">6</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Too few eggs"</span><span class="pun">],</span><span class="pln">
    max</span><span class="pun">:</span><span class="pln"> </span><span class="lit">12</span><span class="pun">,</span><span class="pln">
    required</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="kwd">true</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Why no eggs?"</span><span class="pun">],</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  drink</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">
    </span><span class="kwd">enum</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="str">"Coffee"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Tea"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Water"</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://mongoosejs.com/docs/validation.html" rel="external nofollow">التحقق من صحة البيانات</a> في توثيق Mongoose للحصول على معلومات كاملة حول التحقق من صحة الحقول.
</p>

<h4 id="-9">
	الخاصيات الافتراضية
</h4>

<p>
	الخاصيات الافتراضية هي خاصيات المستند التي يمكنك جلبها وضبطها دون استمرار وجودها في قاعدة بيانات MongoDB، إذ تُعَد الجوالب Getters مفيدة لتنسيق الحقول أو دمجها، وتكون الضوابط Setters مفيدة في تفكيك قيمة واحدة إلى قيم متعددة لتخزينها. يبني المثال الموجود في توثيق Mongoose (ويهدم) خاصية افتراضية للاسم الكامل من حقل الاسم الأول والأخير، ويُعَد ذلك أسهل وأنظف من بناء اسم كامل في كل مرة يُستخدَم أحدها في قالب.
</p>

<p>
	<strong>ملاحظة</strong>: سنستخدم خاصية افتراضية في موقع المكتبة المحلية لتعريف عنوان URL فريد لكل سجل نموذج باستخدام مسار وقيمة <code>‎_id</code> الخاصة بالسجل.
</p>

<p>
	اطلع على <a href="https://mongoosejs.com/docs/guide.html#virtuals" rel="external nofollow">الخاصيات الافتراضية</a> في توثيق Mongoose لمزيد من المعلومات.
</p>

<h4 id="-10">
	التوابع والاستعلامات المساعدة
</h4>

<p>
	يمكن أن يحتوي المخطط أيضًا على نسخ من التوابع Instance methods وتوابع ثابتة static methods واستعلامات مساعدة query helpers، إذ تتشابه نسخ التوابع والتوابع الثابتة، ولكن مع وجود اختلاف واضح في أن نسخ التوابع مرتبطة بسجل معين ويمكنها الوصول إلى الكائن الحالي. تسمح الاستعلامات المساعدة بتوسيع واجهة برمجة تطبيقات باني الاستعلامات القابلة للتسلسل الخاصة بمكتبة mongoose، مثل السماح بإضافة استعلام وفق الاسم "byName"، إضافةً إلى توابع <code>find()‎</code> و <code>findOne()‎</code> و <code>findById()‎</code>.
</p>

<h3 id="-11">
	استخدام النماذج
</h3>

<p>
	يمكنك بعد إنشاء مخطط استخدامه لإنشاء النماذج، إذ يمثل النموذج مجموعة من المستندات الموجودة في قاعدة البيانات التي يمكنك البحث عنها، بينما تمثل نسخ النموذج المستندات الفردية التي يمكنك حفظها واسترجاعها. سنقدم فيما يلي نظرة عامة موجزة، لذا يمكنك الاطلاع على <a href="https://mongoosejs.com/docs/models.html" rel="external nofollow">النماذج</a> في توثيق Mongoose لمزيد من المعلومات.
</p>

<p>
	<strong>ملاحظة</strong>: يُعَد إنشاء السجلات وتحديثها وحذفها والاستعلام عنها عمليات غير متزامنة تعيد وعدًا. سنوضح في الأمثلة التالية استخدام التوابع المتعلقة بهذا الموضوع والتابع <code>await</code>، أي سنوضح الشيفرة البرمجية الأساسي لاستخدام التوابع، إذ سنحذف دالة <code>async function</code> المحيطة وكتلة <code>try...catch</code> لالتقاط الأخطاء للتوضيح.
</p>

<h4 id="-12">
	إنشاء وتعديل المستندات
</h4>

<p>
	يمكنك إنشاء سجل من خلال تعريف نسخة من النموذج ثم استدعاء <code>save()‎</code>. تفترض الأمثلة التالية أن <code>SomeModel</code> هو نموذج (له حقل واحد هو <code>name</code>) أنشأناه من المخطط.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2931_25" style=""><span class="com">// ‫أنشئ نسخة من النموذج SomeModel</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> awesome_instance </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">SomeModel</span><span class="pun">({</span><span class="pln"> name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"awesome"</span><span class="pln"> </span><span class="pun">});</span><span class="pln">

</span><span class="com">// احفظ نسخة النموذج الجديدة بطريقة غير متزامنة</span><span class="pln">
</span><span class="kwd">await</span><span class="pln"> awesome_instance</span><span class="pun">.</span><span class="pln">save</span><span class="pun">();</span></pre>

<p>
	يمكنك أيضًا استخدام <code>create()‎</code> لتعريف نسخة من النموذج في الوقت الذي تحفظها فيه. سننشئ فيما يلي نسخة واحدة فقط، ولكن يمكنك إنشاء نسخ متعددة من خلال تمرير مصفوفة من الكائنات.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2931_27" style=""><span class="kwd">await</span><span class="pln"> </span><span class="typ">SomeModel</span><span class="pun">.</span><span class="pln">create</span><span class="pun">({</span><span class="pln"> name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"also_awesome"</span><span class="pln"> </span><span class="pun">});</span></pre>

<p>
	لكل نموذج اتصاله المرتبط به، والذي سيكون الاتصال الافتراضي عند استخدام <code>mongoose.model()‎</code>، ويمكنك إنشاء اتصال جديد واستدعاء <code>‎.model()‎</code> لإنشاء المستندات في قاعدة بيانات مختلفة.
</p>

<p>
	يمكنك الوصول إلى الحقول في هذا السجل الجديد باستخدام الصيغة النقطية وتغيير القيم، ويجب استدعاء <code>save()‎</code> أو <code>update()‎</code> لتخزين القيم المُعدَّلة في قاعدة البيانات.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2931_29" style=""><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">awesome_instance</span><span class="pun">.</span><span class="pln">name</span><span class="pun">);</span><span class="pln"> </span><span class="com">// يجب تسجيل‫ 'also_awesome' أيضًا</span><span class="pln">

</span><span class="com">// تغيير السجل من خلال تعديل الحقول ثم استدعاء‫ save()‎</span><span class="pln">
awesome_instance</span><span class="pun">.</span><span class="pln">name </span><span class="pun">=</span><span class="pln"> </span><span class="str">"New cool name"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">await</span><span class="pln"> awesome_instance</span><span class="pun">.</span><span class="pln">save</span><span class="pun">();</span></pre>

<h4 id="-13">
	البحث عن السجلات
</h4>

<p>
	يمكنك البحث عن السجلات باستخدام توابع الاستعلام من خلال تحديد شروط الاستعلام بوصفها مستند JSON. يوضح جزء الشيفرة التالي كيفية العثور على جميع الرياضيين Athlete الذين يلعبون كرة المضرب في قاعدة بيانات، ويعيد فقط حقول اسم name وعُمر age الرياضي، إذ نحدد فقط حقلًا واحدًا مطابقًا (الرياضة sport)، ولكن يمكنك إضافة مزيدٍ من المعايير أو تحديد معايير التعبير النمطي أو إزالة جميع الشروط لإعادة جميع الرياضيين.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2931_31" style=""><span class="kwd">const</span><span class="pln"> </span><span class="typ">Athlete</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">"Athlete"</span><span class="pun">,</span><span class="pln"> yourSchema</span><span class="pun">);</span><span class="pln">

</span><span class="com">// ‫العثور على جميع الرياضيين الذين يلعبون كرة المضرب مع تحديد حقول 'name' و 'age'</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> tennisPlayers </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> </span><span class="typ">Athlete</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"> sport</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Tennis"</span><span class="pln"> </span><span class="pun">},</span><span class="pln">
  </span><span class="str">"name age"</span><span class="pln">
</span><span class="pun">).</span><span class="pln">exec</span><span class="pun">();</span></pre>

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

<p>
	تعيد واجهات برمجة تطبيقات الاستعلام مثل <code>find()‎</code> متغيرًا من النوع <a href="https://mongoosejs.com/docs/api/query.html" rel="external nofollow">Query</a>، ويمكنك استخدام كائن استعلام لبناء استعلام ضمن أجزاء قبل تنفيذه باستخدام التابع <code>exec()‎</code> الذي ينفّذ الاستعلام ويعيد وعدًا يمكنك انتظاره باستخدام <code>await</code> للحصول على النتيجة.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2931_33" style=""><span class="com">// العثور على جميع الرياضيين الذين يلعبون كرة المضرب</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> query </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Athlete</span><span class="pun">.</span><span class="pln">find</span><span class="pun">({</span><span class="pln"> sport</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Tennis"</span><span class="pln"> </span><span class="pun">});</span><span class="pln">

</span><span class="com">// اختيار حقول‫ 'name' و 'age'</span><span class="pln">
query</span><span class="pun">.</span><span class="pln">select</span><span class="pun">(</span><span class="str">"name age"</span><span class="pun">);</span><span class="pln">

</span><span class="com">// قصر نتائجنا على 5 عناصر</span><span class="pln">
query</span><span class="pun">.</span><span class="pln">limit</span><span class="pun">(</span><span class="lit">5</span><span class="pun">);</span><span class="pln">

</span><span class="com">// الفرز وفق العمر</span><span class="pln">
query</span><span class="pun">.</span><span class="pln">sort</span><span class="pun">({</span><span class="pln"> age</span><span class="pun">:</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pln"> </span><span class="pun">});</span><span class="pln">

</span><span class="com">// تنفيذ الاستعلام في وقت لاحق</span><span class="pln">
query</span><span class="pun">.</span><span class="pln">exec</span><span class="pun">();</span></pre>

<p>
	عرّفنا شروط الاستعلام في التابع <code>find()‎</code>، ويمكننا تطبيق ذلك أيضًا باستخدام الدالة <code>where()‎</code>، ويمكننا سَلسَلة جميع أجزاء الاستعلام مع بعضها باستخدام المعامل النقطي (.) بدلًا من إضافتها بصورة منفصلة. جزء الشيفرة البرمجية التالي هو الاستعلام السابق نفسه مع شرط إضافي للعُمر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2931_35" style=""><span class="typ">Athlete</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">where</span><span class="pun">(</span><span class="str">"sport"</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">.</span><span class="pln">equals</span><span class="pun">(</span><span class="str">"Tennis"</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">.</span><span class="pln">where</span><span class="pun">(</span><span class="str">"age"</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">.</span><span class="pln">gt</span><span class="pun">(</span><span class="lit">17</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">.</span><span class="pln">lt</span><span class="pun">(</span><span class="lit">50</span><span class="pun">)</span><span class="pln"> </span><span class="com">// ‫استعلام where إضافي</span><span class="pln">
  </span><span class="pun">.</span><span class="pln">limit</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">sort</span><span class="pun">({</span><span class="pln"> age</span><span class="pun">:</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pln"> </span><span class="pun">})</span><span class="pln">
  </span><span class="pun">.</span><span class="pln">select</span><span class="pun">(</span><span class="str">"name age"</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">.</span><span class="pln">exec</span><span class="pun">();</span></pre>

<p>
	يحصل التابع <code>find()‎</code> على جميع السجلات المطابقة، ولكنك تريد الحصول على تطابق واحد فقط في أغلب الأحيان، لذا يمكنك استخدام توابع الاستعلام التالية لسجل واحد:
</p>

<ul>
	<li>
		<code>findById()‎</code>: يبحث عن المستند باستخدام المعرّف <code>id</code>، فلكل مستندٍ معرّفٌ فريد.
	</li>
	<li>
		<code>findOne()‎</code>: يبحث عن مستند واحد يطابق معاييرًا محدَّدة.
	</li>
	<li>
		<code>findByIdAndRemove()‎</code> و <code>findByIdAndUpdate()‎</code> و <code>findOneAndRemove()‎</code> و <code>findOneAndUpdate()‎</code>: تبحث عن مستند واحد باستخدام المعرّف <code>id</code> أو المعايير، فإما أن تحدّثه أو تزيله، إذ تُعَد هذه الدوال ملائمة ومفيدة لتحديث السجلات وإزالتها.
	</li>
</ul>

<p>
	<strong>ملاحظة</strong>: هناك أيضًا التابع <code>countDocuments()‎</code> الذي يمكنك استخدامه للحصول على عدد العناصر التي تطابق الشروط، ويُعَد مفيدًا إذا أردتَ إجراء تعداد دون جلب السجلات فعليًا.
</p>

<p>
	هناك الكثير من الأمور التي يمكنك تطبيقها على الاستعلامات، لذا اطّلع على <a href="https://mongoosejs.com/docs/queries.html" rel="external nofollow">الاستعلامات</a> في توثيق Mongoose لمزيد من المعلومات.
</p>

<h4 id="population">
	العمل مع المستندات- الملء Population
</h4>

<p>
	يمكنك إنشاء مراجعٍ من نسخة مستند أو نموذج إلى آخر باستخدام حقل المخطط <code>ObjectId</code>، أو من مستند إلى عدة مستندات باستخدام مصفوفة من <code>ObjectId</code>. يخزّن هذا الحقل معرّف النموذج المرتبط به، وإذا كنت بحاجة إلى محتوى المستند الفعلي، فيمكنك استخدام التابع <code>populate()‎</code> في استعلام لاستبدال المعرّف بالبيانات الفعلية.
</p>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2931_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"> </span><span class="typ">Schema</span><span class="pln"> </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="pln">

</span><span class="kwd">const</span><span class="pln"> authorSchema </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Schema</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">
  stories</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">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">"Story"</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"> storySchema </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Schema</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"> type</span><span class="pun">:</span><span class="pln"> </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">"Author"</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">String</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">Story</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">"Story"</span><span class="pun">,</span><span class="pln"> storySchema</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">Author</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">"Author"</span><span class="pun">,</span><span class="pln"> authorSchema</span><span class="pun">);</span></pre>

<p>
	يمكننا حفظ المراجع التي تشير إلى المستند من خلال إسناد قيمة <code>‎_id</code> إليها، إذ سننشئ فيما يلي مؤلفًا ثم ننشئ قصة ونسند معرّف المؤلف إلى حقل مؤلف القصة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2931_39" style=""><span class="kwd">const</span><span class="pln"> bob </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Author</span><span class="pun">({</span><span class="pln"> name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Bob Smith"</span><span class="pln"> </span><span class="pun">});</span><span class="pln">

</span><span class="kwd">await</span><span class="pln"> bob</span><span class="pun">.</span><span class="pln">save</span><span class="pun">();</span><span class="pln">

</span><span class="com">// ‫Bob موجود الآن، لذا لننشئ قصة‫</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> story </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Story</span><span class="pun">({</span><span class="pln">
  title</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Bob goes sledding"</span><span class="pun">,</span><span class="pln">
  author</span><span class="pun">:</span><span class="pln"> bob</span><span class="pun">.</span><span class="pln">_id</span><span class="pun">,</span><span class="pln"> </span><span class="com">// إسناد‫ ‎_id للمؤلف Bob، إذ يُنشَأ هذا المعرّف افتراضيًا</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="kwd">await</span><span class="pln"> story</span><span class="pun">.</span><span class="pln">save</span><span class="pun">();</span></pre>

<p>
	<strong>ملاحظة</strong>: إحدى الفوائد الرائعة لهذا النمط من البرمجة هي أنه لا يتعين علينا تعقيد المسار الرئيسي لشيفرتنا البرمجية من خلال التحقق من الأخطاء، فإذا فشلت أيّ عملية حفظ <code>save()‎</code>، سيُرفَض الوعد وسيُرمَى خطأ. تتعامل شيفرة معالجة الأخطاء مع ذلك بصورة منفصلة (في كتلة <code>catch()‎</code> عادةً)، لذا يُعَد الهدف من شيفرتنا البرمجية واضحًا جدًا.
</p>

<p>
	يحتوي مستند القصة الآن على مؤلف يُشار إليه باستخدام معرّف مستند المؤلف، ونستخدم التابع <code>populate()‎</code> كما هو موضح فيما يلي للحصول على معلومات المؤلف في نتائج القصة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2931_41" style=""><span class="typ">Story</span><span class="pun">.</span><span class="pln">findOne</span><span class="pun">({</span><span class="pln"> title</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Bob goes sledding"</span><span class="pln"> </span><span class="pun">})</span><span class="pln">
  </span><span class="pun">.</span><span class="pln">populate</span><span class="pun">(</span><span class="str">"author"</span><span class="pun">)</span><span class="pln"> </span><span class="com">// استبدال معرّف المؤلف بمعلومات المؤلف الفعلية في النتائج</span><span class="pln">
  </span><span class="pun">.</span><span class="pln">exec</span><span class="pun">();</span></pre>

<p>
	<strong>ملاحظة</strong>: سيلاحظ القراء المتمرسون أننا أضفنا مؤلفًا إلى القصة، لكننا لم نفعل أي شيء لإضافة القصة إلى مصفوفة <code>stories</code> الخاصة بالمؤلف. تتمثل إحدى الطرق للحصول على جميع القصص لمؤلف معين في إضافة القصة إلى مصفوفة القصص، ولكن ذلك يمكن أن يؤدي إلى وجود مكانين للاحتفاظ بالمعلومات المتعلقة بالمؤلفين والقصص. توجد طريقة أفضل، وهي الحصول على معرّف <code>‎_id</code> المؤلف، ثم استخدام <code>find()‎</code> للبحث عنه في حقل المؤلف عبر جميع القصص.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2931_43" style=""><span class="typ">Story</span><span class="pun">.</span><span class="pln">find</span><span class="pun">({</span><span class="pln"> author</span><span class="pun">:</span><span class="pln"> bob</span><span class="pun">.</span><span class="pln">_id </span><span class="pun">}).</span><span class="pln">exec</span><span class="pun">();</span></pre>

<p>
	اطلّع على <a href="https://mongoosejs.com/docs/populate.html" rel="external nofollow">Population</a> في توثيق Mongoose لمزيد من المعلومات التفصيلية.
</p>

<h3 id="-14">
	مخطط أو نموذج واحد لكل ملف
</h3>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2931_45" style=""><span class="com">// ‫الملف: ‎./models/somemodel.js</span><span class="pln">

</span><span class="com">// طلب‫ Mongoose</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">
</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">Schema</span><span class="pln"> </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="pln">

</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">SomeModelSchema</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Schema</span><span class="pun">({</span><span class="pln">
  a_string</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</span><span class="pun">,</span><span class="pln">
  a_date</span><span class="pun">:</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="com">// ‫تصدير دالة لإنشاء صنف النموذج "SomeModel"</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">"SomeModel"</span><span class="pun">,</span><span class="pln"> </span><span class="typ">SomeModelSchema</span><span class="pun">);</span></pre>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2931_47" style=""><span class="com">// إنشاء نموذج‫ SomeModel من خلال طلب الوحدة</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">SomeModel</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"../models/somemodel"</span><span class="pun">);</span><span class="pln">

</span><span class="com">// استخدام كائن (نموذج‫) SomeModel للعثور على كافة سجلات SomeModel</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> modelInstances </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> </span><span class="typ">SomeModel</span><span class="pun">.</span><span class="pln">find</span><span class="pun">().</span><span class="pln">exec</span><span class="pun">();</span></pre>

<h2 id="mongodb-1">
	إعداد قاعدة بيانات MongoDB
</h2>

<p>
	تعرّفنا على ما يمكن أن تفعله مكتبة Mongoose وكيفية تصميم نماذجنا، وحان الوقت الآن لبدء العمل على موقع المكتبة المحلية LocalLibrary، وأول شيء يجب فعله هو إعداد قاعدة بيانات MongoDB التي يمكننا استخدامها لتخزين بيانات مكتبتنا.
</p>

<p>
	سنستخدم في هذا المقال قاعدة البيانات التجريبية المُستضافَة على السحابة <a href="https://www.mongodb.com/atlas/database" rel="external nofollow">MongoDB Atlas</a>، إذ لا تُعَد طبقة قاعدة البيانات هذه مناسبة لمواقع الويب في بيئة الإنتاج لأنها لا تحتوي على تكرار Redundancy، ولكنها رائعة لعملية التطوير والنماذج الأولية، وسنستخدمها لأنها مجانية وسهلة الإعداد، ولأنها بائع شائع لقاعدة البيانات التي تمثل خدمة، والتي يمكن أن تختارها لقاعدة بيانات الإنتاج الخاصة بك، وتشمل الخيارات الشائعة الأخرى Compose و ScaleGrid و ObjectRocket.
</p>

<p>
	<strong>ملاحظة</strong>: يمكنك أيضًا إعداد قاعدة بيانات MongoDb محليًا من خلال <a href="https://www.mongodb.com/download-center/community/releases" rel="external nofollow">تنزيل وتثبيت الملفات الثنائية المناسبة لنظامك</a>، إذ ستكون بقية الإرشادات الواردة في هذا المقال متشابهة باستثناء عنوان URL لقاعدة البيانات الذي يمكن أن تحدده عند الاتصال. نستضيف لاحقًا في مقال نشر تطبيق Express في بيئة الإنتاج كلًا من التطبيق وقاعدة البيانات على منصة Railway، ولكن يمكن أيضًا استخدام قاعدة بيانات على MongoDB Atlas.
</p>

<p>
	يجب أولًا <a href="https://www.mongodb.com/cloud/atlas/register" rel="external nofollow">إنشاء حساب</a> على MongoDB Atlas، وهو مجاني ويتطلب فقط إدخال تفاصيل الاتصال الأساسية والإقرار بشروط الخدمة. ستنتقل بعد تسجيل الدخول إلى الشاشة الرئيسية، لذا اتبع الخطوات التالية:
</p>

<p>
	أولًا، انقر على زر  "إنشاء Create" في قسم نظرة عامة Overview.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="138646" href="https://academy.hsoub.com/uploads/monthly_2023_11/02_mongodb_atlas_-_createdatabase.jpg.3159a6d4a141d9de6799c3fc500fb1e2.jpg" rel=""><img alt="02 mongodb atlas   createdatabase" class="ipsImage ipsImage_thumbnailed" data-fileid="138646" data-unique="24ry2k5sh" style="width: 600px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2023_11/02_mongodb_atlas_-_createdatabase.thumb.jpg.0ddb85b2451d0eb6ae6066bd5b9856b0.jpg"> </a>
</p>

<p>
	ثانيًا، سيؤدي ذلك إلى فتح شاشة نشر قاعدة بيانات سحابية Deploy a cloud database. انقر على زر <strong>MO FREE</strong>.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="138645" href="https://academy.hsoub.com/uploads/monthly_2023_11/03_mongodb_atlas_-_deploy.jpg.dee175e666dc2cf2def45101e1fca0fc.jpg" rel=""><img alt="03 mongodb atlas   deploy" class="ipsImage ipsImage_thumbnailed" data-fileid="138645" data-unique="ehidkz8bn" style="width: 800px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2023_11/03_mongodb_atlas_-_deploy.thumb.jpg.2efabb37e5ff20687273f2f4014456a0.jpg"> </a>
</p>

<p>
	ثالثًا، سيؤدي ذلك إلى سرد خيارات مختلفة للاختيار بينها:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="138644" href="https://academy.hsoub.com/uploads/monthly_2023_11/04_mongodb_atlas_-_createsharedcluster.jpg.8022324c76d43362d4a45af0d3df2da0.jpg" rel=""><img alt="04 mongodb atlas   createsharedcluster" class="ipsImage ipsImage_thumbnailed" data-fileid="138644" data-unique="fclowv49l" style="width: 600px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2023_11/04_mongodb_atlas_-_createsharedcluster.thumb.jpg.2980d5f255291be93463a4d9b379b7be.jpg"> </a>
</p>

<p>
	حدد أي مزوّد من قسم المزوّد والمنطقة Provider &amp; Region، إذ تقدّم المناطق المختلفة مزوّدين مختلفين. يمكنك تغيير اسم العنقود ضمن قسم اسم العنقود Cluster Name، إذ سنسميه <code>Cluster0</code>. انقر بعد ذلك على زر "إنشاء عنقود Create Cluster"، وسيستغرق إنشاء العنقود بضع دقائق.
</p>

<p>
	رابعًا، سيؤدي ذلك إلى فتح قسم بداية سريعة للأمان Security Quickstart.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="138643" href="https://academy.hsoub.com/uploads/monthly_2023_11/05_mongodb_atlas_-_securityquickstart.jpg.408c8f85762e0de88072185da5462f2b.jpg" rel=""><img alt="05 mongodb atlas   securityquickstart" class="ipsImage ipsImage_thumbnailed" data-fileid="138643" data-unique="6hyb3vkqc" src="https://academy.hsoub.com/uploads/monthly_2023_11/05_mongodb_atlas_-_securityquickstart.thumb.jpg.d2d1d747941c7cff4143c040f3bab877.jpg"> </a>
</p>

<p>
	أدخِل اسم المستخدم وكلمة المرور، وتذكّر نسخ الاعتماديات وتخزينها بأمان إذ سنحتاج إليها لاحقًا. انقر على زر "إنشاء مستخدم Create User".
</p>

<p>
	<strong>ملاحظة</strong>: تجنب استخدام محارف خاصة في كلمة مرور مستخدم MongoDB لأن مكتبة Mongoose يمكن ألّا يحلّل سلسلة الاتصال بصورة صحيحة.
</p>

<p>
	أدخل <code>0.0.0.0/0</code> في حقل عنوان IP الذي يخبر قاعدة بيانات MongoDB أننا نريد السماح بالوصول من أيّ مكان، ثم انقر على زر "إضافة إدخال Add Entry".
</p>

<p>
	<strong>ملاحظة</strong>: يُعَد قصر عناوين IP التي يمكنها الاتصال على قاعدة بياناتك ومواردك الأخرى من أفضل الممارسات، ولكن سنسمح في مثالنا بالاتصال من أيّ مكان لأننا لا نعرف من أين سيأتي الطلب بعد النشر.
</p>

<p>
	انقر بعد ذلك على زر "إنهاء وإغلاق Finish and Close".
</p>

<p>
	خامسًا، سيؤدي ذلك إلى فتح الشاشة التالية، لذا انقر على زر "الانتقال إلى قواعد البيانات Go to Databases".
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="138642" href="https://academy.hsoub.com/uploads/monthly_2023_11/06_mongodb_atlas_-_accessrules.jpg.441c8c509cf4e997233976f7c0a3c07d.jpg" rel=""><img alt="06 mongodb atlas   accessrules" class="ipsImage ipsImage_thumbnailed" data-fileid="138642" data-unique="x1vpbcdoa" style="width: 600px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2023_11/06_mongodb_atlas_-_accessrules.thumb.jpg.f2a61734bda15605c993fe6c40a9fe06.jpg"> </a>
</p>

<p>
	سادسًا، ستعود بعد ذلك إلى شاشة نظرة عامة Overview. انقر على قسم قاعدة البيانات Database تحت قائمة "Deployment" الموجودة على اليسار وانقر على زر استعراض التجميعات Browse Collections.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="138641" href="https://academy.hsoub.com/uploads/monthly_2023_11/07_mongodb_atlas_-_createcollection.jpg.8333e7de2f7c05318d37dee5966e63cf.jpg" rel=""><img alt="07 mongodb atlas   createcollection" class="ipsImage ipsImage_thumbnailed" data-fileid="138641" data-unique="o8cbbaoma" style="width: 800px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2023_11/07_mongodb_atlas_-_createcollection.thumb.jpg.e555c20f2d1e33fde9d2de5609c44bf1.jpg"> </a>
</p>

<p>
	سابعًا، سيؤدي ذلك إلى فتح قسم التجميعات Collections. انقر على زر "إضافة بياناتي الخاصة Add My Own Data".
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="138640" href="https://academy.hsoub.com/uploads/monthly_2023_11/08_mongodb_atlas_-_adddata.jpg.4a0fce54547a1c59686ece2d79a55dc1.jpg" rel=""><img alt="08 mongodb atlas   adddata" class="ipsImage ipsImage_thumbnailed" data-fileid="138640" data-unique="9yttlzsb0" style="width: 800px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2023_11/08_mongodb_atlas_-_adddata.thumb.jpg.f36d327bb6a9274fe85f9fd5cb0c36d5.jpg"> </a>
</p>

<p>
	ثامنًا، ستظهر الآن شاشة إنشاء قاعدة بيانات Create Database.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="138639" href="https://academy.hsoub.com/uploads/monthly_2023_11/09_mongodb_atlas_-_databasedetails.jpg.1b4be39ee295a45e937be08da86afc10.jpg" rel=""><img alt="09 mongodb atlas   databasedetails" class="ipsImage ipsImage_thumbnailed" data-fileid="138639" data-unique="i0m63yl99" style="width: 400px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2023_11/09_mongodb_atlas_-_databasedetails.jpg.1b4be39ee295a45e937be08da86afc10.jpg"> </a>
</p>

<p>
	أدخِل الاسم <code>local_library</code> لاسم قاعدة البيانات الجديدة، ثم أدخِل اسم المجموعة <code>Collection0</code>، ثم انقر على زر "إنشاء Create" لإنشاء قاعدة البيانات.
</p>

<p>
	تاسعًا، ستعود إلى شاشة المجموعات Collections مع وجود قاعدة بياناتك التي أنشأتها.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="138638" href="https://academy.hsoub.com/uploads/monthly_2023_11/10_mongodb_atlas_-_databasecreated.jpg.f26c745c70fbb3c0d1a0d76e9f9191db.jpg" rel=""><img alt="10 mongodb atlas   databasecreated" class="ipsImage ipsImage_thumbnailed" data-fileid="138638" data-unique="eyq617ntq" style="width: 800px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2023_11/10_mongodb_atlas_-_databasecreated.thumb.jpg.cbe6b46deafb0392b639a3af5dc81ab1.jpg"> </a>
</p>

<p>
	انقر على نافذة "نظرة عامة Overview" للعودة إلى شاشة نظرة عامة على العنقود.
</p>

<p>
	عاشرًا، انقر على زر "اتصال Connect" من شاشة نظرة عامة Overview للعنقود Cluster0.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="138637" href="https://academy.hsoub.com/uploads/monthly_2023_11/11_mongodb_atlas_-_connectbutton.jpg.3dc52cdd7ff0afcbfc3229a09dce8627.jpg" rel=""><img alt="11 mongodb atlas   connectbutton" class="ipsImage ipsImage_thumbnailed" data-fileid="138637" data-unique="e86j059lh" style="width: 800px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2023_11/11_mongodb_atlas_-_connectbutton.thumb.jpg.7019dbb86ab3222534562e9b1c706507.jpg"> </a>
</p>

<p>
	سيؤدي ذلك إلى فتح شاشة الاتصال بالعنقود Connect to Cluster. انقر على خيار Drivers الموجود تحت خيار الاتصال بتطبيقك Connect your application.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="138636" href="https://academy.hsoub.com/uploads/monthly_2023_11/12_mongodb_atlas_-_chooseaconnectionmethod.jpg.c6f7ef89e5c44fe2c185ded062a41d4a.jpg" rel=""><img alt="12 mongodb atlas   chooseaconnectionmethod" class="ipsImage ipsImage_thumbnailed" data-fileid="138636" data-unique="im1xd7owj" style="width: 400px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2023_11/12_mongodb_atlas_-_chooseaconnectionmethod.thumb.jpg.46db4ac96a23bb01849f49a4a05ec82f.jpg"> </a>
</p>

<p>
	أخيرًا، ستظهر لك شاشة الاتصال Connect.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="138635" href="https://academy.hsoub.com/uploads/monthly_2023_11/13_mongodb_atlas_-_connectforshortsrv.jpg.bb24571286cdb0a17a011ff987b76ab4.jpg" rel=""><img alt="13 mongodb atlas   connectforshortsrv" class="ipsImage ipsImage_thumbnailed" data-fileid="138635" data-unique="1t6qhz1en" style="width: 400px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2023_11/13_mongodb_atlas_-_connectforshortsrv.thumb.jpg.86dbf352b8dede78f7d01035c33c151d.jpg"> </a>
</p>

<p>
	اتبع بعد ذلك الخطوات التالية:
</p>

<ul>
	<li>
		حدد مشغّل driver ونسخة Node كما هو موضح في الشكل السابق.
	</li>
	<li>
		انقر على أيقونة النسخ Copy لنسخ سلسلة الاتصال.
	</li>
	<li>
		الصقها في محرر نصوصك المحلي.
	</li>
	<li>
		حدّث اسم المستخدم وكلمة المرور بكلمة مرور مستخدمك.
	</li>
	<li>
		أدخِل اسم قاعدة البيانات "local_library" في المسار قبل الخيارات (<code>...mongodb.net/local_library?retryWrites...</code>).
	</li>
	<li>
		احفظ الملف الذي يحتوي على هذه السلسلة في مكان آمن.
	</li>
</ul>

<p>
	أنشأتَ قاعدة البيانات، ولديك عنوان URL (مع اسم مستخدم وكلمة مرور) الذي يمكن استخدامه للوصول إليها، إذ سيبدو كما يلي:
</p>

<pre class="ipsCode">mongodb+srv://your_user_name:your_password@cluster0.lz91hw2.mongodb.net/local_library?retryWrites=true&amp;w=majority
</pre>

<h2 id="mongoose-1">
	تثبيت Mongoose
</h2>

<p>
	افتح موجّه الأوامر وانتقل إلى المجلد الذي أنشأتَ فيه موقعك الهيكلي الخاص بالمكتبة المحلية، ثم أدخِل الأمر التالي لتثبيت مكتبة Mongoose (واعتمادياتها) وضِفها إلى ملف package.json، إن لم تكن قد فعلتَ ذلك مسبقًا عند قراءة فقرة مقدمة إلى مكتبة Mongoose.
</p>

<pre class="ipsCode">npm install mongoose
</pre>

<h2 id="mongodb-2">
	الاتصال بقاعدة بيانات MongoDB
</h2>

<p>
	افتح الملف "‎/app.js" في جذر مشروعك وانسخ النص التالي في مكان التصريح عن كائن تطبيق Express (بعد سطر <code>const app = express();‎</code>). ضع عنوان URL الخاص بالموقع الذي يمثل قاعدة بياناتك (أي باستخدام المعلومات الواردة من mongoDB Atlas) مكان سلسلة عنوان URL لقاعدة البيانات ('insert_your_database_url_here').
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3002_7" style=""><span class="com">// ‫إعداد اتصال mongoose</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">
mongoose</span><span class="pun">.</span><span class="kwd">set</span><span class="pun">(</span><span class="str">"strictQuery"</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"> mongoDB </span><span class="pun">=</span><span class="pln"> </span><span class="str">"insert_your_database_url_here"</span><span class="pun">;</span><span class="pln">

main</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"> 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">async</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">await</span><span class="pln"> mongoose</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="pln">mongoDB</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تنشئ هذه الشيفرة البرمجية الاتصال الافتراضي بقاعدة البيانات ويبلّغ عن وجود أيّ أخطاء في الطرفية.
</p>

<h2 id="-15">
	تعريف مخطط المكتبة المحلية
</h2>

<p>
	سنعرّف وحدة منفصلة لكل نموذج كما وضّحنا سابقًا. ابدأ بإنشاء مجلد للنماذج في جذر المشروع (‎/models) ثم أنشئ ملفات منفصلة لكل نموذج كما يلي:
</p>

<pre class="ipsCode">/express-locallibrary-tutorial  // the project root
  /models
    author.js
    book.js
    bookinstance.js
    genre.js
</pre>

<h3 id="author">
	نموذج المؤلف Author
</h3>

<p>
	انسخ شيفرة مخطط المؤلف <code>Author</code> التالية والصقها في ملف "‎./models/author.js"، إذ يعرّف هذا المخطط مؤلفًا يحتوي على حقول من نوع المخطط <code>String</code> للاسم الأول واسم العائلة (مطلوبة بحد أقصى 100 محرف) وحقول من النوع <code>Date</code> لتواريخ الميلاد والوفاة.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3002_9" 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="typ">Schema</span><span class="pln"> </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="pln">

</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">AuthorSchema</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Schema</span><span class="pun">({</span><span class="pln">
  first_name</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"> maxLength</span><span class="pun">:</span><span class="pln"> </span><span class="lit">100</span><span class="pln"> </span><span class="pun">},</span><span class="pln">
  family_name</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"> maxLength</span><span class="pun">:</span><span class="pln"> </span><span class="lit">100</span><span class="pln"> </span><span class="pun">},</span><span class="pln">
  date_of_birth</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="pln"> </span><span class="pun">},</span><span class="pln">
  date_of_death</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="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="typ">AuthorSchema</span><span class="pun">.</span><span class="pln">virtual</span><span class="pun">(</span><span class="str">"name"</span><span class="pun">).</span><span class="kwd">get</span><span class="pun">(</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">
  </span><span class="com">// يمكن تجنب الأخطاء في الحالات التي لا يحمل فيها المؤلف اسم عائلة أو اسمًا أولًا</span><span class="pln">
  </span><span class="com">// من خلال التأكد من معالجة الاستثناء عبر إعادة سلسلة فارغة لهذه الحالة</span><span class="pln">
 </span><span class="kwd">let</span><span class="pln"> fullname </span><span class="pun">=</span><span class="pln"> </span><span class="str">""</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">first_name </span><span class="pun">&amp;&amp;</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">family_name</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fullname </span><span class="pun">=</span><span class="pln"> </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">family_name</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">first_name</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"> fullname</span><span class="pun">;</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="com">// الخاصية الافتراضية لعنوان‫ URL الخاص بالمؤلف</span><span class="pln">
</span><span class="typ">AuthorSchema</span><span class="pun">.</span><span class="pln">virtual</span><span class="pun">(</span><span class="str">"url"</span><span class="pun">).</span><span class="kwd">get</span><span class="pun">(</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">
  </span><span class="com">// لا نستخدم دالة سهمية لأننا نحتاج إلى هذا الكائن</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">`/</span><span class="pln">catalog</span><span class="pun">/</span><span class="pln">author</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">
</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">"Author"</span><span class="pun">,</span><span class="pln"> </span><span class="typ">AuthorSchema</span><span class="pun">);</span></pre>

<p>
	صرّحنا عن الخاصية الافتراضية لمخطط المؤلف AuthorSchema بالاسم "url"، والتي تعرض عنوان URL المطلق المطلوب للحصول على نسخة معينة من النموذج، إذ سنستخدم هذه الخاصية في قوالبنا كلما احتجنا إلى الحصول على ارتباط إلى مؤلف معين.
</p>

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

<p>
	نصدّر بعد ذلك النموذج في نهاية الوحدة.
</p>

<h3 id="book">
	نموذج الكتاب Book
</h3>

<p>
	انسخ شيفرة مخطط الكتاب <code>Book</code> التالية والصقها في الملف "‎./models/book.js"، والتي تشبه في معظمها نموذج المؤلف، إذ صرّحنا عن مخطط يحتوي على عدد من الحقول من النوع String وخاصية افتراضية للحصول على عنوان URL لسجلات كتاب معينة، ثم صدّرنا النموذج.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3002_11" 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="typ">Schema</span><span class="pln"> </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="pln">

</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">BookSchema</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </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="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="pln"> </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"> type</span><span class="pun">:</span><span class="pln"> </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">"Author"</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">
  summary</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="pln"> </span><span class="pun">},</span><span class="pln">
  isbn</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="pln"> </span><span class="pun">},</span><span class="pln">
  genre</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">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">"Genre"</span><span class="pln"> </span><span class="pun">}],</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="com">// ‫الخاصية الافتراضية لعنوان URL الخاص بالكتاب</span><span class="pln">
</span><span class="typ">BookSchema</span><span class="pun">.</span><span class="pln">virtual</span><span class="pun">(</span><span class="str">"url"</span><span class="pun">).</span><span class="kwd">get</span><span class="pun">(</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">
  </span><span class="com">// لا نستخدم دالة سهمية لأننا نحتاج إلى هذا الكائن</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">`/</span><span class="pln">catalog</span><span class="pun">/</span><span class="pln">book</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">
</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">"Book"</span><span class="pun">,</span><span class="pln"> </span><span class="typ">BookSchema</span><span class="pun">);</span></pre>

<p>
	الاختلاف الرئيسي هنا هو أننا أنشأنا مرجعين إلى نماذج أخرى هما:
</p>

<ul>
	<li>
		المؤلف author هو مرجع إلى كائن نموذج <code>Author</code> واحد، وهو مطلوب.
	</li>
	<li>
		النوع genre هو مرجع إلى مصفوفة من كائنات نموذج <code>Genre</code>، ولكننا لم نصرّح عن هذا الكائن بعد.
	</li>
</ul>

<h3 id="bookinstance">
	نموذج نسخة الكتاب BookInstance
</h3>

<p>
	انسخ شيفرة مخطط <code>BookInstance</code> التالية والصقها في الملف "‎./models/bookinstance.js"، إذ يمثل <code>BookInstance</code> نسخةً محددةً من الكتاب الذي يمكن أن يستعيره شخص ما ويتضمن معلومات حول ما إذا كانت النسخة متوفرة، والتاريخ المتوقع لاسترجاعها، وتفاصيل "الطبعة" أو النسخة.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3002_13" 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="typ">Schema</span><span class="pln"> </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="pln">

</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">BookInstanceSchema</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Schema</span><span class="pun">({</span><span class="pln">
  book</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">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">"Book"</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"> </span><span class="com">// reference to the associated book</span><span class="pln">
  imprint</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="pln"> </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">
    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">
    </span><span class="kwd">enum</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="str">"Available"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Maintenance"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Loaned"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Reserved"</span><span class="pun">],</span><span class="pln">
    </span><span class="kwd">default</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Maintenance"</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  due_back</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"> </span><span class="kwd">default</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">

</span><span class="com">// الخاصية الافتراضية لعنوان‫ URL الخاص بالكتاب</span><span class="pln">
</span><span class="typ">BookInstanceSchema</span><span class="pun">.</span><span class="pln">virtual</span><span class="pun">(</span><span class="str">"url"</span><span class="pun">).</span><span class="kwd">get</span><span class="pun">(</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">
  </span><span class="com">// لا نستخدم دالة سهمية لأننا نحتاج إلى هذا الكائن</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">`/</span><span class="pln">catalog</span><span class="pun">/</span><span class="pln">bookinstance</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">
</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">"BookInstance"</span><span class="pun">,</span><span class="pln"> </span><span class="typ">BookInstanceSchema</span><span class="pun">);</span></pre>

<p>
	الأشياء الجديدة هي خيارات الحقول التالية:
</p>

<ul>
	<li>
		<code>enum</code>: يسمح بضبط القيم المسموح بها من نوع السلسلة النصية String، إذ نستخدمه في هذه الحالة لتحديد حالة توفر الكتب. يعني استخدام <code>enum</code> أنه يمكننا منع الأخطاء الإملائية والقيم العشوائية للحالة.
	</li>
	<li>
		<code>default</code>: نستخدمه لضبط الحالة الافتراضية لنسخ الكتاب التي أنشأناها على القيمة "في الصيانة Maintenance" وتاريخ <code>due_back</code> الافتراضي على القيمة <code>now</code>. لاحظ كيفية استدعاء دالة التاريخ Date عند ضبط التاريخ.
	</li>
</ul>

<p>
	يجب أن يكون كل شيء آخر مألوفًا من المخطط السابق.
</p>

<h3 id="genre">
	نموذج نوع الكتاب Genre- التحدي
</h3>

<p>
	افتح الملف "‎./models/genre.js" وأنشئ مخططًا لتخزين أنواع الكتب (فئة الكتاب مثل ما إذا كان كتابًا خياليًا أو غير خيالي أو عاطفيًا أو تاريخيًا عسكريًا وغير ذلك).
</p>

<p>
	سيكون التعريف مشابهًا جدًا للنماذج الأخرى:
</p>

<ul>
	<li>
		يجب أن يحتوي النموذج على نوع المخطط <code>String</code> بالاسم <code>name</code> لوصف نوع الكتاب.
	</li>
	<li>
		يجب أن يكون هذا الاسم مطلوبًا ويتكون من 3 إلى 100 محرف.
	</li>
	<li>
		التصريح عن الخاصية الافتراضية لعنوان URL الخاص بنوع الكتاب بالاسم <code>url</code>.
	</li>
	<li>
		تصدير النموذج.
	</li>
</ul>

<h2 id="-16">
	إنشاء بعض العناصر للاختبار
</h2>

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

<p>
	أولًا، نزّل أو أنشئ الملف <a href="https://raw.githubusercontent.com/mdn/express-locallibrary-tutorial/main/populatedb.js" rel="external nofollow">populatedb.js</a> ضمن المجلد express-locallibrary-tutorial (في مستوى الملف <code>package.json</code> نفسه).
</p>

<p>
	<strong>ملاحظة</strong>: لست بحاجة إلى معرفة كيفية عمل الملف "populatedb.js"، فهو يضيف فقط عينة بيانات إلى قاعدة البيانات.
</p>

<p>
	ثانيًا، شغّل السكريبت باستخدام أمر <code>node</code> في موجه أوامرك مع تمرير عنوان URL لقاعدة بيانات MongoDB (عنوان URL نفسه الذي وضعته مكان العنصر البديل insert_your_database_url_here في الملف <code>app.js</code> سابقًا):
</p>

<pre class="ipsCode">node populatedb &lt;your mongodb url&gt;
</pre>

<p>
	<strong>ملاحظة</strong>: يجب تغليف عنوان URL لقاعدة البيانات ضمن علامات اقتباس (") مزدوجة في نظام ويندوز، ويمكن أن تحتاج إلى علامات اقتباس مفردة (') في أنظمة التشغيل الأخرى.
</p>

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

<p>
	<strong>ملاحظة</strong>: انتقل إلى قاعدة بياناتك على MongoDB Atlas في نافذة التجميعات Collections، إذ يجب أن تكون الآن قادرًا على التنقل في مجموعات الكتب والمؤلفين والأنواع ونسخ الكتب والتحقق من المستندات الفردية.
</p>

<h2 id="-17">
	الخلاصة
</h2>

<p>
	تعلمنا في هذا المقال بعض الأشياء عن قواعد البيانات ونماذج ORM على Node/Express، وتعرّفنا على كيفية تعريف مخطط ونماذج Mongoose، ثم استخدمنا هذه المعلومات لتصميم وتقديم نماذج <code>Book</code> و <code>BookInstance</code> و <code>Author</code> و <code>Genre</code> لموقع المكتبة المحلية LocalLibrary، واختبرنا نماذجنا من خلال إنشاء عدد من نسخ باستخدام سكريبت مستقل. سنتعرّف في المقال التالي على إنشاء بعض الصفحات لعرض هذه الكائنات.
</p>

<p>
	ترجمة -وبتصرُّف- للمقال <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose" rel="external nofollow">Express Tutorial Part 3: Using a Database with Mongoose</a>.
</p>

<h2 id="-18">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2170/" rel="">تطبيق عملي لتعلم Express - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/servers/databases/%D9%85%D9%82%D8%A7%D8%B1%D9%86%D8%A9-%D8%A8%D9%8A%D9%86-mysql-%D9%88-mongodb-r627/" rel="">مقارنة بين MySQL و MongoDB</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%AC%D9%87%D9%8A%D8%B2-%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-postgresql-%D9%88%D8%A7%D9%84%D8%AA%D9%91%D8%B9%D8%B1%D9%8A%D9%81-%D8%A8%D9%85%D9%81%D9%87%D9%88%D9%85%D9%8A-orm-%D9%88%D8%A5%D8%B6%D8%A7%D9%81%D8%A7%D8%AA-flask-r506/" rel="">تجهيز قاعدة البيانات PostgreSQL والتّعريف بمفهومي ORM وإضافات Flask</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>
</ul>
]]></description><guid isPermaLink="false">2171</guid><pubDate>Thu, 30 Nov 2023 13:01:01 +0000</pubDate></item><item><title>&#x62A;&#x637;&#x628;&#x64A;&#x642; &#x639;&#x645;&#x644;&#x64A; &#x644;&#x62A;&#x639;&#x644;&#x645; Express - &#x627;&#x644;&#x62C;&#x632;&#x621; &#x627;&#x644;&#x623;&#x648;&#x644;: &#x625;&#x646;&#x634;&#x627;&#x621; &#x645;&#x648;&#x642;&#x639; &#x648;&#x64A;&#x628; &#x647;&#x64A;&#x643;&#x644;&#x64A; &#x644;&#x645;&#x643;&#x62A;&#x628;&#x629; &#x645;&#x62D;&#x644;&#x64A;&#x629;</title><link>https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2170/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_11/-----.png.ae1d011f58488a01a3af1ee0c6640233.png" /></p>
<p>
	يشرح هذا المقال ما ستتعلمه لبناء موقع ويب باستخدام إطار عمل <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/" rel="">Express</a>، ويوفر نظرةً عامة على مثال موقع المكتبة المحلية الذي سنعمل عليه ونطوّره في المقالات اللاحقة، وسنوضّح كيفية إنشاء مشروع موقع ويب هيكلي يمكنك ملؤه لاحقًا بالوجهات Routes والعروض Views أوالقوالب Templates واستدعاءات قاعدة البيانات الخاصة بالموقع.
</p>

<ul>
	<li>
		<strong>المتطلبات الأساسية</strong>: قراءة <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-express-%D9%88%D8%A8%D9%8A%D8%A6%D8%A9-node-r2168/" rel="">مدخل إلى إطار عمل Express</a>،و<a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-node-%D9%85%D8%B9-express-r2169/" rel="">إعداد بيئة تطوير Node مع Express</a>.
	</li>
	<li>
		<strong>الهدف</strong>: مقدمة إلى التطبيق العملي الذي سنبنيه في المقالات اللاحقة، وفهم الموضوعات التي سنتناولها، والقدرة على بدء مشاريعك لمواقع الويب الجديدة باستخدام مولّد تطبيقات Express.
	</li>
</ul>

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

<ul>
	<li>
		استخدام أداة مولّد تطبيقات Express لإنشاء موقع ويب وتطبيق هيكلي.
	</li>
	<li>
		بدء وإيقاف خادم ويب Node.
	</li>
	<li>
		استخدم قاعدة بيانات لتخزين بيانات تطبيقك.
	</li>
	<li>
		إنشاء الوجهات لطلب معلومات مختلفة، وقوالب أو عروض لعرض البيانات بتنسيق <a href="https://academy.hsoub.com/programming/html/" rel="">HTML</a> لعرضها في المتصفح.
	</li>
	<li>
		العمل مع <a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-forms-%D9%88%D8%A7%D9%84%D8%B9%D8%B1%D9%88%D8%B6-%D8%A7%D9%84%D8%B9%D8%A7%D9%85%D8%A9-%D9%88%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D8%A7%D9%84%D8%B3%D8%A7%D9%83%D9%86%D8%A9-%D9%81%D9%8A-django-r476/" rel="">الاستمارات Forms</a>.
	</li>
	<li>
		نشر تطبيقك في بيئة الإنتاج.
	</li>
</ul>

<p>
	تعلّمت مسبقًا عن بعض هذه الموضوعات، وتعرّفت إلى بعضها الآخر بإيجاز، ولكن يجب أن تعرف ما يكفي لتطوير تطبيقات Express بنفسك في نهاية هذه السلسلة من المقالات المتفرعة من سلسلة <a href="https://academy.hsoub.com/tags/%D8%AA%D8%B9%D9%84%D9%85%20%D8%AA%D8%B7%D9%88%D9%8A%D8%B1%20%D8%A7%D9%84%D9%88%D9%8A%D8%A8/" rel="">تعلم تطوير الويب</a>.
</p>

<h2 id="locallibrary">
	موقع المكتبة المحلية LocalLibrary
</h2>

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

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

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

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

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

<p>
	إذا واجهتك مشكلة، فيمكنك العثور على النسخة المُطوَّرة بالكامل من موقع الويب على <a href="https://github.com/mdn/express-locallibrary-tutorial" rel="external nofollow">GitHub</a>.
</p>

<p>
	<strong>ملاحظة</strong>: توجد قائمة بالنسخ المحددة من Node و Express والوحدات الأخرى التي جرى اختبار هذه السلسلة من المقالات على أساسها في الملف <a href="https://github.com/mdn/express-locallibrary-tutorial/blob/main/package.json" rel="external nofollow">package.json</a> الخاص بالمشروع.
</p>

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

<h2 id="">
	إنشاء موقع ويب هيكلي
</h2>

<p>
	سنوضح الآن كيفية إنشاء موقع ويب هيكلي باستخدام أداة مولّد تطبيقات Express، والتي يمكنك بعد ذلك ملؤها بالوجهات و<a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D9%84%D8%B9%D8%B1%D9%88%D8%B6-%D9%88%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-%D9%81%D9%8A-django-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-r436/" rel="">العروض أو القوالب</a> واستدعاءات قاعدة البيانات الخاصة بالموقع، إذ سنستخدم هذه الأداة لإنشاء إطار عمل لموقع المكتبة المحلية الذي سنضيف إليه لاحقًا جميع الشيفرات البرمجية الأخرى التي يحتاجها الموقع. تُعَد هذه العملية بسيطة جدًا، وتتطلب فقط استدعاء المولّد في سطر الأوامر باسم مشروع جديد، وتحديد محرّك قوالب الموقع ومولد <a href="https://academy.hsoub.com/programming/html/html-%D9%88-css-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86-%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%AA%D9%86%D8%B3%D9%8A%D9%82%D8%A7%D8%AA-css-r272/" rel="">شيفرة CSS</a> اختياريًا.
</p>

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

<p>
	<strong>ملاحظة</strong>:
</p>

<ul>
	<li>
		لا يُعَد مولّد تطبيقات Express المولّد الوحيد لتطبيقات Express، ولا يُعَد المشروع المُولَّد الطريقة الوحيدة القابلة للتطبيق لبناء هيكيلية ملفاتك ومجلداتك، ولكن يحتوي الموقع المُولَّد على بنية معيارية يسهل توسيعها وفهمها. اطلع على <a href="https://expressjs.com/en/starter/hello-world.html" rel="external nofollow">مثال Hello world</a> في توثيق Express لمزيد من المعلومات حول الحد الأدنى من تطبيقات Express.
	</li>
	<li>
		يصرّح مولّد تطبيقات Express عن معظم المتغيرات باستخدام <code>var</code>، ولكننا غيّرنا معظمها إلى <code>const</code>، وعددًا قليلًا منها إلى <code>let</code>، لأننا نريد عرض ممارسات <a href="https://academy.hsoub.com/programming/javascript/" rel="">جافا سكريبت Javascript</a> الحديثة.
	</li>
	<li>
		يستخدم هذا المقال نسخة Express والاعتماديات الأخرى المُعرَّفة في الملف package.json التي أنشأها مولّد تطبيقات Express، إذ ليست بالضرورة أن تكون النسخة الأحدث، وقد ترغب في تحديثها عند نشر تطبيق حقيقي في بيئة الإنتاج.
	</li>
</ul>

<h3 id="-1">
	استخدام مولد التطبيق
</h3>

<p>
	لا بد أنك ثبّتَ المولّد أثناء <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-node-%D9%85%D8%B9-express-r2169/" rel="">إعداد بيئة تطوير Node مع Express</a>، ولكن يمكنك تثبيت أداة المولّد على مستوى الموقع باستخدام <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</a> كما يلي:
</p>

<pre class="ipsCode">npm install express-generator -g
</pre>

<p>
	يحتوي المولّد على عدد من الخيارات التي يمكنك عرضها في سطر الأوامر باستخدام الأمر <code>‎--help</code> (أو <code>‎-h</code><span class="ipsEmoji">?</span>
</p>

<pre class="ipsCode">&gt; express --help

    Usage: express [options] [dir]

  Options:

        --version        output the version number
    -e, --ejs            add ejs engine support
        --pug            add pug engine support
        --hbs            add handlebars engine support
    -H, --hogan          add hogan.js engine support
    -v, --view &lt;engine&gt;  add view &lt;engine&gt; support (dust|ejs|hbs|hjs|jade|pug|twig|vash) (defaults to jade)
        --no-view        use static html instead of view engine
    -c, --css &lt;engine&gt;   add stylesheet &lt;engine&gt; support (less|stylus|compass|sass) (defaults to plain CSS)
        --git            add .gitignore
    -f, --force          force on non-empty directory
    -h, --help           output usage information
</pre>

<p>
	يمكنك استخدام الأمر <code>express</code> لإنشاء مشروع ضمن المجلد الحالي باستخدام محرّك العروض Jade وشيفرة CSS، وإذا حدّدتَ اسم مجلد، فسيُنشَأ المشروع في مجلد فرعي بهذا الاسم.
</p>

<pre class="ipsCode">express
</pre>

<p>
	يمكنك أيضًا اختيار محرك عروض أو قوالب باستخدام <code>‎--view</code> و/أو محرّك توليد شيفرة CSS باستخدام <code>‎--css</code>.
</p>

<p>
	<strong>ملاحظة</strong>: أهملنا الخيارات الأخرى لاختيار محركات القوالب مثل <code>‎--hogan</code> و <code>‎--ejs</code> و <code>‎--hbs</code> وغيرها، لذا استخدم الخيار <code>‎--view</code> أو <code>‎-v</code>
</p>

<h4 id="-2">
	محرك العروض الواجب استخدامه
</h4>

<p>
	يتيح مولّد تطبيقات Express ضبطَ عددٍ من محرّكات العروض أو القوالب الشائعة، بما في ذلك EJS و Hbs و Pug (أو Jade) و Twig و Vash، ويختار Jade افتراضيًا إن لم تحدد خيار العرض، ويمكن أن يدعم Express أيضًا عددًا كبيرًا من <a href="https://github.com/expressjs/express/wiki#template-engines" rel="external nofollow">لغات القوالب</a> الأخرى.
</p>

<p>
	<strong>ملاحظة</strong>: إذا أردتَ استخدام محرك قوالب لا يدعمه المولّد، فاطلع على <a href="https://expressjs.com/en/guide/using-template-engines.html" rel="external nofollow">استخدام محركات القوالب مع Express</a> في توثيق Express وتوثيق محرك العروض الذي تريد استخدامه.
</p>

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

<ul>
	<li>
		الوقت المستغرق للحصول على نتائج: إذا كان فريقك خبيرًا في استخدام لغة القوالب، فيُحتمَل أن يكونوا منتجين بصورة أسرع باستخدام تلك اللغة. إن لم يكن الأمر كذلك، فيجب التفكير في منحنى التعلّم النسبي لمحركات القوالب المُرشَّحة للاستخدام.
	</li>
	<li>
		الشعبية والنشاط: راجع شعبية المحرّك وما إذا كان لديه مجتمع نشط، فمن المهم أن تكون قادرًا على الحصول على الدعم عند ظهور مشاكل طوال مدة عمل الموقع.
	</li>
	<li>
		التنسيق: تستخدم بعض محركات القوالب <a href="https://academy.hsoub.com/programming/html/%D8%AA%D8%B9%D9%84%D9%85-%D9%84%D8%BA%D8%A9-html-r1702/" rel="">شيفرة HTML التوصيفية</a> للإشارة إلى المحتوى المُدخَل إلى شيفرة HTML عادية، بينما تبني محركات القوالب الأخرى شيفرة HTML باستخدام صيغة مختلفة (مثل استخدام المسافة البادئة وأسماء الكتل).
	</li>
	<li>
		الأداء أو الوقت اللازم للتقديم rendering time.
	</li>
	<li>
		الميزات: يجب أن تفكر فيما إذا كانت هذه المحركات توفّر الميزات التالية:
		<ul>
			<li>
				توريث التخطيط Layout: يسمح بتعريف قالب أساسي ثم يمكنك وراثة أجزاء منه فقط وهي الأجزاء التي تريد أن تكون مختلفة لصفحة معينة، ويُعَد ذلك أسلوبًا أفضل من بناء القوالب من خلال تضمين عدد من المكونات المطلوبة أو بناء قالب من نقطة الصفر في كل مرة.
			</li>
			<li>
				دعم التضمين: يسمح ببناء قوالب من خلال تضمين قوالب أخرى.
			</li>
			<li>
				صيغة مُختصَرة للتحكم في المتغيرات والحلقات.
			</li>
			<li>
				القدرة على ترشيح قيم المتغيرات على مستوى القالب مثل جعل المتغيرات بأحرف كبيرة أو تنسيق قيمة التاريخ.
			</li>
			<li>
				القدرة على توليد تنسيقات الخرج بتنسيقات مختلفة عن تنسيق HTML مثل <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> أو <a href="https://academy.hsoub.com/programming/java/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D9%85%D8%AE%D8%AA%D8%B5%D8%B1%D8%A9-%D9%84%D9%84%D8%BA%D8%A9-xml-%D9%88%D8%A7%D8%B3%D8%AA%D8%B9%D9%85%D8%A7%D9%84%D9%87%D8%A7-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%AC%D8%A7%D9%81%D8%A7-r1475/" rel="">XML</a>.
			</li>
			<li>
				دعم العمليات والتدفق غير المتزامن.
			</li>
			<li>
				ميزات من طرف العميل: إذا أمكن استخدام محرّك قوالب من طرف العميل، فسيسمح ذلك بإمكانية إجراء جميع أو معظم التقديم من طرف العميل.
			</li>
		</ul>
	</li>
</ul>

<p>
	سنستخدم في مشروعنا محرك القوالب Pug (وهو محرك Jade الذي أُعيدت تسميته مؤخرًا) الذي يُعَد من أشهر لغات قوالب Express/جافا سكريبت ويدعمه المولّد.
</p>

<h4 id="css">
	محرك تنسيق أو شيفرة CSS الذي يجب استخدامه
</h4>

<p>
	يتيح مولّد تطبيقات Express إنشاء مشروع مضبوط لاستخدام أكثر محركات تنسيق أو شيفرة CSS شيوعًا مثل <a href="https://lesscss.org/" rel="external nofollow">LESS</a> و <a href="https://sass-lang.com/" rel="external nofollow">SASS</a> و <a href="http://compass-style.org/" rel="external nofollow">Compass</a> و <a href="https://stylus-lang.com/" rel="external nofollow">Stylus</a>.
</p>

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

<p>
	يجب أن تستخدم محرك تنسيقات يسمح لفريقك أن يكون أكثر إنتاجية، إذ سنستخدم في مشروعنا شيفرة CSS الصرفة (الافتراضية) لأن متطلبات شيفرة CSS ليست معقدة بما يكفي لاستخدام شيء آخر.
</p>

<h4 id="-3">
	قاعدة البيانات الواجب استخدامها
</h4>

<p>
	لا تستخدم أو تضمّن الشيفرة البرمجية المُولَّدة أيّ قاعدة بيانات، ويمكن لتطبيقات Express استخدام أيّ آلية قاعدة بيانات تدعمها بيئة Node، إذ لا يحدد Express أي سلوك أو متطلبات إضافية محددة لإدارة قاعدة البيانات (سنناقش كيفية التكامل مع قاعدة بيانات في مقال لاحق).
</p>

<h3 id="-4">
	إنشاء المشروع
</h3>

<p>
	سننشئ مشروعًا بالاسم "express-locallibrary-tutorial" باستخدام مكتبة قوالب Pug بدون محرّك شيفرة CSS.
</p>

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

<pre class="ipsCode">express express-locallibrary-tutorial --view=pug
</pre>

<p>
	سينشئ المولّد ملفات المشروع ويسردها كما يلي:
</p>

<pre class="ipsCode">  create : express-locallibrary-tutorial\
   create : express-locallibrary-tutorial\public\
   create : express-locallibrary-tutorial\public\javascripts\
   create : express-locallibrary-tutorial\public\images\
   create : express-locallibrary-tutorial\public\stylesheets\
   create : express-locallibrary-tutorial\public\stylesheets\style.css
   create : express-locallibrary-tutorial\routes\
   create : express-locallibrary-tutorial\routes\index.js
   create : express-locallibrary-tutorial\routes\users.js
   create : express-locallibrary-tutorial\views\
   create : express-locallibrary-tutorial\views\error.pug
   create : express-locallibrary-tutorial\views\index.pug
   create : express-locallibrary-tutorial\views\layout.pug
   create : express-locallibrary-tutorial\app.js
   create : express-locallibrary-tutorial\package.json
   create : express-locallibrary-tutorial\bin\
   create : express-locallibrary-tutorial\bin\www

   تغيير المجلد‫:
     &gt; cd express-locallibrary-tutorial

   تثبيت الاعتماديات‫:
     &gt; npm install

   ‫تشغيل التطبيق (باستخدام Bash على نظام لينكس أو ماك)
     &gt; DEBUG=express-locallibrary-tutorial:* npm start

   ‫تشغيل التطبيق (باستخدام PowerShell على نظام ويندوز)
     &gt; $ENV:DEBUG = "express-locallibrary-tutorial:*"; npm start

   تشغيل التطبيق (باستخدام موجّه الأوامر على نظام ويندوز‫)
     &gt; SET DEBUG=express-locallibrary-tutorial:* &amp; npm start
</pre>

<p>
	يوفر المولّد في نهاية الخرج إرشادات حول كيفية تثبيت الاعتماديات (كما هو مذكور في الملف package.json) وكيفية تشغيل التطبيق على أنظمة تشغيل مختلفة.
</p>

<p>
	<strong>ملاحظة</strong>: تعرّف الملفات التي أنشأها المولّد جميع المتغيرات بوصفها <code>var</code>، لذا افتح جميع الملفات المُولَّدة وغيّر تصريحات <code>var</code> إلى <code>const</code> قبل المتابعة، إذ سنفترض لاحقًا أنك أجريت هذا التعديل.
</p>

<h3 id="-5">
	تشغيل الموقع الهيكلي
</h3>

<p>
	لدينا حاليًا مشروع هيكلي كامل، ولا يجري موقع الويب الكثير من الأمور حتى الآن، لكن الأمر يستحق تشغيله لإثبات أنه يعمل بنجاح.
</p>

<p>
	أولًا، ثبّت الاعتماديات باستخدام الأمر <code>install</code> الذي يجلب جميع حزم الاعتماديات المُدرَجة في الملف package.json الخاص بالمشروع:
</p>

<pre class="ipsCode">cd express-locallibrary-tutorial
npm install
</pre>

<p>
	ثانيًا، شغّل التطبيق من خلال استخدام الأمر التالي في موجّه أوامر ويندوز CMD:
</p>

<pre class="ipsCode">SET DEBUG=express-locallibrary-tutorial:* &amp; npm start
</pre>

<p>
	واستخدم الأمر التالي في Powershell ضمن نظام ويندوز:
</p>

<pre class="ipsCode">ENV:DEBUG = "express-locallibrary-tutorial:*"; npm start
</pre>

<p>
	<strong>ملاحظة</strong>: لم نشرح أوامر Powershell بصورة أكبر في هذا المقال، إذ تفترض أوامر ويندوز التي سنستخدمها أنك تستخدم موجّه أوامر ويندوز CMD.
</p>

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

<pre class="ipsCode">DEBUG=express-locallibrary-tutorial:* npm start
</pre>

<p>
	ثالثًا، حمّل بعد ذلك العنوان <code><a href="http://localhost:3000/%E2%80%8E" ipsnoembed="false" rel="external nofollow">http://localhost:3000/‎</a></code> في متصفحك للوصول إلى التطبيق.
</p>

<p>
	يجب أن تشاهد صفحة متصفح تشبه ما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2023_11/01_expressgeneratorskeletonwebsite.png.cb8db6d5155b22c50af176e757e9702f.png" data-fileid="138634" data-fileext="png" rel=""><img alt="01 expressgeneratorskeletonwebsite" class="ipsImage ipsImage_thumbnailed" data-fileid="138634" data-unique="hkyhbkm92" src="https://academy.hsoub.com/uploads/monthly_2023_11/01_expressgeneratorskeletonwebsite.png.cb8db6d5155b22c50af176e757e9702f.png"> </a>
</p>

<p>
	أصبح لديك الآن تطبيق Express يعمل بنجاح ويمكن الوصول إليه عبر المنفذ 3000.
</p>

<p>
	<strong>ملاحظة</strong>: يمكنك بدء تشغيل التطبيق فقط باستخدام الأمر <code>npm start</code>. يفعّل تحديد المتغير <code>DEBUG</code> كما هو موضح فيما يلي تسجيل الطرفية أو تنقيح الأخطاء، فمثلًا سترى خرج تنقيح الأخطاء التالي عندما تزور الصفحة السابقة:
</p>

<pre class="ipsCode">SET DEBUG=express-locallibrary-tutorial:* &amp; npm start

&gt; express-locallibrary-tutorial@0.0.0 start D:\github\mdn\test\exprgen\express-locallibrary-tutorial
&gt; node ./bin/www

  express-locallibrary-tutorial:server Listening on port 3000 +0ms
GET / 304 490.296 ms - -
GET /stylesheets/style.css 200 4.886 ms - 111
</pre>

<h3 id="-6">
	تفعيل إعادة بدء الخادم عند إجراء تغييرات على الملف
</h3>

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

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

<pre class="ipsCode">npm install --save-dev nodemon
</pre>

<p>
	إذا أردت تثبيت الأداة nodemon على المستوى العام في جهازك، وليس فقط على الملف package.json الخاص بمشروعك، فاستخدم الأمر التالي:
</p>

<pre class="ipsCode">npm install -g nodemon
</pre>

<p>
	إذا فتحتَ الملف package.json الخاص بمشروعك، فسترى الآن قسمًا جديدًا مع الاعتمادية التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1874_11" style=""><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.4"</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	بما أننا لم نثبّت الأداة على المستوى العام، فلا يمكننا تشغيلها من سطر الأوامر (إلا في حالة إضافتها إلى المسار)، ولكن يمكننا استدعاؤها من سكريبت npm، لأن مدير الحزم npm على علم بكل شيء عن الحزم المُثبَّتة. ابحث عن القسم <code>scripts</code> في الملف package.json الذي سيحتوي في البداية على سطر واحد يبدأ بكلمة <code>"start"</code>، لذا حدّثه بوضع فاصلة في نهاية هذا السطر، وضِف سطري <code>"devstart"</code> و <code>"serverstart"</code>.
</p>

<p>
	سيبدو قسم السكربتات scripts كما يلي في نظامي لينكس وماك أو إس:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1874_13" style=""><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 ./bin/www"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"devstart"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"nodemon ./bin/www"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"serverstart"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"DEBUG=express-locallibrary-tutorial:* npm run devstart"</span><span class="pln">
  </span><span class="pun">},</span></pre>

<p>
	واستخدم الأمر التالي في نظام ويندوز:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1874_16" style=""><span class="str">"serverstart"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"SET DEBUG=express-locallibrary-tutorial:* &amp; npm run devstart"</span></pre>

<p>
	يمكننا الآن بدء الخادم بالطريقة السابقة نفسها تمامًا ولكن باستخدام الأمر <code>devstart</code>.
</p>

<p>
	<strong>ملاحظة</strong>: إذا عدّلتَ الآن أيّ ملف في المشروع، فسيُعاد بدء الخادم، أو يمكنك إعادة بدء الخادم بكتابة <code>rs</code> في موجّه الأوامر في أيّ وقت، وستظل بحاجة إلى إعادة تحميل المتصفح لتحديث الصفحة.
</p>

<p>
	يجب الآن استدعاء الأمر "<code>npm run &lt;scriptname&gt;‎</code>" بدلًا من مجرد استخدام <code>npm start</code>، لأن "start" هو أمر npm يُربَط بالسكريبت. كان بإمكاننا استبدال الأمر في السكربت start ولكننا نريد فقط استخدام الأداة nodemon أثناء عملية التطوير، لذلك من المنطقي إنشاء أمر سكربت جديد.
</p>

<p>
	يُعَد الأمر <code>serverstart</code> المُضاف إلى قسم السكريبتات scripts في الملف package.json مثالًا جيدًا، إذ يعني استخدام هذا الأسلوب أنك لم تعد مضطرًا إلى كتابة أمر طويل لبدء الخادم. لاحظ أن الأمر المحدد المضاف إلى السكريبت يعمل على نظام ماك أو لينكس فقط.
</p>

<h3 id="-7">
	المشروع الناتج
</h3>

<p>
	لنطّلع الآن على المشروع الذي أنشأناه.
</p>

<h4 id="-8">
	بنية المجلد
</h4>

<p>
	يمتلك المشروع المُولَّد الآن بنية الملفات التالية (الملفات هي العناصر التي ليس أولها "/") بعد تثبيت الاعتماديات. يعرّف الملف package.json اعتماديات التطبيق ومعلومات أخرى، ويعرّف سكريبتًا لبدء التشغيل يستدعي نقطة الدخول إلى التطبيق التي هي ملف جافا سكربت "‎/bin/www"، ممّا يؤدي إلى إعداد معالجة أخطاء التطبيق ثم تحميل الملف app.js لينفّذ بقية العمل. تُخزَّن وجهات Routes التطبيق في وحدات منفصلة ضمن المجلد "‎/routes‎"، وتُخزَّن القوالب في المجلد "‎/views".
</p>

<pre class="ipsCode">express-locallibrary-tutorial
    app.js
    /bin
        www
    package.json
    package-lock.json
    /node_modules
        [about 6700 subdirectories and files]
    /public
        /images
        /javascripts
        /stylesheets
            style.css
    /routes
        index.js
        users.js
    /views
        error.pug
        index.pug
        layout.pug
</pre>

<p>
	سنشرح في الأقسام التالية هذه الملفات بمزيد من التفصيل.
</p>

<h4 id="packagejson">
	الملف package.json
</h4>

<p>
	يعرّف الملف package.json اعتماديات التطبيق ومعلومات أخرى كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1874_18" 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">"express-locallibrary-tutorial"</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.0"</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">"start"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"node ./bin/www"</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">"cookie-parser"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"~1.4.4"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"debug"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"~2.6.9"</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.1"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"http-errors"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"~1.6.3"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"morgan"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"~1.9.1"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"pug"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"2.0.0-beta11"</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.4"</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تتضمن الاعتماديات الحزمة express وحزمة محرّك العروض المختار pug، وتوجد الحزم التالية المفيدة في العديد من تطبيقات الويب:
</p>

<ul>
	<li>
		cookie-parser: تُستخدَم لتحليل ترويسة <a href="https://academy.hsoub.com/programming/javascript/%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D8%AA%D8%B9%D8%B1%D9%8A%D9%81-%D8%A7%D9%84%D8%A7%D8%B1%D8%AA%D8%A8%D8%A7%D8%B7-%D9%88%D8%B6%D8%A8%D8%B7%D9%87%D8%A7-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1330/" rel="">ملف تعريف الارتباط Cookie</a> وملء <code>req.cookies</code> الذي يوفّر تابعًا ملائمًا للوصول إلى معلومات ملفات تعريف الارتباط.
	</li>
	<li>
		debug: أداة مساعدة صغيرة لتنقيح أخطاء Node المُصمَّمة على غرار تقنية تنقيح أخطاء Node الأساسية.
	</li>
	<li>
		morgan: برمجية وسيطة لتسجيل طلبات HTTP خاصة ببيئة Node.
	</li>
	<li>
		http-errors: تنشئ أخطاء HTTP عند الحاجة (لمعالجة أخطاء Express).
	</li>
</ul>

<p>
	يعرّف قسم السكريبتات scripts أولًا السكريبت "start"، وهو السكريبت الذي نستدعيه عند استدعاء الأمر <code>npm start</code> لبدء الخادم، إذ أضاف مولّد تطبيقات Express هذا السكربت. يمكنك أن ترى من تعريف السكريبت أنه يمثّل بداية ملف جافا سكريبت "‎./bin/www" باستخدام node.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1874_20" style=""><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 ./bin/www"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"devstart"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"nodemon ./bin/www"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"serverstart"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"DEBUG=express-locallibrary-tutorial:* npm run devstart"</span><span class="pln">
  </span><span class="pun">},</span></pre>

<p>
	يمكن استخدام السكريبتات devstart و serverstart لبدء الملف "‎./bin/www" نفسه باستخدام nodemon بدلًا من node.
</p>

<h4 id="www">
	الملف www
</h4>

<p>
	يُعَد الملف "‎/bin/www" نقطة الدخول إلى التطبيق، وأول شيء يفعله هو استدعاء الدالة <code>require()‎</code> لطلب نقطة الدخول الحقيقية إلى التطبيق (أي الملف app.js في جذر المشروع)، والذي يضبط ويعيد كائن تطبيق <code>express()‎</code>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1874_23" style=""><span class="pun">#!</span><span class="str">/usr/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">env node

</span><span class="com">/**
 * اعتماديات الوحدة
 */</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></pre>

<p>
	<strong>ملاحظة</strong>: تُعَد الدالة <code>require()‎</code> دالة Node عامة تُستخدَم لاستيراد الوحدات إلى الملف الحالي. نحدد في مثالنا وحدة app.js باستخدام مسار نسبي ونحذف امتداد الملف الاختياري (‎.js).
</p>

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

<h4 id="appjs">
	الملف app.js
</h4>

<p>
	ينشئ هذا الملف كائن تطبيق <code>express</code> (يسمى <code>app</code> اصطلاحيًا)، ويُعِد التطبيق باستخدام إعدادات وبرمجيات وسيطة متنوعة مختلفة، ثم يصدّر التطبيق من الوحدة. توضّح الشيفرة البرمجية التالية أجزاء الملف التي تنشئ وتصدر كائن التطبيق:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1874_25" 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">
</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><span class="pun">;</span></pre>

<p>
	وهو الكائن <code>module.exports</code> في ملف نقطة الدخول www كما ذكرنا سابقًا، حيث يُزوَّد المستدعِي بهذا الكائن عند استيراد هذا الملف.
</p>

<p>
	لنتعرّف على الملف app.js بالتفصيل. أولًا، نستورد بعض مكتبات Node المفيدة إلى الملف باستخدام الدالة <code>require()‎</code> بما في ذلك http-errors و express و morgan و cookie-parser التي نزّلناها مسبقًا لتطبيقنا باستخدام مدير حزم npm، ومكتبة path وهي مكتبة Node أساسية لتحليل مسارات الملفات والمجلدات.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1874_27" style=""><span class="kwd">const</span><span class="pln"> createError </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"http-errors"</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"> path </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"path"</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> cookieParser </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"cookie-parser"</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">"morgan"</span><span class="pun">);</span></pre>

<p>
	نطلب بعد ذلك باستخدام الدالة <code>require()‎</code> وحدات من مجلد الوجهات "routes"، إذ تحتوي هذه الوحدات أو الملفات على شيفرة برمجية للتعامل مع مجموعات معينة من "الوجهات" (<a href="https://academy.hsoub.com/programming/general/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%B9%D9%86%D9%88%D8%A7%D9%86-url-%D9%88%D8%A3%D9%86%D9%88%D8%A7%D8%B9%D9%87-r1435/" rel="">مسارات URL</a>). إذا أردنا توسيع التطبيق الهيكلي لسرد جميع الكتب في المكتبة مثلًا، فسنضيف ملفًا جديدًا للتعامل مع الوجهات المتعلقة بالكتب.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1874_29" style=""><span class="kwd">const</span><span class="pln"> indexRouter </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"./routes/index"</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">"./routes/users"</span><span class="pun">);</span></pre>

<p>
	<strong>ملاحظة</strong>: استوردنا الوحدة، ولم نستخدم وجهاتها حتى الآن، ولكن سيحدث ذلك لاحقًا.
</p>

<p>
	ننشئ بعد ذلك الكائن <code>app</code> باستخدام وحدة express المستوردة، ثم نستخدمها لإعداد محرك العروض (أو القوالب)، إذ أن هناك قسمان لإعداد المحرك هما: ضبط قيمة "<code>views</code>" لتحديد المجلد الذي ستُخزَّن القوالب فيه، وهو في حالتنا المجلد الفرعي "‎/views"، ثم ضبط قيمة "<code>view engine</code>" لتحديد مكتبة القوالب، وهي في حالتنا "pug".
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1874_31" style=""><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"> 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></pre>

<p>
	تستدعي المجموعة التالية من الدوال <code>app.use()‎</code> لإضافة مكتبات البرمجيات الوسيطة التي استوردناها سابقًا إلى سلسلة معالجة الطلبات، فمثلًا هناك حاجة إلى استخدام <code>express.json()‎</code> و <code>express.urlencoded()‎</code> لملء متن الطلب <code>req.body</code> بحقول الاستمارة. نستخدم أيضًا بعد هذه المكتبات البرمجيةَ الوسيطة <code>express.static</code> التي تجعل إطار عمل Express يخدّم جميع الملفات الثابتة في المجلد "‎/public" ضمن جذر المشروع.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1874_33" 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="str">"dev"</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="pln">use</span><span class="pun">(</span><span class="pln">cookieParser</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>
	ضبطنا جميع البرمجيات الوسيطة الأخرى، وسنضيف الآن شيفرة معالجة الوجهات (التي استوردناها سابقًا) إلى سلسلة معالجة الطلبات، إذ ستعرّف الشيفرة المستوردة وجهات معينة لأجزاء مختلفة من الموقع:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1874_35" style=""><span class="pln">app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="str">"/"</span><span class="pun">,</span><span class="pln"> indexRouter</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">"/users"</span><span class="pun">,</span><span class="pln"> usersRouter</span><span class="pun">);</span></pre>

<p>
	<strong>ملاحظة</strong>: تُعامَل المسارات المحددة السابقة (<code>'/'</code> و '‎/users') بوصفها بادئةً للوِجهات المُعرَّفة في الملفات المستوردة، فمثلًا إذا عرّفت وحدةُ المستخدمين users المُستورَدة وجهة المسار <code>‎/profile</code>، فيمكنك الوصول إلى هذا الوجهة من خلال <code>‎/users/profile</code> (سنتحدث أكثر عن الوِجهات في مقال لاحق).
</p>

<p>
	تضيف البرمجيات الوسيطة الأخيرة في الملف توابع معالجة للأخطاء واستجابات HTTP 404:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1874_37" style=""><span class="com">// التقاط 404 وتوجيه معالج الخطأ</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">use</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">
  next</span><span class="pun">(</span><span class="pln">createError</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">

</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">err</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="com">// ‫ضبط locals، وتوفير خطأ في عملية التطوير فقط</span><span class="pln">
  res</span><span class="pun">.</span><span class="pln">locals</span><span class="pun">.</span><span class="pln">message </span><span class="pun">=</span><span class="pln"> err</span><span class="pun">.</span><span class="pln">message</span><span class="pun">;</span><span class="pln">
  res</span><span class="pun">.</span><span class="pln">locals</span><span class="pun">.</span><span class="pln">error </span><span class="pun">=</span><span class="pln"> req</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">"env"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">===</span><span class="pln"> </span><span class="str">"development"</span><span class="pln"> </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">
  res</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="pln">err</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">
  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>
	ضُبِط كائن تطبيق Express (أو app) كاملًا الآن، والخطوة الأخيرة هي إضافته إلى <code>module.exports</code>، مما يسمح بأن يستورده الملف ‎/bin/www.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1874_39" style=""><span class="pln">module</span><span class="pun">.</span><span class="pln">exports </span><span class="pun">=</span><span class="pln"> app</span><span class="pun">;</span></pre>

<h4 id="-9">
	الوجهات
</h4>

<p>
	اطّلع على ملف الوجهات "‎/routes/users.js" التالي، حيث تشترك ملفات الوجهات ببنية مماثلة، لذلك لا حاجة لعرض الملف index.js أيضًا. يحمّل هذا الملف أولًا الوحدة express ويستخدمها للحصول على الكائن <code>express.Router</code>، ثم يحدد وجهةً لهذا الكائن ويصدِّر الموجّه من الوحدة، مما يسمح باستيراد الملف في app.js.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1874_41" 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"> router </span><span class="pun">=</span><span class="pln"> 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">
router</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"> 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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"respond with a resource"</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"> router</span><span class="pun">;</span></pre>

<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-%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="">دالة رد نداء callback</a> ستُستدعَى عند اكتشاف <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%B7%D9%84%D8%A8%D8%A7%D8%AA-http-%D9%81%D9%8A-nodejs-r1868/" rel="">طلب HTTP</a> من النوع <code>GET</code> بالنمط الصحيح، إذ يُعَد نمط المطابقة هو الوجهة المُحدَّدة عند استيراد الوحدة ('<code>‎/users</code>')، إضافةً إلى كل ما جرى تعريفه في هذا الملف ('<code>/</code>')، وبالتالي ستُستخدَم هذه الوجهة عند تلقي عنوان URL للمستخدمين <code>/users/</code>.
</p>

<p>
	<strong>ملاحظة</strong>: جرب ذلك من خلال تشغيل الخادم باستخدام node وزيارة عنوان URL في متصفحك <code><a href="http://localhost:3000/users/%E2%80%8E" ipsnoembed="false" rel="external nofollow">http://localhost:3000/users/‎</a></code>، ويجب أن ترى الرسالة "respond with a resource".
</p>

<p>
	تمتلك <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>next</code>، وبالتالي هي دالة وسيطة بدلًا من كونها دالة رد نداء بسيطة للوجهة. لا تستخدم الشيفرة البرمجية الوسيط <code>next</code> حاليًا، ولكنه مفيدٌ مستقبلًا إذا أردتَ إضافة معالجات وجهات متعددة إلى مسار الوجهة <code>'/'</code>.
</p>

<h4 id="-10">
	العروض أو القوالب
</h4>

<p>
	تُخزَّن العروض (أو القوالب) في المجلد "‎/views" (كما هو مُحدَّد في الملف app.js) وتُمنَح امتداد الملف <strong>‎.pug</strong>، ويُستخدَم التابع <code>Response.render()‎</code> لتصيير قالب محدد مع قيم المتغيرات المُسمَّاة الممرَّرة في كائن، ثم إرسال النتيجة بوصفها استجابة. يمكنك أن ترى في الشيفرة التالية من الملف "‎/routes/index.js" كيف تصيّر هذه الوجهة استجابةً باستخدام القالب "index" الذي يمرّر متغير القالب "title".
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1874_43" style=""><span class="com">/* الحصول على الصفحة الرئيسية */</span><span class="pln">
router</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"> 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">
  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"> title</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Express"</span><span class="pln"> </span><span class="pun">});</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	القالب المقابل للوجهة السابقة مذكور فيما يلي (index.pug). سنتحدث أكثر عن الصيغة لاحقًا، ولكن كل ما تحتاج إلى معرفته الآن هو أن المتغير <code>title</code> (مع القيمة "<code>Express</code>") يُدرَج في المكان المُحدَّد في القالب.
</p>

<pre class="ipsCode">extends layout

block content
  h1= title
  p Welcome to #{title}
</pre>

<h2 id="-11">
	تحدى نفسك
</h2>

<p>
	أنشئ وجهةً جديدة في الملف "‎/routes/users.js" تعرض النص "You're so cool" على العنوان <code>/users/cool/</code>، واختبرها من خلال تشغيل الخادم وزيارة العنوان <code><a href="http://localhost:3000/users/cool/%E2%80%8E" ipsnoembed="false" rel="external nofollow">http://localhost:3000/users/cool/‎</a></code> في متصفحك.
</p>

<h2 id="-12">
	الخلاصة
</h2>

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

<p>
	ترجمة -وبتصرُّف- للمقالين <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website" rel="external nofollow">Express Tutorial: The Local Library website</a> و <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website" rel="external nofollow">Express Tutorial Part 2: Creating a skeleton website</a>.
</p>

<h2 id="-13">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-node-%D9%85%D8%B9-express-r2169/" rel="">إعداد بيئة تطوير Node مع Express</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-nodejs-%D9%88%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-express-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86-r1441/" rel="">دليل استخدام Node.js وإطار العمل Express للمبتدئين</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2090/" rel="">تطبيق عملي لتعلم إطار عمل جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2170</guid><pubDate>Sat, 25 Nov 2023 13:05:01 +0000</pubDate></item><item><title>&#x625;&#x639;&#x62F;&#x627;&#x62F; &#x628;&#x64A;&#x626;&#x629; &#x62A;&#x637;&#x648;&#x64A;&#x631; Node &#x645;&#x639; Express</title><link>https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-node-%D9%85%D8%B9-express-r2169/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_11/---Node-01.png.4870534e1d4547225e0baf1c60b0e2c0.png" /></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-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-express-%D9%88%D8%A8%D9%8A%D8%A6%D8%A9-node-r2168/" rel="">إطار عمل Express</a>، وسنوضّح كيفية إعداد واختبار بيئة تطوير Node/Express على نظام ويندوز أو لينكس (أوبنتو) أو ماك أو إس macOS، إذ يوفر هذا المقال ما تحتاجه لبدء تطوير تطبيقات Express بالنسبة لأيّ <a href="https://academy.hsoub.com/apps/operating-systems/%D9%86%D8%B8%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84/" rel="">نظام تشغيل</a> من هذه الأنظمة.
</p>

<ul>
	<li>
		<strong>المتطلبات الأساسية</strong>: معرفة باستخدام <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> أو الطرفية Terminal وكيفية تثبيت الحزم البرمجية على نظام تشغيل حاسوب التطوير خاصتك.
	</li>
	<li>
		<strong>الهدف</strong>: إعداد بيئة تطوير لإطار عمل Express على حاسوبك.
	</li>
</ul>

<h2 id="express">
	نظرة عامة على بيئة تطوير Express
</h2>

<p>
	تسهّل كلٌّ من بيئة Node وإطار عمل Express إعداد حاسوبك للبدء في تطوير <a href="https://academy.hsoub.com/apps/web/" rel="">تطبيقات الويب</a>، إذ سنقدم في هذا القسم نظرةً عامة على الأدوات المطلوبة، وسنشرح بعض أبسط الطرق لتثبيت Node و Express على أوبنتو وماك أو اس macOS وويندوز، وسنوضح كيفية اختبار التثبيت.
</p>

<h3 id="express-1">
	ما هي بيئة تطوير Express؟
</h3>

<p>
	تتضمن بيئة تطوير Express تثبيت بيئة Node.js و<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</a> ومولّد تطبيقات Express (اختياريًا) على حاسوبك المحلي.
</p>

<p>
	تُثبَّت بيئة Node ومدير الحزم npm مع بعضها من الحزم الثنائية المُعَدّة أو المثبِّتات Installers أو مديري حزم نظام التشغيل أو من المصدر (كما هو موضح في الأقسام التالية). يثبِّت <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</a> إطار عمل Express بوصفه اعتمادية Dependency على تطبيقات ويب Express مع المكتبات الأخرى، مثل محركات القوالب ومشغّلات قواعد البيانات وبرمجيات الاستيثاق الوسيطة والبرمجيات الوسيطة لتخديم الملفات الثابتة وغير ذلك.
</p>

<p>
	يمكن أيضًا استخدام مدير حزم npm لتثبيت موّلد تطبيقات Express (على المستوى العام)، وهو أداة مفيدة لإنشاء تطبيقات Express الهيكلية التي تتبع <a href="https://developer.mozilla.org/en-US/docs/Glossary/MVC" rel="external nofollow">نمط MVC</a>. يُعَد مولّد التطبيقات اختياريًا لأنك لست بحاجة إلى استخدامه لإنشاء تطبيقات تستخدم إطار عمل Express أو بناء تطبيقات Express التي لها التخطيط المعماري أو الاعتماديات نفسها، ولكننا سنستخدمه لأنه يجعل البدء أسهل ويطوّر بنية التطبيق المعيارية.
</p>

<p>
	<strong>ملاحظة</strong>: لا تتضمن بيئة التطوير خادم ويب منفصل للتطوير على عكس بعض <a href="https://academy.hsoub.com/devops/servers/%D8%A3%D8%B7%D8%B1-%D8%B9%D9%85%D9%84-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D9%85%D9%86-%D8%B7%D8%B1%D9%81-%D8%A7%D9%84%D8%AE%D8%A7%D8%AF%D9%85-r784/" rel="">أطر عمل الويب</a> الأخرى، إذ ينشئ ويشغّل تطبيق الويب خادمَ الويب الخاص به في Node/Express.
</p>

<p>
	هناك أدوات طرفية أخرى تشكل جزءًا من بيئة تطوير نموذجية بما في ذلك <a href="https://academy.hsoub.com/programming/workflow/%D9%85%D8%AD%D8%B1%D8%B1%D8%A7%D8%AA-%D8%A7%D9%84%D9%86%D8%B5%D9%88%D8%B5-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%B9%D9%85%D9%84%D8%A9-%D9%81%D9%8A-%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D9%85%D9%88%D8%A7%D9%82%D8%B9-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r1438/" rel="">محررات النصوص</a> أو <a href="https://academy.hsoub.com/programming/workflow/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%A7%D9%84%D9%85%D8%AA%D9%83%D8%A7%D9%85%D9%84%D8%A9-ide-r1513/" rel="">بيئات التطوير المتكاملة IDE</a> لتعديل الشيفرة البرمجية وأدوات إدارة التحكم بالشيفرة المصدرية مثل <a href="https://academy.hsoub.com/programming/workflow/git/" rel="">Git</a> لإدارة النسخ المختلفة من شيفرتك البرمجية بأمان. نفترض أنك ثبَّتَ هذه الأنواع من الأدوات وبالأخص محرر النصوص.
</p>

<h3 id="">
	أنظمة التشغيل المدعومة
</h3>

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

<p>
	سنقدم في هذا المقال تعليمات الإعداد لأنظمة ويندوز وماك macOS ولينكس (أوبنتو).
</p>

<h3 id="nodeexpress">
	نسخة Node/Express التي يجب استخدامها
</h3>

<p>
	هناك العديد من إصدارات Node، إذ تحتوي الإصدارات الأحدث على إصلاحات للأخطاء ودعم للنسخ الأحدث من معايير ECMAScript (<a href="https://academy.hsoub.com/programming/javascript/" rel="">جافا سكريبت</a>) وتحسينات على واجهة برمجة تطبيقات Node.
</p>

<p>
	يجب أن تستخدم الإصدار الأحدث من الإصدار المدعوم على المدى الطويل Long-term Supported -أو LTS اختصارًا، إذ سيكون هذا الإصدار أكثر استقرارًا من الإصدار الحالي مع وجود ميزات حديثة نسبيًا (ولا تزال صيانته موجودةً بنشاط)، ولكن يجب أن تستخدم الإصدار الحالي إذا كنت بحاجة إلى ميزة غير موجودة في نسخة LTS. يجب عليك دائمًا استخدام أحدث نسخة بالنسبة إلى إطار عمل Express.
</p>

<h3 id="-1">
	قواعد البيانات والاعتماديات الأخرى
</h3>

<p>
	تُعَد الاعتماديات الأخرى -مثل مشغّلات قاعدة البيانات ومحرّكات القوالب ومحركات الاستيثاق وغير ذلك- جزءًا من التطبيق، وتُستورد إلى بيئة التطبيق باستخدام مدير الحزم npm (سنناقشها لاحقًا في مقالات خاصة بالتطبيق).
</p>
<iframe allowfullscreen="" data-controller="core.front.core.autosizeiframe" data-embedauthorid="3889" data-embedcontent="" src="https://academy.hsoub.com/files/32-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-nodejs/?do=embed" style="margin: auto;"></iframe>

<h2 id="node">
	تثبيت بيئة Node
</h2>

<p>
	يجب أن تثبّتَ Nodejs ومدير حزم Node -أو npm اختصارًا- على نظام تشغيلك لاستخدام إطار عمل Express، ولتسهيل ذلك، سنثبّت أولًا مدير نسخ Node، ثم سنستخدمه لتثبيت أحدث النسخ المدعومة على المدى الطويل LTS من Node و npm.
</p>

<p>
	<strong>ملاحظة</strong>: يمكنك أيضًا تثبيت nodejs و npm باستخدام المثبِّتات المتوفرة على <a href="https://nodejs.org/en" rel="external nofollow">موقع Nodejs</a> (اضغط على الزر لتنزيل إصدار LTS المُوصَى به لمعظم المستخدمين)، أو يمكنك التثبيت باستخدام <a href="https://nodejs.org/en/download/package-manager" rel="external nofollow">مدير حزم نظام تشغيلك</a>. نوصي جدًا باستخدام مدير نسخ Node لأنه يسهّل التثبيت والترقية والتبديل بين نسخ Node و npm.
</p>

<h3 id="-2">
	نظام ويندوز
</h3>

<p>
	يوجد عدد من مديري نسخ Node لنظام تشغيل ويندوز، وسنستخدم في مثالنا <a href="https://github.com/coreybutler/nvm-windows" rel="external nofollow">nvm-windows</a> الذي يحظى باحترام كبير بين مطوري Node. ثبّت أحدث نسخة باستخدام المثبِّت الذي تختاره من صفحة <a href="https://github.com/coreybutler/nvm-windows/releases" rel="external nofollow">nvm-windows/releases</a>، ثم افتح بعد تثبيت <code>nvm-windows</code> موجه الأوامر (أو PowerShell) وأدخِل الأمر التالي لتنزيل أحدث نسخة LTS من nodejs و npm:
</p>

<pre class="ipsCode">nvm install lts
</pre>

<p>
	لنفترض أن نسخة LTS من nodejs هي 18.15.0، وبالتالي يمكنك ضبطها بوصفها النسخة الحالية للاستخدام باستخدام الأمر التالي:
</p>

<pre class="ipsCode">nvm use 18.15.0
</pre>

<p>
	<strong>ملاحظة</strong>: إذا تلقيت تحذيرات رفض الوصول "Access Denied"، فيجب تشغيل هذا الأمر في موجه الأوامر باستخدام أذونات المدير.
</p>

<p>
	استخدم الأمر <code>nvm --help</code> لمعرفة خيارات سطر الأوامر الأخرى مثل سرد جميع نسخ Node المتاحة وجميع نسخ NVM التي جرى تنزيلها.
</p>

<h3 id="macos">
	نظام تشغيل أوبنتو وماك أو إس macOS
</h3>

<p>
	يوجد عدد من مديري نسخ Node لنظامي أوبنتو وماك أو إس، إذ يُعَد مدير نسخ <a href="https://github.com/nvm-sh/nvm" rel="external nofollow">nvm</a> واحدًا من أكثرها شيوعًا، وهو النسخة الأصلية التي يعتمد عليها <code>nvm-windows</code> (اطلع على <a href="https://github.com/nvm-sh/nvm#install--update-script" rel="external nofollow">إرشادات الطرفية</a> لتثبيت أحدث نسخة من nvm).
</p>

<p>
	ثبّت nvm، ثم افتح الطرفية وأدخِل الأمر التالي لتنزيل أحدث نسخة LTS من nodejs و npm:
</p>

<pre class="ipsCode">nvm install --lts
</pre>

<p>
	لنفترض أن نسخة LTS من nodejs هي 18.15.0، إذ يعرض الأمر <code>nvm list</code> مجموعة النسخ الذي جرى تنزيلها والنسخة الحالية. يمكنك ضبط نسخة معينة بوصفها النسخة الحالية باستخدام الأمر التالي (أمر <code>nvm-windows</code> نفسه):
</p>

<pre class="ipsCode">nvm use 18.15.0
</pre>

<p>
	استخدم الأمر <code>nvm --help</code> لمعرفة خيارات سطر الأوامر الأخرى التي تكون مشابهةً أو مماثلةً لتلك التي يوفّرها <code>nvm-windows</code>.
</p>

<h3 id="nodejsnpm">
	اختبار تثبيت Nodejs و npm
</h3>

<p>
	يمكنك اختبار التثبيت بعد ضبط <code>nvm</code> لاستخدام نسخة Node معينة، إذ توجد طريقة جيدة لذلك وهي استخدام أمر النسخة "version" في موجه الأوامر او الطرفية والتحقق من إعادة سلسلة النسخة المتوقعة كما يلي:
</p>

<pre class="ipsCode">&gt; node -v
v18.15.0
</pre>

<p>
	يجب أيضًا تثبيت مدير حزم npm، ويمكن اختباره بالطريقة نفسها:
</p>

<pre class="ipsCode">&gt; npm -v
9.3.1
</pre>

<p>
	لننشئ خادم Node نقي وبسيط يطبع الجملة "Hello World" في المتصفح عندما تزور عنوان URL الصحيح في متصفحك.
</p>

<p>
	أولًا، انسخ النص التالي إلى ملف بالاسم hellonode.js، إذ تستخدم الشيفرة التالية ميزات Node النقية بدون ميزات Express:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6164_9" style=""><span class="com">// حمّل وحدة‫ HTTP</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"> 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"> 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="com">// ‫أنشئ خادم HTTP واستمع إلى المنفذ 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">
  </span><span class="com">// ‫اضبط استجابة ترويسة HTTP بحالة HTTP ونوع المحتوى</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="pun">;</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">

</span><span class="com">// الاستماع إلى الطلبات على المنفذ 3000، وتسجيل المنفذ الذي استمعنا إليه بوصفه دالة رد نداء</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></pre>

<p>
	تستورد الشيفرة البرمجية <a href="https://wiki.hsoub.com/Node.js/http" rel="external">وحدة http</a> وتستخدمها لإنشاء خادم <code>createServer()‎</code> يستمع إلى <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%B7%D9%84%D8%A8%D8%A7%D8%AA-http-%D9%81%D9%8A-nodejs-r1868/" rel="">طلبات HTTP</a> على المنفذ 3000، ثم يطبع السكريبت رسالةً إلى الطرفية حول عنوان URL للمتصفح الذي يمكنك استخدامه لاختبار الخادم. تأخذ الدالة <code>createServer()‎</code> وسيطًا هو دالة رد النداء التي ستُستدعَى عند تلقي طلب HTTP، وتعيد هذه الدالة استجابة مع رمز حالة HTTP هو 200 ("OK") والنص "Hello World".
</p>

<p>
	<strong>ملاحظة</strong>: لا تقلق إن لم تفهم بالضبط ما تفعله هذه الشيفرة البرمجية حتى الآن، إذ سنشرحها بمزيد من التفصيل بمجرد أن نبدأ باستخدام Express.
</p>

<p>
	ثانيًا، ابدأ الخادم من خلال الانتقال إلى مجلد الملف hellonode.js نفسه في موجّه الأوامر، ثم استدعاء <code>node</code> مع اسم السكريبت كما يلي:
</p>

<pre class="ipsCode">&gt;node hellonode.js
Server running at http://127.0.0.1:3000/
</pre>

<p>
	ثالثًا، انتقل إلى العنوان <code>http://127.0.0.1:3000</code>. إذا عمل كل شيء بنجاح، فيجب أن يعرض المتصفح السلسلة النصية "Hello World".
</p>

<h2 id="npm">
	استخدام مدير الحزم npm
</h2>

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

<p>
	<strong>ملاحظة</strong>: يُعَد إطار Express من منظور بيئة Node مجرد حزمة أخرى يجب تثبيتها باستخدام مدير الحزم npm ثم تطلبها في شيفرتك البرمجية.
</p>

<p>
	يمكنك استخدام مدير حزم npm يدويًا لجلب الحزم المطلوبة بصورة منفصلة، ويمكن إدارة الاعتماديات باستخدام ملف تعريف نصي بالاسم <a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D9%88%D8%AD%D8%AF%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-%D9%81%D9%8A-nodejs-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-npm-%D9%88%D9%85%D9%84%D9%81-packagejson-r1728/" rel="">package.json</a>، إذ يسرد هذا الملف جميع الاعتماديات الخاصة بحزمة جافا سكريبت معينة، بما في ذلك اسم الحزمة ونسختها ووصفها والملف الأولي المطلوب تنفيذه واعتماديات الإنتاج واعتماديات التطوير ونسخ Node التي يمكن أن العمل معها وغير ذلك. يجب أن يحتوي ملف package.json على كل شيء يحتاجه مدير حزم npm لجلب التطبيق وتشغيله، فإذا كنت بصدد كتابة مكتبة قابلة لإعادة الاستخدام، فيمكنك استخدام هذا التعريف لرفع حزمتك إلى مستودع npm وإتاحتها لمستخدمين آخرين.
</p>

<h3 id="-3">
	إضافة الاعتماديات
</h3>

<p>
	توضح الخطوات التالية كيفية استخدام مدير حزم npm لتنزيل حزمة وحفظها في اعتماديات المشروع ثم طلبها في تطبيق Node.
</p>

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

<p>
	أولًا، أنشئ مجلدًا لتطبيقك الجديد وانتقل إليه كما يلي:
</p>

<pre class="ipsCode">mkdir myapp
cd myapp
</pre>

<p>
	ثانيًا، استخدم أمر npm الذي هو <code>init</code> لإنشاء ملف package.json لتطبيقك. يطالبك هذا الأمر بعدد من الأشياء، بما في ذلك اسم ونسخة تطبيقك واسم ملف نقطة الدخول الأولي (وهو index.js افتراضيًا)، وما عليك حاليًا سوى قبول الإعدادات الافتراضية:
</p>

<pre class="ipsCode">npm init
</pre>

<p>
	إذا عرضتَ ملف package.json (باستخدام الأمر <code>cat package.json</code>)، فسترى الإعدادات الافتراضية التي قبلتها وينتهي الملف بالترخيص License كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6164_11" 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">"myapp"</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">""</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">"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="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="str">"author"</span><span class="pun">:</span><span class="pln"> </span><span class="str">""</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">"ISC"</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	ثالثًا، ثبّت Express في المجلد <code>myapp</code> واحفظه في قائمة الاعتماديات لملف package.json:
</p>

<pre class="ipsCode">npm install express
</pre>

<p>
	سيظهر الآن قسم الاعتماديات "dependencies" الخاصة بملف package.json في نهايته وسيتضمن Express كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6164_13" 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">"myapp"</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">""</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">"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="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="str">"author"</span><span class="pun">:</span><span class="pln"> </span><span class="str">""</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">"ISC"</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="pun">}</span></pre>

<p>
	رابعًا، يمكن استخدام مكتبة Express من خلال استدعاء الدالة <code>require()‎</code> في الملف index.js لتضمينها في تطبيقك. أنشئ هذا الملف الآن في جذر مجلد التطبيق "myapp"، وضع فيه المحتويات التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6164_15" 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">
</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="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">"Hello World!"</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="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 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>
	تُظهِر هذه الشيفرة البرمجية أصغر تطبيق Express وهو "مرحبًا للعالم"، إذ تستورد هذه الشيفرة الوحدةَ "express" باستخدام الدالة <code>require()‎</code> وتستخدمها لإنشاء خادم (<code>app</code>) يستمع إلى طلبات HTTP على المنفذ 3000 ويطبع رسالة إلى الطرفية تشرح عنوان URL للمتصفح الذي يمكنك استخدامه لاختبار الخادم. تستجيب الدالة <code>app.get()‎</code> فقط لطلبات HTTP من النوع <code>GET</code> بمسار URL المحدد ('/') من خلال استدعاء دالة لإرسال رسالة Hello World!"‎" في مثالنا.
</p>

<p>
	<strong>ملاحظة</strong>: تسمح علامات الاقتباس المائلة في <code>!{Example app listening on port ${port</code> بإدخال قيمة <code>‎$port</code> في السلسلة النصية.
</p>

<p>
	خامسًا، يمكنك بدء تشغيل الخادم من خلال استدعاء Node مع السكربت في موجه الأوامر كما يلي:
</p>

<pre class="ipsCode">&gt;node index.js
</pre>

<p>
	سترى الخرج التالي على الطرفية:
</p>

<pre class="ipsCode">Example app listening on port 3000
</pre>

<p>
	سادسًا، انتقل إلى عنوان <code><a href="http://localhost:3000/%E2%80%8E" ipsnoembed="false" rel="external nofollow">http://localhost:3000/‎</a></code>، فإذا عمل كل شيء على ما يرام، فيجب أن يعرض المتصفح السلسلة النصية "Hello World!‎".
</p>

<h3 id="-4">
	تطوير الاعتماديات
</h3>

<p>
	إذا اُستخدِمت الاعتمادية أثناء مرحلة التطوير فقط، فيجب حفظها بوصفها "اعتمادية تطوير" حتى لا يضطر مستخدمو الحزمة إلى تثبيتها في مرحلة الإنتاج، فمثلًا يمكنك استدعاء أمر npm التالي لاستخدام أداة تنقيح صياغة شيفرة جافا سكريبت الشهيرة <a href="https://eslint.org/" rel="external nofollow">ESLint</a>:
</p>

<pre class="ipsCode">npm install eslint --save-dev
</pre>

<p>
	ويمكنك الاطلاع على مقال <a 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="">تقييم صلاحية بيانات التطبيق واستخدام المدقق ESLint</a> على أكاديمية حسوب لمزيد من المعلومات عن المدقق ESLint.
</p>

<p>
	سيُضاف بعد ذلك الإدخال التالي إلى ملف package.json الخاص بتطبيقك:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6164_17" style=""><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">"eslint"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^7.10.0"</span><span class="pln">
  </span><span class="pun">}</span></pre>

<p>
	<strong>ملاحظة</strong>: تُعَد منقحات الصياغة Linters أدوات تجري تحليلًا ثابتًا على البرمجيات للتعرف على الالتزام أو عدم الالتزام بمجموعة معينة من أفضل ممارسات كتابة الشيفرة البرمجية والإبلاغ عنها.
</p>

<h3 id="-5">
	تشغيل المهام
</h3>

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

<p>
	<strong>ملاحظة</strong>: يمكن أيضًا استخدام مشغِّلات المهام مثل Gulp و Grunt لتشغيل الاختبارات والأدوات الخارجية الأخرى.
</p>

<p>
	يمكن أن نضيف مثلًا كتلة السكريبت التالية إلى ملف package.json (بافتراض أن شيفرة تطبيقنا المصدرية موجودة في المجلد ‎/src/js) لتعريف سكريبت لتشغيل اعتمادية تطوير eslint التي حددناها في القسم السابق:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6164_19" style=""><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">"lint"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"eslint src/js"</span><span class="pln">
  </span><span class="com">// …</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يُعَد <code>eslint src/js</code> أمرًا يمكننا إدخاله في سطر الأوامر أو في الطرفية لتشغيل <code>eslint</code> على ملفات جافا سكريبت الموجودة في المجلد <code>src/js</code> ضمن مجلد تطبيقنا. يوفّر تضمين ما ورد سابقًا ضمن الملف package.json الخاص بتطبيقنا اختصارًا لهذا الأمر وهو <code>lint</code>.
</p>

<p>
	يمكننا بعد ذلك تشغيل eslint باستخدام npm من خلال استدعاء ما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6164_21" style=""><span class="pln">npm run</span><span class="pun">-</span><span class="pln">script lint
</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">
npm run lint</span></pre>

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

<h2 id="express-2">
	تثبيت مولد تطبيقات Express
</h2>

<p>
	تولّد أداة مولّد تطبيقات Express Application Generator تطبيق Express هيكلي، إذ يمكنك تثبيت هذا المولّد باستخدام npm كما يلي:
</p>

<pre class="ipsCode">npm install express-generator -g
</pre>

<p>
	<strong>ملاحظة</strong>: يمكن أن تحتاج إلى أن تبدأ هذا السطر بالكلمة <code>sudo</code> على نظامي أوبنتو أو ماك أو إس، وتثبّت الراية <code>‎-g</code> الأداة بصورة عامة بحيث يمكنك استدعاؤها من أيّ مكان.
</p>

<p>
	يمكن إنشاء تطبيق Express بالاسم "helloworld" مع الإعدادات الافتراضية من خلال الانتقال إلى المكان الذي تريد إنشاءه فيه وتشغيل التطبيق كما يلي:
</p>

<pre class="ipsCode">express helloworld
</pre>

<p>
	<strong>ملاحظة</strong>: يمكنك بدلًا مما سبق تخطي التثبيت وتشغيل express-generator باستخدام <a href="https://github.com/npm/npx#readme" rel="external nofollow">npx</a> إن لم تستخدم نسخةً قديمة من nodejs أقل من 8.2.0، فالأمر التالي له التأثير نفسه للتثبيت ثم تشغيل express-generator ولكنه لا يثبّت الحزمة على نظامك:
</p>

<pre class="ipsCode">npx express-generator helloworld
</pre>

<p>
	يمكنك أيضًا تحديد مكتبة القوالب المُراد استخدامها وعدد من الإعدادات الأخرى، لذا استخدم الأمر <code>help</code> لرؤية جميع هذه الخيارات.
</p>

<pre class="ipsCode">express --help
</pre>

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

<p>
	سيحتوي التطبيق الجديد على ملف package.json في مجلده الجذر، ويمكنك فتحه لمعرفة الاعتماديات التي جرى تثبيتها، بما في ذلك Express ومكتبة القوالب Jade:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6164_23" 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">"helloworld"</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.0"</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">"start"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"node ./bin/www"</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">"cookie-parser"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"~1.4.4"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"debug"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"~2.6.9"</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.1"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"http-errors"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"~1.6.3"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"jade"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"~1.11.0"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"morgan"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"~1.9.1"</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	ثبّت جميع الاعتماديات الخاصة بتطبيق helloworld باستخدام npm كما يلي:
</p>

<pre class="ipsCode">cd helloworld
npm install
</pre>

<p>
	شغّل التطبيق (تكون الأوامر مختلفة قليلًا لأنظمة تشغيل ويندوز ولينكس/ماك macOS) كما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6164_26" style=""><span class="com"># شغّل تطبيق‫ helloworld على ويندوز باستخدام موجّه الأوامر</span><span class="pln">
SET DEBUG</span><span class="pun">=</span><span class="pln">helloworld</span><span class="pun">:*</span><span class="pln"> </span><span class="pun">&amp;</span><span class="pln"> npm start

</span><span class="com"># شغّل تطبيق‫ helloworld على ويندوز باستخدام PowerShell</span><span class="pln">
SET DEBUG</span><span class="pun">=</span><span class="pln">helloworld</span><span class="pun">:*</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> npm start

</span><span class="com"># شغّل تطبيق‫ helloworld على لينكس/ماك macOS</span><span class="pln">
DEBUG</span><span class="pun">=</span><span class="pln">helloworld</span><span class="pun">:*</span><span class="pln"> npm start</span></pre>

<p>
	ينشئ الأمر <code>DEBUG</code> تسجيلًا مفيدًا ينتج عنه خرج يشبه الخرج التالي:
</p>

<pre class="ipsCode">&gt;SET DEBUG=helloworld:* &amp; npm start

&gt; helloworld@0.0.0 start D:\GitHub\expresstests\helloworld
&gt; node ./bin/www

  helloworld:server Listening on port 3000 +0ms
</pre>

<p>
	افتح متصفحك وانتقل إلى العنوان <code><a href="http://localhost:3000/%E2%80%8E" ipsnoembed="false" rel="external nofollow">http://localhost:3000/‎</a></code> لمشاهدة صفحة ترحيب Express الافتراضية.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="138633" href="https://academy.hsoub.com/uploads/monthly_2023_11/01_express_default_screen.png.cbf70394bbb040e33a89a18f9860a22c.png" rel=""><img alt="01 express default screen" class="ipsImage ipsImage_thumbnailed" data-fileid="138633" data-unique="ebl7vztgn" src="https://academy.hsoub.com/uploads/monthly_2023_11/01_express_default_screen.png.cbf70394bbb040e33a89a18f9860a22c.png"> </a>
</p>

<p>
	سنتحدث أكثر عن التطبيق المُولَّد عندما ننتقل إلى المقال التالي.
</p>

<h2 id="-6">
	الخلاصة
</h2>

<p>
	لديك الآن بيئة تطوير Node تعمل على حاسوبك والتي يمكن استخدامها لإنشاء تطبيقات ويب Express، وتعرّفت على كيفية استخدام npm لاستيراد Express في تطبيق وكيفية إنشاء تطبيقات باستخدام أداة مولّد تطبيقات Express ثم تشغيلها. نبدأ في المقال التالي العمل باستخدام تطبيق عملي لإنشاء تطبيق ويب كامل باستخدام هذه البيئة والأدوات المرتبطة بها.
</p>

<p>
	ترجمة -وبتصرُّف- للمقال <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment" rel="external nofollow">Setting up a Node development environment</a>.
</p>

<h2 id="-7">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-express-%D9%88%D8%A8%D9%8A%D8%A6%D8%A9-node-r2168/" rel="">مدخل إلى إطار عمل الويب Express وبية Node</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%A3%D9%88%D9%84-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D8%AC-%D9%81%D9%8A-%D8%A8%D9%8A%D8%A6%D8%A9-nodejs-%D9%88%D8%AA%D9%86%D9%81%D9%8A%D8%B0%D9%87-r1711/" rel="">كتابة أول برنامج في بيئة Node.js وتنفيذه</a>
	</li>
	<li>
		<a href="https://wiki.hsoub.com/Node.js" rel="external">توثيق Node.js</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2169</guid><pubDate>Tue, 21 Nov 2023 13:07:02 +0000</pubDate></item><item><title>&#x645;&#x62F;&#x62E;&#x644; &#x625;&#x644;&#x649; &#x625;&#x637;&#x627;&#x631; &#x639;&#x645;&#x644; &#x627;&#x644;&#x648;&#x64A;&#x628; Express &#x648;&#x628;&#x64A;&#x626;&#x629; Node</title><link>https://academy.hsoub.com/programming/javascript/nodejs/express/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-express-%D9%88%D8%A8%D9%8A%D8%A6%D8%A9-node-r2168/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_11/-----Express.png.faf61852d89d88897f6d5ed8a1dc0572.png" /></p>
<p>
	يُعَد <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/" rel="">Express</a> إطار عمل ويب شائع الاستخدام وغير مشتبث برأيه Unopinionated، أي لديه آراء حول الطريقة الصحيحة للتعامل مع أيّ مهمة معينة، ويدعم التطور السريع أو حل المشاكل في مجال معين، ومكتوب بلغة <a href="https://wiki.hsoub.com/JavaScript" rel="external">جافا سكريبت Javascript</a> ومُستضاف في بيئة تشغيل <a href="https://academy.hsoub.com/files/32-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-nodejs/" rel="">Node.js</a>. سنوضح في هذه السلسلة من المقالات المتفرعة عن السلسة الرئيسية <a href="https://academy.hsoub.com/tags/%D8%AA%D8%B9%D9%84%D9%85%20%D8%AA%D8%B7%D9%88%D9%8A%D8%B1%20%D8%A7%D9%84%D9%88%D9%8A%D8%A8/" rel="">تعلم تطوير الويب</a> بعض الفوائد الرئيسية لإطار عمل Express، وكيفية إعداد بيئة التطوير وتطبيق مهام تطوير ونشر الويب الشائعة.
</p>

<h2 id="">
	المتطلبات الأساسية
</h2>

<p>
	يجب قبل البدء التعرف على مفهوم برمجة الويب من طرف الخادم وأطر الويب من خلال الاطلاع على مقال <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D9%88%D8%A7%D9%82%D8%B9-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D9%85%D9%86-%D8%B7%D8%B1%D9%81-%D8%A7%D9%84%D8%AE%D8%A7%D8%AF%D9%85-r783/" rel="">مدخل إلى برمجة مواقع الويب من طرف الخادم</a>. يوصَى بشدة بمعرفة مفاهيم <a href="https://academy.hsoub.com/programming/general/%D9%83%D9%8A%D9%81-%D8%AA%D8%AA%D8%B9%D9%84%D9%85-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%86%D8%B5%D8%A7%D8%A6%D8%AD-%D9%88%D8%A3%D8%AF%D9%88%D8%A7%D8%AA-%D9%84%D8%B1%D8%AD%D9%84%D8%AA%D9%83-%D9%81%D9%8A-%D8%B9%D8%A7%D9%84%D9%85-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-r206/" rel="">البرمجة</a> ولغة <a href="https://academy.hsoub.com/files/27-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA/" rel="">جافا سكريبت</a>، ولكنها ليست ضرورية لفهم المفاهيم الأساسية.
</p>

<p>
	ملاحظة: اطلع على مقال <a href="https://academy.hsoub.com/programming/javascript/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1689/" rel="">أساسيات لغة جافا سكريبت</a> في سياق التطوير من طرف العميل، إذ تعَد لغة ومفاهيم جافا سكريبت الأساسية هي نفسها بالنسبة للتطوير من طرف الخادم في بيئة Node.js. تقدم Node.js <a href="https://wiki.hsoub.com/Node.js" rel="external">واجهات برمجة تطبيقات إضافية</a> لدعم الوظائف المفيدة في البيئات التي لا تستخدم المتصفحات (لإنشاء خوادم HTTP والوصول إلى نظام الملفات مثلًا)، ولكنها لا تدعم واجهات برمجة تطبيقات جافا سكريبت للعمل مع المتصفحات و<a href="https://academy.hsoub.com/programming/javascript/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-dom-r644/" rel="">نموذج DOM</a>. ستوفر هذه السلسلة من المقالات بعض المعلومات حول العمل مع بيئة Node.js وإطار عمل Express.
</p>

<p>
	تتألف هذه السلسلة من المقالات التالية:
</p>

<ul>
	<li>
		مدخل إلى إطار عمل الويب Express: نجيب في هذا المقال على أسئلة "ما هي بيئة Node؟" و"ما هو إطار عمل Express؟"، وسنأخذ فكرةً عامة على ما يجعل إطار عمل Express مميزًا، وسنحدد الميزات الرئيسية وسنعرض بعض البنى الأساسية لتطبيق Express، بالرغم من أنه لن يكون لديك بعد بيئة تطوير لاختبارها في هذه المرحلة.
	</li>
	<li>
		إعداد بيئة تطوير Node (في إطار عمل Express) (هذا المقال): سنوضح في هذا المقال كيفية إعداد واختبار بيئة تطوير Node/Express على أنظمة تشغيل ويندوز ولينكس (أوبنتو) وماك أو اس macOS. يجب أن يوفر لك هذا المقال ما تحتاجه لتتمكن من بدء تطوير تطبيقات Express مهما كان <a href="https://academy.hsoub.com/apps/operating-systems/%D9%86%D8%B8%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84/" rel="">نظام التشغيل</a> الذي تستخدمه.
	</li>
	<li>
		تطبيق عملي لتعلم Express - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية: يشرح المقال الأول من التطبيق العملي لتعلم إطار عمل Express ما ستتعلمه، ويقدم نظرة عامة على مثال موقع "المكتبة المحلية" الذي سنعمل عليه ونطوره في المقالات اللاحقة. يوضح هذا المقال كيفية إنشاء مشروع موقع ويب هيكلي، والذي يمكنك ملؤه لاحقًا بالوجهات Routes والقوالب أو العروض وقواعد البيانات الخاصة بالموقع.
	</li>
	<li>
		تطبيق عملي لتعلم Express - الجزء الثاني: استخدام قاعدة البيانات (باستخدام مكتبة Mongoose): يوضح هذا المقال بإيجاز قواعد بيانات Node/Express، ثم يوضح كيفية استخدام مكتبة <a href="https://mongoosejs.com/" rel="external nofollow">Mongoose</a> لتوفير الوصول إلى قاعدة البيانات لموقع المكتبة المحلية LocalLibrary، ويشرح كيفية التصريح عن مخطط Schema الكائنات والنماذج وأنواع الحقول الرئيسية والتحقق من صحة البيانات الأساسي، ويعرض بإيجاز بعض الوجهات الرئيسية التي يمكنك من خلالها الوصول إلى بيانات النموذج.
	</li>
	<li>
		تطبيق عملي لتعلم Express - الجزء الثالث: الوجهات Routes والمتحكمات Controllers: سنُعِد في هذا المقال الوجهات (شيفرة معالجة عناوين URL) باستخدام دوال معالجة "وهمية dummy" لجميع النقاط النهائية للموارد التي سنحتاجها في موقع LocalLibrary. سيكون لدينا في النهاية بنية معيارية لشيفرة معالجة الوجهات، والتي يمكننا توسيعها باستخدام دوال معالجة حقيقية في المقالات اللاحقة. سنوضح أيضًا كيفية إنشاء وجهات معيارية باستخدام إطار عمل Express.
	</li>
	<li>
		تطبيق عملي لتعلم Express - الجزء الرابع: عرض بيانات المكتبة والعمل مع الاستمارات: سنضيف في هذا المقال الصفحات التي تعرض كتب موقع LocalLibrary وبيانات أخرى، حيث ستتضمن الصفحات صفحة رئيسية توضح عدد السجلات لكل نوع نموذج وصفحات القائمة والصفحات التفصيلية لجميع نماذجنا، وسنكتسب في هذا المقال خبرة عملية في الحصول على السجلات من قاعدة البيانات واستخدام القوالب. سنشرح أيضًا كيفية العمل مع استمارات HTML في إطار عمل Express باستخدام مكتبة Pug، وخاصة كيفية كتابة الاستمارات لإنشاء المستندات وتحديثها وحذفها من قاعدة البيانات.
	</li>
	<li>
		تطبيق عملي لتعلم Express - الجزء الخامس: النشر في بيئة الإنتاج: أنشأت موقع المكتبة المحلية LocalLibrary، ولكنك تريد تثبيته على خادم ويب عام حتى يصل إليه موظفو وأعضاء المكتبة عبر الإنترنت، لذا يقدّم هذا المقال نظرة عامة حول كيفية البحث عن مضيف لنشر موقعك، وما عليك فعله لتجهيز موقعك لمرحلة الإنتاج.
	</li>
</ul>

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

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D8%AF%D9%88%D9%86%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-4-%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D8%AC%D9%84%D8%B3%D8%A7%D8%AA-r22/" rel="">استخدام الجلسات</a>.
	</li>
	<li>
		استيثاق المستخدمين.
	</li>
	<li>
		الترخيص للمستخدمين وأذوناتهم.
	</li>
	<li>
		اختبار تطبيق Express.
	</li>
	<li>
		أمان الويب لتطبيقات Express.
	</li>
</ul>

<p>
	كما سيكون وجود تقييم نهائي إضافةً رائعة.
</p>

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

<ul>
	<li>
		<p>
			<strong>المتطلبات الأساسية</strong>: المهارات الحاسوبية الأساسية، وفهم عام <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D9%88%D8%A7%D9%82%D8%B9-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D9%85%D9%86-%D8%B7%D8%B1%D9%81-%D8%A7%D9%84%D8%AE%D8%A7%D8%AF%D9%85-r783/" rel="">لبرمجة مواقع الويب من طرف الخادم</a> وخاصةً <a href="https://academy.hsoub.com/devops/servers/%D9%86%D8%B8%D8%B1%D8%A9-%D8%B9%D9%84%D9%89-%D8%AA%D9%81%D8%A7%D8%B9%D9%84%D8%A7%D8%AA-%D8%A7%D9%84%D8%AE%D8%A7%D8%AF%D9%85-%D9%85%D8%B9-%D8%A7%D9%84%D8%B9%D9%85%D9%8A%D9%84-%D9%81%D9%8A-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D8%AF%D9%8A%D9%86%D8%A7%D9%85%D9%8A%D9%83%D9%8A-r782/" rel="">تفاعلات الخادم مع العميل في مواقع الويب</a>.
		</p>
	</li>
	<li>
		<p>
			<strong>الهدف:</strong> التعرف على إطار عمل Express وكيفية ملاءمته مع بيئة Node، والوظائف التي يوفرها، والمكونات الأساسية لتطبيق Express.
		</p>
	</li>
</ul>

<h2 id="node">
	مدخل إلى بيئة Node
</h2>

<p>
	تُعَد Node -أو 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>

<p>
	تتمتع بيئة Node بعدد من الفوائد من منظور تطوير خادم الويب، وهي:
</p>

<ul>
	<li>
		أداء رائع: صُمِّمت بيئة Node لتحسين الإنتاجية وقابلية التوسع في تطبيقات الويب، وتُعَد حلًا جيدًا للعديد من مشاكل تطوير الويب الشائعة، مثل تطبيقات الويب في الوقت الفعلي.
	</li>
	<li>
		تُكتَب الشيفرة البرمجية باستخدام لغة جافا سكريبت قديمة وبسيطة، مما يقلل الوقت في التعامل مع "تغيير السياق" بين اللغات عند كتابة الشيفرة البرمجية من طرف العميل والخادم.
	</li>
	<li>
		تُعَد لغة جافا سكريبت لغة برمجة جديدة نسبيًا وتستفيد من التحسينات في تصميم اللغة عند مقارنتها مع لغات خادم الويب التقليدية الأخرى، مثل <a href="https://academy.hsoub.com/programming/python/" rel="">بايثون</a> و <a href="https://academy.hsoub.com/programming/php/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%84%D8%BA%D8%A9-php-r2040/" rel="">PHP</a> وغير ذلك. تُصرَّف أو تُحوَّل العديد من اللغات الجديدة والشائعة إلى لغة جافا سكريبت حتى يمكنك استخدام لغات TypeScript و CoffeeScript و ClojureScript و Scala و LiveScript وغيرها.
	</li>
	<li>
		يوفر مدير حزم Node -أو اختصارًا npm- الوصول إلى مئات الألوف من الحزم القابلة لإعادة الاستخدام، ولديه التحليل Resolution الأفضل للاعتماديات Dependency Resolution، ويمكن استخدامه لأتمتة معظم سلسلة أدوات البناء.
	</li>
	<li>
		تُعَد بيئة Node.js قابلة للنقل، فهي متوفرة على أنظمة تشغيل مايكروسوفت ويندوز وماك أو اس macOS ولينكس و Solaris و FreeBSD و OpenBSD و WebOS و NonStop OS، ويدعمها العديد من مزودي خدمات استضافة الويب الذين يوفرون في أغلب الأحيان بنية تحتية وتوثيقًا محددًا لاستضافة مواقع Node.
	</li>
	<li>
		تمتلك نظام بيئي ومجتمع مطورين خارجي نشط جدًا بوجود الكثير من الأشخاص المستعدين للمساعدة.
	</li>
</ul>

<p>
	يمكنك استخدام بيئة ب Node.js لإنشاء خادم ويب بسيط باستخدام حزمة Node HTTP.
</p>

<h3 id="nodejs">
	مرحبا Node.js
</h3>

<p>
	ينشئ المثال التالي خادم ويب يستمع إلى أيّ نوع من طلبات HTTP على عنوان URL هو <code>http://127.0.0.1:8000/‎</code>، إذ سيستجيب السكريبت بالسلسلة النصية "Hello World" عند تلقي طلبٍ ما. إذا كانت بيئة Node مثبَّتةً لديك مسبقًا، فيمكنك اتباع الخطوات التالية لتجربة مثالنا:
</p>

<p>
	أولًا، افتح الطرفية، أو افتح الأداة المساعدة لسطر الأوامر على نظام ويندوز.
</p>

<p>
	ثانيًا، أنشئ المجلد الذي تريد حفظ البرنامج فيه مثل المجلد "test-node"، ثم انتقل إليه من خلال إدخال الأمر التالي في الطرفية:
</p>

<pre class="ipsCode">cd test-node
</pre>

<p>
	ثالثًا، أنشئ ملفًا بالاسم "hello.js" وضع فيه الشيفرة البرمجية التالية باستخدام <a href="https://academy.hsoub.com/programming/workflow/%D9%85%D8%AD%D8%B1%D8%B1%D8%A7%D8%AA-%D8%A7%D9%84%D9%86%D8%B5%D9%88%D8%B5-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%B9%D9%85%D9%84%D8%A9-%D9%81%D9%8A-%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D9%85%D9%88%D8%A7%D9%82%D8%B9-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r1438/" rel="">محرر النصوص</a> المفضل لديك:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4130_9" style=""><span class="com">// ‫حمّل وحدة HTTP</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"> 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"> port </span><span class="pun">=</span><span class="pln"> </span><span class="lit">8000</span><span class="pun">;</span><span class="pln">

</span><span class="com">// أنشئ خادم‫ HTTP</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="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"> </span><span class="pun">{</span><span class="pln">
  </span><span class="com">// ‫اضبط ترويسة استجابة HTTP بحالة HTTP ونوع المحتوى</span><span class="pln">
  res</span><span class="pun">.</span><span class="pln">writeHead</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"> </span><span class="str">"Content-Type"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"text/plain"</span><span class="pln"> </span><span class="pun">});</span><span class="pln">

  </span><span class="com">// أرسل متن الاستجابة‫ "Hello World"</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">

</span><span class="com">// اطبع سجلًا بمجرد أن يبدأ الخادم بالاستماع</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="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">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></pre>

<p>
	رابعًا، احفظ الملف في المجلد الذي أنشأته سابقًا.
</p>

<p>
	خامسًا، ارجع إلى الطرفية واكتب الأمر التالي:
</p>

<pre class="ipsCode">node hello.js
</pre>

<p>
	أخيرًا، انتقل إلى العنوان <code><a href="http://localhost:8000" ipsnoembed="false" rel="external nofollow">http://localhost:8000</a></code> في متصفح الويب، إذ يجب أن ترى النص "Hello World" في الجزء العلوي الأيسر من صفحة ويب أخرى فارغة.
</p>

<h3 id="-1">
	أطر عمل الويب
</h3>

<p>
	لا تدعم بيئة Node مهام تطوير الويب الشائعة الأخرى مباشرةً بنفسها، فإذا أدرتَ إضافة معالجة محددة لأفعال HTTP المختلفة، مثل <code>GET</code> و <code>POST</code> و <code>DELETE</code> وغير ذلك، فعالج الطلبات بصورة منفصلة في مسارات URL المختلفة ("الوجهات Routes")، أو خدّم الملفات الثابتة، أو استخدم <a href="https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r1737/" rel="">القوالب Templates</a> لإنشاء الاستجابة ديناميكيًا، إذ لن تكون بيئة Node ذات فائدة كبيرة من تلقاء نفسها. يجب عليك إما كتابة الشيفرة البرمجية بنفسك، أو يمكنك تجنب إعادة اختراع العجلة واستخدام إطار عمل ويب.
</p>

<h2 id="express">
	مقدمة إلى إطار عمل Express
</h2>

<p>
	يُعَد Express إطار عمل الويب الخاص ببيئة Node الأكثر شيوعًا، وهو المكتبة الأساسية لعدد من أطر عمل الويب الخاصة ببيئة Node الشائعة الأخرى، ويوفر آليات بهدف:
</p>

<ul>
	<li>
		كتابة معالجات للطلبات ذات أفعال HTTP مختلفة في وجهات URL المختلفة.
	</li>
	<li>
		التكامل مع محرّكات تقديم "العرض View" لتوليد استجابات من خلال إدخال بيانات في القوالب.
	</li>
	<li>
		ضبط إعدادات تطبيقات الويب الشائعة مثل المنفذ المُستخدَم للاتصال وموقع القوالب المستخدمة لتقديم الاستجابة.
	</li>
	<li>
		إضافة برمجيات وسيطة middleware لمعالجة الطلبات الإضافية في أيّ وقت ضمن خط معالجة الطلبات.
	</li>
</ul>

<p>
	يُعَد إطار عمل Express بسيط إلى حدٍ ما، ولكن أنشأ المطورون حزمًا وسيطة متوافقة لمعالجة أيّ مشكلة في تطوير الويب تقريبًا، وتوجد مكتبات للعمل مع ملفات تعريف الارتباط والجلسات وتسجيل دخول المستخدمين ومعاملات عنوان URL وبيانات <code>POST</code> وترويسات الأمان وغير ذلك الكثير. اطلع على <a href="https://expressjs.com/en/resources/middleware.html" rel="external nofollow">قائمة الحزم الوسيطة</a> التي يهتم بها فريق Express مع قائمة ببعض الحزم الخارجية الشائعة.
</p>

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

<h3 id="nodeexpress">
	تاريخ بيئة Node وإطار عمل Express
</h3>

<p>
	أُصدِرت بيئة Node في البداية لنظام لينكس فقط في عام 2009، وأُصدِر <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</a> في عام 2010، وأُضيف دعم ويندوز الأصلي في عام 2012.
</p>

<p>
	أُصدِر إطار عمل Express في البداية في الشهر 11 من عام 2010 وهو حاليًا في الإصدار الرئيسي الرابع من <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>، ويمكنك التحقق من <a href="https://expressjs.com/en/changelog/4x.html" rel="external nofollow">سجل التغييرات</a> للحصول على معلومات حول التغييرات في الإصدار الحالي و <a href="https://github.com/expressjs/express/blob/master/History.md" rel="external nofollow">GitHub</a> للحصول على ملاحظات الإصدارات التاريخية الأكثر تفصيلًا.
</p>

<h3 id="nodeexpress-1">
	ما مدى شعبية Node و Express؟
</h3>

<p>
	تُعد شعبية إطار عمل الويب أمرًا مهمًا لأنه مؤشر على استمرار صيانته والموارد التي يُحتمَل أن تكون متاحة من حيث التوثيق والمكتبات الإضافية والدعم الفني.
</p>

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

<h3 id="expressopinionated">
	هل Express إطار عمل قائم على رأيه Opinionated؟
</h3>

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

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

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

<h3 id="express-1">
	كيف تبدو شيفرة Express البرمجية؟
</h3>

<p>
	ينتظر تطبيق الويب طلبات HTTP من متصفح الويب (أو عميل آخر) في موقع ويب تقليدي مُوجَّه بالبيانات. يعمل التطبيق عند تلقي طلب على تحديد الإجراء المطلوب بناءً على نمط عنوان URL وربما على المعلومات المرتبطة به الواردة في بيانات <code>POST</code> أو بيانات <code>GET</code>، ثم يمكنه بعد ذلك -اعتمادًا على ما هو مطلوب- قراءة المعلومات أو كتابتها في قاعدة بيانات أو أداء مهام أخرى مطلوبة لتلبية الطلب، ثم سيعيد التطبيق استجابةً إلى متصفح الويب، وينشئ غالبًا صفحة <a href="https://academy.hsoub.com/programming/html/" rel="">HTML</a> ديناميكيًا ليعرضها المتصفح من خلال إدخال البيانات المُسترجَعة ضمن عناصر بديلة في قالب HTML.
</p>

<p>
	يوفر Express توابعًا لتحديد الدالة المستدعاة لفعل HTTP معين، مثل <code>GET</code> و <code>POST</code> و <code>SET</code> وغير ذلك ونمط عنوان URL ("الوِجهة Route")، وتوابعًا لتحديد محرّك القالب ("العرض View") المستخدَم، ومكان وجود ملفات القالب، والقالب الذي يجب استخدامه لتقديم الاستجابة. يمكنك استخدام برمجيات Express الوسيطة لإضافة الدعم لملفات تعريف الارتباط والجلسات والمستخدمين والحصول على معاملات <code>POST</code> / <code>GET</code> وغير ذلك، ويمكنك استخدام أيّ آلية قاعدة بيانات تدعمها بيئة Node، إذ لا يحدد إطار عمل Express أيّ سلوك متعلق بقاعدة البيانات.
</p>

<p>
	سنشرح في الأقسام التالية بعض الأشياء التي ستراها عند العمل باستخدام شيفرة Express و Node.
</p>

<h4 id="helloworldexpress">
	مثال لطباعة مرحبا بالعالم Helloworld باستخدام إطار عمل Express
</h4>

<p>
	أولًا، ضع في بالك مثال <a href="https://expressjs.com/en/starter/hello-world.html" rel="external nofollow">مرحبًا بالعالم</a> المعياري، إذ سنناقش كل جزء منه فيما يلي وفي الأقسام التالية.
</p>

<p>
	<strong>ملاحظة</strong>: إذا كان لديك بيئة Node وإطار عمل Express مُثبَّتين مسبقًا (أو إذا ثبّتهما كما هو موضح في المقال التالي)، فيمكنك حفظ الشيفرة البرمجية التالية في ملف نصي بالاسم "app.js" وتشغيله في موجه أوامر <a href="https://academy.hsoub.com/devops/linux/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%B5%D8%AF%D9%81%D8%A9-%D8%A8%D8%A7%D8%B4-bash-r606/" rel="">باش Bash</a> من خلال استدعاء الأمر: <code>node ./app.js</code>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4130_11" 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">
</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="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="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"> </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">"Hello World!"</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 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>
	يطلب السطران الأوليان باستخدام <code>require()‎</code> الوحدةَ express وينشئان تطبيق Express. يحتوي هذا الكائن- الذي يُطلَق عليه تقليديًا الاسم <code>app</code>- على توابع لتوجيه طلبات HTTP وضبط البرمجيات الوسيطة وتصيير عروض HTML وتسجيل محرك القوالب وتعديل إعدادات التطبيق التي تتحكم في سلوك التطبيق، مثل وضع البيئة وما إذا كانت تعريفات الوجهة حساسة لحالة الأحرف وغير ذلك.
</p>

<p>
	يعرض الجزء الأوسط من الشيفرة (الأسطر الثلاثة التي تبدأ بالتابع <code>app.get</code>) تعريف الوجهة، إذ يحدّد التابع <code>app.get()‎</code> دالة رد النداء callback المُستدعاة عندما يكون هناك طلب HTTP من النوع <code>GET</code> له المسار (<code>/</code>) المتعلق بجذر الموقع. تأخذ دالة رد النداء طلبًا وكائن استجابة بوصفهما وسيطين، وتستدعي التابع <code>send()‎</code> للاستجابة لإعادة السلسلة النصية "Hello World!‎".
</p>

<p>
	تشغّل الكتلة النهائية الخادم على منفذ محدد ("3000") وتطبع تعليق سجل في الطرفية، ويمكنك مع تشغيل الخادم الانتقال إلى <code>localhost:3000</code> في متصفحك لترى مثال الاستجابة المُعادة.
</p>

<h4 id="-2">
	استيراد وإنشاء الوحدات
</h4>

<p>
	<a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D9%88%D8%AD%D8%AF%D8%A7%D8%AA-modules-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1286/" rel="">الوحدة Module</a> هي مكتبة أو ملف جافا سكريبت يمكنك استيراده في شيفرة برمجية أخرى باستخدام دالة <code>require()‎</code> الخاصة ببيئة Node، إذ يُعَد إطار عمل Express بحد ذاته وحدةً مثل مكتبات البرمجيات الوسيطة وقواعد البيانات التي نستخدمها في تطبيقات Express.
</p>

<p>
	توضح الشيفرة البرمجية التالية كيفية استيراد وحدة باسمها باستخدام إطار عمل Express، إذ نستدعي أولًا الدالة <code>require()‎</code> من خلال تحديد اسم الوحدة بوصفها سلسلة نصية (<code>'express'</code>)، ثم نستدعي الكائن المُعاد لإنشاء <a href="https://expressjs.com/en/4x/api.html#app" rel="external nofollow">تطبيق Express</a>، ويمكننا بعد ذلك الوصول إلى خاصيات ودوال كائن التطبيق.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4130_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"> app </span><span class="pun">=</span><span class="pln"> express</span><span class="pun">();</span></pre>

<p>
	يمكنك أيضًا إنشاء وحداتك الخاصة التي يمكن استيرادها بالطريقة نفسها.
</p>

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

<p>
	يمكن جعل الكائنات متاحةً خارج الوحدة من خلال جعلها خاصيات إضافية في الكائن <code>exports</code>، فمثلًا تُعَد الوحدة square.js التالية ملفًا يصدّر التوابع <code>area()‎</code> و <code>perimeter()‎</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4130_15" style=""><span class="pln">exports</span><span class="pun">.</span><span class="pln">area </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">width</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"> width </span><span class="pun">*</span><span class="pln"> width</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">perimeter </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">width</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="lit">4</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> width</span><span class="pun">;</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	يمكننا استيراد هذه الوحدة باستخدام الدالة <code>require()‎</code>، ثم استدعاء التابع (أو التوابع) المُصدَّرة كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4130_17" style=""><span class="kwd">const</span><span class="pln"> square </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"./square"</span><span class="pun">);</span><span class="pln"> </span><span class="com">// ‫نطلب هنا باستخدام الدالة require()‎ اسم الملف بدون لاحقة الملف ‎.js (الاختيارية)</span><span class="pln">
console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(`</span><span class="typ">The</span><span class="pln"> area </span><span class="kwd">of</span><span class="pln"> a square </span><span class="kwd">with</span><span class="pln"> a width </span><span class="kwd">of</span><span class="pln"> </span><span class="lit">4</span><span class="pln"> is $</span><span class="pun">{</span><span class="pln">square</span><span class="pun">.</span><span class="pln">area</span><span class="pun">(</span><span class="lit">4</span><span class="pun">)}`);</span></pre>

<p>
	<strong>ملاحظة</strong>: يمكنك أيضًا تحديد مسار مطلق، (أو اسم ما كما فعلنا في البداية) للوحدة.
</p>

<p>
	إذا أردتَ تصدير كائن كامل في مهمة واحدة بدلًا من بناء خاصيةً واحدة في كل مرة، فأسنده إلى <code>module.exports</code> كما يلي، ويمكنك إجراء ذلك لجعل جذر كائن exports <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%A8%D8%A7%D9%86%D9%8A-%D9%88%D8%A7%D9%84%D8%B9%D8%A7%D9%85%D9%84-new-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r801/" rel="">بانيًا Constructor</a> أو دالة أخرى:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4130_19" 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">
  area</span><span class="pun">(</span><span class="pln">width</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"> width </span><span class="pun">*</span><span class="pln"> width</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">

  perimeter</span><span class="pun">(</span><span class="pln">width</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="lit">4</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> width</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>exports</code> بوصفه اختصارًا للمصطلح <code>module.exports</code> ضمن وحدة معينة، ولكن يُعَد <code>exports</code> مجرد متغير مهيَّأ بقيمة <code>module.exports</code> قبل تقييم الوحدة، وهذه القيمة هي مرجع إلى كائن (كائن فارغ في هذه الحالة)، وهذا يعني أن <code>exports</code> يحتوي على مرجع للكائن نفسه الذي يشير إليه <code>module.exports</code>، مما يعني أيضًا أن <code>exports</code> لم يعد مرتبطًا بالكائن <code>module.exports</code> من خلال إسناد قيمة أخرى إلى <code>exports</code>.
</p>

<p>
	اطلع على مقال <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> و<a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%88%D8%AD%D8%AF%D8%A7%D8%AA-%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-modules-%D9%81%D9%8A-nodejs-r1742/" rel="">إنشاء وحدات برمجية Modules في Node.js</a> و<a href="https://wiki.hsoub.com/Node.js/Topics" rel="external">الوحدات Modules</a> في توثيق Node لمزيد من المعلومات.
</p>

<h4 id="-3">
	استخدام واجهات برمجة التطبيقات غير المتزامنة
</h4>

<p>
	تستخدم شيفرة جافا سكريبت في أغلب الأحيان واجهات برمجة تطبيقات غير متزامنة بدلًا من واجهات برمجة تطبيقات متزامنة للعمليات التي يمكن أن تستغرق بعض الوقت لتكتمل، حيث تُعَد واجهة برمجة التطبيقات المتزامنة واجهة يجب أن تكتمل فيها العملية قبل أن تبدأ العملية التي تليها مثل دوال log المتزامنة التالية التي ستطبع النص إلى الطرفية بالترتيب (First, Second):
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4130_21" style=""><span class="pln">console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"First"</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">"Second"</span><span class="pun">);</span></pre>

<p>
	بينما تبدأ واجهة برمجة التطبيقات غير المتزامنة عمليةً وتُعاد مباشرةً قبل اكتمال العملية، وستستخدم واجهة برمجة التطبيقات <abbr title="Application Programming Interface | واجهة برمجية">API</abbr> بعض الآليات لإجراء عمليات إضافية بمجرد انتهاء العملية، فمثلًا ستطبع الشيفرة التالية "Second, First" لأنه العملية لا تكتمل إلا بعد عدة ثوانٍ بالرغم من استدعاء التابع <code>setTimeout()‎</code> أولًا والإعادة مباشرةً.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4130_23" style=""><span class="pln">setTimeout</span><span class="pun">(</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">"First"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">},</span><span class="pln"> </span><span class="lit">3000</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">"Second"</span><span class="pun">);</span></pre>

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

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

<p>
	<strong>ملاحظة</strong>: يمكن أن يكون استخدام دوال رد النداء "فوضويًا" إذا كان لديك سلسلة من العمليات غير المتزامنة الاعتمادية التي يجب إجراؤها بالترتيب لأن ذلك ينتج عنه مستويات متعددة من دوال رد النداء المتداخلة. تُعرف هذه المشكلة عمومًا باسم "جحيم دوال رد النداء Callback Hell"، ويمكن تقليل هذه المشكلة من خلال ممارسات كتابة الشيفرة البرمجية الجيدة باستخدام وحدةٍ مثل الوحدة async، أو إعادة إنتاج الشيفرة البرمجية إلى ميزات جافا سكريبت أصيلة مثل الوعود Promises واللاتزامن والانتظار async/await، حيث توفّر بيئة Node الدالة <code>utils.promisify</code> لتحويل دالة رد النداء إلى وعدٍ بطريقة مريحة.
</p>

<p>
	<strong>ملاحظة</strong>: من الأمور الشائعة لكلٍّ من Node و Express استخدام دوال رد النداء ذات قيمة الخطأ أولًا، حيث تكون القيمة الأولى في دوال رد النداء قيمة خطأ، بينما تحتوي الوسائط اللاحقة على بيانات نجاح.
</p>

<h4 id="routehandlers">
	إنشاء معالجات الوجهة Route Handlers
</h4>

<p>
	عرّفنا في مثال مرحبًا بالعالم باستخدام إطار عمل Express السابق دالة معالجة الوجهة (دالة رد لنداء) لطلبات HTTP من النوع <code>GET</code> إلى جذر الموقع ('/').
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4130_25" 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="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"> </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">"Hello World!"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	تأخذ دالة رد النداء كائني طلب واستجابة بوصفهما وسطاء، وتستدعي التابع <code>send()‎</code> في هذه الحالة للاستجابة لإعادة السلسلة النصية "Hello World!‎". هناك عدد من <a href="https://expressjs.com/en/guide/routing.html#response-methods" rel="external nofollow">توابع الاستجابة الأخرى</a> لإنهاء دورة الطلب/الاستجابة، فمثلًا يمكنك استدعاء التابع <code>res.json()‎</code> لإرسال <a href="https://wiki.hsoub.com/Next.js/api_routes#.D8.A5.D8.B1.D8.B3.D8.A7.D9.84_.D8.A7.D8.B3.D8.AA.D8.AC.D8.A7.D8.A8.D8.A9_.D8.A8.D8.AA.D9.86.D8.B3.D9.8A.D9.82_JSON" rel="external">استجابة بتنسيق JSON</a> أو التابع <code>res.sendFile()‎</code> لإرسال ملف.
</p>

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

<p>
	يوفّر كائن تطبيق Express توابعًا لتعريف معالجات الوجهة لجميع أفعال HTTP الأخرى، والتي تُستخدَم غالبًا بالطريقة نفسها تمامًا وهي:
</p>

<pre class="ipsCode" id="ips_uid_4130_27">checkout(), copy(), delete(), get(), head(), lock(), merge(), mkactivity(), mkcol(), move(), m-search(), notify(), options(), patch(), post(), purge(), put(), report(), search(), subscribe(), trace(), unlock(), unsubscribe()</pre>

<p>
	يوجد تابع توجيه خاص هو <code>app.all()‎</code> يُستدعَى في استجابة أيّ تابع HTTP، ويُستخدَم هذا التابع لتحميل دوال وسيطة في مسار معين لجميع توابع الطلب. يوضّح المثال التالي (من توثيق Express) معالجًا يُنفَّذ لطلبات القسم <code>‎/secret</code> بغض النظر عن فعل HTTP المُستخدَم (بشرط أن تدعمه <a href="https://wiki.hsoub.com/Node.js/http" rel="external">وحدة http</a>).
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4130_29" style=""><span class="pln">app</span><span class="pun">.</span><span class="pln">all</span><span class="pun">(</span><span class="str">"/secret"</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">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">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Accessing the secret section…"</span><span class="pun">);</span><span class="pln">
  next</span><span class="pun">();</span><span class="pln"> </span><span class="com">// تمرير التحكم إلى المعالج التالي</span><span class="pln">
</span><span class="pun">});</span></pre>

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

<p>
	من المفيد تجميع معالجات الوجهة لجزء معين من الموقع مع بعضها بعضًا والوصول إليها باستخدام بادئة وجهة مشتركة، فمثلًا يحتوي موقعٌ له خاصية ويكي Wiki على جميع الوجهات المتعلقة بها في ملف واحد ويمكن الوصول إليها باستخدام بادئة وجهة هي "/wiki/"، ويمكن تحقيق ذلك في إطار عمل Express باستخدام كائن <code>express.Router</code>. يمكننا مثلًا إنشاء وجهة wiki في وحدة بالاسم wiki.js ثم تصدير كائن <code>Router</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4130_31" style=""><span class="com">// ‫wiki.js - وحدة وجهة‫ Wiki</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"> router </span><span class="pun">=</span><span class="pln"> 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">
router</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="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"> </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">"Wiki home page"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="com">// ‫وجهة الصفحة About</span><span class="pln">
router</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"/about"</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">req</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">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"About this wiki"</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"> router</span><span class="pun">;</span></pre>

<p>
	<strong>ملاحظة</strong>: تشبه إضافة الوجهات إلى الكائن <code>Router</code> إضافةَ الوجهات إلى الكائن <code>app</code> كما هو موضح سابقًا.
</p>

<p>
	سنطلب باستخدام الدالة <code>require()‎</code> وحدة الوجهة (wiki.js) لاستخدام الموجّه في ملف تطبيقنا الرئيسي، ثم نستدعي التابع <code>use()‎</code> في تطبيق Express لإضافة الكائن Router إلى مسار المعالجة الوسيطة، ويمكن بعد ذلك الوصول إلى الوجهتين من <code>/wiki/</code> و <code>/wiki/about/</code>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4130_33" style=""><span class="kwd">const</span><span class="pln"> wiki </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"./wiki.js"</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">"/wiki"</span><span class="pun">,</span><span class="pln"> wiki</span><span class="pun">);</span></pre>

<p>
	اطلع على مقال الموجّهات والمتحكمات لمزيد من التفاصيل حول العمل مع الوجهات وخاصة استخدام الكائن <code>Router</code>.
</p>

<h4 id="-4">
	استخدام البرمجيات الوسيطة
</h4>

<p>
	تُستخدَم البرمجيات الوسيطة على نطاق واسع في تطبيقات Express -للمهام بدءًا تخديم الملفات الثابتة إلى معالجة الأخطاء- لضغط استجابات HTTP. تنهي دوال الوجهات دورة طلب واستجابة HTTP من خلال إعادة الاستجابة لعميل HTTP، ولكن تجري الدوال الوسيطة بعض العمليات على الطلب أو الاستجابة ثم تستدعي الدالة التالية في المكدس stack، والتي يمكن أن تكون برمجية وسيطة أو معالج وجهة، ويعود ترتيب استدعاء البرمجيات الوسيطة إلى مطور التطبيق.
</p>

<p>
	<strong>ملاحظة</strong>: يمكن للبرمجيات الوسيطة إجراء أيّ عملية وتنفيذ أيّ شيفرة برمجية وإجراء تغييرات على كائن الطلب والاستجابة، ويمكنها إنهاء دورة الطلب والاستجابة. إذا لم تنتهِ الدورة، فيجب استدعاء التابع <code>next()‎</code> لتمرير التحكم إلى الدالة الوسيطة التالية، أو سيُترَك الطلب مُعلَّقًا.
</p>

<p>
	تستخدم معظم التطبيقات برمجيات وسيطة خارجية لتبسيط مهام تطوير الويب الشائعة، مثل العمل مع <a href="https://academy.hsoub.com/programming/javascript/%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D8%AA%D8%B9%D8%B1%D9%8A%D9%81-%D8%A7%D9%84%D8%A7%D8%B1%D8%AA%D8%A8%D8%A7%D8%B7-%D9%88%D8%B6%D8%A8%D8%B7%D9%87%D8%A7-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1330/" rel="">ملفات تعريف الارتباط cookies</a> والجلسات واستيثاق المستخدمين والوصول إلى طلب <code>POST</code> وبيانات جسون JSON والتسجيل وغير ذلك. يمكنك العثور على <a href="https://expressjs.com/en/resources/middleware.html" rel="external nofollow">قائمة بحزم البرمجيات الوسيطة التي يهتم بها فريق Express</a>، والتي تتضمن أيضًا الحزم الخارجية الشائعة الأخرى، وتتوفر حزم Express الأخرى في مدير الحزم npm.
</p>

<p>
	يجب تثبيت برمجية وسيطة خارجية في تطبيقك باستخدام مدير الحزم npm لاستخدامها، فمثلًا يمكن تثبيت البرمجية الوسيطة morgan لتسجيل طلبات HTTP باستخدام الأمر التالي:
</p>

<pre class="ipsCode">npm install morgan
</pre>

<p>
	يمكنك بعد ذلك استدعاء التابع <code>use()‎</code> في كائن تطبيق Express لإضافة البرمجيات الوسيطة إلى المكدس:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4130_35" 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"> logger </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"morgan"</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="pln">use</span><span class="pun">(</span><span class="pln">logger</span><span class="pun">(</span><span class="str">"dev"</span><span class="pun">));</span><span class="pln">
</span><span class="com">// …</span></pre>

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

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

<p>
	يمكنك إضافة دالة وسيطة إلى سلسلة المعالجة لجميع الاستجابات باستخدام التابع <code>app.use()‎</code>، أو لفعل HTTP محدَّد باستخدام التابع المرتبط به مثل: <code>app.get()‎</code> و <code>app.post()‎</code> وغير ذلك، وتُحدَّد الوجهة بالطريقة نفسها لكلتا الحالتين، بالرغم من أن الوجهة اختيارية عند استدعاء التابع <code>app.use()‎</code>.
</p>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4130_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">

</span><span class="com">// مثال على دالة وسيطة</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> a_middleware_function </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">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">
  </span><span class="com">// إجراء بعض العمليات</span><span class="pln">
  next</span><span class="pun">();</span><span class="pln"> </span><span class="com">// استدعاء‫ next()‎ ليستدعي Express الدالة الوسيطة التالية في السلسلة</span><span class="pln">
</span><span class="pun">};</span><span class="pln">

</span><span class="com">// ‫الدالة المُضافة باستخدام التابع use()‎ لجميع الوجهات والأفعال</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">a_middleware_function</span><span class="pun">);</span><span class="pln">

</span><span class="com">// الدالة المُضافة باستخدام التابع‫ use()‎ لوجهة محددة</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="str">"/someroute"</span><span class="pun">,</span><span class="pln"> a_middleware_function</span><span class="pun">);</span><span class="pln">

</span><span class="com">// الدالة الوسيطة المُضافة لفعل‫ HTTP ووجهة محددة</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"> a_middleware_function</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></pre>

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

<h4 id="-5">
	تخديم الملفات الثابتة
</h4>

<p>
	يمكنك استخدام البرمجية الوسيطة express.static لتخديم الملفات الثابتة بما في ذلك الصور و<a href="https://academy.hsoub.com/programming/html/html-%D9%88-css-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86-%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%AA%D9%86%D8%B3%D9%8A%D9%82%D8%A7%D8%AA-css-r272/" rel="">ملفات CSS</a> وجافا سكريبت، وتُعَد <code>static()‎</code> الدالة الوسيطة الوحيدة التي هي جزء من إطار عمل Express. يمكنك مثلًا استخدام السطر التالي لتخديم الصور وملفات CSS وجافا سكريبت من مجلد بالاسم public في المستوى نفسه الذي تستدعيه فيه Node:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4130_39" 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">"public"</span><span class="pun">));</span></pre>

<p>
	تُخدَّم الملفات في المجلد public من خلال إضافة اسم هذه الملفات (بالنسبة إلى المجلد "public" الأساسي) إلى عنوان URL الأساسي كما في الأمثلة التالية:
</p>

<pre class="ipsCode">http://localhost:3000/images/dog.jpg
http://localhost:3000/css/style.css
http://localhost:3000/js/app.js
http://localhost:3000/about.html
</pre>

<p>
	يمكنك استدعاء الدالة <code>static()‎</code> عدة مرات لتخديم مجلدات متعددة، بحيث إذا لم تعثر دالة وسيطة على ملفٍ ما، فسيُمرَّر إلى البرمجية الوسيطة التالية، إذ يعتمد ترتيب استدعاء البرمجيات الوسيطة على ترتيب تصريحها.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4130_42" 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">"public"</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">"media"</span><span class="pun">));</span></pre>

<p>
	يمكنك أيضًا إنشاء بادئة افتراضية لعناوين URL الثابتة الخاصة بك بدلًا من إضافة الملفات إلى عنوان URL الأساسي، فمثلًا نحدد فيما يلي مسار ربط Mount Path بحيث تُحمَّل الملفات باستخدام البادئة "‎/media":
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4130_44" style=""><span class="pln">app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="str">"/media"</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></pre>

<p>
	يمكنك الآن تحميل الملفات الموجودة في المجلد public من بادئة المسار <code>‎/media</code>:
</p>

<pre class="ipsCode">http://localhost:3000/media/images/dog.jpg
http://localhost:3000/media/video/cat.mp4
http://localhost:3000/media/cry.mp3
</pre>

<h4 id="-6">
	معالجة الأخطاء
</h4>

<p>
	تُعالَج الأخطاء باستخدام دالة وسيطة خاصة واحدة أو أكثر لها أربعة وسائط بدلًا من الوسائط الثلاثة المعتادة: <code>(err, req, res, next)</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4130_46" style=""><span class="pln">app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</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"> 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">
  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">stack</span><span class="pun">);</span><span class="pln">
  res</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">send</span><span class="pun">(</span><span class="str">"Something broke!"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	تعيد الشيفرة السابقة أيّ محتوًى مطلوب، ولكن يجب استدعاؤها بعد جميع توابع <code>app.use()‎</code> واستدعاءات الوجهات الأخرى، بحيث تكون آخر برمجية وسيطة في عملية معالجة الطلب.
</p>

<p>
	يحتوي إطار عمل Express على معالج أخطاء مبني مسبقًا، والذي يهتم بأيّ أخطاء متبقية يمكن أن تواجهها في التطبيق، وتُضاف دالة معالجة الأخطاء الافتراضية هذه في نهاية مكدس الدوال الوسيطة. إذا مرّرتَ خطأً إلى الدالة <code>next()‎</code> ولم تعالجه في معالج الأخطاء، فسيعالجه معالج الأخطاء المبني مسبقًا، إذ سيُكتَب الخطأ إلى العميل باستخدام متعقّب المكدس stack trace.
</p>

<p>
	لا يُضمَّن متعقّب المكدس في بيئة الإنتاج، لذا يجب ضبط متغير البيئة NODE_ENV على القيمة <code>'production'</code> لتشغيله في وضع الإنتاج.
</p>

<p>
	<strong>ملاحظة</strong>: لا يجري التعامل مع HTTP404 ورموز حالة الخطأ الأخرى بوصفها أخطاء، فإذا أردتَ معالجتها، فيمكنك إضافة دالة وسيطة لذلك.
</p>

<h4 id="-7">
	استخدام قواعد البيانات
</h4>

<p>
	يمكن لتطبيقات Express استخدام أيّ آلية قاعدة بيانات تدعمها بيئة Node، ولا يعرّف Express أيّ سلوك أو متطلبات إضافية محددة لإدارة قاعدة البيانات، إذ هناك العديد من الخيارات لاستخدامها مثل <a href="https://academy.hsoub.com/devops/servers/databases/postgresql/" rel="">PostgreSQL</a> و <a href="https://academy.hsoub.com/devops/servers/databases/mysql/" rel="">MySQL</a> و <a href="https://academy.hsoub.com/devops/servers/databases/redis/" rel="">Redis</a> و <a href="https://academy.hsoub.com/devops/servers/databases/%D9%83%D9%8A%D9%81-%D9%88%D9%85%D8%AA%D9%89-%D9%86%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-sqlite-r111/" rel="">SQLite</a> و <a href="https://academy.hsoub.com/devops/servers/databases/mongodb/" rel="">MongoDB</a> وغير ذلك.
</p>

<p>
	يجب أولًا تثبيت مشغّل قاعدة البيانات باستخدام مدير الحزم npm، فمثلًا يمكنك استخدام الأمر التالي لتثبيت مشغّل قاعدة بيانات NoSQL MongoDB:
</p>

<pre class="ipsCode">npm install mongodb
</pre>

<p>
	يمكنك تثبيت قاعدة البيانات محليًا أو على خادم سحابي، إذ ستطلب المشغّل في شيفرة Express، ثم تتصل بقاعدة البيانات، ثم تجري عمليات الإنشاء والقراءة والتحديث والحذف -أو اختصارًا CRUD. يوضح المثال التالي (من توثيق Express) كيفية العثور على سجلات "mammal" باستخدام قاعدة بيانات MongoDB، وتعمل الشيفرة التالية مع الإصدارات الأقدم من إصدار mongodb الذي هو 2.2.33:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4130_48" style=""><span class="kwd">const</span><span class="pln"> </span><span class="typ">MongoClient</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"mongodb"</span><span class="pun">).</span><span class="typ">MongoClient</span><span class="pun">;</span><span class="pln">

</span><span class="typ">MongoClient</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="str">"mongodb://localhost:27017/animals"</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"> db</span><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">throw</span><span class="pln"> err</span><span class="pun">;</span><span class="pln">

  db</span><span class="pun">.</span><span class="pln">collection</span><span class="pun">(</span><span class="str">"mammals"</span><span class="pun">)</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">toArray</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">=&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">throw</span><span class="pln"> err</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="pun">});</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	يمكنك استخدام الشيفرة التالية بالنسبة للإصدار رقم 3.0 من mongodb والإصدارات الأحدث:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4130_50" style=""><span class="kwd">const</span><span class="pln"> </span><span class="typ">MongoClient</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"mongodb"</span><span class="pun">).</span><span class="typ">MongoClient</span><span class="pun">;</span><span class="pln">

</span><span class="typ">MongoClient</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="str">"mongodb://localhost:27017/animals"</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"> client</span><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">throw</span><span class="pln"> err</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"> client</span><span class="pun">.</span><span class="pln">db</span><span class="pun">(</span><span class="str">"animals"</span><span class="pun">);</span><span class="pln">
  db</span><span class="pun">.</span><span class="pln">collection</span><span class="pun">(</span><span class="str">"mammals"</span><span class="pun">)</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">toArray</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">=&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">throw</span><span class="pln"> err</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">
      client</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>
	هناك طريقة شائعة أخرى وهي الوصول إلى قاعدة بياناتك بطريقة غير مباشرة باستخدام <a href="https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%AC%D9%87%D9%8A%D8%B2-%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-postgresql-%D9%88%D8%A7%D9%84%D8%AA%D9%91%D8%B9%D8%B1%D9%8A%D9%81-%D8%A8%D9%85%D9%81%D9%87%D9%88%D9%85%D9%8A-orm-%D9%88%D8%A5%D8%B6%D8%A7%D9%81%D8%A7%D8%AA-flask-r506/" rel="">رابط الكائنات العلائقي Object Relational Mapper</a> -أو ORM اختصارًا، إذ تعرّف في هذه الطريقة بياناتك بوصفها كائنات أو نماذج ويربط رابط ORM هذه البيانات بتنسيق قاعدة البيانات الأساسية. تتمتع هذه الطريقة بفائدة أنه يمكنك -بصفتك مطورًا- الاستمرار في التفكير وفق مصطلحات كائنات جافا سكريبت بدلًا من التفكير وفق دلالات قاعدة البيانات، وأن هناك مكانًا واضحًا لإجراء التحقق من صحة البيانات الواردة وفحصها (سنتحدث أكثر عن قواعد البيانات في مقال لاحق (استخدام قاعدة البيانات).
</p>

<h4 id="views">
	تصيير البيانات- العروض Views
</h4>

<p>
	تسمح محركات القوالب (يُشار إليها أيضًا باسم "محركات العروض" في توثيق Express) بتحديد بنية مستندات الخرج في قالبٍ ما باستخدام العناصر البديلة للبيانات التي ستُملَأ عند توليد الصفحة، إذ تُستخدَم القوالب لإنشاء صفحات HTML، ولكن يمكنها أيضًا إنشاء أنواع أخرى من المستندات، ويدعم Express عددًا من <a href="https://expressjs.com/en/resources/template-engines.html" rel="external nofollow">محركات القوالب</a>.
</p>

<p>
	تضبط في شيفرة إعدادات تطبيقك محركَ القوالب لاستخدامه وتضبط الموقع الذي يجب أن يبحث فيه Express عن القوالب باستخدام إعدادات <code>'views'</code> و <code>'view engine'</code> كما يلي، ويجب أيضًا تثبيت الحزمة التي تحتوي على مكتبة قوالبك:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4130_52" 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"> path </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"path"</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="com">// اضبط المجلد ليتضمن القالب‫ ('views')</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">

</span><span class="com">// اضبط محرك القوالب للاستخدام، ويكون في هذه الحالة‫ 'some_template_engine_name'</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">"some_template_engine_name"</span><span class="pun">);</span></pre>

<p>
	يعتمد مظهر القالب على المحرك الذي تستخدمه. بافتراض أن لديك ملف قالب بالاسم "index.<template_extension>‎" الذي يحتوي على عناصر بديلة لمتغيرات البيانات المُسمَّاة "title" و "message"، فيمكنك استدعاء <code>Response.render()‎</code> في دالة معالج الوجهة لإنشاء استجابة HTML وإرسالها كما يلي:</template_extension>
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4130_54" 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="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"> </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"> title</span><span class="pun">:</span><span class="pln"> </span><span class="str">"About dogs"</span><span class="pun">,</span><span class="pln"> message</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Dogs rock!"</span><span class="pln"> </span><span class="pun">});</span><span class="pln">
</span><span class="pun">});</span></pre>

<h4 id="-8">
	بنية الملفات
</h4>

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

<ul>
	<li>
		النموذج Model الذي يعني بيانات التطبيق فهو يتفاعل مباشرة مع قاعدة البيانات الخاصة بك ويسترد المعلومات منها.
	</li>
	<li>
		العرض View الذي يعني واجهة التطبيق فهو يعرض الصفحات التي يتفاعل معها المستخدم مباشرة.
	</li>
	<li>
		المتحكم Controller وهو صلة الوصل بين العرض والنموذج فهو يستقبل طلبات المستخدمين ويسترد البيانات المطلوبة من النموذج ويعالجها ويرسلها إلى صفحات العرض.
	</li>
</ul>

<p>
	سنستخدم في مقال لاحق مولّد تطبيقات Express أو Express Application Generator الذي يُنشِئ تطبيقًا هيكليًا معياريًا يمكننا توسيعه بسهولة لإنشاء تطبيقات الويب.
</p>

<h2 id="-9">
	الخلاصة
</h2>

<p>
	أكملنا الخطوة الأولى في رحلة تعلم Express/Node، إذ يجب أن تفهم الآن فوائد Express و Node الرئيسية، وما تبدو عليه الأجزاء الرئيسية لتطبيق Express (الوجهات والبرمجيات الوسيطة ومعالجة الأخطاء وشيفرة القوالب)، ويجب أن تفهم أيضًا أن الطريقة التي تجمع بها هذه الأجزاء مع بعضها بعضًا والمكتبات التي تستخدمها أمرٌ متروكٌ لك بسبب كون Express إطار عمل غير قائم على رأيه.
</p>

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

<p>
	ترجمة -وبتصرُّف- للمقالين <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs" rel="external nofollow">Express web framework (Node.js/JavaScript)</a> و <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction" rel="external nofollow">Express/Node introduction</a>.
</p>

<h1 id="-10">
	اقرأ أيضًا
</h1>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A3%D9%85%D8%A7%D9%86-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2123/" rel="">تعرف على أمان تطبيقات جانغو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/servers/%D8%A3%D8%B7%D8%B1-%D8%B9%D9%85%D9%84-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D9%85%D9%86-%D8%B7%D8%B1%D9%81-%D8%A7%D9%84%D8%AE%D8%A7%D8%AF%D9%85-r784/" rel="">أطر عمل الويب من طرف الخادم</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-nodejs-%D9%88%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-express-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86-r1441/" rel="">دليل استخدام Node.js وإطار العمل Express للمبتدئين</a>
	</li>
	<li>
		<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 وExpress</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2168</guid><pubDate>Wed, 08 Nov 2023 13:00:00 +0000</pubDate></item><item><title>&#x643;&#x64A;&#x641;&#x64A;&#x629; &#x62A;&#x62D;&#x62F;&#x64A;&#x62F; &#x627;&#x644;&#x645;&#x633;&#x627;&#x631;&#x627;&#x62A; &#x648;&#x623;&#x646;&#x648;&#x627;&#x639; &#x637;&#x644;&#x628;&#x627;&#x62A; HTTP &#x641;&#x64A; &#x625;&#x637;&#x627;&#x631; &#x627;&#x644;&#x639;&#x645;&#x644; Express</title><link>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/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_01/61e31f5663eac_-----HTTP---2-Express.png.0ed31cb6b6fd2a031889ef766dc798b8.png" /></p>

<p>
	سنشرح في هذا المقال كيفية التعامل مع المسارات routes و<a href="https://academy.hsoub.com/programming/general/%D8%B1%D9%85%D9%88%D8%B2-%D8%A7%D9%84%D8%A5%D8%AC%D8%A7%D8%A8%D8%A9-%D9%81%D9%8A-http-r75/" rel="">أنواع طلبات HTTP</a> في إطار العمل Express بتطبيق مشروع عملي، وسنتعلم كيفية تحديد المسارات واستخدام طلبات HTTP من نوع GET و POST و PUT و DELETE لمعالجة البيانات.
</p>

<p>
	تتعامل المسارات مع انتقال المستخدم إلى عناوين URL مختلفة، ويسهل بروتوكول HTTP عمليات التواصل ونقل البيانات من خادم Express إلى متصفح الويب.
</p>

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

<h2>
	إعداد المشروع
</h2>

<p>
	احرص على أن تكون لديك <a href="https://wiki.hsoub.com/Node.js/" rel="external">بيئة Node.js</a> جاهزة ومثبتة على حاسوبك حتى تتمكن من استخدام إطار العمل Express.
</p>

<p>
	نفذ الخطوات التالية في الطرفية Terminal:
</p>

<p>
	أولًا، أنشئ مجلدًا جديدًا خاص بالمشروع باسم <code>node-express-routing</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7772_6" style="">
<span class="pln">mkdir node</span><span class="pun">-</span><span class="pln">express</span><span class="pun">-</span><span class="pln">routing</span></pre>

<p>
	ثم انتقل إلى المجلد الجديد:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7772_8" style="">
<span class="pln">cd node</span><span class="pun">-</span><span class="pln">express</span><span class="pun">-</span><span class="pln">routing</span></pre>

<p>
	ثانيًا، أنشئ مشروعك الجديد معتمدًا الإعدادات الافتراضية، ومن ضمنها ملف package.json حتى تتمكن من الوصول إلى الاعتماديات dependencies:
</p>

<pre class="ipsCode">
npm init -y
</pre>

<p>
	ثم أنشئ ملف index.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_7772_13" style="">
<span class="pln">touch index</span><span class="pun">.</span><span class="pln">js</span></pre>

<p>
	ثبت الحزمتين Express و <code>nodemon</code>، إذ سنحتاج إلى الحزمة الأخيرة لإعادة تشغيل المشروع باستمرار عند كل تغيير في ملف index.js:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7772_15" style="">
<span class="pln">npm install express </span><span class="pun">--</span><span class="pln">save
npm install nodemon </span><span class="pun">--</span><span class="pln">save</span><span class="pun">-</span><span class="pln">dev</span></pre>

<p>
	افتح الملف package.json باستخدام إحدى محررات النصوص وأضف سكربت البدء <code>start</code> لتشغيل ملف index.js عبر الأمر <code>nodemon</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7772_17" 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">"node-express-routing"</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">""</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">"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 index.js"</span><span class="pln">
 </span><span class="pun">},</span><span class="pln">
 </span><span class="str">"keywords"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[],</span><span class="pln">
 </span><span class="str">"author"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Paul Halliday"</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>
	يتيح لك ذلك استخدام الأمر <code>npm start</code> في الطرفية لتشغيل خادم Express وحفظ التعديلات. تهانينا! أصبحت جاهزًا لإنشاء خادم Express بعد أن أعددت الحزمة <code>nodemon</code> لتعيد تشغيل الخادم عند التعديل على الملف <code>index.js</code>.
</p>

<h2>
	تشغيل خادم Express
</h2>

<p>
	ستتعامل مع منطق دمج المسارات ومختلف أنواع طلبيات HTTP في خادم Express الخاص بك، وستعمل على إعداد وتشغيل خادم خاص بك لتتصور كيف سيبدو مشروعك في المتصفح.
</p>

<p>
	اطلب وحدة Express عبر التابع <code>require</code>، في ملف index.js ثم خزن نسخة instance في المتغير <code>app</code>، وبعد ذلك أعلن عن متغير <code>PORT</code> واضبطه إلى القيمة 3000.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7772_21" 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">
</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="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">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"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(`</span><span class="typ">Express</span><span class="pln"> server currently running on port $</span><span class="pun">{</span><span class="pln">PORT</span><span class="pun">}`));</span></pre>

<p>
	ثم استدعِ التابع <code>()listen</code> مع التابع <code>()app</code> ومرر المتغير <code>PORT</code> كوسيط أول، ودالة رد نداء callback كوسيط ثاني، إذ تقوم الدالة الوسيطة <code>()listen</code> بإنشاء خادم محلي على المنفذ المحدد بالمتغير <code>PORT</code> لمعاينة التعديلات.
</p>

<p>
	مرر الوسيط <code>()express.json</code> إلى التابع <code>()app.use</code> لتحليل البيانات الواردة من خلال طلبات HTTP الخاصة بك. اعتمدت الإصدارات السابقة على تبعية المحلل <code>body-parser</code>، أما في الإصدارات الأحدث ضمنت Express برمجيات وسيطة معدة مسبقًا لتحليل البيانات خصوصًا التي بصيغة JOSN.
</p>

<p>
	اكتب الأمر التالي في الطرفية لتبدأ تشغيل المشروع:
</p>

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

<p>
	سيُخدَّم المشروع على العنوان <code><a href="http://localhost:3000" ipsnoembed="false" rel="external nofollow">http://localhost:3000</a></code>، وستلاحظ رسالة الخطأ التالية عند الانتقال إلى المتصفح:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="89369" href="https://academy.hsoub.com/uploads/monthly_2022_01/node-routing-route-error.png.4ec6492cc4113d3250572f99de4ce9ec.png" rel=""><img alt="node-routing-route-error.png" class="ipsImage ipsImage_thumbnailed" data-fileid="89369" data-unique="oq0m4qchc" src="https://academy.hsoub.com/uploads/monthly_2022_01/node-routing-route-error.thumb.png.d22a4c8826bf7f3aad0c742edae8825a.png" style="width: 450px; height: auto;"></a>
</p>

<p>
	تعد هذه خطوة البداية، الآن علينا تعريف طلبيات HTTP للتخاطب الصحيح بين المتصفح والخادم.
</p>

<h3>
	استقبال الخادم طلبية GET من طلبيات HTTP
</h3>

<p>
	يمكنك إرسال البيانات من خادم Express الخاص بك إلى المتصفح لمعاينة مشروعك بناءً على إرساله طلبية GET محددة الوجهة، ولفعل ذلك استدعِ التابع <code>()get.</code> مع المتغير <code>app</code> ولا تحدد الوجهة حاليًا، ومرر له دالة تأخذ الوسطين <code>request</code> و <code>response</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7772_29" 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">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">send</span><span class="pun">(</span><span class="str">'Hello'</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	لاحظ أن الوسيط <code>request</code> يحتوي على معلومات عن الطلبية GET التي أرسلها المتصفح، بينما يرسل التابع <code>()response.send</code> بيانات إلى المتصفح، إما على شكل سلسلة، أو كائن، أو مصفوفة كرد أو إجابة على الطلب المرسل.
</p>

<p>
	لنتعرف على المسارات وطلبات HTTP الأخرى، بعد أن طبقنا طلبية GET.
</p>

<h2>
	التعامل مع المسارات Routes
</h2>

<p>
	أنشئ طلبية GET مع تحديد المسار <code>'‎/accounts'</code> والمسار <code>'‎/accounts/:id'</code>، ثم صَرّح عن مصفوفة حسابات باسم <code>accounts</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7772_31" style="">
<span class="pln">let accounts </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">"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">"username"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"paulhal"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"role"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"admin"</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="lit">2</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"username"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"johndoe"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"role"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"guest"</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="lit">3</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"username"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"sarahjane"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"role"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"guest"</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="kwd">get</span><span class="pun">(</span><span class="str">'/accounts'</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">json</span><span class="pun">(</span><span class="pln">accounts</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">'/accounts/: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="kwd">const</span><span class="pln"> accountId </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Number</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">const</span><span class="pln"> getAccount </span><span class="pun">=</span><span class="pln"> accounts</span><span class="pun">.</span><span class="pln">find</span><span class="pun">((</span><span class="pln">account</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> account</span><span class="pun">.</span><span class="pln">id </span><span class="pun">===</span><span class="pln"> accountId</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">getAccount</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">status</span><span class="pun">(</span><span class="lit">500</span><span class="pun">).</span><span class="pln">send</span><span class="pun">(</span><span class="str">'Account not found.'</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">json</span><span class="pun">(</span><span class="pln">getAccount</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_7201_23" style="">
<span class="pln"> http</span><span class="pun">:</span><span class="com">//localhost:3000/accounts</span></pre>

<p>
	 حيث ستكون نتيجة الخرج كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7772_33" 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="lit">1</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"username"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"paulhal"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"role"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"admin"</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="lit">2</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"username"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"johndoe"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"role"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"guest"</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="lit">3</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"username"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"sarahjane"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"role"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"guest"</span><span class="pln">
 </span><span class="pun">}</span><span class="pln">
</span><span class="pun">]</span></pre>

<p>
	يمكن طلب معلومات حساب محدد بعينه من خلال إرسال مُعرِّفه إلى نقطة الوصول endpoint التالية: <code>id:/</code>، حيث يعتبر إطار العمل Express أن <code>‎:id</code> في نقطة الوصول <code>‎/accounts/:id</code> هي نص بديل لأحد معاملات المستخدم ويطابقها مع القيمة المقابلة في الرابط المرسل لنقطة الوصول.
</p>

<p>
	لاحظ النتيجة عند الانتقال إلى العنوان <code><a href="http://localhost:3000/accounts/3" ipsnoembed="false" rel="external nofollow">http://localhost:3000/accounts/3</a></code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7772_35" style="">
<span class="pun">{</span><span class="pln">
 </span><span class="str">"id"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3</span><span class="pun">,</span><span class="pln">
 </span><span class="str">"username"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"sarahjane"</span><span class="pun">,</span><span class="pln">
 </span><span class="str">"role"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"guest"</span><span class="pln">
</span><span class="pun">}</span></pre>

<h2>
	التعامل مع الطلبيات POST و PUT و DELETE
</h2>

<p>
	توفر طلبيات HTTP الأخرى غير النوع GET (اجلب) مرونة أكبر في التعامل مع البيانات وهي الطلبيات POST (انشر) و PUT (ضع) و DELETE (احذف)، إذ تنشئ الطلبية POST بيانات جديدة في الخادم (تنشر على الخادم)، بينما تعدل الطلبية PUT على البيانات الموجودة (تضع بيانات في الخادم)، أما الطلبية DELETE فتمسح البيانات من الخادم.
</p>

<h3>
	طلبية POST
</h3>

<p>
	استخدم طلبية POST لإنشاء بيانات جديدة في مصفوفة الحسابات <code>accounts</code>، عن طريق استدعاء التابع <code>()post.</code> مع المتغير <code>app</code> وتحديد المسار <code>accounts/</code> في حقل الوسيط الأول:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7772_37" style="">
<span class="pln">app</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/accounts'</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"> incomingAccount </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">

 accounts</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">incomingAccount</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">accounts</span><span class="pun">);</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	ستُرسل البيانات القادمة من الطلبية POST إلى مصفوفة الحسابات <code>accounts</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>accounts</code> أصبحت تحتوي على مستخدم جديد:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7772_40" 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="lit">1</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"username"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"paulhal"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"role"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"admin"</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="lit">2</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"username"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"johndoe"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"role"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"guest"</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="lit">3</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"username"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"sarahjane"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"role"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"guest"</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="lit">4</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"username"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"davesmith"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"role"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"admin"</span><span class="pln">
 </span><span class="pun">}</span><span class="pln">
</span><span class="pun">]</span></pre>

<h3>
	طلبية PUT
</h3>

<p>
	يمكن تعديل حساب ما إن أرسلت طلبية PUT إلى الخادم، ويمكن له أن يعالجها عن طريق استدعاء التابع <code>()put.</code> مع المتغير <code>app</code> وتمرير المسار <code>'‎/accounts/:id'</code> في حقل الوسيط الأول، وستجد من خلاله مُعرِّف الحساب المدخل، وتستخدم التابع الشرطي <code>if</code> لتعديل البيانات الجديدة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7772_42" style="">
<span class="pln">app</span><span class="pun">.</span><span class="pln">put</span><span class="pun">(</span><span class="str">'/accounts/: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="kwd">const</span><span class="pln"> accountId </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Number</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">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="pun">;</span><span class="pln">
 </span><span class="kwd">const</span><span class="pln"> account </span><span class="pun">=</span><span class="pln"> accounts</span><span class="pun">.</span><span class="pln">find</span><span class="pun">((</span><span class="pln">account</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> account</span><span class="pun">.</span><span class="pln">id </span><span class="pun">===</span><span class="pln"> accountId</span><span class="pun">);</span><span class="pln">
 </span><span class="kwd">const</span><span class="pln"> index </span><span class="pun">=</span><span class="pln"> accounts</span><span class="pun">.</span><span class="pln">indexOf</span><span class="pun">(</span><span class="pln">account</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">account</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">status</span><span class="pun">(</span><span class="lit">500</span><span class="pun">).</span><span class="pln">send</span><span class="pun">(</span><span class="str">'Account not found.'</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"> updatedAccount </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="pun">...</span><span class="pln">account</span><span class="pun">,</span><span class="pln"> </span><span class="pun">...</span><span class="pln">body </span><span class="pun">};</span><span class="pln">

  accounts</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"> updatedAccount</span><span class="pun">;</span><span class="pln">

  response</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="pln">updatedAccount</span><span class="pun">);</span><span class="pln">
 </span><span class="pun">}</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	يمكنك الآن تعديل البيانات في مصفوفة الحسابات، فإن أرسلت طلبية PUT إلى الخادم عبر الوجهة التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7201_21" style="">
<span class="pln">http</span><span class="pun">:</span><span class="com">//localhost:3000/accounts/1 </span></pre>

<p>
	لطلب تغيير دور مستخدم محدد مثل إرسال البيانات التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7772_44" style="">
<span class="pun">{</span><span class="pln">
  </span><span class="str">"role"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"guest"</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	فسيتغير دور <code>"role"</code> ذلك المستخدم من مدير <code>admin</code> إلى ضيف <code>guest</code>، وذلك عند الانتقال إلى العنوان <code><a href="http://localhost:3000/accounts/1" ipsnoembed="false" rel="external nofollow">http://localhost:3000/accounts/1</a></code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7772_46" style="">
<span class="pun">{</span><span class="pln">
 </span><span class="str">"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">"username"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"paulhal"</span><span class="pun">,</span><span class="pln">
 </span><span class="str">"role"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"guest"</span><span class="pln">
</span><span class="pun">}</span></pre>

<h3>
	الطلبية DELETE
</h3>

<p>
	يمكن حذف المستخدمين وبياناتهم إن أرسلت طلبية DELETE إلى الخادم والذي يعالجها عن طريق استدعاء التابع <code>()delete.</code> مع المتغير <code>app</code> وتضمين المسار <code>'/accounts/:id'</code> في حقل الوسيط الأول. سيبحث التابع بعدها ضمن مصفوفة الحسابات عن الحساب ذو المعرف المراد حذفه ويتخذ الإجراء المناسب.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7772_48" style="">
<span class="pln">app</span><span class="pun">.</span><span class="kwd">delete</span><span class="pun">(</span><span class="str">'/accounts/: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="kwd">const</span><span class="pln"> accountId </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Number</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">const</span><span class="pln"> newAccounts </span><span class="pun">=</span><span class="pln"> accounts</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">((</span><span class="pln">account</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> account</span><span class="pun">.</span><span class="pln">id </span><span class="pun">!=</span><span class="pln"> accountId</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">newAccounts</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">status</span><span class="pun">(</span><span class="lit">500</span><span class="pun">).</span><span class="pln">send</span><span class="pun">(</span><span class="str">'Account not found.'</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">
  accounts </span><span class="pun">=</span><span class="pln"> newAccounts</span><span class="pun">;</span><span class="pln">
  response</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="pln">accounts</span><span class="pun">);</span><span class="pln">
 </span><span class="pun">}</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	لاحظ أنه عند إرسال طلبية DELETE إلى العنوان <code><a href="http://localhost:3000/accounts/1" ipsnoembed="false" rel="external nofollow">http://localhost:3000/accounts/1</a></code> أن الحساب ذا المعرف 1 من مصفوفة الحسابات <code>accounts</code> سيُحذَف.
</p>

<h2>
	ختامًا
</h2>

<p>
	تعلمنا في هذا المقال كيف يساعد تحديد المسارات واستخدام طلبيات HTTP في تفاعل الخادم مع الطلبيات التي يرسلها المتصفح كما وضحنا في مثال المستخدمين والتعامل مع بياناتهم من إنشاء وتحديث وحذف في خادم Express.
</p>

<p>
	للحصول على المساعدة والدعم يمكنك إضافة سؤالك في قسم الأسئلة والأجوبة في <a href="https://academy.hsoub.com/questions/" rel="">أكاديمية حسوب</a>.
</p>

<p>
	ترجمة- وبتصرف للمقال <a href="https://www.digitalocean.com/community/tutorials/nodejs-express-routing" rel="external nofollow">How To Define Routes and HTTP Request Methods in Express</a> لصاحبه Paul Halliday.
</p>

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

<ul>
<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D9%83%D8%A7%D8%A6%D9%86-%D8%A7%D9%84%D8%A5%D8%AC%D8%A7%D8%A8%D8%A9-res-%D8%B9%D9%84%D9%89-%D8%B7%D9%84%D8%A8%D9%8A%D8%A9-http-%D9%81%D9%8A-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-express-r1443/" rel="">التعامل مع كائن الإجابة res على طلبية HTTP في إطار العمل Express </a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D9%83%D8%A7%D8%A6%D9%86-%D8%A7%D9%84%D8%B7%D9%84%D8%A8%D9%8A%D8%A9-req-%D9%81%D9%8A-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-express-r1442/" rel="">التعامل مع كائن الطلبية req في إطار العمل Express</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-nodejs-%D9%88%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-express-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86-r1441/" rel="">دليل استخدام Node.js وإطار العمل Express للمبتدئين</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1444</guid><pubDate>Sun, 23 Jan 2022 16:08:00 +0000</pubDate></item><item><title>&#x627;&#x644;&#x62A;&#x639;&#x627;&#x645;&#x644; &#x645;&#x639; &#x643;&#x627;&#x626;&#x646; &#x627;&#x644;&#x625;&#x62C;&#x627;&#x628;&#x629; res &#x639;&#x644;&#x649; &#x637;&#x644;&#x628;&#x64A;&#x629; HTTP &#x641;&#x64A; &#x625;&#x637;&#x627;&#x631; &#x627;&#x644;&#x639;&#x645;&#x644; Express</title><link>https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D9%83%D8%A7%D8%A6%D9%86-%D8%A7%D9%84%D8%A5%D8%AC%D8%A7%D8%A8%D8%A9-res-%D8%B9%D9%84%D9%89-%D8%B7%D9%84%D8%A8%D9%8A%D8%A9-http-%D9%81%D9%8A-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-express-r1443/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_01/61e31aa52e7eb_---res----Express.png.f2c66bbf245ea98ebd3698055d76da82.png" /></p>

<p>
	سنتعرف في هذا المقال على الكائن <code>res</code> في إطار العمل Express وكيفية استخدامه، إذ يُعد الكائن <code>response</code> "إجابة" واختصارًا <code>res</code> جزءًا أساسيًا من الثنائي response-request طلب-إجابة، حيث يقوم بإرسال البيانات من الخادم إلى العميل من خلال طلبات HTTP.
</p>

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

<ul>
<li>
		يُفضل أن يكون لديك معرفة مسبقة ببيئة Node.js، لكن ذلك ليس ضروريًا، انظر صفحة التعريف <a href="https://wiki.hsoub.com/Node.js" rel="external">Node.js</a> على موسوعة حسوب وننصح بقراءة صفحة <a href="https://wiki.hsoub.com/Node.js/synopsis" rel="external">طريقة الاستعمال وتشغيل الأمثلة</a> أيضًا من التوثيق نفسه.
	</li>
	<li>
		معرفة بطلبات HTTP، ارجع إلى مقال <a href="https://academy.hsoub.com/programming/general/http-%D9%84%D9%86%D9%86%D8%B7%D9%84%D9%82-%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%88%D9%85-r74/" rel="">مدخل إلى HTTP: شرح التخاطب بين العميل والخادم</a>.
	</li>
</ul>
<h2>
	اختبار التابعين ()status. و ()append.
</h2>

<p>
	يرسل التابع <code>()send.</code> المُستخدَم في الكائن <code>res</code> البيانات المعرفة في حقل الوسيط إلى طرف العميل. يتعامل هذا التابع مع الوسطاء سواء كانت سلسلة، أو مصفوفة، أو كائن.
</p>

<p>
	استخدم طلب GET مع تحديد وجهته كالتالي <code>'home/'</code>، إليك طريقة كتابة الشيفرة في ملف <code>index.js</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2681_6" style="">
<span class="pln">app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/home'</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">'Hello World!'</span><span class="pun">))</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	نلاحظ أن الطلب GET يقبل تمرير دالة رد نداء إليه تقبل الوسيطين <code>res</code> و <code>req</code>؛ ويمكنك استخدام الكائن <code>res</code> آنذاك لإرسال السلسلة "!Hello World" إلى طرف العميل.
</p>

<p>
	يُعَّرف التابع <code>()send.</code> ترويساته بشكل محلي بناءً على نوع البيانات <code>Content-Type</code> وطولها <code>Content-Length</code>.
</p>

<p>
	يُحَدد الكائن <code>res</code> حالة شيفرات HTTP عن طريق التابع <code>()status.</code>، لذا استدعيه مع الكائن <code>res</code>، بتمرير أحد شيفرات حالة HTTP كوسيط:
</p>

<pre class="ipsCode">
res.status(404).send('Not Found');
</pre>

<p>
	يحدد التابع <code>()status.</code> <a href="https://academy.hsoub.com/programming/general/%D8%B1%D9%85%D9%88%D8%B2-%D8%A7%D9%84%D8%A5%D8%AC%D8%A7%D8%A8%D8%A9-%D9%81%D9%8A-http-r75/" rel="">رمز حالة HTTP</a> بالقيمة <code>404</code>، والتي تخبر العميل بأن البيانات أو الصفحة المطلوبة غير موجودة "not found". يمكنك إرسال شرح للرمز للعميل باستخدام التابع <code>()send.</code>.
</p>

<p>
	يُدمَج عمل التابعين <code>status</code> و <code>send</code> في التابع <code>()sendStatus.</code> لسهولة التنفيذ:
</p>

<pre class="ipsCode">
res.sendStatus(404);
</pre>

<p>
	نلاحظ أن التابع <code>()sendStatus.</code> يحدد رمز حالة HTTP بالرمز 404 ويرسلها إلى طرف العميل بخطوة واحدة، أو باستدعاء واحد.
</p>

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

<p>
	استخدم التابع <code>()append.</code> لتعريف ترويسة في استجابة أو رد الخادم، عن طريق تمرير الترويسة كوسيط أول عند استدعاء التابع <code>()append.</code>، وتمرير قيمة في الوسيط الثاني، كما يلي:
</p>

<pre class="ipsCode">
res.append('Content-Type', 'application/javascript; charset=UTF-8');
res.append('Connection', 'keep-alive')
res.append('Set-Cookie', 'divehours=fornightly')
res.append('Content-Length', '5089990');
</pre>

<p>
	نلاحظ أن التابع <code>()append.</code> يقبل ترويسات قياسية وغير قياسية في السطر الواحد.
</p>

<h2>
	التعرف على عمل التوابع redirect و render و method
</h2>

<p>
	يوجه التابع <code>()redirect.</code> العميل إلى صفحة أخرى، عند استدعائه من طرف الكائن <code>res</code>. أي أنه عندما يُدخل المستخدم معلومات تسجيل الدخول في طرف العميل، يسهل التابع <code>()redirect.</code> إعادة توجيهه إلى الصفحة المطلوبة.
</p>

<p>
	استدعِ التابع <code>()redirect.</code> مع الكائن <code>res</code> كما يلي:
</p>

<pre class="ipsCode">
res.redirect('/sharks/shark-facts')
</pre>

<p>
	يوجه التابع <code>()redirect.</code> العميل إلى الوجهة 'sharks/shark-facts/'.
</p>

<p>
	أما التابع <code>()render.</code> فيقبل ملف HTML في حقل الوسيط ويرسله إلى العميل، ويقبل وسيطًا ثانيًا هو كائن محلي مع خاصيات محددة لتعريف الملف المرسل إلى طرف العميل.
</p>

<p>
	طَبق طلب GET مع تحديد الوجهة إلى 'shark-game/':
</p>

<pre class="ipsCode">
app.get('/shark-game', (req, res) =&gt; {
 res.render('shark.html', {status: 'good'});
});
</pre>

<p>
	لاحظ أن استدعاء التابع <code>()render.</code> مع الكائن <code>res</code> يؤدي إلى إرسال ملف HTML التالي <code>shark.html</code> مع خاصية الحالة <code>status</code> إلى العميل.
</p>

<p>
	ينهي التابع <code>()end.</code> عمل حلقة الإجابة response cycle، وينصح باستدعائه في أخر خطوة عند إرسال بيانات إلى طرف العميل.
</p>

<p>
	استدعِ التابع <code>()sentStatus.</code> مترافقًا مع التابع <code>()end.</code> كالتالي:
</p>

<pre class="ipsCode">
res.sendStatus(404).end();
</pre>

<p>
	يكمل التابع <code>()end.</code> رد الخادم ويرسله إلى طرف العميل بمجرد أن يحدد التابع <code>()sentStatus.</code> رمز حالة HTTP إلى 404، ونلاحظ مما سبق أن الكائن <code>res</code> يسهل عملية إرسال البيانات والملفات أيضًا.
</p>

<p>
	دعنا نتعرف على طرق أخرى للتعامل مع الملفات باستخدام الكائن <code>res</code>.
</p>

<h2>
	التعامل مع الملفات باستخدام الكائن res
</h2>

<p>
	يوفر التابع <code>()sendFile.</code>إمكانية إرسال ملفات <a href="http://wiki.hsoub.com/HTML" rel="external">HTML</a> و <a href="https://wiki.hsoub.com/CSS" rel="external">CSS</a> و <a href="https://wiki.hsoub.com/JavaScript" rel="external">JavaScript</a> إلى العميل، عند استدعائه مع الكائن <code>res</code>.
</p>

<p>
	استدعِ الطلب <code>GET</code> مع ضبط الوجهة إلى 'gallery/:fileName/':
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2681_14" style="">
<span class="com">// GET https://sharks.com/gallery/shark-image.jpg</span><span class="pln">

app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/gallery/:fileName'</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">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">

 </span><span class="kwd">var</span><span class="pln"> options </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  root</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">
 </span><span class="pun">};</span><span class="pln">

 res</span><span class="pun">.</span><span class="pln">sendFile</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">fileName</span><span class="pun">,</span><span class="pln"> options</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"> </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"> next</span><span class="pun">(</span><span class="pln">err</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">else</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'Sent:'</span><span class="pun">,</span><span class="pln"> fileName</span><span class="pun">);</span><span class="pln">
 </span><span class="pun">});</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	لاحظ أن المتغير <code>options</code> هو كائن فيه الخاصية <code>root</code> التي تشير إلى المسار المطلق للمجلد العام <code>public</code> بجمع <code>‎__dirname</code> مع <code>public</code> عبر التابع <code>path.join()‎</code>.
</p>

<p>
	تشمل محتويات المجلد العام <code>public</code> كلًا من ملفات HTML و <a href="https://academy.hsoub.com/programming/css/%d8%aa%d8%b9%d8%b1%d9%91%d9%81-%d8%b9%d9%84%d9%89-%d8%a3%d8%b3%d8%a7%d8%b3%d9%8a%d8%a7%d8%aa-css-r70/" rel="">CSS</a> و JavaScript، ويقبل التابع <code>()sendFile</code> المتغير <code>options</code> كوسيط ثاني، ومعالج للأخطاء كوسيط ثالث، وهكذا تُرسل الملفات المخزنة في مجلد <code>public</code> إلى طرف العميل.
</p>

<p>
	يمكنك استدعاء التابع <code>()download.</code> مع الكائن <code>res</code> لتسهيل التعامل مع الملفات، فأضف معالجة لطلب GET المرسل إلى الوجهة 'gallery/:fileName/' كالتالي:
</p>

<pre class="ipsCode">
// GET https://sharkss.com/gallery/shark-image.jpg

app.get('/gallery/:fileName', function(req, res){
 const file = `${__dirname}/public/${req.params.fileName}`;
 res.download(file);
});
</pre>

<p>
	يحث التابع <code>()download.</code> طرف العميل على تنزيل ملف ما ويرفق ترويسات مناسبة لنوع الملف، وذلك كله باستدعاء واحد.
</p>

<p>
	استخدم التابع <code>()type.</code> عند استدعاء الكائن <code>res</code> لتحديد قيمة <code>Content-Header</code> بتحديد صيغة الملف كالتالي:
</p>

<pre class="ipsCode">
res.type('png')       // =&gt; 'image/png'
res.type('html')       // =&gt; 'text/html'
res.type('application/json') // =&gt;'application/json'
</pre>

<p>
	لاحظ أن التابع <code>()type.</code> يظهر نوع الملف مع قيمته في <code>Content-Header</code>.
</p>

<h2>
	ختاما
</h2>

<p>
	تعرفنا في هذا المقال على الكائن <code>res</code>، ومحتوياته من توابع تسهل إرسال البيانات والملفات عبر طلبات HTTP المرسلة من خادم Express إلى طرف العميل.
</p>

<p>
	لمزيد من المعلومات عن الكائن <code>res</code> يرجى الإطلاع على <a href="https://expressjs.com/en/api.html#res" rel="external nofollow">التوثيق الرسمي من موقع Express</a>.
</p>

<p>
	للحصول على المساعدة والدعم يمكنك إضافة سؤالك في قسم الأسئلة والأجوبة في <a href="https://academy.hsoub.com/questions/" rel="">أكاديمية حسوب</a>.
</p>

<p>
	ترجمة- وبتصرف للمقال <a href="https://www.digitalocean.com/community/tutorials/nodejs-res-object-in-expressjs" rel="external nofollow">How To Use the res Object in Express</a> لصاحبه William Le.
</p>

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

<ul>
<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D9%83%D8%A7%D8%A6%D9%86-%D8%A7%D9%84%D8%B7%D9%84%D8%A8%D9%8A%D8%A9-req-%D9%81%D9%8A-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-express-r1442/" rel="">التعامل مع كائن الطلبية req في إطار العمل Express </a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-nodejs-%D9%88%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-express-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86-r1441/" rel="">دليل استخدام Node.js وإطار العمل Express للمبتدئين</a>
	</li>
	<li>
		<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 وExpress</a>
	</li>
</ul>
<p>
	 
</p>
]]></description><guid isPermaLink="false">1443</guid><pubDate>Sat, 15 Jan 2022 19:23:24 +0000</pubDate></item><item><title>&#x627;&#x644;&#x62A;&#x639;&#x627;&#x645;&#x644; &#x645;&#x639; &#x643;&#x627;&#x626;&#x646; &#x627;&#x644;&#x637;&#x644;&#x628;&#x64A;&#x629; req &#x641;&#x64A; &#x625;&#x637;&#x627;&#x631; &#x627;&#x644;&#x639;&#x645;&#x644; Express</title><link>https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D9%83%D8%A7%D8%A6%D9%86-%D8%A7%D9%84%D8%B7%D9%84%D8%A8%D9%8A%D8%A9-req-%D9%81%D9%8A-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-express-r1442/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_01/61e314e71e663_----req--Express.png.71d712c1dbf369797c2d6e0c09658caf.png" /></p>

<p>
	يُعد الكائن <code>request</code> واختصارًا <code>req</code> جزءًا أساسيًا من الثنائي <a href="https://expressjs.com/en/api.html#req" rel="external nofollow"><code>request</code></a> و<a href="https://www.digitalocean.com/community/tutorials/nodejs-res-object-in-expressjs" rel="external nofollow"><code>response</code></a>، أي طلب وجواب، إذ يفحص الاستدعاءات من طرف العميل، ويرسل طلبات HTTP، ويتعامل مع البيانات الواردة بشكل سلسلة محرفية أو <a href="https://academy.hsoub.com/programming/javascript/%D8%AA%D8%B9%D9%84%D9%85-json-r604/" rel="">كائن JSON</a>.
</p>

<p>
	ستتعرف في هذا المقال بالتفصيل على كائن الطلبية <code>req</code> في إطار العمل Express.
</p>

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

<ul>
<li>
		يُفضل أن يكون لديك معرفة مسبقة ببيئة Node.js، لكن ذلك ليس ضروريًا، انظر صفحة التعريف <a href="https://wiki.hsoub.com/Node.js" rel="external">Node.js</a> على موسوعة حسوب وننصح بقراءة صفحة <a href="https://wiki.hsoub.com/Node.js/synopsis" rel="external">طريقة الاستعمال وتشغيل الأمثلة</a> أيضًا من التوثيق نفسه.
	</li>
	<li>
		معرفة بطلبات HTTP، ارجع إلى مقال <a href="https://academy.hsoub.com/programming/general/http-%D9%84%D9%86%D9%86%D8%B7%D9%84%D9%82-%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%88%D9%85-r74/" rel="">مدخل إلى HTTP: شرح التخاطب بين العميل والخادم</a>.
	</li>
</ul>
<h2>
	إدارة البيانات الواردة من العميل
</h2>

<p>
	يستقبل إطار العمل Express بيانات من طرف العميل بواسطة الكائن <code>req</code> في ثلاث نسخ Instances هي: <code>req.params</code> و<code>req.query</code> و<code>req.body</code>.
</p>

<p>
	يلتقط الكائن <code>req.params</code> البيانات بناءً على المعاملات المحددة في العنوان URL. استخدم الطلب <code>GET</code> مع المعامل <code>userid:/</code> في ملف <code>index.js</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8941_6" style="">
<span class="com">// GET https://example.com/user/1</span><span class="pln">
app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/:userid'</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">
console</span><span class="pun">.</span><span class="pln">log</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">userid</span><span class="pun">)</span><span class="pln"> </span><span class="com">// "1"</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	يوجه الكائن <code>req.params</code> إطار العمل Express إلى إظهار نتيجة معرف المستخدم user's id بواسطة المعامل <code>userid:/</code> المأخوذة من العنوان، فمثلًا طلبية <code>GET</code> للعنوان <code><a href="https://example.com/user/1" ipsnoembed="false" rel="external nofollow">https://example.com/user/1</a></code> تطابق القيمة 1 للمعامل <code>userid</code> وتطبعه في الطرفية Console.
</p>

<p>
	استخدم الكائن <code>req.query</code> للوصول إلى الاستعلامات المرسلة في العنوان URL، حيث يمكن لهذا الكائن البحث عن البيانات المرسلة في العنوان وترشيحها وفرزها.
</p>

<p>
	أضف الطلب <code>GET</code> مع تحديد الوجهة <code>search/</code>، في ملف index.js كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8941_8" style="">
<span class="com">// GET https://example.com/search?keyword=great-white</span><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"> </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">
 console</span><span class="pun">.</span><span class="pln">log</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">keyword</span><span class="pun">)</span><span class="pln"> </span><span class="com">// "great-white"</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	يطابق الكائن <code>req.query</code> البيانات التي أرسلها العميل في الرابط على شكل استعلام (بوضع كلمة مفتاحية وقيمة بعد الإشارة ?)، فنجد مثلًا في المثال السابق أن العنوان المرسل بطلبية <code>GET</code> يطابق أولًا المسار <code>search/</code> ويزودنا بمعلومات (تدعى استعلامات) مرسلة في العنوان نصل إليها عن طريق الكائن <code>query</code> الذي يوفره Express وهي كلمة البحث <code>keyword</code> التي قيمتها <code>great-white</code> في حالة المثال السابق. أي تُظهِر نتيجة إضافة الوسيط <code>keyword.</code> إلى الكائن <code>req.query</code> الرسالة التالية: <code>great-white</code> في طرفية المتصفح.
</p>

<p>
	يُتيح الكائن <code>req.body</code> الوصول إلى البيانات التي أرسلها العميل بشكل سلسة أو كائن JSON، ويُستخدم عادة للحصول على البيانات من طلبي <code>POST</code> و <code>PUT</code> في خادم Express.
</p>

<p>
	استخدم طلب <code>POST</code> مع تحديد الوجهة <code>login/</code>، في ملف <code>index.js</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8941_10" style="">
<span class="com">// POST https://example.com/login</span><span class="pln">
</span><span class="com">//</span><span class="pln">
</span><span class="com">//   {</span><span class="pln">
</span><span class="com">//    "email": "user@example.com",</span><span class="pln">
</span><span class="com">//    "password": "helloworld"</span><span class="pln">
</span><span class="com">//   }</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/login'</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">
 console</span><span class="pun">.</span><span class="pln">log</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">email</span><span class="pun">)</span><span class="pln"> </span><span class="com">// "user@example.com"</span><span class="pln">
 console</span><span class="pun">.</span><span class="pln">log</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">password</span><span class="pun">)</span><span class="pln"> </span><span class="com">// "helloworld"</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	يخزن الكائن <code>req.body</code> معلومات البريد الإلكتروني وكلمة المرور التي يدخلها المستخدم، ويرسلها إلى خادم Express، فيطبع المثال التالي قيمة البريد <code>email</code> وكلمة المرور <code>password</code> التي أرسلها العميل في طلبيته إلى الرابط <a href="https://example.com/login" ipsnoembed="false" rel="external nofollow">https://example.com/login</a> (كما نفعل عندما نريد تسجيل الدخول إلى أي موقع).
</p>

<p>
	بعد أن تعلمنا طرق تنفيذ الكائن <code>req</code>، سنتعرف على الأساليب الأخرى لاستخدامه في خادم Express.
</p>

<h2>
	فحص العنوان باستخدام خاصيات الكائن req
</h2>

<p>
	يمكن لخاصيات الكائن <code>req</code> أن تعرض الأجزاء الأساسية للعنوان URL وفقًا للبنية العامة، ومنها:
</p>

<ul>
<li>
		<code>protocol</code>: البرتوكول.
	</li>
	<li>
		<code>hostname</code>: اسم المضيف.
	</li>
	<li>
		<code>path</code>: مسار العنوان.
	</li>
	<li>
		<code>originalUrl</code>: العنوان الأساسي.
	</li>
	<li>
		<code>subdomains</code>: النطاق الفرعي.
	</li>
</ul>
<p>
	أعِد الطلب <code>GET</code> مع تحديد الوجهة <code>creatures/</code>، في ملف <code>index.js</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8941_12" style="">
<span class="com">// https://ocean.example.com/creatures?filter=sharks</span><span class="pln">

app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/creatures'</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">
 console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">req</span><span class="pun">.</span><span class="pln">protocol</span><span class="pun">)</span><span class="pln">   </span><span class="com">// "https"</span><span class="pln">
 console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">req</span><span class="pun">.</span><span class="pln">hostname</span><span class="pun">)</span><span class="pln">   </span><span class="com">// "example.com"</span><span class="pln">
 console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">req</span><span class="pun">.</span><span class="pln">path</span><span class="pun">)</span><span class="pln">     </span><span class="com">// "/creatures"</span><span class="pln">
 console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">req</span><span class="pun">.</span><span class="pln">originalUrl</span><span class="pun">)</span><span class="pln"> </span><span class="com">// "/creatures?filter=sharks"</span><span class="pln">
 console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">req</span><span class="pun">.</span><span class="pln">subdomains</span><span class="pun">)</span><span class="pln">  </span><span class="com">// "['ocean']"</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	يمكننا الوصول إلى عدة أجزاء من العنوان URL باستخدام الخاصيات الجاهزة built-in properties مثل خاصية البروتوكول <code>protocol</code> وخاصية اسم المضيف <code>hostname</code>، وتؤدي طباعة الكائن <code>req</code> في الطرفية مع الخاصيات إلى الحصول على بنية العنوان URL.
</p>

<h2>
	تحليل الخاصيات الإضافية للكائن req
</h2>

<p>
	يحتوي الكائن <code>res</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>.
</p>

<p>
	استخدم خاصية <code>method.</code> في الكائن <code>req</code>، لمعرفة نوع طلبية HTTP المرسلة أي إن كانت GET أو POST أو PUT أو DELETE، فمثلًا يوضح المثال التالي طلبية DELETE نعرض فيها نوع الطلبية عبر الخاصية req.method والتي ستكون قيمتها بالفعل DELETE:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8941_21" style="">
<span class="pln">app</span><span class="pun">.</span><span class="kwd">delete</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">
 console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">req</span><span class="pun">.</span><span class="pln">method</span><span class="pun">)</span><span class="pln"> </span><span class="com">// "DELETE"</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	استعمل التابع <code>()header.</code> الذي يوفره الكائن <code>req</code> للحصول على بيانات الترويسات المرسلة إلى خادمك، مثلًا نفذ طلب POST مع تحديد الوجهة <code>login/</code>، في ملف <code>index.js</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8941_23" style="">
<span class="pln">app</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/login'</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">
 req</span><span class="pun">.</span><span class="pln">header</span><span class="pun">(</span><span class="str">'Content-Type'</span><span class="pun">)</span><span class="pln"> </span><span class="com">// "application/json"</span><span class="pln">
 req</span><span class="pun">.</span><span class="pln">header</span><span class="pun">(</span><span class="str">'user-agent'</span><span class="pun">)</span><span class="pln">  </span><span class="com">// "Mozilla/5.0 (Macintosh Intel Mac OS X 10_8_5) AppleWebKi..."</span><span class="pln">
 req</span><span class="pun">.</span><span class="pln">header</span><span class="pun">(</span><span class="str">'Authorization'</span><span class="pun">)</span><span class="pln"> </span><span class="com">// "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	تعرض الدالة <code>()req.header</code> نوع الترويسة مثل نوع المحتوى <code>Content-Type</code> والاستيثاق <code>Authorization</code>، ووسطاؤها غير حساسة لحالة الأحرف لذلك يمكنك استخدام <code>req.header('Content-Type')‎</code> أو <code>req.header('content-type')‎</code> على حد سواء.
</p>

<p>
	إذا أضفت <a href="https://github.com/expressjs/cookie-parser" rel="external nofollow"><code>cookie-parser</code></a> كملف اعتمادية dependency في خادم Express، حينها ستخزن الخاصية <code>req.cookie</code> القيم الموجودة في المحلل parser. أضف الخاصية <code>req.cookie</code> ثم طبق الخاصية <code>sessionDate</code> في ملف <code>index.js</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8941_25" style="">
<span class="com">// Cookie sessionDate=2019-05-28T01:49:11.968Z</span><span class="pln">
req</span><span class="pun">.</span><span class="pln">cookies</span><span class="pun">.</span><span class="pln">sessionDate </span><span class="com">// "2019-05-28T01:49:11.968Z"</span></pre>

<p>
	لاحظ أنه عند استدعاء الكائن <code>req</code> سيستدعي النتيجة من تاريخ جلسة <a href="https://academy.hsoub.com/programming/javascript/%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D8%AA%D8%B9%D8%B1%D9%8A%D9%81-%D8%A7%D9%84%D8%A7%D8%B1%D8%AA%D8%A8%D8%A7%D8%B7-%D9%88%D8%B6%D8%A8%D8%B7%D9%87%D8%A7-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1330/" rel="">ملف تعريف الارتباط cookie</a>.
</p>

<h2>
	ختاما
</h2>

<p>
	تهانينا! لقد تعلمت كيف يزودنا إطار Express بخاصيات مُعّدة مسبقًا لنتمكن من استخدام الكائن <code>req</code> كجزء من ثنائي request-response للتعامل مع طلبات HTTP والبيانات من طرف العميل. انتقل إلى <a href="https://expressjs.com/en/api.html#req" rel="external nofollow">موقع Express الرسمي</a> للاطلاع على التوثيقات الرسمية للكائن <code>req</code>. للحصول على المساعدة والدعم يمكنك إضافة سؤالك في قسم الأسئلة والأجوبة في <a href="https://academy.hsoub.com/questions/" rel="">أكاديمية حسوب</a>.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/nodejs-req-object-in-expressjs" rel="external nofollow">How To Use the req Object in Express</a> لصاحبه William Le.
</p>

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

<ul>
<li>
		<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 وExpress</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-nodejs-%D9%88%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-express-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86-r1441/" rel="">دليل استخدام Node.js وإطار العمل Express للمبتدئين </a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%d8%a5%d9%86%d8%b4%d8%a7%d8%a1-%d9%85%d8%af%d9%88%d9%91%d9%86%d8%a9-%d8%a8%d8%a7%d8%b3%d8%aa%d8%ae%d8%af%d8%a7%d9%85-nodejs-%d9%88express-%d8%a7%d9%84%d8%ac%d8%b2%d8%a1-%d8%a7%d9%84%d8%a3%d9%88%d9%84-r19/" rel="">إنشاء مدوّنة باستخدام Node.js وExpress (الجزء الأول)</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1442</guid><pubDate>Tue, 11 Jan 2022 16:00:00 +0000</pubDate></item></channel></rss>
