<?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/page/2/?d=2</link><description>&#x627;&#x644;&#x628;&#x631;&#x645;&#x62C;&#x629;: Express</description><language>ar</language><item><title>&#x62F;&#x644;&#x64A;&#x644; &#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; Node.js &#x648;&#x625;&#x637;&#x627;&#x631; &#x627;&#x644;&#x639;&#x645;&#x644; Express &#x644;&#x644;&#x645;&#x628;&#x62A;&#x62F;&#x626;&#x64A;&#x646;</title><link>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/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_01/61e30b5683175_--Node.js--Express-.png.73ce8c47ffcbddefeab0e02de02ff405.png" /></p>

<p>
	يسمح إطار تطبيقات الويب <a href="https://expressjs.com/" rel="external nofollow">Express</a> المستخدم في بيئة <a href="https://wiki.hsoub.com/Node.js" rel="external">Node.js</a> بتسريع عمل <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> وخوادم الويب بسهولة، وهو عبارة عن حزمة خفيفة الحجم لا تحجب ميزات Node.js الأساسية.
</p>

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

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

<ul>
<li>
		بيئة تطوير محلية من أجل <a href="https://academy.hsoub.com/devops/linux/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-nodejs-%D8%B9%D9%84%D9%89-%D9%86%D8%B8%D8%A7%D9%85-%D8%A3%D8%A8%D9%88%D9%86%D8%AA%D9%88-1804-r419/" rel="">Node.js</a>.
	</li>
	<li>
		يتوافق هذا المقال مع كل من الإصدارات Node v15.14.0، و ، express v4.171 و npm v7.10.0، و server-index v1.9.1
	</li>
</ul>
<h2>
	إعداد المشروع
</h2>

<p>
	افتح واجهة الطرفية Terminal وانشئ مجلدًا للمشروع الجديد، كما يلي:
</p>

<pre class="ipsCode prettyprint prettyprinted" id="ips_uid_5344_6" style="">
<span class="pln">$ mkdir express</span><span class="pun">-</span><span class="pln">example</span></pre>

<p>
	انتقل بعدها إلى المجلد الذي أنشأته آنفًا:
</p>

<pre class="ipsCode prettyprint prettyprinted" id="ips_uid_5344_10" style="">
<span class="pln">$ cd express</span><span class="pun">-</span><span class="pln">example</span></pre>

<p>
	أصبح بإمكانك الآن استخدام مشروع npm جديد:
</p>

<pre class="ipsCode">
$ npm init -y
</pre>

<p>
	ثبت حزمة <code>express</code> باستخدام الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-swift prettyprinted" id="ips_uid_5344_12" style="">
<span class="pln">npm install express@4</span><span class="pun">.</span><span class="lit">17.1</span></pre>

<p>
	أصبح لديك مشروع Express جاهز للاستخدام.
</p>

<h2>
	إنشاء خادم Express
</h2>

<p>
	أنشئ ملفًا باسم server.js وافتحه بواسطة أحد محررات <a href="https://academy.hsoub.com/programming/general/%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-%D8%A7%D9%84%D9%86%D8%B5%D9%88%D8%B5-%D9%81%D9%8A-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-r1341/" rel="">النصوص البرمجية</a>، ثم أضف الأسطر البرمجية التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5344_14" 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>
	يعمل السطر الأول على جلب وحدة Express module الرئيسية من الحزم التي ثبتها سابقًا، هذه الوحدة هي عبارة عن دالة تُنفَّذ في السطر الثاني لإنشاء المتغير <code>app</code>. يمكنك إنشاء عدة متغيرات app بهذه الطريقة لكل منها طلباتها requests وردودها responses الخاصة بها.
</p>

<pre class="ipsCode">
const express = require('express');
const app = express();
app.get('/', (req, res) =&gt; {
 res.send('Successful response.');
});
</pre>

<p>
	نوجه خادم Express لتخديم طلبات GET القادمة إلى الخادم بواسطة الأسطر البرمجية السابقة.
</p>

<p>
	يتضمن Express دوال مشابهة لطلبيات POST وطلبيات PUT وهي الدالة <code>()app.post</code> والدالة <code>()app.put</code>، وغيرها. تقبل هذه الدوال معاملين أساسين، الأول هو عنوان URL الذي ستعالجه الدالة، ونتعامل هنا مع <code>/</code> والذي يعبر عن الصفحة الرئيسية للموقع في حالتنا: <code>localhost:3000</code>.
</p>

<p>
	أما المعامل الثاني فهو دالة لها وسيطين: <code>req</code> و <code>res</code>، إذ يعبر الوسيط <code>req</code> عن الطلب المرسل إلى الخادم، ويمكننا استخدامه لمعرفة ما يطلبه العميل client، أما الوسيط <code>res</code> فيعبر عن الرد الذي سنرسله للعميل.
</p>

<p>
	ثم استدعينا دالة <code>send</code> بواسطة <code>res</code> لإظهار الرسالة: ".Successful response".
</p>

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

</span><span class="kwd">const</span><span class="pln"> app </span><span class="pun">=</span><span class="pln"> express</span><span class="pun">();</span><span class="pln">

app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
 res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">'Successful response.'</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

app</span><span class="pun">.</span><span class="pln">listen</span><span class="pun">(</span><span class="lit">3000</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'Example app is listening on port 3000.'</span><span class="pun">));</span></pre>

<p>
	يجب أن نشغل الخادم بمجرد انتهائها من إعداد الطلبات requests، وذلك بتمرير القيمة 3000 إلى الدالة <code>listen</code> التي تخبر المتغير <code>app</code> بالمنفذ port الذي يجب استعماله. يُعد تمرير دالة في المعامل الثاني أمرًا اختياريًا، حيث تُنفذ هذه الدالة عند <a href="https://academy.hsoub.com/devops/servers/%D8%AF%D9%84%D9%8A%D9%84-%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%AE%D8%A7%D8%AF%D9%85-%D9%88%D9%8A%D8%A8-%D9%85%D8%AD%D9%84%D9%8A-%D8%AE%D8%B7%D9%88%D8%A9-%D8%A8%D8%AE%D8%B7%D9%88%D8%A9-r422/" rel="">تشغيل الخادم</a>، الأمر الذي يمكننا من معرفة فيما إذا كان التطبيق يعمل في الطرفية console.
</p>

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

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

<p>
	اكتب <code>localhost:3000</code> في متصفح الانترنت، ستلاحظ ظهور رسالة: "Successful response"، وستعرض واجهة الطرفية الرسالة التالية: Example app is listening on port 3000.
</p>

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

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

<h2>
	استخدام البرامج الوسيطة Middleware
</h2>

<p>
	يمكننا كتابة دوال وسيطة middleware functions في Express لها صلاحية وصول لكل <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>

<ul>
<li>
		تنفيذ أي عملية على الطلبيات.
	</li>
	<li>
		إجراء تعديلات على الكائنين request و response.
	</li>
	<li>
		إنهاء دورة الطلبية-الرد request-response.
	</li>
	<li>
		استدعاء الدالة الوسيطة التالية في المكدس.
	</li>
</ul>
<p>
	يمكننا كتابة دوال وسيطة خاصة بنا أو استخدام برامج وسيطة تابعة لجهات خارجية عن طريق استيرادها بالطريقة نفسها التي نتبعها مع الحزم.
</p>

<p>
	سنبدأ بتعلم كيفية كتابة دالة وسيطة خاصة بنا، ثم سنستخدم بعض البرامج الوسيطة لتخديم الملفات الثابتة، ونستدعي <code>()app.use</code> لتعريف دالة وسيطة ثم تمريرها إلى دالة أخرى.
</p>

<p>
	فيما يلي دالة وسيطة لطباعة الوقت الحالي في وحدة التحكم عند كل طلب:
</p>

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

</span><span class="kwd">const</span><span class="pln"> app </span><span class="pun">=</span><span class="pln"> express</span><span class="pun">();</span><span class="pln">

app</span><span class="pun">.</span><span class="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">
 console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'Time: '</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">
 next</span><span class="pun">();</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

app</span><span class="pun">.</span><span class="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">'Successful response.'</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

app</span><span class="pun">.</span><span class="pln">listen</span><span class="pun">(</span><span class="lit">3000</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'Example app is listening on port 3000.'</span><span class="pun">));</span></pre>

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

<p>
	كما يمكننا تمرير مسار إلى الدالة الوسيطة، وعندها ستعالج فقط الطلبات القادمة إلى ذلك المسار، كما يلي:
</p>

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

</span><span class="kwd">const</span><span class="pln"> app </span><span class="pun">=</span><span class="pln"> express</span><span class="pun">();</span><span class="pln">

app</span><span class="pun">.</span><span class="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">
 console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'Time: '</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">
 next</span><span class="pun">();</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="str">'/request-type'</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">
 console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'Request type: '</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">
 next</span><span class="pun">();</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

app</span><span class="pun">.</span><span class="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">'Successful response.'</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

app</span><span class="pun">.</span><span class="pln">listen</span><span class="pun">(</span><span class="lit">3000</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'Example app is listening on port 3000.'</span><span class="pun">));</span></pre>

<p>
	تعالج الدالة الطلبات الواردة إلى <code>localhost:3000/request-type</code> فقط، عند تمرير <code>request-type/</code> في الوسيط الأول للدالة <code>()app.use</code>.
</p>

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

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

<p>
	اكتب <code>localhost:3000/request-type</code> في متصفح الانترنت، ستلاحظ أن واجهة الطرفية ستعرض وقت الطلب والرسالة "Request type: GET".
</p>

<p>
	سنستخدم الآن دالة وسيطة جاهزة لتخديم الملفات الثابتة static files، حيث يُزَود Express بدالة وسيطة جاهزة <a href="http://expressjs.com/en/5x/api.html#express.static" rel="external nofollow"><code>express.static</code></a>، وسنستخدم دالة وسيطة خارجية، وهي <a href="https://www.npmjs.com/package/serve-index" rel="external nofollow"><code>serve-index</code></a> لعرض الملفات الملفات مفهرسةً وفق فهرس.
</p>

<p>
	أنشئ مجلدًا باسم <code>public</code> داخل المجلد الموجود فيه خادم express، وضع فيه بعض الملفات ثم ثبت حزمة <code>serve-index</code>:
</p>

<pre class="ipsCode">
$ npm install serve-index@1.9.1
</pre>

<p>
	استورد حزمة <code>serve-index</code>في أعلى ملف الخادم، ثم ضَمّن البرنامجين الوسيطين <code>express.static</code> و <code>serve-index</code> وأخبرهما بمسار الوصول للمجلد واسمه:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5344_26" 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"> serveIndex </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'serve-index'</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">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">
 console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'Time: '</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">
 next</span><span class="pun">();</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="str">'/request-type'</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">
 console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'Request type: '</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">
 next</span><span class="pun">();</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="str">'/public'</span><span class="pun">,</span><span class="pln"> express</span><span class="pun">.</span><span class="kwd">static</span><span class="pun">(</span><span class="str">'public'</span><span class="pun">));</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="str">'/public'</span><span class="pun">,</span><span class="pln"> serveIndex</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="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">'Successful response.'</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

app</span><span class="pun">.</span><span class="pln">listen</span><span class="pun">(</span><span class="lit">3000</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'Example app is listening on port 3000.'</span><span class="pun">));</span></pre>

<p>
	أعد تشغيل الخادم وانتقل إلى العنوان <code>localhost:3000/public</code>، ستظهر لك قائمة بجميع ملفاتك.
</p>

<h2>
	ختامًا
</h2>

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

<p>
	للحصول على المساعدة والدعم يمكنك إضافة سؤالك في قسم الأسئلة والأجوبة في <a href="https://academy.hsoub.com/questions/" rel="">أكاديمية حسوب</a>
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/nodejs-express-basics" rel="external nofollow">How To Get Started with Node.js and Express</a> من موقع <a href="https://www.digitalocean.com" rel="external nofollow">Digital Ocean</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%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">1441</guid><pubDate>Tue, 04 Jan 2022 16:00:00 +0000</pubDate></item><item><title>&#x645;&#x62F;&#x62E;&#x644; &#x625;&#x644;&#x649; Node.js &#x648;Express</title><link>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/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2021_01/p13.jpg.1c72ff37a9d525d52af4c656cdbbc707.jpg" /></p>

<p>
	سنوجّه اهتمامنا في هذا القسم إلى التعامل مع الواجهة الخلفية وآلية تطوير التطبيق بإضافة وظائف جديدة تنفذ هذه المرة من قبل الخادم. سنبني تلك الوظائف باستخدام <a href="https://wiki.hsoub.com/Node.js" rel="external">NodeJS</a> وهي بيئة تشغيل JavaScript مبنية على محرك JavaScript من تصميم Google يدعى <a href="https://developers.google.com/v8/" rel="external nofollow">Chrome V8</a>.
</p>

<p>
	كُتبَت المادة العلمية للمنهاج باستخدام Node.js 10.18.0، وتأكد من أن النسخة المثبتة على جهازك هي على الأقل مطابقة للإصدار السابق، ويمكنك استخدام الأمر <code>node -v</code> للتحقق من الإصدار المثبت لديك.
</p>

<p>
	أشرنا سابقًا في مقال <a href="https://academy.hsoub.com/programming/javascript/react/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-%D8%A7%D9%84%D9%84%D8%A7%D8%B2%D9%85%D8%A9-%D9%84%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-react-r1073/" rel="">أساسيات جافاسكربت اللازمة للعمل مع React</a> أن المتصفحات لا تدعم كامل الميزات الأحدث للغة JavaScript مباشرةً، لذلك من الضروري نقل الشيفرة التي تعمل على المتصفح إلى إصدار أقدم باستخدام <a href="https://babeljs.io/" rel="external nofollow">babel</a> مثلًا. لكن الأمر سيختلف تمامًا مع JavaScript التي تُنفّذ في الواجهة الخلفية، ذلك أن الإصدار الأحدث من Node.js سيدعم الغالبية العظمى من الميزات الجديدة للغة، فلا حاجة عندها للنقل.
</p>

<p>
	سنضيف شيفرة تتعامل مع تطبيق الملاحظات الذي تعرفنا عليه سابقًا في مقالات سابقة من <a href="https://academy.hsoub.com/tags/full_stack_101/" rel="">هذه السلسلة</a> لكن تنفيذها سيكون في الواجهة الخلفية. لكن علينا أولًا تعلم الأساسيات من خلال كتابة التطبيق التقليدي "Hello world".
</p>

<p>
	<strong>ملاحظة</strong>: لن تكون جميع التطبيقات والتمارين في هذا القسم تطبيقات React، ولن نستخدم create-react-app في تهيئة المشاريع التي تضم التطبيقات.
</p>

<p>
	لقد تعرفنا سابقًا في مقال إحضار البيانات من الخادم في تطبيقات React على مدير الحزم <a href="https://academy.hsoub.com/programming/javascript/react/%D8%A5%D8%AD%D8%B6%D8%A7%D8%B1-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%85%D9%86-%D8%A7%D9%84%D8%AE%D8%A7%D8%AF%D9%85-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-react-r1096/" rel="">npm</a> وهو أداة لإدارة حزم JavaScript تعود أصلًا إلى بيئة Node.js. انتقل إلى مجلد مناسب وأنشئ قالبًا لتطبيقنا مستخدمًا الأمر <code>npm init</code>. أجب عن الأسئلة التي تولدها الأداة، وستكون النتيجة إنشاء الملف package.json ضمن جذر المشروع يضم معلومات عنه.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7804_7" 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">"backend"</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.1"</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">"Matti Luukkainen"</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>
	من هذه المعلومات بالطبع -ستكون قد أدخلتها عند إجابتك عن الأسئلة- اسم المشروع ونقطة انطلاق التطبيق والتي هي الملف index.js في تطبيقنا.
</p>

<p>
	لنجري بعض التعديلات على محتوى الكائن <code>scripts</code> في الملف:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7804_9" style="">
<span class="pun">{</span><span class="pln">
  </span><span class="com">// ...</span><span class="pln">
  </span><span class="str">"scripts"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">"start"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"node index.js"</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="com">// ...</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	لننشئ الإصدار الأول لتطبيقنا بإضافة ملف باسم index.js إلى المجلد الجذري الذي أنشأنا فيه المشروع. اكتب الأمر التالي في الملف:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7804_11" style="">
<span class="pln">console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'hello world'</span><span class="pun">)</span></pre>

<p>
	يمكن تشغيل التطبيق من node بكتابة التالي في سطر الأوامر:
</p>

<pre class="ipsCode">
node index.js
</pre>

<p>
	كما يمكن تشغيله كسكريبت (<a href="https://docs.npmjs.com/misc/scripts" rel="external nofollow">npm script</a>):
</p>

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

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7804_13" style="">
<span class="pun">{</span><span class="pln">
  </span><span class="com">// ...</span><span class="pln">
  </span><span class="str">"scripts"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">"start"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"node index.js"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"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="com">// ...</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	سيُنفِّذ npm المشروع عند استدعاء الملف index.js من سطر الأوامر. يعتبر تشغيل التطبيق كسكريبت npm أمرًا اختياريًا. يعرّف ملف package.json افتراضيًا سكريبت npm أخرى تدعى npm test. وطالما أن المشروع لا يضم حتى الآن مكتبة للاختبارات، سينفذ npm الأمر التالي:
</p>

<pre class="ipsCode">
echo "Error: no test specified" &amp;&amp; exit 1
</pre>

<h2>
	خادم ويب بسيط
</h2>

<p>
	لنحوّل تطبيقنا إلى خادم ويب:
</p>

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

</span><span class="kwd">const</span><span class="pln"> app </span><span class="pun">=</span><span class="pln"> http</span><span class="pun">.</span><span class="pln">createServer</span><span class="pun">((</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  res</span><span class="pun">.</span><span class="pln">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">
  res</span><span class="pun">.</span><span class="pln">end</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">

</span><span class="kwd">const</span><span class="pln"> PORT </span><span class="pun">=</span><span class="pln"> </span><span class="lit">3001</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">listen</span><span class="pun">(</span><span class="pln">PORT</span><span class="pun">)</span><span class="pln">
console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(`</span><span class="typ">Server</span><span class="pln"> running on port $</span><span class="pun">{</span><span class="pln">PORT</span><span class="pun">}`)</span></pre>

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

<pre class="ipsCode">
Server running on port 3001
</pre>

<p>
	سنشغل تطبيقنا المتواضع بطلب عنوان الموقع <a href="http://localhost:3001/" rel="external nofollow">http://localhost:3001</a> من المتصفح:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="55488" href="https://academy.hsoub.com/uploads/monthly_2021_01/app_server_001.png.c49e9b664296103a6e4c1fd0e7f96965.png" rel=""><img alt="app_server_001.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55488" data-unique="z6yfjt5tc" src="https://academy.hsoub.com/uploads/monthly_2021_01/app_server_001.png.c49e9b664296103a6e4c1fd0e7f96965.png"></a>
</p>

<p>
	سيعمل الخادم كما سبق بغض النظر عن بقية أقسام العنوان، حتى أن كتابة عنوان الموقع بالشكل <a href="http://localhost:3001/foo/bar" ipsnoembed="false" rel="external nofollow">http://localhost:3001/foo/bar</a> يعطي النتيجة نفسها.
</p>

<p>
	<strong>ملاحظة</strong>: إن كان المنفذ 3001 محجوزًا من قبل تطبيق آخر، سينتج عن تشغيل الخادم رسالة الخطأ التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7804_18" style="">
<span class="pun">➜</span><span class="pln">  hello npm start

</span><span class="pun">&gt;</span><span class="pln"> hello@1</span><span class="pun">.</span><span class="lit">0.0</span><span class="pln"> start </span><span class="pun">/</span><span class="typ">Users</span><span class="pun">/</span><span class="pln">mluukkai</span><span class="pun">/</span><span class="pln">opetus</span><span class="pun">/</span><span class="pln">_2019fullstack</span><span class="pun">-</span><span class="pln">code</span><span class="pun">/</span><span class="pln">part3</span><span class="pun">/</span><span class="pln">hello
</span><span class="pun">&gt;</span><span class="pln"> node index</span><span class="pun">.</span><span class="pln">js

</span><span class="typ">Server</span><span class="pln"> running on port </span><span class="lit">3001</span><span class="pln">
events</span><span class="pun">.</span><span class="pln">js</span><span class="pun">:</span><span class="lit">167</span><span class="pln">
      </span><span class="kwd">throw</span><span class="pln"> er</span><span class="pun">;</span><span class="pln"> </span><span class="com">// Unhandled 'error' event</span><span class="pln">
      </span><span class="pun">^</span><span class="pln">

</span><span class="typ">Error</span><span class="pun">:</span><span class="pln"> listen EADDRINUSE </span><span class="pun">:::</span><span class="lit">3001</span><span class="pln">
    at </span><span class="typ">Server</span><span class="pun">.</span><span class="pln">setupListenHandle </span><span class="pun">[</span><span class="pln">as _listen2</span><span class="pun">]</span><span class="pln"> </span><span class="pun">(</span><span class="pln">net</span><span class="pun">.</span><span class="pln">js</span><span class="pun">:</span><span class="lit">1330</span><span class="pun">:</span><span class="lit">14</span><span class="pun">)</span><span class="pln">
    at listenInCluster </span><span class="pun">(</span><span class="pln">net</span><span class="pun">.</span><span class="pln">js</span><span class="pun">:</span><span class="lit">1378</span><span class="pun">:</span><span class="lit">12</span><span class="pun">)</span></pre>

<p>
	في هذه الحالة ستكون أمام خياران: إما أن تغلق التطبيق الذي يَشغُل المنفذ 3001، أو اختيار منفذ آخر. لنتأمل الآن السطر الأول من شيفرة تطبيق الخادم:
</p>

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

<p>
	يُدرج التطبيق وحدة خادم الويب <a href="https://nodejs.org/docs/latest-v8.x/api/http.html" rel="external nofollow">web server</a> المدمجة ضمن Node. لقد تعلمنا إدراج الوحدات في شيفرة الواجهة الأمامية لكن بعبارة مختلفة قليلًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7804_22" style="">
<span class="kwd">import</span><span class="pln"> http from </span><span class="str">'http'</span></pre>

<p>
	تُستعمل حاليًا وحدات ES6 ضمن شيفرة الواجهة الأمامية. وتذكّر أنّ تعريف الوحدات يكون باستعمال التعليمة <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export" rel="external nofollow">export</a> واستخدامها ضمن الشيفرة باستعمال التعليمة <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import" rel="external nofollow">import</a>.
</p>

<p>
	تَستعمل Node.js ما يسمى <a href="https://en.wikipedia.org/wiki/CommonJS" rel="external nofollow">CommonJS</a> (وحدات JavaScript المشتركة). ذلك أن بيئتها تطلبت وجود الوحدات قبل أن تدعمها JavaScript بوقت طويل. بدأت Node.js بالدعم التجريبي لوحدات ES6 مؤخّرًا فقط.
</p>

<p>
	لن نجد فرقًا تقريبًا بين وحدات ES6 ووحدات CommonJS، على الأقل ضمن حدود منهاجنا. ستبدو القطعة التالية من الشيفرة على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7804_24" style="">
<span class="kwd">const</span><span class="pln"> app </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">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">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">
  response</span><span class="pun">.</span><span class="pln">end</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>createServer</code> الموجود ضمن الوحدة <a href="https://nodejs.org/docs/latest-v8.x/api/http.html" rel="external nofollow">http</a> لإنشاء خادم ويب جديد. تعرّف الشيفرة بعد ذلك معالج حدث ضمن الخادم، يُستدعى كلما ورد طلب HTTP إلى العنوان <a href="http://localhost:3001/" rel="external nofollow">http://localhost:3001</a>. يستجيب الخادم برمز الحالة (200) معيدًا ترويسة "نوع المحتوى" على أنها text/plain (نص أو فارغ)، وكذلك محتوى صفحة الويب التي ستُعرض، وهذا المحتوى هو العبارة "Hello World". تهيئ أسطر الشيفرة الأخيرة خادم http الذي أُسند إلى المتغيّر <code>App</code> لينصت إلى طلبات HTTP القادمة إلى المنفذ 3001:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7804_27" style="">
<span class="kwd">const</span><span class="pln"> PORT </span><span class="pun">=</span><span class="pln"> </span><span class="lit">3001</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">listen</span><span class="pun">(</span><span class="pln">PORT</span><span class="pun">)</span><span class="pln">
console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(`</span><span class="typ">Server</span><span class="pln"> running on port $</span><span class="pun">{</span><span class="pln">PORT</span><span class="pun">}`)</span></pre>

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

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

let notes </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
 </span><span class="pun">{</span><span class="pln">
   id</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln">
   content</span><span class="pun">:</span><span class="pln"> </span><span class="str">"HTML is easy"</span><span class="pun">,</span><span class="pln">
   date</span><span class="pun">:</span><span class="pln"> </span><span class="str">"2019-05-30T17:30:31.098Z"</span><span class="pun">,</span><span class="pln">
   important</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln">
 </span><span class="pun">},</span><span class="pln">
 </span><span class="pun">{</span><span class="pln">
   id</span><span class="pun">:</span><span class="pln"> </span><span class="lit">2</span><span class="pun">,</span><span class="pln">
   content</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Browser can execute only Javascript"</span><span class="pun">,</span><span class="pln">
   date</span><span class="pun">:</span><span class="pln"> </span><span class="str">"2019-05-30T18:39:34.091Z"</span><span class="pun">,</span><span class="pln">
   important</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">false</span><span class="pln">
 </span><span class="pun">},</span><span class="pln">
 </span><span class="pun">{</span><span class="pln">
   id</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3</span><span class="pun">,</span><span class="pln">
   content</span><span class="pun">:</span><span class="pln"> </span><span class="str">"GET and POST are the most important methods of HTTP protocol"</span><span class="pun">,</span><span class="pln">
   date</span><span class="pun">:</span><span class="pln"> </span><span class="str">"2019-05-30T19:20:14.298Z"</span><span class="pun">,</span><span class="pln">
   important</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln">
 </span><span class="pun">}</span><span class="pln">
</span><span class="pun">]</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> app </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">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">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">'application/json'</span><span class="pln"> </span><span class="pun">})</span><span class="pln">
 response</span><span class="pun">.</span><span class="pln">end</span><span class="pun">(</span><span class="pln">JSON</span><span class="pun">.</span><span class="pln">stringify</span><span class="pun">(</span><span class="pln">notes</span><span class="pun">))</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> PORT </span><span class="pun">=</span><span class="pln"> </span><span class="lit">3001</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">listen</span><span class="pun">(</span><span class="pln">PORT</span><span class="pun">)</span><span class="pln">
console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(`</span><span class="typ">Server</span><span class="pln"> running on port $</span><span class="pun">{</span><span class="pln">PORT</span><span class="pun">}`)</span></pre>

<p>
	لنعد تشغيل الخادم ولنحدث المتصفح.
</p>

<p>
	<strong>ملاحظة</strong>: يمكنك إغلاق الخادم بالضغط على ctrl+c من طرفية سطر أوامر Node.js.
</p>

<p>
	تُعلِم القيمة (application/JSON) الموجودة في ترويسة "نوع المحتوى" متلقي البيانات أنها بصيغة JSON.تُحوَّل المصفوفة <code>notes</code> إلى JSON باستخدام التابع <code>()JSON.stringify</code>. ستظهر المعلومات على المتصفح تمامًا كما ظهرت في مقال <a href="https://academy.hsoub.com/programming/javascript/react/%D8%A5%D8%AD%D8%B6%D8%A7%D8%B1-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%85%D9%86-%D8%A7%D9%84%D8%AE%D8%A7%D8%AF%D9%85-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-react-r1096/" rel="">إحضار البيانات من الخادم في تطبيقات React</a> عندما استخدمنا <a href="https://github.com/typicode/json-server" rel="external nofollow">خادم JSON</a>.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="55496" href="https://academy.hsoub.com/uploads/monthly_2021_01/json_data_web_server_002.png.88b779dbf76a367a13280053c2a40544.png" rel=""><img alt="json_data_web_server_002.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55496" data-unique="aldbly9xf" src="https://academy.hsoub.com/uploads/monthly_2021_01/json_data_web_server_002.png.88b779dbf76a367a13280053c2a40544.png"></a>
</p>

<h2>
	مكتبة Express
</h2>

<p>
	يمكن كما رأينا كتابة شيفرة الخادم مباشرة باستخدام الوحدة <a href="https://nodejs.org/docs/latest-v8.x/api/http.html" rel="external nofollow">http</a> المدمجة ضمن Node.js.لكن الأمر سيغدو مربكًا عندما يزداد حجم التطبيق.
</p>

<p>
	طوّرت العديد من المكتبات لتسهّل تطوير تطبيقات الواجهة الخلفية باستخدام Node، وذلك بتقديم واجهة أكثر ملائمة للعمل بالموازنة مع وحدة http المدمجة. تعتبر المكتبة <a href="http://expressjs.com/" rel="external nofollow">express</a> حتى الآن الأكثر شعبية لتحقيق المطلوب.
</p>

<p>
	لنضع express موضع التنفيذ بتعريفها كملف اعتمادية dependency وذلك بتنفيذ الأمر:
</p>

<pre class="ipsCode">
npm install express --save
</pre>

<p>
	يُضاف ملف الاعتمادية أيضًا إلى الملف package.json:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7804_31" style="">
<span class="pun">{</span><span class="pln">
  </span><span class="com">// ...</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>
	تُثبت الشيفرة المصدرية لملف الاعتمادية ضمن المجلد node_modules الموجود في المجلد الجذري للمشروع. يمكنك إيجاد عدد كبير من ملفات الاعتمادية بالإضافة إلى express ضمن هذا المجلد.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="55491" href="https://academy.hsoub.com/uploads/monthly_2021_01/express_node_modules_003.png.756aeba291e87f7dae5014fc036b4359.png" rel=""><img alt="express_node_modules_003.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55491" data-unique="wxva0menr" src="https://academy.hsoub.com/uploads/monthly_2021_01/express_node_modules_003.png.756aeba291e87f7dae5014fc036b4359.png"></a>
</p>

<p>
	في الواقع سيضم المجلد السابق ملفات اعتمادية express وملفات اعتمادية متعلقة بملفات اعتمادية express وهكذا. ندعو هذا الترتيب بملفات الاعتمادية الانتقالية <a href="https://lexi-lambda.github.io/blog/2016/08/24/understanding-the-npm-dependency-model/" rel="external nofollow">transitive dependencies</a> للمشروع.
</p>

<p>
	ثُبِّتت في مشروعنا المكتبة express 4.17.1. لكن ما الذي تعنيه إشارة (^) أمام رقم الإصدار في ملف package.json؟
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7804_33" style="">
<span class="str">"express"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^4.17.1"</span></pre>

<p>
	يستخدم npm ما يسمى بآلية الإصدار الدلالية <a href="https://docs.npmjs.com/getting-started/semantic-versioning" rel="external nofollow">semantic versioning</a>، وتعني الدلالة (^) أنه إذا حُدِّثت ملفات اعتمادية المشروع فإن إصدار express سيبقى 4.17.1. يمكن أن يتغير رقم الدفعة ضمن الإصدار (الرقم الأخير) أو رقم الإصدار الثانوي (الرقم الأوسط) لكن رقم الإصدار الرئيسي (الرقم الأول) يجب أن يبقى كما هو.
</p>

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

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

<p>
	يمكننا تثبيت أحدث ملفات اعتمادية معرفة في ملف package.json إذا أردنا أن نعمل على مشروعنا في حاسوب آخر باستخدام الأمر:
</p>

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

<p>
	وبشكل عام عند تحديث ملف اعتمادية، يدل عدم تغير رقم الإصدار الرئيسي أن الإصدار الأحدث سيبقى <a href="https://en.wikipedia.org/wiki/Backward_compatibility" rel="external nofollow">متوافقًا مع الإصدار الأقدم</a> دون الحاجة لتغييرات في الشيفرة. بينما تغير رقم الإصدار الرئيسي سيدل أن الإصدار الأحدث <a href="https://expressjs.com/en/guide/migrating-5.html" rel="external nofollow">قد يحوي</a> تغييرات قد تمنع التطبيق من العمل. فقد لا يعمل تطبيقنا إن كان إصدار المكتبة express فيه 4.17.7 مثلًا وتم تحديثها إلى الإصدار 5.0.0 (الذي قد نراه مستقبلًا)، بينما سيعمل مع الإصدار 4.99.1.
</p>

<h2>
	استخدام express في تطوير صفحات الويب
</h2>

<p>
	لنعد إلى تطبيقنا ونجري بعض التعديلات عليه:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7804_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"> app </span><span class="pun">=</span><span class="pln"> express</span><span class="pun">()</span><span class="pln">

let notes </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
  </span><span class="pun">...</span><span class="pln">
</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">'&lt;h1&gt;Hello World!&lt;/h1&gt;'</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">'/api/notes'</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">json</span><span class="pun">(</span><span class="pln">notes</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> PORT </span><span class="pun">=</span><span class="pln"> </span><span class="lit">3001</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">listen</span><span class="pun">(</span><span class="pln">PORT</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(`</span><span class="typ">Server</span><span class="pln"> running on port $</span><span class="pun">{</span><span class="pln">PORT</span><span class="pun">}`)</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	سنعيد تشغيل التطبيق حتى نحصل على النسخة الجديدة منه. طبعًا لن نلاحظ تغييرًا كليًا فلقد أدرجنا <code>express</code> على شكل دالة ستنشئ تطبيق express يُخزّن ضمن المتغيّر <code>App</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7804_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></pre>

<p>
	وبعدها عرّفنا مسارين للتطبيق، يعرّف الأول معالج حدث يتعامل مع طلبات HTTP-GET الموجهة إلى المجلد الجذري للتطبيق (' / '):
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7804_39" 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">'&lt;h1&gt;Hello World!&lt;/h1&gt;'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	حيث تقبل دالة المعالج معاملين الأول <a href="http://expressjs.com/en/4x/api.html#req" rel="external nofollow">request</a> ويحوي كل المعلومات عن طلب HTTP، ويستخدم الثاني <a href="http://expressjs.com/en/4x/api.html#res" rel="external nofollow">response</a> لتحديد آلية الاستجابة للطلب. يستجيب الخادم إلى الطلب في شيفرتنا باستخدام التابع <a href="http://expressjs.com/en/4x/api.html#res.send" rel="external nofollow">send</a> العائد للكائن <code>response</code>. حيث يرسل الخادم عند الاستجابة العبارة النصية <code>&lt;h1&gt;Hello World!&lt;/h1&gt;</code>التي مُرّرت كمعامل للتابع <code>send</code>. وطالما أن القيمة المعادة نصية ستَسند express القيمة text/html إلى ترويسة "نوع المحتوى" ويعاد رمز الحالة 200. يمكننا التحقق من ذلك من خلال النافذة Network في طرفية تطوير المتصفح:
</p>

<p style="text-align: center;">
	<img alt="server_express_response1_004.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55506" data-unique="8ia8idtm1" src="https://academy.hsoub.com/uploads/monthly_2021_01/server_express_response1_004.png.bee9b9398751bd8fa5a7f468b868c9a0.png"></p>

<p>
	بالنسبة للمسار الثاني فإنه يعرّف معالج حدث يتعامل مع طلبات HTTP-GET الموجهة إلى موقع وجود الملاحظات، نهاية المسار notes:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7804_41" style="">
<span class="pln">app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">notes</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	يستجيب الخادم إلى الطلب باستخدام التابع <a href="http://expressjs.com/en/4x/api.html#res.json" rel="external nofollow">json</a> العائد للكائن <code>response</code>. حيث تُرسل مصفوفة الملاحظات التي مُرّرت للتابع بصيغة JSON النصية. وكذلك ستتكفل express بإسناد القيمة application/json إلى ترويسة "نوع المحتوى".
</p>

<p style="text-align: center;">
	<img alt="server_express_response2_005.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55507" data-unique="kqdvk31sb" src="https://academy.hsoub.com/uploads/monthly_2021_01/server_express_response2_005.png.5b34810fea8b692aead2ecf8b34ab9e7.png"></p>

<p>
	سنلقي تاليًا نظرة سريعة على البيانات التي أرسلت بصيغة JSON. كان علينا سابقًا تحويل البيانات إلى نصوص مكتوبة بصيغة JSON باستخدام التابع <code>JSON.stringify</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7804_43" style="">
<span class="pln">response</span><span class="pun">.</span><span class="pln">end</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">notes</span><span class="pun">))</span></pre>

<p>
	لا حاجة لذلك عند استخدام express، فهي تقوم بذلك تلقائيًا. وتجدر الإشارة هنا أنه لا فائدة من كون <a href="https://en.wikipedia.org/wiki/JSON" rel="external nofollow">JSON</a> مجرد نص، بل يجب أن يكون كائن JavaScript مثل notes الذي أُسند إليه. ستشرح لك التجربة الموضحة في الشكل التالي هذه الفكرة:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="55495" href="https://academy.hsoub.com/uploads/monthly_2021_01/json_as_object_006.png.4e82b328fa20d0b9a977035a7ed43c22.png" rel=""><img alt="json_as_object_006.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55495" data-unique="9h1sfdgup" src="https://academy.hsoub.com/uploads/monthly_2021_01/json_as_object_006.png.4e82b328fa20d0b9a977035a7ed43c22.png"></a>
</p>

<p>
	أُنجز الاختبار السابق باستخدام الوحدة التفاعلية <a href="https://nodejs.org/docs/latest-v8.x/api/repl.html" rel="external nofollow">node-repl</a>. يمكنك تشغيل هذه الوحدة بكتابة node في سطر الأوامر. تفيدك هذه الوحدة بشكل خاص لتوضيح طريقة عمل الأوامر، وننصح بشدة أن تستخدمها أثناء كتابة الشيفرة.
</p>

<h2>
	مكتبة nodemon
</h2>

<p>
	رأينا سابقًا ضرورة إعادة تشغيل التطبيق عند تعديله حتى تظهر نتائج التعديلات. ونقوم بذلك عن طريق إغلاق التطبيق أولًا باستخدام ctrl+c ثم تشغيله من جديد. طبعًا فالأمر مربك بالموازنة مع طريقة عمل React التي تقوم بذلك تلقائيًا بمجرد تغيّر الشيفرة. إن حل هذه المشكلة يكمن في استخدام <a href="https://github.com/remy/nodemon" rel="external nofollow">nodemon</a>.
</p>

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

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

<p>
	سنثبت الآن nodemon كملف اعتمادية باستخدام الأمر:
</p>

<pre class="ipsCode">
npm install --save-dev nodemon
</pre>

<p>
	سيتغير أيضًا محتوى الملف package.json:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7804_45" style="">
<span class="pun">{</span><span class="pln">
  </span><span class="com">//...</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="pun">,</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.2"</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	إن أخطأت في كتابة الأمر وظهر ملف اعتمادية nodemon تحت مسمى ملفات الاعتمادية "dependencies" بدلًا من ملفات اعتمادية التطوير"devDependencies"، أصلح الأمر يدويًا في ملف package.json. إن الإشارة لملف اعتمادية على أنه ملف اعتمادية تطوير هو للدلالة على الحاجة له أثناء تطوير التطبيق فقط، ليعيد على سبيل المثال تشغيل التطبيق كما تفعل nodemon. ولن تحتاجها لاحقًا عندما تشغل التطبيق على خادم الاستثمار الفعلي مثل Heroku.
</p>

<p>
	لتشغيل التطبيق مع <code>nodemon</code> اكتب الأمر التالي:
</p>

<pre class="ipsCode">
node_modules/.bin/nodemon index.js
</pre>

<p>
	سيسبب الآن أي تغيير في الشيفرة إعادة تشغيل الخادم تلقائيًا. وطبعًا لا فائدة من إعادة تشغيل الخادم إن لم نحدث المتصفح الذي يعرض الصفحة، لذلك علينا القيام بذلك يدويًا. فلا نمتلك حاليًا وسيلة <a href="https://gaearon.github.io/react-hot-loader/getstarted/" rel="external nofollow">لإعادة التحميل الدائم</a> (hot reload) كما في React.
</p>

<p>
	يبدو الأمر السابق طويلًا، فلنعرف إذًا سكريبت npm خاصة بتشغيل التطبيق ضمن الملف package.json:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7804_48" style="">
<span class="pun">{</span><span class="pln">
  </span><span class="com">// ..</span><span class="pln">
  </span><span class="str">"scripts"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">"start"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"node index.js"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"dev"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"nodemon index.js"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"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="com">// ..</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	لا حاجة في هذه السكريبت لتحديد المسار التالي <em>node_modules/.bin/nodemon</em> للمكتبة nodemon، لأن npm يعرف أين سيبحث عنها.
</p>

<p>
	لنشغل الخادم بوضعية التطوير كما يلي:
</p>

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

<p>
	على خلاف مخطوطتي start و test يجب إضافة التعليمة <code>run</code> إلى الأمر.
</p>

<h2>
	العمل مع واجهة التطبيقات REST
</h2>

<p>
	لنجعل تطبيقنا قادرًا على تأمين واجهة http متوافقة مع REST كما فعلنا مع <a href="https://github.com/typicode/json-server#routes" rel="external nofollow">خادم JSON</a>. قدم روي فيلدينغ مفهوم نقل حالة العرض (REpresentational State Transfer) واختصارًا REST، عام 2000 في <a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm" rel="external nofollow">أطروحته للدكتوراه</a> وهي عبارة عن أسلوب تصميمي لبناء تطبيقات ويب بمقاسات قابلة للتعديل.
</p>

<p>
	لن نغوص في تعريف REST، أو نهدر وقتنا في التفكير بالواجهات المتوافقة أو غير المتوافقة معها، بل سنعتمد <a href="https://en.wikipedia.org/wiki/Representational_state_transfer#Applied_to_Web_services" rel="external nofollow">مقاربة ضيقة</a> تهتم فقط بكيفية فهم التوافق مع REST من منظور تطبيقات الويب، فمفهوم REST الأصلي ليس محدودًا بتطبيقات الويب.
</p>

<p>
	لقد أشرنا في القسم السابق أن REST تعتبر كل الأشياء الفردية -كالملاحظات في تطبيقنا- موردًا. ولكل مورد موقع محدد URL يمثل العنوان الفريد لهذا المورد. إن أحد الأعراف المتبعة في إنشاء عنوان فريد للمورد هو دمج نوع المورد مع المعرف الفريد له. فلو افترضنا أن الموقع الجذري للخدمة هو <em>www.example.com/api</em>، وأننا عرّفنا نوع المورد الذي يمثل الملاحظات على أنه note وأننا نحتاج إلى المورد note ذو المعرف 10، سيكون عنوان موقع المورد www.example.com/api/notes/10. وسيكون عنوان موقع مجموعة الملاحظات www.example.com/api/notes.
</p>

<p>
	يمكننا تنفيذ العديد من العمليات على الموارد، وتعرّف العملية التي نريد تنفيذها على مورد على أنها فعل HTTP:
</p>

<table>
<thead><tr>
<th>
				الموقع
			</th>
			<th>
				الفعل
			</th>
			<th>
				الوظيفة
			</th>
		</tr></thead>
<tbody>
<tr>
<td>
				notes/10
			</td>
			<td>
				GET
			</td>
			<td>
				إحضار مورد واحد
			</td>
		</tr>
<tr>
<td>
				notes
			</td>
			<td>
				GET
			</td>
			<td>
				إحضار كل موارد المجموعة
			</td>
		</tr>
<tr>
<td>
				notes
			</td>
			<td>
				POST
			</td>
			<td>
				إنشاء مورد جديد بناء على البيانات الموجودة في الطلب
			</td>
		</tr>
<tr>
<td>
				notes/10
			</td>
			<td>
				DELETE
			</td>
			<td>
				حذف المورد المحدد
			</td>
		</tr>
<tr>
<td>
				notes/10
			</td>
			<td>
				PUT
			</td>
			<td>
				استبدال كامل المورد المحدد بالبيانات الموجودة في الطلب
			</td>
		</tr>
<tr>
<td>
				notes/10
			</td>
			<td>
				PATCH
			</td>
			<td>
				استبدال جزء من المورد المحدد بالبيانات الموجودة في الطلب
			</td>
		</tr>
</tbody>
</table>
<style type="text/css">
table {
    width: 100%;
}

thead {
    vertical-align: middle;

    text-align: center;} 

td, th {
    border: 1px solid #dddddd;
    text-align: right;
    padding: 8px;
    text-align: inherit;

}
tr:nth-child(even) {
    background-color: #dddddd;
}</style>
<p>
	وهكذا نحدد الخطوط العامة لما تعنيه REST <a href="https://en.wikipedia.org/wiki/Representational_state_transfer#Architectural_constraints" rel="external nofollow">كواجهة نموذجية</a>، أي أنها تعرّف أسلوبُا ثابتُا ومنسقًا للواجهات، تجعل الأنظمة المختلفة قادرة على العمل المشترك.
</p>

<p>
	إنّ المفهوم الذي اعتمدناه حول REST يصنف <a href="https://martinfowler.com/articles/richardsonMaturityModel.html" rel="external nofollow">كمستوًى ثانٍ من التوافق</a> وفق نموذج توافق ريتشاردسون. مع هذا فنحن إذ عّرفنا <a href="http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven" rel="external nofollow">واجهة REST</a>، لم نعرفها لتتوافق تمامًا مع المعايير التي قدمها روي فيلدينغ في أطروحته، وهذا حال الغالبية العظمى من الواجهات التي تدعي توافقها الكامل مع REST.
</p>

<p>
	يشار إلى نموذجنا الذي يقدم <a href="https://en.wikipedia.org/wiki/Create,_read,_update_and_delete" rel="external nofollow">واجهة أساسية (إنشاء-قراءة-تحديث-حذف)</a> (CRUD: Create-Read-Update-Delete) في العديد من المراجع (<a href="http://shop.oreilly.com/product/9780596529260.do" rel="external nofollow">Richardson, Ruby: RESTful Web Services</a>) على أنه تصميم موجه للعمل مع الموارد <a href="https://en.wikipedia.org/wiki/Resource-oriented_architecture" rel="external nofollow">resource oriented architecture</a> بدلًا من كونه متوافقًا مع REST.
</p>

<h2>
	إحضار مورد واحد
</h2>

<p>
	لنوسع تطبيقنا بحيث يقدم واجهة REST تتعامل مع الملاحظات بشكل فردي. لكن علينا أولًا إنشاء <a href="http://expressjs.com/en/guide/routing.html" rel="external nofollow">مسار</a> لإحضار المورد. سنعتمد نموذج (نوع/معرف) في الوصول إلى موقع المورد (notes/10 مثلًا).
</p>

<p>
	يمكن تعريف <a href="http://expressjs.com/en/guide/routing.html#route-parameters" rel="external nofollow">معاملات</a> المسار في express كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7804_50" style="">
<span class="pln">app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/api/notes/:id'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> id </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="kwd">const</span><span class="pln"> note </span><span class="pun">=</span><span class="pln"> notes</span><span class="pun">.</span><span class="pln">find</span><span class="pun">(</span><span class="pln">note </span><span class="pun">=&gt;</span><span class="pln"> note</span><span class="pun">.</span><span class="pln">id </span><span class="pun">===</span><span class="pln"> id</span><span class="pun">)</span><span class="pln">
  response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">note</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	سيتعامل المسار <code>(...,'app.get('/api/notes/:id</code> مع طلبات HTTP-GET التي تأتي بالصيغة api/notes/SOMETHING حيث يشير SOMETHING إلى نص افتراضي. يمكن الوصول إلى المعامل <code>id</code> للمسار من خلال الكائن <a href="http://expressjs.com/en/api.html#req" rel="external nofollow">request</a>:
</p>

<pre class="ipsCode">
const id = request.params.id
</pre>

<p>
	نستخدم التابع find الخاص بالمصفوفات للبحث عن الملاحظة من خلال معرفها الفريد id والذي يجب أن يتطابق مع قيمة المعامل، ثم تعاد الملاحظة إلى مرسل الطلب. لكن لو جربنا الشيفرة المكتوبة واستخدمنا المتصفح للوصول إلى العنوان <a href="http://localhost:3001/api/notes/1%D8%8C" ipsnoembed="false" rel="external nofollow">http://localhost:3001/api/notes/1،</a> فلن يعمل التطبيق كما هو متوقع، وستظهر صفحة فارغة. لن يفاجئني هذا كمطور اعتاد على المشاكل، لذا فقد حان وقت التنقيح. لنزرع الأمر <code>console.log</code> كما اعتدنا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7804_52" style="">
<span class="pln">app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/api/notes/:id'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> id </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
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">id</span><span class="pun">)</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> note </span><span class="pun">=</span><span class="pln"> notes</span><span class="pun">.</span><span class="pln">find</span><span class="pun">(</span><span class="pln">note </span><span class="pun">=&gt;</span><span class="pln"> note</span><span class="pun">.</span><span class="pln">id </span><span class="pun">===</span><span class="pln"> id</span><span class="pun">)</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">note</span><span class="pun">)</span><span class="pln">
  response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">note</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	بمجرد انتقال المتصفح إلى العنوان السابق ستُظهر الطرفية الرسالة التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="55492" href="https://academy.hsoub.com/uploads/monthly_2021_01/fetch_single_res_error_007.png.b69531bfab1a56193f6d0511fc711b2c.png" rel=""><img alt="fetch_single_res_error_007.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55492" data-unique="4tiep2jrh" src="https://academy.hsoub.com/uploads/monthly_2021_01/fetch_single_res_error_007.png.b69531bfab1a56193f6d0511fc711b2c.png"></a>
</p>

<p>
	لقد مُرِّر المعامل <code>id</code> إلى التطبيق، لكن التابع find لم يجد ما يتطابق معه. وللتعمق في تقصي مصدر الخطأ زرعنا الأمر <code>console.log</code> ضمن الدالة التي تُمرَّر كمعامل للتابع find. وكان علينا لتنفيذ ذلك إعادة كتابة الدالة السهمية بشكلها الموسع واستخدام عبارة <code>return</code> في نهايتها:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7804_54" style="">
<span class="pln">app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/api/notes/:id'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> id </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="kwd">const</span><span class="pln"> note </span><span class="pun">=</span><span class="pln"> notes</span><span class="pun">.</span><span class="pln">find</span><span class="pun">(</span><span class="pln">note </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">note</span><span class="pun">.</span><span class="pln">id</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">typeof</span><span class="pln"> note</span><span class="pun">.</span><span class="pln">id</span><span class="pun">,</span><span class="pln"> id</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">typeof</span><span class="pln"> id</span><span class="pun">,</span><span class="pln"> note</span><span class="pun">.</span><span class="pln">id </span><span class="pun">===</span><span class="pln"> id</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> note</span><span class="pun">.</span><span class="pln">id </span><span class="pun">===</span><span class="pln"> id
  </span><span class="pun">})</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">note</span><span class="pun">)</span><span class="pln">
  response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">note</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	سيطبع لنا الأمر <code>console.log</code> (معرف الملاحظة، نوعه، المتغير id، نوعه، هل هناك تطابق). وعندما نتوجه مجددًا نحو عنوان الملاحظة عبر المتصفح، ستُطبع عبارة مختلفة على الطرفية مع كل استدعاء للدالة:
</p>

<pre class="ipsCode">
1 'number' '1' 'string' false
2 'number' '1' 'string' false
3 'number' '1' 'string' false
</pre>

<p>
	يبدو أن سبب المشكلة قد توضّح الآن. إن المتغيّر <code>id</code> يضم قيمة نصية هي "1"، بينما يحمل معرف الملاحظة قيم صحيحة. حيث يعتبر عامل المساواة الثلاثي === في JavaScript وبشكل افتراضي أن القيم من أنواع مختلفة غير متساوية. لنصحح الخطأ بتحويل قيمة المتغير <code>id</code> إلى <a href="https://wiki.hsoub.com/JavaScript/Number" rel="external">عدد</a>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7804_56" style="">
<span class="pln">app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/api/notes/:id'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </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"> id </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"> note </span><span class="pun">=</span><span class="pln"> notes</span><span class="pun">.</span><span class="pln">find</span><span class="pun">(</span><span class="pln">note </span><span class="pun">=&gt;</span><span class="pln"> note</span><span class="pun">.</span><span class="pln">id </span><span class="pun">===</span><span class="pln"> id</span><span class="pun">)</span><span class="pln">
  response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">note</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	لقد تم الأمر!
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="55493" href="https://academy.hsoub.com/uploads/monthly_2021_01/fetch_single_res_ok_008.png.848ec9d5d8bf1757dc9698ce36f3f61b.png" rel=""><img alt="fetch_single_res_ok_008.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55493" data-unique="1vi7mnld0" src="https://academy.hsoub.com/uploads/monthly_2021_01/fetch_single_res_ok_008.png.848ec9d5d8bf1757dc9698ce36f3f61b.png"></a>
</p>

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

<p style="text-align: center;">
	<img alt="server_resp_nodata_009.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55509" data-unique="4dyvl9ivj" src="https://academy.hsoub.com/uploads/monthly_2021_01/server_resp_nodata_009.png.d6a8b050ad972013e51c49ce90c3af23.png"></p>

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

<p>
	إن السبب الكامن خلف هذا السلوك، هو أن المتغير <code>note</code> سيأخذ القيمة <code>undefined</code> إن لم نحصل على تطابق. وبالتالي لابد من التعامل مع هذه المشكلة على الخادم الذي يجب أن يعيد رمز الحالة <a href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.5" rel="external nofollow">not found 404</a> بدلًا من 200. لنعدل الشيفرة كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7804_58" style="">
<span class="pln">app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/api/notes/:id'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> id </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"> note </span><span class="pun">=</span><span class="pln"> notes</span><span class="pun">.</span><span class="pln">find</span><span class="pun">(</span><span class="pln">note </span><span class="pun">=&gt;</span><span class="pln"> note</span><span class="pun">.</span><span class="pln">id </span><span class="pun">===</span><span class="pln"> id</span><span class="pun">)</span><span class="pln">

  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">note</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">    
response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">note</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
 </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">404</span><span class="pun">).</span><span class="pln">end</span><span class="pun">()</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	عندما لا تكون هناك بيانات لإعادتها نستخدم التابع <a href="http://expressjs.com/en/4x/api.html#res.status" rel="external nofollow">status</a> لإعداد حالة التطبيق، والتابع <a href="http://expressjs.com/en/4x/api.html#res.end" rel="external nofollow">end</a> للاستجابة على الطلب دون إرسال أية بيانات.
</p>

<p>
	تظهر لك العبارة الشرطية if أن كل كائنات JavaScript <a href="ttps://developer.mozilla.org/en-US/docs/Glossary/Truthy" rel="external nofollow">محققة</a> (تعيد القيمة المنطقية "صحيح" في عمليات الموازنة)، بينما يعتبر الكائن undefiend <a href="https://developer.mozilla.org/en-US/docs/Glossary/Falsy" rel="external nofollow">خاطئ</a>.
</p>

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

<h2>
	حذف الموارد
</h2>

<p>
	لنضف مسارًا لحذف مورد محدد. يتم ذلك من خلال الطلب HTTP-DELETE إلى موقع المورد:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7804_60" style="">
<span class="pln">app</span><span class="pun">.</span><span class="kwd">delete</span><span class="pun">(</span><span class="str">'/api/notes/:id'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> id </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">
  notes </span><span class="pun">=</span><span class="pln"> notes</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="pln">note </span><span class="pun">=&gt;</span><span class="pln"> note</span><span class="pun">.</span><span class="pln">id </span><span class="pun">!==</span><span class="pln"> id</span><span class="pun">)</span><span class="pln">

  response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">204</span><span class="pun">).</span><span class="pln">end</span><span class="pun">()</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	إن نجحت عملية الحذف، أي أن المورد وُجد وحُذف، سيجيب التطبيق على الطلب برمز الحالة <a href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.5" rel="external nofollow">no content 204 </a> دون إعادة أية بيانات. لا يوجد رمز حالة محدد لإعادته عند محاولة حذف مورد غير موجود، لذلك ولتبسيط الأمر سنعيد الرمز 204 في الحالتين.
</p>

<h2>
	اختبار التطبيقات باستخدام Postman
</h2>

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

<p>
	ستجد العديد من الأدوات الجاهزة لتسهيل الاختبارات على الواجهة الخلفية. فمنها على سبيل المثال <a href="https://curl.haxx.se/" rel="external nofollow">curl</a> وهو برنامج سطر أوامر أشرنا إليه في القسم السابق، لكننا سنستخدم هنا <a href="https://www.getpostman.com/" rel="external nofollow">Postman</a> للقيام بهذه المهمة. إذًا لنثبت Postman:
</p>

<p style="text-align: center;">
	<img alt="postman_install_010.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55500" data-unique="kz4jdvzpx" src="https://academy.hsoub.com/uploads/monthly_2021_01/postman_install_010.png.960f91808286ccaef8adc0679e8b92d6.png"></p>

<p>
	من السهل جدًا استخدام Postman في حالتنا هذه، إذ يكفي أن تختار موقعا وطلب HTTP مناسب (DELETE في حالتنا). يستجيب خادم الواجهة الخلفية بشكل صحيح كما يبدو. فلو أرسلنا إلى الموقع <a href="http://localhost:3001/api/notes" ipsnoembed="false" rel="external nofollow">http://localhost:3001/api/notes</a> طلب HTTP-GET سنجد أن الملاحظة التي معرّفها 2 غير موجودة ضمن قائمة الملاحظات، فقد نجحت عملية الحذف.
</p>

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

<h2>
	عميل REST في بيئة التطوير Visual Studio Code
</h2>

<p>
	إن كنت تستخدم Visual Studio Code في تطوير التطبيقات يمكنك تثبيت الإضافة <a href="https://marketplace.visualstudio.com/items?itemName=humao.rest-client" rel="external nofollow">REST client</a> واستعمالها بدلًا من Postman حيث ننشئ مجلدًا يدعى requests عند جذر التطبيق، ثم نحفظ كل طلبات REST client في ملف ينتهي باللاحقة rest. ونضعه في المجلد السابق.
</p>

<p>
	لننشئ الآن الملف get<em>all</em>notes.rest ونعرّف فيه كل طلبات HTTP التي تحضر الملاحظات:
</p>

<p style="text-align: center;">
	<img alt="rest_client_file_011.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55504" data-unique="uc3c3q9p6" src="https://academy.hsoub.com/uploads/monthly_2021_01/rest_client_file_011.png.b51eecfbf73795244fe4543658bd6a22.png"></p>

<p>
	بالنقر على النص Send Requests، سينفذ REST client طلبات HTTP وستظهر استجابة الخادم في نافذة المحرر.
</p>

<p style="text-align: center;">
	<img alt="rest_client_exe_012.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55503" data-unique="eg2q1cq2m" src="https://academy.hsoub.com/uploads/monthly_2021_01/rest_client_exe_012.png.bc7c1ea1dfe36e499c960d5cd3792e5f.png"></p>

<h2>
	استقبال البيانات
</h2>

<p>
	سنوسع التطبيق بحيث نغدو قادرين على إضافة ملاحظة جديدة إلى الخادم. تنفذ العملية باستخدام طلب HTTP-POST إلى العنوان <a href="http://localhost:3001/api/notes%D8%8C" ipsnoembed="false" rel="external nofollow">http://localhost:3001/api/notes،</a> حيث تُرسل المعلومات عن الملاحظة الجديدة بصيغة JSON ضمن جسم الطلب. وحتى نصل إلى معلومات الملاحظة بسهولة سنحتاج إلى <a href="https://expressjs.com/en/api.html" rel="external nofollow">مفسّر JSON</a> الذي تقدمه express ويستخدم عبر تنفيذ الأمر <code>(()app.use(express.json</code>.
</p>

<p>
	لنستخدم مفسّر JSON ولنضف معالج حدث أوّلي للتعامل مع طلبات HTTP-POST:
</p>

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

app</span><span class="pun">.</span><span class="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">//...</span><span class="pln">

app</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> note </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">body
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">note</span><span class="pun">)</span><span class="pln">

  response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">note</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	يمكن لدالة معالج الحدث الوصول إلى البيانات من خلال الخاصية <code>body</code> للكائن <code>request</code>. لكن من غير استخدام مفسر JSON ستكون هذه الخاصية غير معرّفة undefined. فوظيفة المفسر استخلاص بيانات JSON من الطلب وتحويلها إلى كائن JavaScript يرتبط مع الخاصية <code>body</code> للكائن <code>request</code>وذلك قبل أن تُستدعى الدالة التي تتعامل مع مسار الطلب.
</p>

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

<p style="text-align: center;">
	<img alt="receive_confirm_postman_013.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55502" data-unique="t7a1zrjzz" src="https://academy.hsoub.com/uploads/monthly_2021_01/receive_confirm_postman_013.png.bc1c6855fbbae6a8998363e53b29dc22.png"></p>

<p>
	يطبع البرنامج البيانات التي أرسلناها ضمن الطلب على الطرفية:
</p>

<p style="text-align: center;">
	<img alt="postman_print_data_014.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55501" data-unique="y31pu2lby" src="https://academy.hsoub.com/uploads/monthly_2021_01/postman_print_data_014.png.79927b7246425feddcbe77923dabfe0b.png"></p>

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

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="55489" href="https://academy.hsoub.com/uploads/monthly_2021_01/console_app_errors_015.png.d1b6d5cf590869ef2b7f1ccc8e982edb.png" rel=""><img alt="console_app_errors_015.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55489" data-unique="iq500an4l" src="https://academy.hsoub.com/uploads/monthly_2021_01/console_app_errors_015.png.d1b6d5cf590869ef2b7f1ccc8e982edb.png"></a>
</p>

<p>
	من المفيد جدًا التحقق باستمرار من الطرفية لتتأكد من أن كل شيء يسير كما هو متوقع ضمن الواجهة الخلفية وفي مختلف الحالات، كما فعلنا عندما أرسلنا البيانات باستخدام الطلب HTTP-POST. ومن الطبيعي استخدام الأمر <code>console.log</code> في مرحلة التطوير لمراقبة الوضع.
</p>

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

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="55494" href="https://academy.hsoub.com/uploads/monthly_2021_01/incorrect_bodytype_016.png.918518a86e1b4a0001d49cb3ff2bf1a9.png" rel=""><img alt="incorrect_bodytype_016.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55494" data-unique="s6fvtgkv5" src="https://academy.hsoub.com/uploads/monthly_2021_01/incorrect_bodytype_016.png.918518a86e1b4a0001d49cb3ff2bf1a9.png"></a>
</p>

<p>
	حيث عُرّف نوع المحتوى على أنه text/plain (نص أو فارغ):
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="55490" href="https://academy.hsoub.com/uploads/monthly_2021_01/error_content_type_017.png.e35e2b2af2ee2ce4cb2c028abd8b3a26.png" rel=""><img alt="error_content_type_017.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55490" data-unique="kiwlgraea" src="https://academy.hsoub.com/uploads/monthly_2021_01/error_content_type_017.png.e35e2b2af2ee2ce4cb2c028abd8b3a26.png"></a>
</p>

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

<p style="text-align: center;">
	<img alt="server_rec_empty_proj_018.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55508" data-unique="8hs6rttsh" src="https://academy.hsoub.com/uploads/monthly_2021_01/server_rec_empty_proj_018.png.dc66e64012427d5626f532b4334a84d9.png"></p>

<p>
	لن يتمكن الخادم من تفسير البيانات بشكل صحيح دون وجود القيمة الصحيحة في الترويسة. ولن يخمن حتى صيغة البيانات نظرًا <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types" rel="external nofollow">للكمية الهائلة</a> من أنواع المحتوى التي قد تكونها البيانات.
</p>

<p>
	إن كنت قد ثبتت VS REST client ستجد أن الطلب HTTP-POST سيرسل بمساعدة REST client كما يلي:
</p>

<p style="text-align: center;">
	<img alt="rest_client_post_019.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55505" data-unique="7zmwn04eq" src="https://academy.hsoub.com/uploads/monthly_2021_01/rest_client_post_019.png.c32b587783385fa073c0c0393c85be64.png"></p>

<p>
	حيث أنشأنا الملف create_note.rest وصغنا الطلب كما يوصي <a href="https://github.com/Huachao/vscode-restclient/blob/master/README.md#usage" rel="external nofollow">توثيق البرنامج</a>.
</p>

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

<p>
	<strong>ملاحظات جانبية مهمة</strong>
</p>

<ul>
<li>
		<p>
			قد تحتاج عند تنقيح التطبيقات إلى معرفة أنواع الترويسات التي ترافق طلبات HTTP. يعتبر استخدام التابع <a href="http://expressjs.com/en/4x/api.html#req.get" rel="external nofollow">get</a> العائد للكائن request، إحدى الطرق التي تسمح بذلك. حيث يستخدم التابع للحصول على قيمة ترويسة محددة، كما يمتلك الكائن request الخاصية headers التي تحوي كل الترويسات الخاصة بطلب محدد.
		</p>
	</li>
	<li>
		<p>
			قد تحدث الأخطاء عند استخدام VS REST client لو أضفت سطرًا فارغًا بين السطر الأعلى والسطر الذي يعرّف ترويسات HTTP. حيث يفسر البرنامج ذلك بأن كل الترويسات قد تُركَت فارغة، وبالتالي لن يعلم خادم الواجهة الخلفية أن البيانات التي استقبلها بصيغة JSON.
		</p>
	</li>
</ul>
<p>
	يمكنك أن ترصد ترويسة "نوع المحتوى" المفقودة إذا ما طبعت كل ترويسات الطلب على الطرفية باستخدام الأمر <code>(console.log(request.headers</code>.
</p>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7804_64" style="">
<span class="pln">app</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> maxId </span><span class="pun">=</span><span class="pln"> notes</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="typ">Math</span><span class="pun">.</span><span class="pln">max</span><span class="pun">(...</span><span class="pln">notes</span><span class="pun">.</span><span class="pln">map</span><span class="pun">(</span><span class="pln">n </span><span class="pun">=&gt;</span><span class="pln"> n</span><span class="pun">.</span><span class="pln">id</span><span class="pun">))</span><span class="pln"> 
    </span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pln">

  </span><span class="kwd">const</span><span class="pln"> note </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">body
  note</span><span class="pun">.</span><span class="pln">id </span><span class="pun">=</span><span class="pln"> maxId </span><span class="pun">+</span><span class="pln"> </span><span class="lit">1</span><span class="pln">

  notes </span><span class="pun">=</span><span class="pln"> notes</span><span class="pun">.</span><span class="pln">concat</span><span class="pun">(</span><span class="pln">note</span><span class="pun">)</span><span class="pln">

  response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">note</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span></pre>

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

<p>
	الشيء الآخر أن تطبيقنا بشكله الحالي يعاني مشكلة مفادها أن طلب HTTP-POST يمكن أن يُستخدَم لإضافة كائن له خصائص غير محددة. لذلك سنحسن التطبيق بأن لا نسمح لقيمة الخاصية <code>content</code> أن تكون فارغة، وسنعطي الخاصيتين <code>important</code> و<code>date</code> قيمًا افتراضية، وسنهمل بقية الخصائص:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7804_66" style="">
<span class="kwd">const</span><span class="pln"> generateId </span><span class="pun">=</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> maxId </span><span class="pun">=</span><span class="pln"> notes</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="typ">Math</span><span class="pun">.</span><span class="pln">max</span><span class="pun">(...</span><span class="pln">notes</span><span class="pun">.</span><span class="pln">map</span><span class="pun">(</span><span class="pln">n </span><span class="pun">=&gt;</span><span class="pln"> n</span><span class="pun">.</span><span class="pln">id</span><span class="pun">))</span><span class="pln">
    </span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> maxId </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">

app</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/api/notes'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> body </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">body

  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">body</span><span class="pun">.</span><span class="pln">content</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">400</span><span class="pun">).</span><span class="pln">json</span><span class="pun">({</span><span class="pln"> 
      error</span><span class="pun">:</span><span class="pln"> </span><span class="str">'content missing'</span><span class="pln"> 
    </span><span class="pun">})</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  </span><span class="kwd">const</span><span class="pln"> note </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    content</span><span class="pun">:</span><span class="pln"> body</span><span class="pun">.</span><span class="pln">content</span><span class="pun">,</span><span class="pln">
    important</span><span class="pun">:</span><span class="pln"> body</span><span class="pun">.</span><span class="pln">important </span><span class="pun">||</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">,</span><span class="pln">
    date</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">(),</span><span class="pln">
    id</span><span class="pun">:</span><span class="pln"> generateId</span><span class="pun">(),</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  notes </span><span class="pun">=</span><span class="pln"> notes</span><span class="pun">.</span><span class="pln">concat</span><span class="pun">(</span><span class="pln">note</span><span class="pun">)</span><span class="pln">

  response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">(</span><span class="pln">note</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	وضعنا الشيفرة التي تولد المعرف id للملاحظة الجديدة ضمن الدالة <code>generateId</code>. فعندما يستقبل الخادم بيانات قيمة الخاصية <code>content</code> لها غير موجودة، سيجيب على الطلب برمز الحالة <a href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1" rel="external nofollow">400 bad request</a>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7804_68" style="">
<span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">body</span><span class="pun">.</span><span class="pln">content</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">400</span><span class="pun">).</span><span class="pln">json</span><span class="pun">({</span><span class="pln"> 
    error</span><span class="pun">:</span><span class="pln"> </span><span class="str">'content missing'</span><span class="pln"> 
  </span><span class="pun">})</span><span class="pln">
</span><span class="pun">}</span></pre>

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

<p>
	إن لم تحمل الخاصية <code>important</code> قيمة فسنعطيها القيمة الإفتراضية <code>false</code>. لاحظ الطريقة الغريبة التي استخدمناها:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7804_70" style="">
<span class="pln">important</span><span class="pun">:</span><span class="pln"> body</span><span class="pun">.</span><span class="pln">important </span><span class="pun">||</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">,</span></pre>

<p>
	وتفسر الشيفرة السابقة كالتالي: إن حملت الخاصية <code>important</code> للبيانات المستقبلة قيمة ستعتمد العبارة السابقة هذه القيمة وإلا ستعطيها القيمة <code>false</code>. أمَّا عندما تكون قيمة الخاصية important هي false عندها ستعيد العبارة التالية <em>body.important || false</em> القيمة false بناء على قيمة الطرف الأيمن من العبارة.
</p>

<p>
	ستجد شيفرة التطبيق بشكله الحالي في المستودع part3-1 على موقع <a href="https://github.com/fullstack-hy2020/part3-notes-backend/tree/part3-12020/part2-notes/tree/part3-1" rel="external nofollow">GitHub</a>. يحوي المسار الرئيسي <a href="https://github.com/fullstack-hy2020/part3-notes-backend/tree/part3-1" rel="external nofollow">للمستودع</a> كما ستلاحظ شيفرات للنسخ التي سنطورها لاحقًا، لكن النسخة الحالية في المسار <a href="https://github.com/fullstack-hy2020/part2-notes/tree/part3-1" rel="external nofollow">part3-1</a>.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="55487" href="https://academy.hsoub.com/uploads/monthly_2021_01/app_curr_state_20.png.f0005b8aa2e22156b5538306d46e7176.png" rel=""><img alt="app_curr_state_20.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55487" data-unique="hw3b9joos" src="https://academy.hsoub.com/uploads/monthly_2021_01/app_curr_state_20.png.f0005b8aa2e22156b5538306d46e7176.png"></a>
</p>

<p>
	إن نسخت المشروع، فتأكد من تنفيذ الأمر <code>npm install</code> قبل أن تشغل التطبيق باستخدام إحدى التعليمتين <code>npm start</code> أو <code>npm run dev</code>.
</p>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7804_72" style="">
<span class="kwd">const</span><span class="pln"> generateId </span><span class="pun">=</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> maxId </span><span class="pun">=</span><span class="pln"> notes</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="typ">Math</span><span class="pun">.</span><span class="pln">max</span><span class="pun">(...</span><span class="pln">notes</span><span class="pun">.</span><span class="pln">map</span><span class="pun">(</span><span class="pln">n </span><span class="pun">=&gt;</span><span class="pln"> n</span><span class="pun">.</span><span class="pln">id</span><span class="pun">))</span><span class="pln">
    </span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> maxId </span><span class="pun">+</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يحتوي جسم الدالة سطرًا يبدو غريبًا نوعًا ما:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7804_74" style="">
<span class="typ">Math</span><span class="pun">.</span><span class="pln">max</span><span class="pun">(...</span><span class="pln">notes</span><span class="pun">.</span><span class="pln">map</span><span class="pun">(</span><span class="pln">n </span><span class="pun">=&gt;</span><span class="pln"> n</span><span class="pun">.</span><span class="pln">id</span><span class="pun">))</span></pre>

<p>
	ينشئ التابع <code>(notes.map(n=&gt;n.id</code> مصفوفة جديدة تضم كل قيم المعرفات id للملاحظات. ثم يعيد التابع <a href="https://wiki.hsoub.com/JavaScript/Math/max" rel="external">Math.max</a> أعلى قيمة من القيم التي مُرّرت إليه. وطالما أن نتيجة تطبيق الأمر <code>(notes.map(n=&gt;n.id</code>s ستكون مصفوفة، فمن الممكن تمريرها مباشرة كمعامل للتابع <code>Math.max</code>. وتذكّر أنه يمكن فصل المصفوفة إلى عناصرها باستخدام عامل <a href="https://wiki.hsoub.com/JavaScript/Spread_Operator" rel="external">النشر</a> (…).
</p>

<h2>
	التمارين 3.1 - 3.6
</h2>

<p>
	<strong>ملاحظة</strong>: من المفضل أن تنفذ تمارين هذا القسم ضمن مستودع خاص على git، وأن تضع الشيفرة المصدرية في جذر المستودع، وإلا ستواجه المشاكل عندما تصل للتمرين 3.10.
</p>

<p>
	<strong>ملاحظة</strong>: لا ننفذ حاليًا مشروعًا للواجهة الأمامية باستخدام React، ولم ننشئ المشروع باستخدام create-react-app. لذلك هيئ المشروع باستعمال الأمر <code>npm init</code> كما شرحنا في هذا الفصل.
</p>

<p>
	<strong>توصية مهمة</strong>: ابق نظرك دائمًا على الطرفية التي تُشغِّل تطبيقك عندما تطور التطبيقات للواجهة الخلفية.
</p>

<h3>
	3.1 دليل الهاتف للواجهة الخلفية: الخطوة 1
</h3>

<p>
	اكتب تطبيقًا باستخدام Node يعيد قائمة من مدخلات دليل هاتف مكتوبة مسبقًا وموجودة في الموقع <a href="http://localhost:3001/api/persons:" ipsnoembed="false" rel="external nofollow">http://localhost:3001/api/persons:</a>
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="55497" href="https://academy.hsoub.com/uploads/monthly_2021_01/phonebook_step1_021.png.f3031d00e479e5f21a1d674f08184335.png" rel=""><img alt="phonebook_step1_021.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55497" data-unique="vtpehrjdp" src="https://academy.hsoub.com/uploads/monthly_2021_01/phonebook_step1_021.png.f3031d00e479e5f21a1d674f08184335.png"></a>
</p>

<p>
	لاحظ أنه ليس للمحرف '/' في المسار <em>api/persons</em> أي معنى خاص بل هو محرف كباقي المحارف في النص. يجب أن يُشّغل التطبيق باستخدام الأمر <code>npm start</code>. كما ينبغي على التطبيق أن يعمل كنتيجة لتنفيذ الأمر <code>npm run dev</code> وبالتالي سيكون قادرًا على إعادة تشغيل الخادم عند حفظ التغيرات التي قد تحدث في ملف الشيفرة المصدرية.
</p>

<h3>
	3.2 دليل الهاتف للواجهة الخلفية: الخطوة 2
</h3>

<p>
	أنشئ صفحة ويب عنوانها <a href="http://localhost:3001/info" ipsnoembed="false" rel="external nofollow">http://localhost:3001/info</a> بحيث تبدو مشابهةً للصفحة التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="55498" href="https://academy.hsoub.com/uploads/monthly_2021_01/phonebook_step2_022.png.76657b3cd05694d971f871e7c1938854.png" rel=""><img alt="phonebook_step2_022.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55498" data-unique="yonsvm988" src="https://academy.hsoub.com/uploads/monthly_2021_01/phonebook_step2_022.png.76657b3cd05694d971f871e7c1938854.png"></a>
</p>

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

<h3>
	3.3 دليل الهاتف للواجهة الخلفية: الخطوة 3
</h3>

<p>
	أضف إمكانية إظهار معلومات مُدخل واحد من مُدخلات دليل الهاتف. يجب أن يكون موقع الحصول على بيانات الشخص الذي معرفه 5 هو <a href="http://localhost:3001/api/persons/5." ipsnoembed="false" rel="external nofollow">http://localhost:3001/api/persons/5.</a> إن لم يكن المُدخَل ذو المعرف المحدد موجودًا، على الخادم أن يستجيب معيدًا رمز الحالة المناسب.
</p>

<h3>
	3.4 دليل الهاتف للواجهة الخلفية: الخطوة 4
</h3>

<p>
	أضف إمكانية حذف مدخل واحد من مُدخلات دليل الهاتف مستعملًا الطلب HTTP-DELETE إلى عنوان المدخل الذي سيُحذَف ثم اختبر نجاح العملية مستخدمًا Postman أو Visual Studio Code REST client.
</p>

<h3>
	3.5 دليل الهاتف للواجهة الخلفية: الخطوة 5
</h3>

<p>
	أضف إمكانية إضافة مدخلات إلى دليل الهاتف مستعملًا الطلب HTTP-POST إلى عنوان مجموعة المدخلات <a href="http://localhost:3001/api/persons." ipsnoembed="false" rel="external nofollow">http://localhost:3001/api/persons.</a>
</p>

<p>
	استعمل الدالة <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random" rel="external nofollow">Math.random</a> لتوليد رقم المعرف id للمُدخل الجديد. واحرص أن يكون مجال توليد الأرقام العشوائية كبيرًا ليقلّ احتمال تكرار المعرف نفسه لمدخلين.
</p>

<h3>
	3.6 دليل الهاتف للواجهة الخلفية: الخطوة 6
</h3>

<p>
	أضف معالج أخطاء للتعامل مع ما قد يحدث عند إنشاء مدخل جديد. حيث لا يُسمح بنجاح العملية إذا كان:
</p>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7804_76" style="">
<span class="pun">{</span><span class="pln"> error</span><span class="pun">:</span><span class="pln"> </span><span class="str">'name must be unique'</span><span class="pln"> </span><span class="pun">}</span></pre>

<h2>
	فكرة عن أنواع طلبات HTTP
</h2>

<p>
	يتحدث <a href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html" rel="external nofollow">معيار HTTP</a> عن خاصيتين متعلقتين بأنواع الطلبات هما الأمان safety والخمول idempotence. فيجب أن يكون طلب HTTP-GET آمنًا:
</p>

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

	<p>
		لقد قضت التوافقات أن لا يمنح التابعين GET و HEAD الأهمية لإجراء أية أفعال ماعدا الحصول على البيانات على وجه الخصوص. فلا بد من اعتبار هذين التابعين آمنين.
	</p>
</blockquote>

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

<p>
	لا يمكن أن نضمن أمان الطلب GET وما ذكر مجرد توصية في معيار HTTP. لكن بالتزام مبادئ التوافق مع REST في واجهة تطبيقنا، ستكون طريقة استخدام GET آمنة دائمًا.
</p>

<p>
	كما عرّف معيار HTTP نمطًا للطلبات هو HEAD وعلى هذا الأخير أن يكون آمنًا أيضًا. يعمل HEAD تمامًا عمل GET، إلا أنه لا يعيد سوى رمز الحالة وترويسات الاستجابة. لن يعيد الخادم جسمًا للاستجابة عندما تستخدم HEAD.
</p>

<p>
	وعلى كل طلبات HTTP أن تكون خاملة ماعدا POST:
</p>

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

	<p>
		يمكن أن تمتلك الطلبات خاصية الخمول idempotence والتي تعني (بعيدا عن الأخطاء ومشاكل التجاوز) أن التأثيرات الجانبية لعدة طلبات متماثلة هي ذاتها لطلب واحد. تشترك GET، HEAD، PUT، DELELTE بهذه الخاصية.
	</p>
</blockquote>

<p>
	ويعني هذا أن التأثيرات الجانبية التي يمكن أن يسببها طلب، يجب أن تبقى كما هي، بغض النظر عن عدد المرات التي يرسل فيها الطلب إلى الخادم. فلو أرسلنا الطلب HTTP-PUT إلى العنوان api/notes/10/ حاملًا البيانات {content:"no sideeffects!", important:true} فإن الاستجابة ستكون نفسها بغض النظر عن عدد المرات التي يُرسَل فيها هذا الطلب.
</p>

<p>
	وكما أن الأمان في GET لا يتحقق دومًا، كذلك الخمول. فكلاهما مجرد توصيات في معيارHTTP لا يمكن ضمانها معتمدين على نوع الطلب فقط. لكن بالتزام مبادئ التوافق مع REST في واجهة تطبيقنا، سنستخدم GET، HEAD PUT، DELELTE بطريقة تحقق خاصية الخمول.
</p>

<p>
	أما الطلب POST فلا يعتبر آمنًا ولا خاملًا. فلو أرسلنا 5 طلبات HTTP-POST إلى الموقع /api/notes، بحيث يضم جسم الطلب البيانات {content: "many same", important: true}، ستكون النتيجة 5 ملاحظات جديدة لها نفس المحتوى.
</p>

<h2>
	البرمجيات الوسيطة Middleware
</h2>

<p>
	يصنف <a href="https://expressjs.com/en/api.html" rel="external nofollow">مفسّر JSON</a> الذي تقدمه express بأنه <a href="http://expressjs.com/en/guide/using-middleware.html" rel="external nofollow">أداة وسطية</a>. فالبرمجيات الوسيطة (Middleware) هي دوال تستخدم لمعالجة كائنات الطلبات والاستجابات. فمفسر JSON الذي تعرفنا عليه سابقًا في هذا الفصل سيأخذ بيانات خام من جسم كائن الطلب، ثم يحولها إلى كائن JavaScript ويسندها إلى الكائن <code>request</code> كقيمة للخاصية <code>body</code>.
</p>

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

<p>
	الأداة الوسطية التي سنستعملها دالة تقبل ثلاث معاملات:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7804_78" style="">
<span class="kwd">const</span><span class="pln"> requestLogger </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'Method:'</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">method</span><span class="pun">)</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'Path:  '</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">path</span><span class="pun">)</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'Body:  '</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">body</span><span class="pun">)</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'---'</span><span class="pun">)</span><span class="pln">
  next</span><span class="pun">()</span><span class="pln">
</span><span class="pun">}</span></pre>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7804_80" style="">
<span class="pln">app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">requestLogger</span><span class="pun">)</span></pre>

<p>
	تستدعى دوال البرمجيات الوسيطة بالتتالي وفق ترتيب استخدامها من قبل express عن طريق التابع <code>use</code>. وانتبه إلى أن مفسر JSON سيستخدم قبل الأداة الوسطية <code>requestLogger</code>، وإلا فلن يهيأ جسم الكائن <code>request</code> عندما تُنفّذ الدالة <code>requestLogger</code>.
</p>

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

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7804_82" style="">
<span class="kwd">const</span><span class="pln"> unknownEndpoint </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">(</span><span class="lit">404</span><span class="pun">).</span><span class="pln">send</span><span class="pun">({</span><span class="pln"> error</span><span class="pun">:</span><span class="pln"> </span><span class="str">'unknown endpoint'</span><span class="pln"> </span><span class="pun">})</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">unknownEndpoint</span><span class="pun">)</span></pre>

<p>
	ستجد شيفرة التطبيق بشكله الحالي في المستودع part3-2 على موقع <a href="https://github.com/fullstack-hy2020/part3-notes-backend/tree/part3-12020/part2-notes/tree/part3-2" rel="external nofollow">GitHub</a>.
</p>

<h2>
	التمارين 3.7 - 3.8
</h2>

<h3>
	3.7 دليل هاتف للواجهة الخلفية: الخطوة 7
</h3>

<p>
	أضف الأداة الوسطية <a href="https://github.com/expressjs/morgan" rel="external nofollow">morgan</a> إلى تطبيقك للمراقبة. اضبط الأداة لطباعة الرسائل على الطرفية وفقًا لنمط التهيئة tiny.
</p>

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

<p>
	ثبت Morgan كأي مكتبة أخرى باستخدام الأمر <code>npm install</code>. واستخدمها تمامًا كما نستخدم أية أداة وسطية بتنفيذ الأمر <code>app.use</code>.
</p>

<h3>
	3.8 دليل هاتف للواجهة الخلفية: الخطوة 8 *
</h3>

<p>
	هيئ Morgan حتى تعرض لك أيضًا بيانات الطلبات HTTP-POST:
</p>

<p style="text-align: center;">
	<img alt="phonebook_step8_023.png" class="ipsImage ipsImage_thumbnailed" data-fileid="55499" data-unique="3byvww5d5" src="https://academy.hsoub.com/uploads/monthly_2021_01/phonebook_step8_023.png.81600470edd267414a9b6a7911ce5968.png"></p>

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

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

<ul>
<li>
		<a href="https://github.com/expressjs/morgan#creating-new-tokens" rel="external nofollow">إنشاء مفاتيح جديدة</a> (new token)
	</li>
	<li>
		<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify" rel="external nofollow">JSON.stringify</a>
	</li>
</ul>
<p>
	ترجمة -وبتصرف- للفصل <a href="https://fullstackopen.com/en/part3/Node_js_and_express" rel="external nofollow">Node.js, Express</a> من سلسلة <a href="https://fullstackopen.com/en/" rel="external nofollow">Deep Dive Into Modern Web Development</a>
</p>
]]></description><guid isPermaLink="false">1099</guid><pubDate>Tue, 12 Jan 2021 13:00:00 +0000</pubDate></item><item><title>&#x625;&#x646;&#x634;&#x627;&#x621; &#x645;&#x62F;&#x648;&#x646;&#x629; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; Express (&#x627;&#x644;&#x62C;&#x632;&#x621; 6): &#x627;&#x639;&#x62A;&#x628;&#x627;&#x631;&#x627;&#x62A; &#x646;&#x634;&#x631; &#x645;&#x634;&#x627;&#x631;&#x64A;&#x639; Node.js &#x648;Express &#x639;&#x644;&#x649; &#x627;&#x644;&#x648;&#x64A;&#x628;</title><link>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/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2015_03/express.png.eb4091af1a39e105f799edc9bbbc5cc8.png" /></p>
<p>استضافة المشروع على الويب تختلف في كثير من نواحي عن البيئة المحلّيّة أثناء التّطوير، علينا أخذ عدّة أمور في عين الاعتبار قبل أن يصبح مشروعنا جاهزًا للنّشر (deployment).</p><h2>الأمان والحماية</h2><p>عندما طوّرنا المشروع، لم نلقِ بالًا لمواضيع الحماية والأمان (سوى تجزئة كلمة المرور)، لأنّ الطّلبات في بيئة التّطوير تصدر وتعود للجهاز نفسه دون خوف من مرورها على جهات أخرى، أمّا على الويب فإنّ الطّلبات ستمرّ عبر عشرات ومئات بل ربّما آلاف الأجهزة قبل أن تصل للطّرف الآخر. ولذا علينا تنفيذ ما يلي كحدّ أدنى لحماية خادومنا والمستخدمين في وقت واحد:</p><ul><li><p><strong>استخدام HTTPS:</strong> عند إنشاء المستخدم لحساب جديد أو تسجيله دخوله، يجب أن تنتقل كلمة المرور <strong>مُعمَّاة</strong> (encrypted) من المتصفّح للخادوم لكي لا تستطيع أطراف أخرى، مثل مزوّد خدمة الإنترنت أو مُخترقي الشّبكة، الاطّلاع عليها أثناء انتقالها، وهو ما يمكن تحقيقه باستخدام بروتوكول HTTPS، كيفيّة اعتماد HTTPS على الخادوم أمر خارج عن نطاق هذه السّلسلة.</p></li><li><p><strong>صلاحية الجلسات:</strong> ذكرنا أنّه من المُفضّل فرض حدّ على صلاحيّة الجلسة يتمّ بعدها إعادة طلب تسجيل الدّخول من المستخدم. إذا استطاع مخترق ما الوصول إلى جهاز المستخدم أو سرقة الكعكات من متصفّحه، فسيصبح بإمكانه انتحال شخصيّة هذا المستخدم والتّعليق باسمه أو إضافة التّدوينات إن كان الكاتب يملك هذا الإذن.</p></li><li><p><strong>التحقق من البريد الإلكتروني:</strong> لم نطلب من المستخدم عنوان بريده الإلكتروني عند إنشاء نموذج التّسجيل في الموقع لغرض تبسيط الدرس، ولكن من الأفضل طلبه مع التّحقّق من صحته بإرسال رسالة تحوي رابط التّحقّق وذلك لحماية المدوّنة من الهجمات الآلية الّتي تهدف إلى خلق ضغط على الخادوم أو إغراقه بالتّعليقات غير المرغوبة.</p></li></ul><h2>الأداء</h2><ul><li><strong>التخزين المؤقت (caching)</strong>: الحواسيب اليوم سريعة للغاية، لا سيما الخواديم، معالجات بقدرات هائلة وأحجام كبيرة من الذاكرة وسعة التّخزين، لكن المشكلة الوحيدة الّتي لا تزال تؤثّر على أدائها، على الرّغم من كلّ التّطوّرات في السّنوات الأخيرة، ما تزال أقراص التّخزين، نعم هناك أنواع جديدة من الأقراص (SSD) تقدّم سرعات أكبر، لكنّها ما تزال مُكلفة وغير شائعة على الخوادم، في كلّ استعلام لقاعدة البيانات سيذهب خادوم MySQL إلى القرص ليجلب النّتائج، وهذا يعني أنّ كلّ صفحة يزورها كلّ مستخدم ستتطلّب على الأقل مرورًا واحدًا على القرص، وإذا كان القرص ميكانيكيًّا، وهي الأقراص الأكثر شيوعًا في الحواسيب إلى اليوم، فإنّ هذا سيستغرق وقتًا ملحوظًا. من الأفضل تخزين النّتائج للاستعلامات الشّائعة والّتي لا تتغيّر كثيرًا (كنصّ التّدوينة، ومعلومات المستخدمين) في الذّاكرة (RAM) لأنّها كبيرة الحجم عادةً، وأسرع بكثير من الأقراص الصّلبة. يمكننا تخزين هذه النّتائج بأسلوب بدائي بشكل كائن JavaScript ضمن متغيّر يبقى ضمن الذّاكرة، لكنّ هذا الحلّ ليس عمليًّا، لذلك وُجدت العديد من الحلول الّتي تعتمد بنية خادم/عميل مثل <a rel="external nofollow" href="http://redis.io">Redis‏</a> و<a rel="external nofollow" href="http://memcached.org">Memcached‏</a>، ببساطة يمكن تخزين نتائج الاستعلامات في قاعدة بيانات Memcached الّتي تعمل في الذّاكرة، والخادوم يتولّى إدارة مساحة الذّاكرة وتوزيعها على عدّة حواسيب إن تطلّب الأمر، كما في المواقع الضّخمة. العديد من المواقع الكبيرة تستخدم Memcached، منها Google وTwitter وWikipedia.</li></ul><p>من الوسائل الأخرى لتسريع عمل الموقع أن نطلّب من المتصفّح تخزين الملفّات الّتي لا تتغيّر كثيرًا لفترة أطول قبل أن يجلبها من جديد، يمكن القيام بهذا من خلال ترويسات في جواب HTTP تُدعى <a rel="external nofollow" href="https://www.mobify.com/blog/beginners-guide-to-http-cache-headers/">بترويسات إدارة الذّاكرة المؤقتة (Cache Control Headers)</a>، يمكن أن نطلب من المتصفّح على سبيل المثال أن يُخزّن ملفّ <code>style.css</code> لمدّة 10 أيّام على جهاز المتصفّح بحيث لا يضطّر لجلبه مجدّدًا لكلّ صفحة يزورها المستخدم. في Express يمكن استخدام خيار <code>maxAge</code> للوظيفة <code>static()‎</code> لتعيين المُدّة القصوى لتخزين الملفّات الثّابتة مُقدّرة بالميلي ثانية:</p><pre class="javascript ipsCode prettyprint">app.use(express.static(__dirname + '/public', { maxAge: 60 * 60 * 24 * 1000 }));</pre><p>وأمّا لطلباتنا الأخرى، فيكمننا تعيين قيم ترويسات الجواب بالطّريقة التّالية:</p><pre class="javascript ipsCode prettyprint">response.set("Cache-Control", "private, max-age=3600");

أو:

response.set({
    "Cache-Control": "private, max-age=3600, must-revalidate",
    "Expires": "Tue, 13 Jan 2015 21:49:10 GMT"
});
</pre><ul><li><p><strong>إضافة الفهارس لقاعدة البيانات:</strong> قاعدة البيانات لدينا ما تزال صغيرة ولا تحتوي الكثير، لكنّنها ستتضخّم مع زيادة التّعليقات والتّدوينات والمستخدمين بلا شكّ، وعندها سيصبح من الضّروريّ إضافة الفهارس على الأعمدة الأكثر استعلامًا لتسريع جلب النّتائج. يمكن تشبيه الفهارس في قواعد البيانات بالفهارس في الكتب؛ إذا أردت الوصول إلى قسم معيّن في كتاب ضخم، فإنّك ستفضّل الاطّلاع على الفهرس، لأنّ ذلك أسرع من المرور على كلّ أقسام الكتاب، على الرّغم من أنّ الفهرس قد يتطلّب بضع صفحات إضافية من الكتاب، الّتي يمكن تشبيهها بمساحة تخزين إضافيّة على القرص.</p></li><li><p><strong>تقليص الملفات:</strong> قد يصبح حجم وعدد ملفّات المشروع الّتي ستصل للمتصفح مثل ملفّات CSS وJavaScript كبيرًا مع تطوّر المشروع، على الرّغم أنّنا لم نستخدم أيّة مكتبات JavaScript ضخمة أو خطوط ويب كبيرة الحجم، إلّا أنّنا سنفعل ذلك في المشاريع الواقعيّة غالبًا، عندها ستصبح الحاجّة لتقليص حجم هذه الملفّات وجمعها أمرًا له ما يُبرّره، المتصفّحات لا تهتمّ إن كانت ملفّات CSS الّتي تصلها خمسة أو واحدًا، ما يهمّها هو المحافظة على ترتيب سطور الشّيفرة، كما أنّها لا تهتم بالفواصل والمسافات في ملفّات JavaScript، ولا حتّى بأسماء المتغيّرات طالما استخدمت الأسماء نفسها، وهذا هو مبدأ التّقليص (minification) والجمع (concatenation)، لنأخذ نصّ JavaScript كهذا:</p><pre class="javascript ipsCode prettyprint">  var myVeryLongVariableName = "something";

  function doSomething() {
      return myVeryLongVariableName;
  }</pre><p>بعد تقليص هذا الملفّ:</p><pre class="javascript ipsCode prettyprint">  var a="something";function doSomething(){return a;}</pre><p>بالطّبع سيصبح فارق الحجم بين الملفّين كبيرًا مع تضخّم الشّيفرة وزيادة تعقيدها. كلا الشّيفرتين تؤدّيان المهمّة ذاتها على المتصفّح، ولكن الأخيرة ستقلل من حجم طلب HTTP وهو ما ينعكس على سرعة تحميل الموقع وبالتّالي على قبول المستخدم للموقع وتجربته. تتوفّر في Node.js الكثير مع الوحدات الّتي تنفّذ مهمّات الجمع والتّقليص لمختلف لغات الويب، ولعلّ من أشهرها <a rel="external nofollow" href="https://github.com/mishoo/UglifyJS2">UglifyJS‏</a> لنصوص JavaScript، و<a rel="external nofollow" href="https://github.com/jakubpawlowicz/clean-css"><code>clean-css</code>‏</a> لملفّات CSS. أمّا كيفيّة استخدامها وأتمتتها فهو موضوع خارج عن نطاق هذه السّلسة.</p></li><li><p><strong>استخدام وسائل ضغط مثل <code>gzip</code></strong>: وهي آليّة لضغط الصّفحات لتقليص حجمها ثمّ تعيين ترويسة Encoding في جواب HTTP لتُشير للمتصفّح بأنّ المحتوى المنقول مضغوط، وسيقوم المتصفّح الّذي يدعم هذه الآليّة بفكّ ضغطه ثمّ التعامل معه كأيّ جواب آخر، يتعرّف الخادوم على المتصفّحات الّتي تدعم gzip عبر ترويسة Accept-Encoding، أمّا المتصفّحات الأخرى فتُرسل لها الإجابة دون ضغط. معظم المتصفّحات الحديثة تدعم gzip، وتتوفّر لـExpress الوحدة <a rel="external nofollow" href="https://github.com/expressjs/compression"><code>compression</code>‏</a> للقيام بالمهمّة تلقائيًّا.</p></li></ul><h2>ملفات المشروع</h2><p>للحصول على نسخة من ملفّات المشروع لتجربتها محلّيًّا والتّطوير عليها، يمكن عمل فرع عن المشروع أو استنساخه من <a rel="external nofollow" href="https://github.com/forabi/expressjs-tutorial">صفحته على GitHub‏</a>.</p>
]]></description><guid isPermaLink="false">24</guid><pubDate>Tue, 03 Mar 2015 18:47:00 +0000</pubDate></item><item><title>&#x625;&#x646;&#x634;&#x627;&#x621; &#x645;&#x62F;&#x648;&#x651;&#x646;&#x629; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; Express (&#x627;&#x644;&#x62C;&#x632;&#x621; 5): &#x646;&#x638;&#x627;&#x645; &#x627;&#x644;&#x62A;&#x639;&#x644;&#x64A;&#x642;&#x627;&#x62A; &#x648;&#x627;&#x644;&#x644;&#x645;&#x633;&#x627;&#x62A; &#x627;&#x644;&#x646;&#x647;&#x627;&#x626;&#x64A;&#x629;</title><link>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-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-5-%D9%86%D8%B8%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D8%B9%D9%84%D9%8A%D9%82%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D9%84%D9%85%D8%B3%D8%A7%D8%AA-%D8%A7%D9%84%D9%86%D9%87%D8%A7%D8%A6%D9%8A%D8%A9-r23/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2015_03/express.png.7156ee52da98d2594cceb9a5359f210b.png" /></p>

<p>بعد أن أنشأنا نظام المستخدمين والجلسات، أصبحنا جاهزين الآن لبناء نظام التّعليقات، ثم إتاحة إمكانية إنشاء تدوينة جديدة وتعديل التّدوينات السّابقة.</p><h2>إتاحة التّعليق</h2><p>لحفظ التّعليقات نحتاج أولًا إلى جدول جديد يحفظ نصّ التعليق وتاريخه وكاتبه والتّدوينة التي أُضيف إليها، افتح صدفة MySQL واتصّل بقاعدة البيانات ثم نفّذ هذا الاستعلام:</p><pre class="html ipsCode prettyprint">CREATE TABLE `comments` (id INT PRIMARY KEY AUTO_INCREMENT, post_id INT NOT NULL, user_id INT NOT NULL, body VARCHAR(500) NOT NULL, created TIMESTAMP, FOREIGN KEY (post_id) REFERENCES `posts` (id), FOREIGN KEY (user_id) REFERENCES `users` (id), INDEX (post_id));</pre><p>سنُعدّل قالب صفحة التّدوينة <code>post.jade</code> ونضيف حقلاً يسمح للمستخدم المُسجّل دخوله بإضافة التّعليق، ويعرض للزوّار إمكانيّة تسجيل الدّخول:<code class="lang-jade"> </code></p><pre class="html ipsCode prettyprint">doctype html
html(lang="ar", dir="rtl")
    head
        title مُدوّنتي!
    body
        style
            :css
                body {
                    font-family: Arial, sans-serif;
                }

        h1 مُدوّنتي
        hr
        h2 #{ post.title }
        p #{ post.body }
        small كُتِبَت #{ formatDate(post.date) }
        #comments
            h3 التعليقات
            if post.comments
                for comment in post.comments
                    p يقول 
                        b #{ comment.full_name }: 
                        br
                        | #{ comment.body }
                    small #{ formatDate(comment.created) }
                    hr
            else
                p لا تعليقات إلى الآن

        if user
            form(action="/posts/" + post.id + "/comments", method="POST")
                textarea(name="comment", placeholder="أكتب تعليقك")
                input(type="submit", value="أرسل التّعليق")
        else
            span لإضافة تعليقاتك، 
            a(href="/login") سجّل دخولك
            |  أو 
            a(href="/signup") أنشئ حسابًا جديدًا
</pre><p>لقد استبقنا الأمور قليلاً، سيرسل النّموذج الذي يشمل التّعليق إلى الرّابط <code>‎/posts/:post_id/comments</code>، لنقم بتوجيه هذا الرّابط:<code class="lang-javascript"> </code></p><pre class="javascript ipsCode prettyprint">app.post("/posts/:post_id/comments", parseBody, function(request, response) {
    var body = request.body.comment;
    var user_id = request.user.id;
    var post_id = request.params.post_id;
    var created = new Date();

    connection.query("INSERT INTO `comments` (post_id, user_id, body, created) VALUES (?, ?, ?, ?)", [ post_id, user_id, body, created ], function(err) {
        if (err) {
            response.status(500);
            response.send("تعذّرت إضافة التّعليق، حاول مجدّدًا.");
            return;
        }

        response.status(201);
        response.send("أُضيف التعليق");
    })
})
</pre><p>ملاحظة: اخترنا أن يُرسل الرّابط إلى النّمط <code>‎/posts/:post_id/comments</code> بدل <code>‎/posts/:slug/comments</code> على سبيل تبسيط الأمور، لا يُميّز Express بين <code>‎:slug</code> و<code>‎:post_id</code> فكلاهما بالنّسبه له متُغيّران لا يخضعان لأيّة شروط، يمكننا التأكد من كون <code>‎:post_id</code> رقمًا باستخدام الوظيفة <code>param()‎</code> على التّطبيق والّتي نشترط بها أن يطابق المُتغيّر <code>post_id</code> التعبير النظامي (regular expression) التّالي:</p><pre><code class="lang-javascript">app.param('post_id', /^[0-9]+$/);
</code></pre><p>وعندها لن تتلقّى هذا الدّالة سوى الرّوابط التي تحمل رقمًا في موقع <code>‎:post_id</code>.</p><p>هذه الشّيفرة تكفي لإضافة التّعليق، لكنّنا بحاجة إلى تعديل دالّة توجيه رابط التّدوينة لإضافة التّعليقات إلى الصّفحة:</p><pre class="javascript ipsCode prettyprint">app.get("/posts/:slug", function(request, response, next) {

    var slug = request.params.slug;

    connection.query("SELECT * from `posts` WHERE slug = ?", [ slug ], function(err, rows) {
        if (err || rows.length == 0) return next();
        var post = rows[0];
        connection.query("SELECT * FROM `comments` JOIN `users` ON comments.user_id=users.id WHERE post_id=?", [ post.id ], function(err, comments) {
            if (err) return next(err);
            post.comments = comments;
            response.render("post", { post: post, formatDate: formatDate, user: request.user });
        })
    });

})</pre><p>لنُجرّب أولًا زيارة صفحة التّدوينة <a rel="external nofollow" href="http://localhost:3000/posts/hello-world">http://localhost:3000/posts/hello-world</a> دون تسجيل الدّخول وقبل إضافة أيّ تعليق:</p><p><a class="ipsAttachLink ipsAttachLink_image" rel="external nofollow" href="https://academy.hsoub.com/uploads/monthly_2015_03/comments-none.jpg.7517b73bd50b22bd35bf47fe2fe2700c.jpg"><img class="ipsImage ipsImage_thumbnailed" data-fileid="109" alt="comments-none.thumb.jpg.2173fac5c11f4ee0" src="https://academy.hsoub.com/uploads/monthly_2015_03/comments-none.thumb.jpg.2173fac5c11f4ee09e7f95284c77e9d6.jpg"></a></p><p>لنقم الآن بتسجيل الدّخول باسم admin وكلمة مرور 123456، ولنعُد للصفحة ونُحدّثها:</p><p><a class="ipsAttachLink ipsAttachLink_image" rel="external nofollow" href="https://academy.hsoub.com/uploads/monthly_2015_03/comments-signed-in.jpg.3ccfa766eb715080097bdae082d98c24.jpg"><img class="ipsImage ipsImage_thumbnailed" data-fileid="112" alt="comments-signed-in.thumb.jpg.51b66fc222d" src="https://academy.hsoub.com/uploads/monthly_2015_03/comments-signed-in.thumb.jpg.51b66fc222dbd3861e5ec2804440dfa9.jpg"></a></p><p>لنجرّب إضافة تعليق:</p><p><a class="ipsAttachLink ipsAttachLink_image" rel="external nofollow" href="https://academy.hsoub.com/uploads/monthly_2015_03/comments-added.jpg.2b01788f24e4969ccaf71ba0d01d229f.jpg"><img class="ipsImage ipsImage_thumbnailed" data-fileid="110" alt="comments-added.thumb.jpg.2f3fbc63772e672" src="https://academy.hsoub.com/uploads/monthly_2015_03/comments-added.thumb.jpg.2f3fbc63772e672a1b4f1d38c1ad8908.jpg"></a></p><p>لنعُد لصفحة التّدوينة ونُعد تحميلها:</p><p><a class="ipsAttachLink ipsAttachLink_image" rel="external nofollow" href="https://academy.hsoub.com/uploads/monthly_2015_03/comments-first-comment.jpg.9d1e0e24c944a93ed0419ec3f5199e9a.jpg"><img class="ipsImage ipsImage_thumbnailed" data-fileid="111" alt="comments-first-comment.thumb.jpg.3635420" src="https://academy.hsoub.com/uploads/monthly_2015_03/comments-first-comment.thumb.jpg.3635420d0d0ac63c1d38e22073711ee4.jpg"></a></p><p>رائع جدًا! لقد أنشأنا نظام تعليق بسيطًا بخطوات بسيطة بعد أن أتممنا القسم الأكبر من العمل عندما أنشأنا الجلسات ونظام المستخدمين.</p><h2>إتاحة كتابة التّدوينات وتعديلها لمدير المُدوّنة</h2><p>لنبدأ أوّلاً بصفحة إنشاء تدوينة جديدة، ولنُنشئ رابطًا جديدًا <code>‎/new</code> لعرض القالب <code>views/post-editor.jade</code>:</p><pre class="html ipsCode prettyprint">doctype html
html(lang="ar", dir="rtl")
    head
        title تدوينة جديدة
    body
        style
            :css
                body {
                    font-family: Arial, sans-serif;
                }

        h1 مُدوّنتي
        hr
        h2 تسجيل الدخول
        form(action="/posts", method="POST")
            label(for="title") عنوان التدوينة: 
            input(type="text", name="title", required)
            br
            label(for="slug") الرابط الفرعي: 
            input(type="text", name="slug" required)
            br
            label(for="body") نص التّدوينة: 
            textarea(name="body")
            br
            input(type="submit", value="أرسل التّدوينة")</pre><pre class="javascript ipsCode prettyprint">app.get("/new", function(request, response, next) {
    if (request.user &amp;&amp; request.user.is_author) {
        response.render("post-editor", { user: request.user });
    } else {
        response.status(403);
        response.send("ليس لديك صلاحيات إضافة تدوينة.");
    }
})

app.post("/posts", parseBody, function(request, response) {
    if (request.user &amp;&amp; request.user.is_author) {
        var title = request.body.title,
            body = request.body.body,
            date = new Date(),
            author_id = request.user.id,
            slug = request.body.slug;

        connection.query("INSERT INTO `posts` (title, body, date, author_id, slug) VALUES (?, ?, ?, ?, ?)", [ title, body, date, author_id, slug ], function(err) {
            if (err) {
                response.status(500);
                response.send("تعّذرت إضافة التّدوينة");
                return;
            }

            response.status(201);
            response.send("أضيفت التّدوينة.");
        })
    } else {
        response.status(403);
        response.send("ليس لديك صلاحيات إضافة تدوينة.");
    }
})</pre><p>الجزء الأكثر أهمّيّة في شفرتنا هو التّحقق من كون المستخدم يحمل صلاحيات الكتابة، فإن لم يكن كذلك، نُرسل الرّمز <code>‎403 Forbidden</code> (محظور). بإمكاننا تجاوز التّحقق قبل عرض القالب ونترك عرض الرسالة المناسب للقالب ذاته، لكنّ المهمّ هو إجراء التّحقّق عند إدخال التّدوينة في فاعدة البيانات.</p><p>سنتيح تعديل التّدوينة على رابط التّدوينة ذاته متبوعًا بـ<code>‎/edit</code>:</p><pre class="javascript ipsCode prettyprint">app.get("/posts/:slug/edit", function(request, response, next) {
    var user_id = request.user.id;
    var slug = request.params.slug;

    connection.query("SELECT * FROM `posts` WHERE author_id=? AND slug=?", [ user_id, slug ], function(err, rows) {
        if (!err &amp;&amp; rows[0]) {
            response.render("post-editor", { post: post });
        } else {
            response.status("401");
            response.send("إمّا أن التّدوينة غير موجودة، أو أنّك لا تملك الصلاحيات للوصول إليها");
        }        
    })
})</pre><p>الجزء المهمّ من شفرتنا هو اشتراط أن يكون مؤلف التّدوينة المطلوب تعديلها هو صاحب الجلسة ذاته وهو ما كتبناه في استعلام MySQL، وإلّا سيكون بإمكان أن شخص أن يضيف <code>‎/edit</code> إلى نهاية التّدوينات ويجري ما يشاء من التغييرات عليها. لنقم بتعديل القالب <code>post-editor.jade</code> لجعله يتعامل مع التّدوينات الموجودة مسبقًا بالإضافة إلى التّدوينات الجديدة:</p><pre class="html ipsCode prettyprint">doctype html
html(lang="ar", dir="rtl")
    head
        title تدوينة جديدة
    body
        style
            :css
                body {
                    font-family: Arial, sans-serif;
                }

        h1 مُدوّنتي
        hr
        - var editMode = post &amp;&amp; post.id
        h2 #{editMode ? "تعديل التّدوينة" : "تدوينة جديدة" }
        form(action= editMode ? ("/posts/" + post.slug + "?_method=PUT") : "/posts", method="POST")
            label(for="title") عنوان التدوينة: 
            input(type="text", name="title", required, value= editMode ? post.title : "")
            br
            if !editMode
                label(for="slug") الرابط الفرعي: 
                input(type="text", name="slug" required)
                br
            label(for="body") نص التّدوينة: 
            textarea(name="body") #{ editMode ? post.body : "" }
            br
            input(type="submit", value="أرسل التّدوينة")</pre><p>سيُرسل طلب التّعديل باستخدام الفعل <code>PUT</code> الّذي يُستخدم للطلب من الخادوم "تحديث" محتوى موجود لديه، خلافًا لـ<code>POST</code> المستخدم لإضافة محتوى جديد. المشكلة أنّ المتصفّحات لا تدعم استخدام سوى فعلين ضمن نماذج HTML هما <code>POST</code> و<code>GET</code>، ولذلك سنضطر إلى إيجاد طريقة "ملتوية" لتجاوز هذه المشكلة. استخدمنا الفعل <code>POST</code> ذاته في حالة التّعديل مع إضافة حقل إلى رابط <code>action</code> في النّموذج، سيستخدم هذا الحقل من قبل وحدة <code>method-override</code> الّتي تقرأه وتغيّر من فعل الطّلب إلى <code>PUT</code> ليمرّ عبر دالّة التّوجيه الّتي كتبناها:</p><pre class="javascript ipsCode prettyprint">var methodOverride = require('method-override');
app.use(methodOverride('_method'));

/*
...
*/

app.put("/posts/:slug", parseBody, function(request, response) {
    if (!requestest.user) {
        response.status(403);
        response.send("يجب تسجيل الدخول لتعديل التدوينات.");
        return;
    }

    var slug = request.params.slug;
    var new_title = request.body.title;
    var new_body = request.body.body;
    var user_id = request.user.id;

    connection.query("UPDATE `posts` SET title=?, body=? WHERE slug=? AND author_id=?", [ new_title, new_body, slug, user_id ], function(err) {
        if (err) {
            console.log(err);
            response.status("500");
            response.send("حدث خطأ أثناء تعديل التدوينة");
            return;
        }

        response.send("حُدِّثت التّدوينة.");
    })
})</pre><p>تأكد من اشتراطك كون مؤلّف التّدوينة هو ذاته صاحب الجلسة مرّة ثانية قبل إدخال البيانات.</p><p>قلنا أنّ أفعال HTTP تستخدم استخدامًا دلاليًّأ (semantic) ولا شيء يُجبرك على استخدام <code>PUT</code>، بل يمكنك استخدام <code>POST</code> للحصول على نفس النّتيجة، لكنّه العرف المتّفق عليه، والذي ستعتاد على اتّباعه عندما تتقدّم في مستويات أعلى كبناء واجهة برمجيّة للمدوّنة (RESTful <abbr title="واجهة برمجية | Application Programming Interface">API</abbr>) الّتي يتوقّع الطّرف الذي يتعامل معها هذا الأسلوب الدّلاليّ.</p><p>يحقّ لنا الاحتفال الآن، فقد أنشأنا مدوّنة حقيقيّة من الصّفر! لنقم الآن بتحسين مظهرها وتنظيف شيفرتنا!</p><h2>تنظيف الشّيفرة</h2><p>حسنًا، قد تبدو شيفرتنا طويلة في الملفّ <code>index.js</code> طويلة بعض الشيء وفيها الكثير من التّكرار، وحالما نُشاهد سطورًا مكرّرة في شيفرة برمجيّة، نعلم أنّ بإمكاننا كتابة شيفرة أفضل. لقد أهملنا ذلك قليلًا لنحصل على برنامج يعمل بأسرع وقت ممكن، لكن علينا الآن أن نعود لنلقي نظرة أكثر إمعانًا في برنامجنا.</p><p>تكرّر في كثير من المواضع استخدامنا للدّالة <code>parseBody</code> لتفسير متن طلبات POST، وحدة <code>body-parser</code> هي واحدة من البرامج الوسيطة التي يمكن استعمالها على مستوى التّطبيق أيضًا، لنقم بحذف عبارة <code>parseBody</code> من كل طلبات POST ولننقل تفسير متن الطّلب إلى مستوى التّطبيق، ذكرنا أنّه بإمكاننا استخدام <code>app.use()‎</code> لذلك:</p><pre class="javascript ipsCode prettyprint">/*
...

*/

var app = express();

var parseBody = bodyParser.urlencoded({ extended: true });

app.use(session({
    secret: "my top secret",
    resave: true,
    saveUninitialized: true
}));

app.use(parseBody);

app.use(cookieParser());

// ...</pre><p>سيكون من المفيد بعد إضافة تدوينة جديدة أو تعديلها أو تعليق جديد على تدوينة العودة مجدًّدا إلى هذه التّدوينة بدل عرض رسالة تفيد بنجاح العمليّة فقط، يوفّر Express الوظيفة <code>redirect()‎</code> على الكائن <code>response</code> التي تُخبر المتصفّح بالانتقال إلى صفحة أخرى كجواب على الطّلب الّذي أُرسل. سأدع لك تنفيذ هذه المهمّات:</p><ul><li>عند كتابة تدوينة جديدة، انتقل إلى صفحة هذه التّدوينة.</li><li>عند إضافة تعليق جديد، عُد إلى صفحة التّدوينة المعنيّة.</li><li>عند إنشاء مستخدم جديد، انتقل إلى صفحة تسجيل الدّخول.</li><li>عند تسجيل الدّخول، انتقل إلى صفحة الملفّ الشّخصيّ.</li></ul><p>في معظم دوالّ التّوجيه التي كتبناها، قمنا بالتّحقّق من الخطأ وإرسال رسالة مناسبة مع رمز حالة مثل <code>404</code> و<code>403</code>... من الأفضل أن نُصمّم صفحة خطأ خاصّة تتلقّى الخطأ ورسالته وتعرضه للمستخدم بأسلوب موحّد، سنحذف كلّ عبارات <code>response.send()‎</code> الّتي ترسل رسالة خطأ ونبدلها بالتّوجيه إلى الدّالة التّالية <code>next()‎</code> الّتي ستعرض قالب صفحة الخطأ <code>views/error.jade</code>:</p><pre class="html ipsCode prettyprint">doctype html
html(lang="ar", dir="rtl")
    head
        title مُدوّنتي! - خطأ
    body
        style
            :css
                body {
                    font-family: Arial, sans-serif;
                }

        h1 مُدوّنتي
        hr
        h2 خطأ #{ response.statusCode }
        p #{ error.message || error.toString() }</pre><p>هذا مثال عن تعديل دالّة التّوجيه للرّابط <code>‎/new</code>:<code class="lang-javascript"> </code></p><pre class="javascript ipsCode prettyprint">app.get("/new", function(request, response, next) {
    if (request.user &amp;&amp; request.user.is_author) {
        response.render("post-editor", { user: request.user });
    } else {
        response.status(403);
        return next(new Error("ليس لديك صلاحيات إضافة تدوينة."));
    }
})</pre><p>تقبل الدّالة <code>next</code> معاملاً اختياريًّا يشير إلى وجود خطأ، وهي الطّريقة المناسبة لتمرير الخطأ عبر دوالّ التّوجيه، عدم تمرير الخطأ يعني أنّ التّوجيه يسير من دالّة إلى أخرى بشكل سليم، يستفيد Express من وجود هذا المعامل لعرض الخطأ في حال انتهت عمليّة التّوجيه دون توفير دالّة تتعامل معه. سيلجأ Express إلى الدّالة التّالية الّتي يجب أن نُضيفها إلى نهاية شيفرتنا:<code class="lang-javascript"> </code></p><pre class="javascript ipsCode prettyprint">app.use(function(err, request, response, next) {
    response.render("error", { error: err, statusCode: response.statusCode })
})
</pre><p>لاحظ أنّه خلافًا لدوالّ التّوجيه السّابقة، فقد استخدمنا 4 معاملات، قد تتساءل كيف يمكن لدالّة واحدة أن تتلقّى عددًا مختلفًا من المعاملات وتتصرّف بطريقة مختلفة، أو كيف تعرف الدّالة أن العنصر الأوّل هو كائن الخطأ وليس كائن الطّلب، الجواب هو أنّ Express يُجري تحقّقًا من عدد المعاملات في دالّة التّوجيه ويغيّر تصرّفه، وهذا الأمر متاح لأن JavaScript توفّر الكائن <code>arguments</code> بشكل تلقائيّ لكلّ الدّوالّ، والذي يمكن التّحقّق من طوله (<code>length</code>) بجملة شرطيّة وتغيير سلوك الدّالة. الهدف النّهائي من هذا أن يكون Express سهل الاستعمال وبديهيًّا، وهذا الأسلوب ستجده كثيرًا في Node.js. من الضّروري استخدام 4 معاملات ليستطيع Express التفريق بين: <code>err, request, response</code> و<code>request, response, next</code>.</p><p>لنجرّب الآن زيارة بعض الصّفحات الّتي نتوقّع حدوث خطأ عندها:</p><p><a class="ipsAttachLink ipsAttachLink_image" rel="external nofollow" href="https://academy.hsoub.com/uploads/monthly_2015_03/cleaning-up-error-403.jpg.499581a24b7f5a54bbbeb707a11e198b.jpg"><img class="ipsImage ipsImage_thumbnailed" data-fileid="113" alt="cleaning-up-error-403.thumb.jpg.0b0b8b29" src="https://academy.hsoub.com/uploads/monthly_2015_03/cleaning-up-error-403.thumb.jpg.0b0b8b29bef0a11bc5dd30c6132d5a97.jpg"></a></p><p><a class="ipsAttachLink ipsAttachLink_image" rel="external nofollow" href="https://academy.hsoub.com/uploads/monthly_2015_03/cleaning-up-error-404.jpg.432130aa3da367b585f53efae3a0cebd.jpg"><img class="ipsImage ipsImage_thumbnailed" data-fileid="114" alt="cleaning-up-error-404.thumb.jpg.a1cf28c9" src="https://academy.hsoub.com/uploads/monthly_2015_03/cleaning-up-error-404.thumb.jpg.a1cf28c989f2df43115b21df2a37b7ce.jpg"></a></p><p>هذا أفضل! توحيد صفحة الخطأ سيجعلنا نفكّر في تقديم حلول لهذا الخطأ بناء على رمز الحالة، مثلاً نستطيع تقديم مربّع بحث في حال كان الرّمز <code>404</code> (غير موجود)، أو نستطيع أن نطلب من المستخدم تسجيل الدّخول في حال كان <code>403</code>... إلخ.</p><p>شيفرة JavaScript نظيفة الآن، لنلقِ نظرةً على القوالب الّتي أنشأناها، يتكرّر في معظمها استخدام ترويسة للصّفحة مع استخدام تنسيق موحّد، قد يبدو تضمين CSS في الصّفحة مقبولًا الآن، لكنّنا سنحتاج إلى نقله إلى ملفّ منفصل عندما نتوسّع في إضافة الأنماط لكي لا نحتاج لتكرارها في كلّ القوالب. لنُنشئ ملفًا للأنماط <code>style.css</code> في مجلّد جديد ضمن المشروع نُسمّيه <code>public</code>، ولننقل إليه شيفرة CSS من أحد القوالب:</p><pre class="css ipsCode prettyprint">body {
    font-family: Arial, sans-serif;
}</pre><p>لنحذف الآن شيفرة CSS من القوالب ونكتب بدلاً منها رابطًا لملفّنا:<code class="lang-jade"> </code></p><pre class="html ipsCode prettyprint">head
    title إنشاء مستخدم جديد
    link(rel="stylesheet", href="/style.css")
body
    h1 مُدوّنتي
    //- ...
</pre><p>حسنًا، لن يعثر الخادوم على الملفّ <code>style.css</code> عندما يطلبه المتصفّح، لأنّنا نحتاج لتوفيره صراحةً. من الشّائع استضافة كلّ الملفّات الثّابتة (static) مثل ملفّات CSS وJavaScript للمتصفّح ضمن مجلّد <code>public</code>، ثمّ توفير هذا المجلّد بكامل محتوياته على الخادوم. تتوفّر آليّة مُدمجة في Express للقيام بذلك:</p><pre class="javascript ipsCode prettyprint">app.use(express.static(__dirname + '/public'));</pre><p>تتوفّر أيضًا وحدات خارجيّة يمكنها القيام بالمهمّة ذاتها وبخيارات أكثر مثل تحديد لواحق الملفّات وأذوناتها...</p><p>مُلاحظة: المُتغيّر <code>‎__dirname</code> توفّره Node.js وهو يشير إلى المجلّد الذي يحوي الملفّ الحاليّ (<code>index.js</code>).</p><p>سنستفيد من هذا المجلّد في استضافة ملفّات favicon وJavaScript الّتي تعمل في المتصفّح عندما نطوّر مدوّنتنا لتستخدم AJAX.</p><h2>تحسين مظهر المدوّنة</h2><p>سنحتاج إلى إجراء تغييرات في القوالب كإضافة بعض المُعرّفات (IDs) والأصناف (classess) لنقوم بتنسيقها وفق القواعد الّتي نكتبها في ملفّ <code>style.css</code> الّذي أنشأناه للتّوّ. يوفّر Express صياغتين مختصرتين للتّعبير عن الأصناف والمُعرّفات لكونهما شديدتي الشّيوع، لإضافة صنفين ومُعرّف على عنصر <code>div</code> ما، يمكن كتابة:</p><pre class="html ipsCode prettyprint">#comments.post-comments.card</pre><p>وهي مكافئة لكتابة:</p><pre class="html ipsCode prettyprint">div(class="post-comments card", id="comments")</pre><p>والتي ستُترجم إلى HTML الّتالي:</p><pre class="html ipsCode prettyprint">&lt;div class="post-comments card" id="comments"&gt;&lt;/div&gt;</pre><p>لاحظ أنّك لست بحاجة لكتابة <code>div</code>، لأنّ Jade يفهم المغزى من هذا الأسلوب على أنّه كائن <code>div</code> تلقائيًّا، لأنّه الكائن الأكثر استخدامًا لإضافة الأصناف والمعرّفات بهدف تنسيق مكوّنات الصّفحة.</p><p>قد تلاحظ أثناء العمل الحاجة لتكرار أجزاء معيّنة من الشّيفرة في كلّ القوالب مثل إظهار عنوان المدوّنة وروابطها على المواقع الاجتماعيّة ومربّع البحث ضمن ترويسة (header) في كلّ الصّفحات، مع تذييل (footer) يحوي بعض الرّوابط الإضافيّة في نهاية كلّ صفحة، يمكنك التّخلصّ من عناء التّكرار باستخدام الكلمة المفتاحية <code>extends</code> لبناء قالب على قالب آخر، فلنقم ببناء قالب يتضمّن الهيكل العامّ لكلّ الصّفحات، ولنسمّه <code>_layout.jade</code> (اجعل اسمّ هذه الملفّات يبدأ بالرّمز <code>_</code> لتستطيع فيما بعد تمييزها بسرعة بين ملفّات القوالب):</p><pre class="html ipsCode prettyprint">doctype html
html(lang="ar", dir="rtl")
    head
        title مُدوّنتي!
        link(rel="stylesheet", href="/style.css")
    body
        header
            h1#blog-title مُدوّنتي
            ul#blog-nav
                li: a(href="/") الرئيسية
                li: a(href="/login") تسجيل الدخول
                li: a(href="/signup") إنشاء حساب
        block content
        footer
            hr
            p جميع الحقوق غير محفوظة</pre><p>العبارة <code>block</code> متبوعةً باسم نختاره نحن كما نشاء، تسمح لنا ببناء قوالب تشترك جميعها في الهيكل العّام لهذا الملفّ وتختلف فقط في هذا الجزء، مثلاً يمكننا الآن إعادة كتابة الصّفحة الرئيسيّة (<code>home.jade</code>) لتصبح:</p><pre class="html ipsCode prettyprint">extends _layout
block content
    for post in posts
        .post
            h2.post-title #{ post.title }
            p.post-body #{ post.body }
            small.post-date كُتِبَت #{ formatDate(post.date) }</pre><p>سيبحث Jade عن القطعة المُسمّاة <code>content</code> ويضيفها في المكان المناسب للقالب. لا داع لإرفاق لاحقة الملفّ فهي معروفة بالنّسبة لـJade.</p><p>ملاحظة: التّعبير <code>li: a(href=...</code> هو اختصار تتيحه Jade للاستغناء عن الحاجة لكتابة الوسمين على سطرين.</p><p>كثيرًا ما تحتاج إلى إدخال أجزاء متكرّرة من HTML مع إجراء بعض التّعديلات عليها، وهذا ما يمكن تنفيذه من خلال الدّوالّ في Jade والّتي تُسمّى mixins، وهي تشبه كثيرًا الدّوال في أي لغة برمجة، لتوضيح المفهوم أكثر، لنفترض أنّنا نريد توحيد مظهر التّدوينات بين صفحة التّدوينة والصّفحة الرئيسيّة، مع فارق بسيط هو جعل نصّ التّدوينة في الصّفحة الرئيسية محدودًا بمئتي حرف مثلاً، يمكن فعل هذا بنقل شيفرة التّدوينة المفردة إلى دالّة في ملفّ منفصل نُسمّيه <code>_mixins.jade</code>:</p><pre class="html ipsCode prettyprint">mixin post(post, full)
    h2.post-title: a(href="/posts/" + post.slug) #{ post.title }
    p.post-body #{ full ? post.body : (post.body.substr(0, 199) + "...") }
    small.post-date كُتِبَت #{ formatDate(post.date) }</pre><p>لاحظ كم تشبه هذه الصّياغة صياغة الدّوالّ في لغات البرمجة، حيث يمكن إمرار مُعاملات لها بين قوسين. يمكن استدعاءها في قوالبنا بالرّمز <code>+</code> بعد تضمين الملفّ <code>_mixins.jade</code> بالكلمة المفتاحية <code>include</code> الّتي تشبه استدعاء وحدة خارجية بـ<code>require</code> في Node.js:<code class="lang-jade"> </code></p><pre class="html ipsCode prettyprint">extends _layout
include _mixins
block content
    for post in posts
        .post
            +post(post, false)</pre><p>سأقوم الآن بتنسيق المُدوّنة بأسلوبي الخاصّ، وسأترك المجال لك لتفعل الأمر ذاته! إذا أردت استلهام بعض الأفكار، أنصحك بالاطّلاع على مواقع مثل <a rel="external nofollow" href="http://tympanus.net/codrops/">Codrops</a>.</p><p>في الدّرس القادم سنطّلع على بعض المواضيع الّتي يجب أخذها بالحُسبان قبل نشر المدوّنة على الويب.</p>
]]></description><guid isPermaLink="false">23</guid><pubDate>Tue, 03 Mar 2015 18:47:00 +0000</pubDate></item><item><title>&#x625;&#x646;&#x634;&#x627;&#x621; &#x645;&#x62F;&#x648;&#x646;&#x629; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; Express (&#x627;&#x644;&#x62C;&#x632;&#x621; 4): &#x625;&#x62F;&#x627;&#x631;&#x629; &#x627;&#x644;&#x62C;&#x644;&#x633;&#x627;&#x62A;</title><link>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/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2015_03/express.png.ad788132254290b201981319402529db.png" /></p>

<p>لا قيمة لمدوّنتنا إن لم يكن باستطاعتنا كتابة التّدوينات الجديدة ونشرها، لذا علينا إنشاء صفحة تُتيح لنا (لنا فقط) كتابة تدوينة جديدة وحفظها في قاعدة البيانات. لكن أوّل ما يتبادر إلى الذّهن تساؤل عن الكيفيّة الّتي نستطيع أن نمنع فيها الزّائر من إضافة التّدوينات، إذ كيف يستطيع المتصفّح والخادوم التّفريق بين صاحب المدوّنة وزائرها؟</p><p>ملايين المواقع على الويب تقدّم محتوىً مخصّصًا لكلّ مستخدم، إذا عدنا لمثال فيس بوك وطرحنا السؤال ذاته: كيف يعرض فيس بوك نشرة أخبار خاصّة بكلّ مستخدم؟ بالطّبع لكلّ مستخدم حساب في الموقع محميّ بكلمة مرور، لكن ما الذي يحدث بين المتصفّح والخادوم ويجعل الخادوم يُرسل الصّفحة الخاصّة بالمستخدم إليه دون غيره؟</p><p>إن كنت سمعت من قبل بالكعكات (cookies) ولم تعرف ما علاقتها بالويب، فقد حان الوقت لنعرف ما هي وكيف تستخدم.</p><h3>الكعكات (Cookies)</h3><p>الكعكات هي أجزاء صغيرة من البيانات تخزّن في المتصفّح وتنتقل بينه وبين الخادوم مع كلّ طلب إلى هذا الخادوم (كترويسة Heading في طلب HTTP)، يمكن للخادوم أن يأمر المتصفّح بحفظ بيانات جديدة ضمن الكعكات، ويمكن للمتصفّح منع تخزين هذه البيانات بأمر المستخدم أو حذفها متى شاء. تُستخدم الكعكات بشكل شائع لتخزين "الجلسة" (session)، وهي الطّريقة الّتي يتذكّر بها الخادوم هذا المتصفّح بين الطّلب والآخر بحيث يستطيع تمييزه من بين الطّلبات الّتي تصله من حواسيب مختلفة حول العالم.</p><p>من المهمّ أن نعرف أنّ طلبات HTTP هي طلبات مستقلّة بذاتها وعديمة الحالة (stateless)، بمعنى أن كلّ طلب يصدر من المتصفّح للخادوم نفسه لا يعرف أيّ شيء عن الطّلبات السّابقة أو اللاحقة، وكذلك الخادوم؛ إلا إذا تمّ إرفاق معرّف مُميّز (session ID) يتّفق عليه الطّرفان وينتقل مع كلّ طلب بين الجهتين. بدون الجلسات كنت ستحتاج إلى إدخال اسم مستخدمك وكلمة المرور في كلّ مرة تنتقل فيها إلى صفحة جديدة على فيس بوك!</p><p>من المهمّ إذًا أن يكون معرّف الجلسة (session ID) مُميّزًا للمتصفّح ولا يتطابق مع معرّف جلسة أي متصفّح آخر، وهذا يتمّ بتوليد معرّف الجلسة بشكل عشوائي على الخادوم أولًا ثمّ إرساله للمتصفّح لحفظه ضمن الكعكات، وسيقوم المتصفّح بنقل الكعكات مع كلّ طلب، ممّا يسمح للخادوم بمعرفة تتابع الطّلبات من نفس المتصفّح.</p><p>من الاستخدامات الأخرى للكعكات تتبّع المستخدمين بين المواقع عن طريق استضافة محتوى من طرف ثالث ضمن صفحة الموقع (third-party cookies) وهي حيلة تستخدم لمعرفة ذوق المستخدم وتوجّهه من خلال أنواع المواقع الّتي يزورها وبالتّالي استهدافه بالإعلانات أو مراقبة نشاطه. لا غرابة أن تعطينا المتصفّحات وسيلة لمنع كعكات الطّرف الثّالث، أو لمنع الكعكات بالكامل!</p><p>لنلخّص الأمر: تسمح الجلسات بربط طلبات HTTP المتتالية بحيث يتعرّف الخادوم على كونها صادرة من جهة واحدة، مما يسمح له بتخصيص الإجابة على هذه السلسلة من الطّلبات دون غيرها، سنستفيد من هذا في حفظ تسجيل الدّخول المستخدم بحيث لا نطلب منه كلمة المرور عندما ينتقل من صفحة لأخرى. لنعد الآن إلى شيفرة تسجيل الدّخول ولنفكّر، ما الذي نحتاجه لحفظ الجلسة؟</p><p>عندما يُسجّل المستخدم دخوله للمرّة الأولى، نحتاج إلى حفظ مُعرّف الجلسة على الخادوم ليكون بإمكاننا مقارنته مع الطّلبات التّالية، وهذا يعني أنّنا بحاجة لوسيلة لحفظ معرّف الجلسة لكل مستخدم. في Express يتوفّر البرنامج الوسيط <code>express-session</code> الذي يتولّى هذه المهمّة كاملةً. قم بتثبيت هذه الوحدة، ثم لنقم باستيرادها:</p><pre class="javascript ipsCode prettyprint">var express = require('express');
// ...

var session = require('express-session');</pre><p>نريد استخدام <code>express-session</code> على مستوى التّطبيق بالكامل، ما يعني أنّنا نريد لها أن تتعقّب كلّ الطّلبات على جميع الرّوابط المسجّلة مما يسمح بمتابعة الجلسة، تسمّى هذه الوحدات البرامج الوسيطة على مستوى التّطبيق (Application-level middleware) بعكس الأسلوب الّذي استخدمناه في <code>body-parser</code> لتفسير متن الطّلب في نماذج إنشاء المستخدم وتسجيل الدّخول (برامج وسيطة على مستوى المُوجّه Router-level middleware). يمكن للوحدة أن تُستعمل بالأسلوبين.</p><p>تستخدم الوحدات على مستوى التّطبيق باستدعاء الوظيفة <code>use()‎</code> لتطبيقنا:<code class="lang-javascript"> </code></p><pre class="javascript ipsCode prettyprint">app.use(session({
    secret: "my top secret",
    resave: false,
    saveUninitialized: true
}));
</pre><p>تستقبل وحدة <code>session</code> كائن الإعدادات الذي يتضمّن:</p><ul><li><code>secret</code>: كلمة سرّيّة تسمح بتجزئة معرّف الجلسة لحمايته، ضع ما تشاء هنا.</li><li><code>resave</code>: هل يجب كتابة الجلسة مع كلّ طلب حتّى وإن لم تتغيّر؟</li><li><code>saveUninitialized</code>: هل يجب حفظ الجلسات الجديدة تلقائيًّا إلى الخادوم؟</li></ul><p>لا تقلق إن كانت هذه الإعدادات غامضة، ستتعرّف على فائدتها مع مرور الوقت.</p><p>تذكّر أنّ ترتيب استدعاءات البرامج الوسيطة مهمّ، يجب أن نضيف الشّيفرة السّابقة قبل تسجيل الروابط لنتمكّن من متابعة الجلسة عبر كلّ الرّوابط المسجّلة.</p><p>حسنًا من المفترض الآن أن يقوم المتصفّح بحفظ معرّف الجلسة ثم نقله مع كلّ طلب، لنتحقّق من هذا؛ شغّل البرنامج ثم زر الصفحة الرئيسية للمدوّنة، افتح أدوات المطوّرين (<code>Ctrl + Shift + K</code> في فيرفكس)، ثم انتقل إلى تبويب الشّبكة واضغط زر إعادة تحميل الصّفحة، سيبدأ المتصفّح بمراقبة الطّلبات، سيظهر طلب للصفحة الرئيسيّة مع بداية تحميلها، انقر عليه وابحث عن ترويسة <code>Cookie</code> في الجانب، لاحظ أنّها تحوي قيمة <code>connect.sid</code>، وهذا هو المعرّف المميّز الذي سينتقل بين الطّلبات، للتأكد من ذلك انتقل إلى صفحة أخرى مثل <code>‎/posts/hello-world</code> وكرّر العمليّة، ستجد أن معرّف الجلسة ثابت لا يتغيّر.</p><p><a class="ipsAttachLink ipsAttachLink_image" rel="external nofollow" href="https://academy.hsoub.com/uploads/monthly_2015_03/devtools-cookie-header.jpg.9375cab155c4b10df243a76c2972fe85.jpg"><img class="ipsImage ipsImage_thumbnailed" data-fileid="106" alt="devtools-cookie-header.thumb.jpg.d957202" src="https://academy.hsoub.com/uploads/monthly_2015_03/devtools-cookie-header.thumb.jpg.d9572025c90b1ea491b80210bdebd025.jpg"></a></p><p>عظيم! أصبح بإمكاننا الآن تمييز الطّلبات ومتابعتها، لكن ما الفائدة التي جنيناها إلى الآن! في الحقيقة لا شيء، نحتاج إلى الاستفادة من كون معرّف الجلسة مميّزًا بحيث نعلم أن المستخدم الذي تحمل طلباته هذا المعرّف قد سجّل دخوله فلا نطلب منه كلمة المرور في كلّ طلب، وسنستفيد من ذلك أيضًا في تخصيص محتوى الصّفحة وإتاحة وصول الكُتَّاب إلى صفحة إنشاء التّدوينات فيما بعد.</p><p>نحتاج إلى حفظ معرّف الجلسة في جدول بقاعدة البيانات لنتمكّن من طلبه لاحقًا ومقارنته مع الطّلبات القادمة، لنُنشئ جدولاً يحفظ معرّفات الجلسات لكلّ مستخدم:</p><pre class="html ipsCode prettyprint">CREATE TABLE `sessions` (session_id VARCHAR(100) NOT NULL PRIMARY KEY, username VARCHAR(50) NOT NULL, FOREIGN KEY (username) REFERENCES `users` (username), INDEX (username));</pre><p>من المهمّ أن نفهم أنّ معرّف الجلسة يحلّ محلّ كلمة المرور واسم المستخدم معًا، لهذا من الضّروري أن يكون مميّزًا (<code>PRIMARY KEY</code>) بحيث لا يتطابق معرّفان لمستخدمين مختلفين، من المهمّ، للسبب ذاته، حماية الجلسة وتوليدها بطريقة عشوائية، وهذا هو سبب استخدامنا للكلمة السّريّة <code>secret</code> في إعدادات <code>express-session</code>، من إجراءات الأمان الشّائعة إضافة مهلة تنتهي بعدها صلاحيّة الجلسة، وهذا هو السّبب الذي تطالبك لأجله بعض المواقع بإعادة تسجيل دخولك بعد فترة من الزّمن؛ لكنّنا لن نُشغل بالنا بهذه التّفاصيل الآن.</p><p>حسنًا لدينا الآن معرّف الجلسة وجدول لحفظه، كل ما نحتاجه عند تسجيل الدّخول بشكل صحيح حفظ معرّف الجلسة إلى الجدول، لنعد إذًا إلى شفرة تسجيل الدّخول الّتي تركناها في الفقرة السّابقة:<code class="lang-javascript"> </code></p><pre class="javascript ipsCode prettyprint">/*
...
*/

var cookieParser = require("cookie-parser");
app.use(cookieParser());

/*
...
*/

app.post("/sessions", parseBody, function(request, response, next) {
    var username = request.body.username;
    var password = request.body.password;

    if (!username || !password) {
        response.status(400);
        response.send("يجب توفير اسم المستخدم وكلمة المرور");
        return;
    }

    connection.query("SELECT username, password FROM `users` WHERE username=?", [ username ], function(err, rows) {
        var user = rows[0];
        if (!user) {
            response.status(400);
            response.send("لا يوجد مستخدم يطابق اسمه اسم المستخدم المطلوب");
            return;
        }

        bcrypt.compare(password, user.password, function(err, result) {
            if (err) {
                response.status(500);
                response.send("وقع خطأ من جهة الخادم، حاول تسجيل الدخول لاحقًا");
                return;
            }

            if (result == true) {

                connection.query("INSERT INTO `sessions` (session_id, username) VALUES (?, ?)", [ request.cookies["connect.sid"], username ], function(err) {
                    if (err) return next(err); // تعامل مع الخطأ
                    response.status(200);
                    response.send("تم تسجيل الدّخول");
                })

            } else {
                response.status(401);
                response.send("كلمة المرور التي أرسلتها خاطئة");
            }

        })
    });

})
</pre><p>توفّر لنا <code>express-session</code> معرّف الجلسة ضمن الكعكة تحت الاسم <code>connect.sid</code> والذي يمكن تغييره بضبط القيمة <code>name</code> في إعدادات الوحدة. استخدمنا الوحدة <code>cookie-parser</code> التي تقوم بما يوحي به اسمها وتوفّر لنا الكعكات ضمن كائن الطّلب للحصول على معرّف الجلسة.</p><p>نكاد ننتهي من إنشاء نظام المستخدمين، بقي علينا فقط إرفاق معلومات المستخدم مع كلّ دالّة توجيه لنتمكّن من عرض اسم المستخدم في الصّفحة وإتاحة تسجيل الخروج، بل يمكننا أيضًا توجيهه إلى صفحات خاصّة به أو منعه من الوصول إلى صفحات أخرى، سنقوم بإضافة دالّة توجيه تسبق جميع روابطنا وتقوم بإرفاق بيانات المستخدم (بعد جلبها من قاعدة البيانات) وإضافتها إلى كائن الطّلب:</p><pre class="javascript ipsCode prettyprint">app.use(function(request, response, next) {
    var session_id = request.cookies["connect.sid"];
    if (session_id) {
        connection.query("SELECT users.id, users.username, full_name, is_author FROM `users` JOIN `sessions` ON users.username=sessions.username WHERE session_id=?", [ session_id ], function(err, rows) {
            if (!err &amp;&amp; rows[0]) {
                request.user = rows[0];
            }

            next();
        })
    } else {
        next();
    }
})
</pre><p>في الحقيقة، ما كتبناه للتوّ ليس سوى برنامج وسيط، لا يختلف في شيء عن البرامج الوسيطة التي استعملناها مثل <code>express-session</code> عدا أنّ الأخيرة يوفّرها مطوّرون آخرون كحزمة على npm يمكن استيرادها.</p><p>في دوال التّوجيه التّالية، سيتوفّر لدينا كائن <code>request.user</code> يتضمّن معلومات المستخدم الحالي، لنجرّب ذلك بإنشاء صفحة الملفّ الشّخصي للمستخدم (<code>views/profile.jade</code>):</p><pre class="html ipsCode prettyprint">doctype html
html(lang="ar", dir="rtl")
    head
        title الملف الشخصي
    body
        style
            :css
                body {
                    font-family: Arial, sans-serif;
                }

        if (user)
            h1 #{ user.full_name } (#{ user.username })
            hr
        else
            p لم تقم بتسجيل دخولك

app.get("/profile", function(request, response) {
    response.render("profile", { user: request.user })
})
</pre><p>سنقوم بتوفير الكائن <code>request.user</code> للقالب، والذي سيكون غير معرّف إن لم يُوجد في قاعدة البيانات أو إن لم يسجل المستخدم دخوله، سيتولى القالب هذه الحالة ويعرض الرسالة المناسبة. لاحظ دعم Jade للجمل الشّرطيّة.</p><p>حسنًا، لنجرّب الآن ما كتبناه، شغّل البرنامج ثم زر الصفحة <a rel="external nofollow" href="http://localhost:3000/login%D8%8C">http://localhost:3000/login،</a> سجّل الدّخول باسم المستخدم admin وكلمة المرور 123456، من المفترض أن تنتقل إلى صفحة تخبرك بنجاح العملية، الآن انتقل إلى <a rel="external nofollow" href="http://localhost:3000/profile">http://localhost:3000/profile</a> لتشاهد الملف الشّخصيّ (حسنًا لا يبدو عظيمًا جدًّا، لكنّنا سنحسّنه لاحقًا):</p><p><a class="ipsAttachLink ipsAttachLink_image" rel="external nofollow" href="https://academy.hsoub.com/uploads/monthly_2015_03/profile-signed-in.jpg.6d520c35147ab48ad5f795b27eaf5aec.jpg"><img class="ipsImage ipsImage_thumbnailed" data-fileid="105" alt="profile-signed-in.thumb.jpg.e0c853564bb9" src="https://academy.hsoub.com/uploads/monthly_2015_03/profile-signed-in.thumb.jpg.e0c853564bb98921321895da45701a5e.jpg"></a></p><p>تهانينا! لقد أنشأنا نظامًا للمستخدمين وأصبح بإمكاننا عرض محتوى مخصّص لكلّ مستخدم! في الجزء القادم سنتيح لأنفسنا كتابة التّدوينات، وللمستخدمين إضافة التعليقات، وستكون أعظم مدوّنة في التّاريخ!</p>
]]></description><guid isPermaLink="false">22</guid><pubDate>Tue, 03 Mar 2015 18:46:00 +0000</pubDate></item><item><title>&#x625;&#x646;&#x634;&#x627;&#x621; &#x645;&#x62F;&#x648;&#x646;&#x629; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; Express (&#x627;&#x644;&#x62C;&#x632;&#x621; 3): &#x625;&#x646;&#x634;&#x627;&#x621; &#x646;&#x638;&#x627;&#x645; &#x627;&#x644;&#x645;&#x633;&#x62A;&#x62E;&#x62F;&#x645;&#x64A;&#x646;</title><link>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-3-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%86%D8%B8%D8%A7%D9%85-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85%D9%8A%D9%86-r21/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2015_03/express.png.3cd401882ea7f1cad8ba2695fc83cb9c.png" /></p>

<p>في الجزء السّابق أنشأنا الصّفحة الرئيسيّة للمدوّنة وصفحات مفردة لكلّ تدوينة بعد تهيئة المشروع وإنشاء قواعد البيانات، الآن سنقوم بإنشاء نظام للمستخدمين لنسمح للقرّاء بالتّعليق.</p><h3>إنشاء صفحة "حساب جديد" و"تسجيل الدّخول"</h3><p>نعلم إذًا أنّنا بحاجة أولاً إلى آلية لإنشاء الحسابات على خادومنا، وأوّل ما نقوم به إنشاء صفحة على الرّابط <code>‎/signup</code> تحوي نموذجًا يعبّئه المستخدم:<code class="lang-jade"> </code></p><pre class="html ipsCode prettyprint">doctype html
html(lang="ar", dir="rtl")
    head
        title إنشاء مستخدم جديد
    body
        style
            :css
                body {
                    font-family: Arial, sans-serif;
                }

        h1 مُدوّنتي
        hr
        h2 إنشاء مستخدم جديد
        form(action="/accounts", method="POST")
            label(for="name") اسمك: 
            input(type="text", name="name", placeholder="الاسم كاملًا", required)
            br
            label(for="name") كلمة المرور: 
            input(type="password", name="password", placeholder="اختر كلمة مرور قويّة", required)
            br
            label(for="username") اسم المستخدم: 
            input(type="text", name="username", placeholder="حروف لاتينية، 50 حرفًا على الأكثر", required)
            br
            input(type="submit", value="أنشئ الحساب")
</pre><p>احفظ النصّ السّابق في ملف <code>signup.jade</code> في مجلّد <code>views</code>؛ ثمّ أضف النّص التّالي إلى <code>index.js</code> قبل آخر سطر:</p><pre class="javascript ipsCode prettyprint">app.get("/signup", function(request, response) {
    response.render("signup");
})</pre><p>زر الصّفحة <a rel="external nofollow" href="http://localhost:3000/signup">http://localhost:3000/signup</a> بعد تشغيل البرنامج وحاول إنشاء مستخدم جديد، سيرسلك Express إلى صفحة تفيد بعد إمكانية تنفيذ الفعل <code>POST</code> على الرّابط <code>‎/accounts</code>، وهو الرابط الذي اخترناه لتلقّي نماذج إنشاء المستخدمين بالنّموذج الّذي أنشأناه (لاحظ الخاصّتين <code>action</code> و<code>method</code> للنّموذج ضمن قالب Jade)، كلّ ما علينا الآن هو تسجيل دالّة تتعامل مع هذا الرّابط:</p><pre class="javascript ipsCode prettyprint">app.post("/accounts", function(request, response) {
    // أنشئ الحساب
})
</pre><p>إن كنت تتساءل لم استخدمنا <code>POST</code> بدلاً من <code>GET</code>، فالإجابة هي أنّ <code>POST</code> يستخدم للطّلب من الخادوم "إنشاء" الأشياء الجديدة (بينما يطلب <code>GET</code> "الحصول" عليها)، هذا أوّلًا، ثانيًا فإنّ إرسال الطّلب باستخدام <code>GET</code>، وعلى الرّغم من أنّه ممكن، إلّا أنّه قد يكشف كلمة المرور الّتي اختارها المستخدم، لأنّ محتويات النّموذج (بما فيها كلمة المرور) ستُرمّز ضمن الرّابط (URL-encoded)، وكلّ المتصفّحات تحتفظ بنسخة من سجلّ تصفّح المستخدم، وهذا قد يجعلها عرضة لأن يراها الآخرون. هذا مثال عن كيفية ترميز النّماذج في طلبات <code>GET</code>:</p><pre><code>http://myblog.com/signup?username=fwz&amp;password=123456&amp;full_name=فواز
</code></pre><p>كما ترى، ليس هذا أفضل ما يمكننا فعله لإخفاء كلمة المرور!</p><p>يرسل المتصفّح محتويات النّموذج بالفعل <code>POST</code> كمتن الطّلب، وعندما يتلقّاه الخادوم فإنّنا بحاجة إلى تحويله من نصّ مجرّد إلى صيغة كائن JavaScript، لا يقدّم Express هذه الإمكانيّة وحده، ولكنّه يوفّر وحدة منفصلة تُدعى <a rel="external nofollow" href="https://github.com/expressjs/body-parser"><code>body-parser‏</code></a> للقيام بهذه المهمّة، قد يبدو هذا غريبًا للقادمين من PHP، لكنّها الطّريقة الّتي تسير بها الأمور في Node.js، ولهذا فوائده إذ يمكنك استبدال وحدة بوحدة أخرى تؤدّي الوظيفة ذاتها لكن قد تكون أسرع أو تقدّم وظائف أكثر، وكذلك يسمح هذا النّهج بتطوير الوحدات الصّغيرة بشكل أسرع دون الانتظار إلى صدور نسخة جديدة من إطار العمل كاملاً.</p><p>كاختبار لك، قم بتثبيت <code>body-parser</code> وحفظه في متطلّبات المشروع.</p><p>هل تذكر عندما تحدّثنا عن <strong>البرامج الوسيطة (middleware)</strong>؟ حسنًا، وحدة <code>body-parser</code> ليست سوى واحدة من هذه البرامج، ويأتي الاسم من كونها تتوسّط وظيفة Express لتوسّع خياراته بشكل منسجم مع سير توجيه الرّوابط. لإخبار Express برغبتنا باستخدام <code>body-parser</code>، علينا استيرادها ثمّ إدخالها <em>كوسيط</em> لعمليّة توجيه الرّابط <code>/accounts</code>:</p><pre class="javascript ipsCode prettyprint">var express = require("express");
var bodyParser = require("body-parser");

/*
...
*/

var parseBody = bodyParser.urlencoded({ extended: true });

app.post("/accounts", parseBody, function(request, response) {
    console.log(request.body);
})

app.listen(3000);</pre><p>هذه إحدى الطّرق لاستخدام البرامج الوسيطة على أحد الرّوابط، يمكن إدخال أي عدد من البرامج الوسيطة وسينفّذها Express واحدًا تلو الآخر حتّى يصل أخيرًا إلى دالّتنا الّتي تتعامل مع الرّابط. يمكننا أيضًا استخدام <code>body-parser</code> وأي برنامج وسيط آخر ليتدخّل في سير التّطبيق كاملاً (ليس على رابط واحد فقط)، وسنرى كيفيّة ذلك في وقتٍ لاحق.</p><p>إن كانت صياغة السّطر <code>var parseBody...‎</code> غامضة فراجع <a rel="external nofollow" href="https://github.com/expressjs/body-parser">صفحة توثيق وحدة <code>body-parser‏</code></a>، الأمر يتعلّق بأسلوب المطوّر الّذي أنشأ الوحدة، قد يكون أسلوب الوحدات الأخرى مختلفًا لكنّ ما يهمّك هو أن تتعلّم كيفيّة استخدام البرامج الوسيطة.</p><p>في دالّة التّوجيه الأخيرة، سنقوم مبدئيًا بتسجيل محتويات النّموذج إلى الطّرفيّة الّتي تشغّل برنامجنا، يتوفّر الكائن <code>request.body</code> فقط لأنّنا استخدمنا <code>body-parser</code> قبل دالّتنا، والذي أتاح محتويات النّموذج في عنصر الطّلب. أعد تشغيل البرنامج وزُر الصّفحة ثم املأ الحقول واضغط "أنشئ الحساب"، عُد للطّرفيّة لتشاهد محتويات النّموذج وقد وصلت للخادوم:</p><p><a class="ipsAttachLink ipsAttachLink_image" rel="external nofollow" href="https://academy.hsoub.com/uploads/monthly_2015_03/body-parser-console-log.jpg.cc8c97812fc72e870abb72c54e7c1b9d.jpg"><img class="ipsImage ipsImage_thumbnailed" data-fileid="103" alt="body-parser-console-log.thumb.jpg.9f2c5b" src="https://academy.hsoub.com/uploads/monthly_2015_03/body-parser-console-log.thumb.jpg.9f2c5b3eccb2dec3344796546a29dc0c.jpg"></a></p><p>حسنًا لقد وصلَنا النّموذج وهو جاهز لإدخاله في قاعدة البيانات، لكن ليس قبل التّحقّق من محتوياته. القاعدة الرئيسيّة في حماية قواعد البيانات: <strong>لا تثق بما يُدخله المستخدم!</strong> تحقّق من سلامة كلّ حقل في النّموذج قبل إدخاله، ماذا لو أرسل المستخدم حقلاً إضافيًا <code>is_author</code> وجعل قيمته <code>true</code>، سيكون بإمكانه حينئذٍ كتابة التّدوينات دون أن نسمح له بذلك!<code class="lang-javascript"> </code></p><pre class="javascript ipsCode prettyprint">app.post("/accounts", parseBody, function(request, response) {
    var username = request.body.username;
    var password = request.body.password;
    var full_name = request.body.name;

    if (!username || !password || username.length &gt; 50) {
        response.status(400);
        response.send("تعذّر إنشاء الحساب، تحقّق من سلامة المُدخلات وأعد المحاولة");
        return;
    }

    connection.query("INSERT INTO `users` (username, password, full_name) VALUES (?, ?, ?)", [username, password, full_name], function(err) {
        if (err) {
            response.status(500);
            response.send("وقع خطأ أثناء إنشاء الحساب، أعد المحاولة");
            return;
        }

        response.status(201);
        response.send("أُنشئ الحساب، يمكنك الآن إنشاء المستخدم");
    });
})
</pre><p>من المّهمّ ألا تأخذ الكائن <code>response.body</code> كاملاً وتلقيه مباشرة في قاعدة بياناتك، فقد يحتوي على حقول إضافية مثل <code>is_author</code>. قمنا بإجراء تحقّق بسيط من طول اسم المستخدم. تتوفّر في Node.js وحدات تُعطينا إمكانيّات أوسع للتحقّق من المدخلات بحسب أنواعها (نصّيّة، أرقام، عناوين البريد... إلخ)، سنستعرض إحداها لاحقًا.</p><p>استخدمنا الرّمز <code>400</code> في حال الخطأ ومعناها <code>Bad Request</code>، تستخدم الأرقام ضمن 400-499 للإشارة إلى خطأ من <a rel="external nofollow" href="https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_Client_Error">جهة مُرسل الطّلب</a> (خلافًا <a rel="external nofollow" href="https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#5xx_Server_Error">للفئة <code>5xx‏</code></a> الّتي تعني أنّ الخطأ من جهة الخادوم). أمّا الرّمز <code>201</code> فيعني <code>Created</code> (أُنشئ).</p><h3>حماية كلمة المرور بدوال التّجزئة (Password hashing functions)</h3><p><a rel="external nofollow" href="https://en.wikipedia.org/wiki/Cryptographic_hash_function">التجزئة (hashing)‏</a> موضوع معقّد للغاية، ويحتاج شرح مفاهيمه إلى سلسلة أطول من هذه! لكنّنا سنحاول توضيحه باختصار شديد للمبتدئين. بحسب ويكيبيديا، فإنّ دالّة التّجزئة التّشفيريّة:</p><blockquote><blockquote data-ipsquote="" data-cite="اقتباس" class="ipsQuote"><p>هي دالّة تجزئة غير قابلة للعكس عمليًّا، بمعنى أنّه من غير الممكن استعادة البيانات المُدخلة من قيمة التّجزئة وحدها.</p></blockquote></blockquote><p>بالطّبع هذا التّعريف غامض جدًّا، والسّبب يعود إلى حدّ ما إلى غياب مصطلح عربيّ معتمد للكلمة hash، لعلّ الصّورة المرفقة مع التّعريف أعلاه تساعدنا في فهم المقصود:</p><p><img alt="خوارزمية التّجزئة SHA-1" src="https://upload.wikimedia.org/wikipedia/commons/thumb/2/2b/Cryptographic_Hash_Function.svg/740px-Cryptographic_Hash_Function.svg.png"></p><p>التّجزئة إذًا هي تحويل النّصوص المقروءة (كلمات المرور مثلاً) إلى تلك المجموعة من الحروف والأرقام الغامضة لنا، والغاية من ذلك الحصول على قيمة مميّزة للنصّ المُدخل دون الحاجة لمعرفة النّصّ ذاته، وبحيث يكون من المستحيل الحصول على نصّين مختلفين لهما قيمة مُجزّأة واحدة. إذا تمكّن شخصٌ ما من الحصول على القيم المجزّئة (يمين الصّورة) فلن يستطيع معرفة النّصّ الأصليّ (يسار الصّورة)، و<strong>الطّريقة الوحيدة الّتي يمكن الاستفادة منها من القيمة المُجزّئة، هي إمكانية الإجابة على هذا السّؤال: هل النّصّ <code>x</code> يطابق تمامًا النّصّ <code>y</code>؟ يمكن الإجابة بنعم بالتّأكيد إذا كانت القيمة المُجزّئة لـ<code>x</code> تطابق القيمة المُجزئّة لـ<code>y</code></strong>.</p><p>نحفظ كلمة المرور مُجزّئة في قاعدة البيانات لأنّنا لا نهتمّ (ولا نرغب) بمعرفة كلمة المرور الّتي اختارها المستخدم. ما يهمّنا فقط هو أن نتحقّق من كون <em>القيمة المُجزّئة</em> المخزّنة في قاعدة البيانات تطابق ما يدخله المستخدم عند تسجيل دخوله <em>بعد تجزئته</em> بنفس الخوارزميّة، من المهمّ كذلك ألّا تتطابق القيمة المجزّئة لكلمتي مرور مختلفتين وإلّا سيتمكّن شخص محظوظ ما (أو ذكيّ) من تسجيل الدّخول باسم مستخدم آخر بكلمة مرور مختلفة!</p><p>علينا أنّ نفرّق التّجزئة عن التعمية (encryption) والّتي هي تحويل نصّ مجرّد (plaintext) إلى نصّ مُشفّر (ciphertext) وفق عمليّة رياضيّة قابلة للعكس، بينما تهدف التّجزئة إلى تحويل البيانات المختلفة الحجم إلى قيمة ثابتة الطّول باتّجاه واحد فقط (one-way).</p><p>في المثال السّابق أدخلنا كلمة المرور في قاعدة البيانات دون تجزئة، وهذا خطأ فادح لأنّه يسمح لمن يستطيع الوصول إلى جدول المستخدمين بالاطّلاع على كلمات مرورهم جميعًا، لعلّك تستخدم <code>md5</code> أو <code>sha1</code> في PHP لتجزئة كلمة المرور بشكل تقليدي، تتوفّر وحدات Node.js تسمح بتجزئة النّصوص بهذه الخوارزميات، لكنّنا سنستخدم خوارزميّة <code>bcrypt</code> الّتي تُعد <a rel="external nofollow" href="http://codahale.com/how-to-safely-store-a-password">أكثر أمانًا بمراحل من الخوارزميّتين</a> سابقتي الذّكر:</p><pre><code class="lang-shell">npm install bcrypt --save
</code></pre><p>ملاحظة: تحتاج الوحدة <code>bcrypt</code> إلى إصدار متوافق من Python مثبّتًا على جهازك، راجع <a rel="external nofollow" href="https://github.com/ncb000gt/node.bcrypt.js">صفحة الوحدة على GitHub‏</a> لمزيد من التّفاصيل.</p><pre class="javascript ipsCode prettyprint">var bcrypt = require("bcrypt");

/*
...
*/

app.post("/accounts", parseBody, function(request, response) {
    var username = request.body.username;
    var password = request.body.password;
    var full_name = request.body.name;

    if (!username || !password || username.length &gt; 50) {
        response.status(400);
        response.send("تعذّر إنشاء الحساب، تحقّق من سلامة المُدخلات وأعد المحاولة");
        return;
    }

    bcrypt.hash(password, 8, function(err, hash) {
        if (err) {
            response.status(500);
            response.send("تعذّر إنشاء الحساب، تحقّق من سلامة المُدخلات وأعد المحاولة");
            return;
        }

        connection.query("INSERT INTO `users` (username, password, full_name) VALUES (?, ?, ?)", [username, hash, full_name], function(err) {
            if (err) {
                response.status(500);
                response.send("وقع خطأ أثناء إنشاء الحساب، أعد المحاولة");
                return;
            }

            response.send(201);
            response.send("أُنشئ الحساب، يمكنك الآن تسجيل الدخول");
        });
    });
})

// ...
</pre><p>لنجرّب إنشاء مستخدم جديد الآن، شغّل البرنامج ثم انتقل إلى <a rel="external nofollow" href="http://localhost:3000/signup%D8%8C">http://localhost:3000/signup،</a> املأ الحقول بمُدخلات سليمة ثمّ اضغط "أنشئ الحساب":</p><p href="https://academy.hsoub.com/uploads/monthly_2015_03/signup-account-created.jpg.2b4a15aa637ef837dc4a529e8a077898.jpg"><img class="ipsImage ipsImage_thumbnailed" data-fileid="77" alt="signup-account-created.jpg.2b4a15aa637ef" src="https://academy.hsoub.com/uploads/monthly_2015_03/signup-account-created.thumb.jpg.fa3e98f43df263f1674afbde8442d125.jpg"></p><p>لنتأكد من وجود الحساب في قاعدة البيانات، افتح صدفة MySQL ونفّذ الاستعلام التّالي بعد الاتّصال بقاعدة البيانات:</p><pre class="html ipsCode prettyprint">SELECT FROM `users` WHERE username="muhammad";</pre><p>بدّل اسم المستخدم بالاسم الذي ملأته في حقل "اسم المستخدم" عند إنشاء الحساب، ستحصل على نتيجة مشابهة لهذه:</p><pre class="html ipsCode prettyprint">+----+----------+--------------------------------------------------------------+-----------+-----------+‎
| id | username | password                                                     | full_name | is_author |
+----+----------+--------------------------------------------------------------+-----------+-----------+
|  2 | muhammad | $2a$08$6GFnpkKY6VQuB6/y4NCrg.AK9jI25XyfS6APz4rP8w1bpICKNR79G | محمد‎      |         0 |
+----+----------+--------------------------------------------------------------+-----------+-----------+
1 row in set (0.00 sec)</pre><p>لاحظ كون كلمة المرور مُجزّئة ممّا يجعل معرفتها مستحيلة لمن يصل لجدول المستخدمين.</p><h3>تسجيل الدّخول</h3><p>لُننشئ صفحة تسجيل الدّخول على الرابط <code>‎/login</code> مع القالب <code>views/login.jade</code>:</p><pre class="html ipsCode prettyprint">doctype html
html(lang="ar", dir="rtl")
    head
        title تسجيل الدخول
    body
        style
            :css
                body {
                    font-family: Arial, sans-serif;
                }

        h1 مُدوّنتي
        hr
        h2 تسجيل الدخول
        form(action="/sessions", method="POST")
            label(for="username") اسم المستخدم: 
            input(type="text", name="username", required)
            br
            label(for="name") كلمة المرور: 
            input(type="password", name="password" required)
            br
            input(type="submit", value="سجّل الدخول")

</pre><p>سنضيف هذه الشيفرة للتّعامل مع تسجيل الدّخول:<code class="lang-javascript"> </code></p><pre class="javascript ipsCode prettyprint">app.get("/login", function(request, response) {
    response.render("login");
})

app.post("/sessions", parseBody, function(request, response) {
    // ابحث عن المستخدم وتأكد من صحة كلمة المرور
})

// ...</pre><p>الخطوة الأولى في تسجيل الدّخول تتضمّن التّحقّق من وجود اسم المستخدم ومقارنة كلمة المرور بعد تجزئتها (hashing) للكلمة المُجزئة في قاعدة البيانات.</p><pre class="javascript ipsCode prettyprint">app.post("/sessions", parseBody, function(request, response) {
    var username = request.body.username;
    var password = request.body.password;

    if (!username || !password) {
        response.status(400);
        response.send("يجب توفير اسم المستخدم وكلمة المرور");
        return;
    }

    connection.query("SELECT username, password FROM `users` WHERE username=?", [ username ], function(err, rows) {
        var user = rows[0];
        if (!user) {
            response.status(400);
            response.send("لا يوجد مستخدم يطابق اسمه اسم المستخدم المطلوب");
            return;
        }

        bcrypt.compare(password, user.password, function(err, result) {
            if (err) {
                response.status(500);
                response.send("وقع خطأ من جهة الخادم، حاول تسجيل الدخول لاحقًا");
                return;
            }

            if (result == true) {
                // كلمتا المرور متطابقتان

                response.status(200);
                // احفظ الجلسة على المتصفّح
            } else {
                response.status(401);
                response.send("كلمة المرور التي أرسلتها خاطئة");
            }

        })
    });

})</pre><p>في البداية نبحث في قاعدة البيانات عن سطر يوافق حقل <code>username</code> فيه القيمة <code>username</code> الّتي أرسلها المتصفّح، إن وُجد هذا المستخدم فإنّنا نستخدم الوظيفة <code>compare()‎</code> الّتي توفّرها <code>bcrypt</code> لمقارنة كلمة المرور المُجزّئة مع كلمة المرور الّتي أرسلها المستخدم، إن كانت نتيجة المعامل <code>result</code> مساوية لـ<code>true</code>، فهذا يعني أنّ كلمة المرور صحيحة. وإلّا فإنّنا نُرسل الرّمز <code>401</code> ويعني <code>Unauthorized</code> (غير مُصرّح له) مع رسالة مناسبة للدّلالة على فشل تسجيل الدّخول.</p><p>لم ننتهِ بعد من تسجيل الدّخول، لكنّنا سنؤجّل الخطوة الثانية قليلاً، لأنّها تعتمد على فهمنا للجلسات (sessions)، الّتي ستكون موضوع الدّرس القادم.</p>
]]></description><guid isPermaLink="false">21</guid><pubDate>Tue, 03 Mar 2015 18:45:00 +0000</pubDate></item><item><title>&#x625;&#x646;&#x634;&#x627;&#x621; &#x645;&#x62F;&#x648;&#x646;&#x629; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; Express (&#x627;&#x644;&#x62C;&#x632;&#x621; 2): &#x62A;&#x648;&#x62C;&#x64A;&#x647; &#x627;&#x644;&#x631;&#x651;&#x648;&#x627;&#x628;&#x637; &#x641;&#x64A; Express</title><link>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-2-%D8%AA%D9%88%D8%AC%D9%8A%D9%87-%D8%A7%D9%84%D8%B1%D9%91%D9%88%D8%A7%D8%A8%D8%B7-%D9%81%D9%8A-express-r20/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2015_03/express.png.91bf102e0b57c80df8b4f1166d16148f.png" /></p>

<p>في الدّرس السّابق قمنا بثتبيت Node.js وخادوم MySQL وبقيّة متطلّبات المشروع، حان الوقت لنبدأ العمل الحقيقيّ!</p><h2>إنشاء صفحة المدوّنة الرئيسيّة</h2><p>أهمّ ما تعرضه الصّفحة الرئيسيّة لكلّ مدوّنة عادةً آخر التّدوينات بتاريخ كتابتها من الأحدث للأقدم، وسنركّز الآن على تطبيق هذا الجزء على أنّ نتوسّع في إضافة الميّزات في وقتٍ لاحق.</p><p>أنشئ الملفّ <code>index.js</code> الّذي يُمثّل نقطة انطلاق مشروعنا، ولنبدأ باستيراد Express ضمنه:</p><pre class="javascript ipsCode prettyprint">var express = require("express");</pre><p>لنُنشئ الآن تطبيق Express جديد، وهو يمثّل الخادوم الذي يُدير مدوّنتنا بالكامل، يتمّ إنشاء تطبيق Express ببساطة باستدعاء دالّة <code>express</code> الّتي أنشأناها لتوّنا:</p><pre class="javascript ipsCode prettyprint">var express = require("express");
var app = express();</pre><p>تكون الصّفحة الرئيسيّة للمدوّنة على الرّابط الجذر للموقع عادةً، وهو ما نُعبّر عنه بـ<code>/</code>، سنطلب من تطبيقنا الاستجابة للطّلبات التي تصل إلى هذا الرّابط بعرض صفحة HTML تحوي آخر 10 تدوينات مرتّبة وفق تاريخ كتابتها من الأحدث إلى الأقدم:</p><pre class="javascript ipsCode prettyprint">var express = require("express");
var app = express();

app.get("/", function(request, response) {
   // أرسل HTML
});</pre><p>لندع إرسال الصّفحة جانبًا ولنفهم أسلوب استخدام Express، لكلّ تطبيق Express وظائف أربعة تُستخدم في استقبال وتوجيه الطّلبات، وهي <code>get</code> و<code>post</code> و<code>put</code> و<code>del</code>، وهذه الوظائف توافق أفعال HTTP الشّائعة. ولكن ما هي أفعال HTTP؟</p><h3>كيف يعمل HTTP؟</h3><p>في كلّ مرّة تزور صفحة على الويب فإنّ متصفّحك يرسل للخادوم الذي يستضيف الموقع طلبًا بالحصول (<code>GET</code>) على المحتوى في الرابط الذي كتبته، يكون طلب HTTP هذا مشابهًا للمثال التّالي (المُبسَّط عمدًا):<code class="lang-http"> </code></p><pre class="html ipsCode prettyprint">GET www.myblog.com/hello-world HTTP/1.1
Accept: text/html
Accept-Language: ar-sy,ar;
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:34.0) Gecko/20100101 Firefox/34.0</pre><p>تُسمّى الحقول <code>Accept</code> و<code>Accept-Language</code>... بترويسات الطّلب (Request Headings)، ولكلّ ترويسة معنىً بالنّسبة للخادوم الذي يستقبل الطّلب، فمثلاً يقوم المتصفّح في الحقل <code>User-Agent</code> بالتّعريف عن نفسه، وهو ما يسمح للخادوم بإرسال جواب مخصّص لكلّ متصفّح مثلاً (إن شاء)، وفي الحقل <code>Accept-Language</code> يُرسِل المتصفّح اللّغات الّتي يرغب المستخدم برؤية الجواب بها، فيقوم الخادوم بإرسالة الصّفحة بالعربيّة (سوريا) <code>ar-sy</code> في حالتنا إن توفّرت لديه، أو بالعربية <code>ar</code> كخيار ثانٍ... وهكذا. يردّ الخادوم على الطّلب بجواب HTTP (‏HTTP Response) الذي يُشبه مثالنا هذا:</p><pre class="html ipsCode prettyprint">HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: 3918

&lt;!DOCTYPE html&gt;
&lt;html lang="ar"&gt;
    &lt;head&gt;
    &lt;title&gt;مُدوّنتي - مرحبًا بالعالم!&lt;/title&gt;
    &lt;/head&gt;
    &lt;body&gt;
    مرحبًا بكم في مدوّنتي المتواضعة! 
    &lt;/body&gt;
&lt;/html&gt;</pre><p>السّطر الأوّل في الجواب يُسمّى سطر الحالة، ويتضمّن حالة الطّلب (حيث الرّقم <code>200</code> يعني أن الخادوم تلقّى الطّلب وردّ عليه بما هو متوقّع)، بقيّة السّطور هي ترويسات الجواب (Response Headings) الّتي تعني كلّ واحدة منها شيئًا ما لمستقبل الجواب (المتصفّح). يلي الترويسات متن الجواب (Response Body) الذي يحوي في حالتنا صفحة HTML الّتي سيقوم المتصفّح بعرضها على المستخدم.</p><p>فعل <code>GET</code> الذي استخدمناه ليس وحيدًا، فهناك أفعال أخرى مثل <code>POST</code> الذي يُستخدم في المتصفّح لإرسال الحقول التي يُعبّئها المستخدم (كتعبئة حقل تسجيل الدّخول)، والفعل <code>DELETE</code> الذّي يستخدم ليطلب من الخادوم <em>حذف</em> محتوى ما (مثل حذف تدوينة من قبل المستخدم). الجدير بالذّكر أن الخادوم حرّ التّصرّف بالطّلبات التي يتلقّاها، والطّريقة التي شرحناها بهذه الأفعال مبنيّة على التّقاليد الشّائعة لاستخدامها، فلا شيء في الحقيقة يمنع الخادوم من حذف تدوينة عندما يتلقّى طلب <code>GET</code> بدلاً من <code>DELETE</code> وإنّما هو عُرف متّفق عليه.</p><p>لنعد الآن إلى مثالنا السّابق، تقبل الوظيفة <code>get</code> مُعاملين أولهما الرّابط المطلوب التّعامل معه، والأخرى دالّة تقرأ الطّلب وتعدّل جوابه قبل إرسال الجواب للمُتصفّح، يمكن إرسال متن الجواب للمتصفّح من خلال الوظيفة <code>send()‎</code> للكائن <code>response</code>:</p><pre class="javascript ipsCode prettyprint">var express = require("express");
var app = express();

app.get("/", function(request, response) {
    var html = "&lt;!DOCTYPE html&gt;&lt;html lang='ar'&gt;" +
        "&lt;head&gt;&lt;title&gt;مُدوّنتي!&lt;/title&gt;&lt;/head&gt;" +
        "&lt;body&gt;" + posts.map(function(post) { return "&lt;li&gt;" + post.title + "&lt;/li&gt;"; }).join("") + "&lt;/body&gt;&lt;/html&gt;"
    response.send(html);
});</pre><p>في الحالة الافتراضية سيكون جواب هذا الطّلب بالرّمز <code>‎200 OK</code> مع متن يطابق محتوى المُتغيّر <code>html</code>. سنتعرّف فيما بعد على كيفيّة تغيير رموز الحالة بحيث نُرسل الرّمز الشّهير <code>‎404 Not Found</code> عندما لا نجد تدوينة على الرّابط المطلوب.</p><p>سيتوقّف البرنامج في هذه الحالة مُعطيًا خطأ بسبب كون <code>posts</code> غير معرّف، كلّ ما علينا الآن هو جلب التّدوينات من خلال قاعدة البيانات وتخزينها ضمن المُتغيّر <code>posts</code>، نحتاج إذًا لتنفيذ استعلام MySQL لجلب أحدث التدوينات، ولهذا سنقوم باستيراد وحدة <code>mysql</code> التي قمنا بتثبيتها وتأمين الاتصال بقاعدة البيانات:</p><pre class="javascript ipsCode prettyprint">var express = require("express");
var mysql = require("mysql");

var connection = mysql.createConnection({ host: "localhost", user: "root", password: "", database: "myblog" });
connection.connect();

var app = express();

app.get("/", function(request, response) {
    connection.query( "SELECT * from `posts` ORDER BY date DESC LIMIT 10;", function(err, posts) {
        if (err) throw err;

        var html = "&lt;!DOCTYPE html&gt;&lt;html lang='ar'&gt;" +
        "&lt;head&gt;&lt;title&gt;مُدوّنتي!&lt;/title&gt;&lt;/head&gt;" +
        "&lt;body&gt;" + posts.map(function(post) { return "&lt;li&gt;" + post.title + "&lt;/li&gt;"; }).join("") + "&lt;/body&gt;&lt;/html&gt;";

        response.send(html);
    });

});

</pre><p>ملاحظة: لا تنسَ تغيير اسم المستخدم وكلمة المرور ليتوافقا مع ما اخترته أثناء تثبيت MySQL. تمتلك وحدة <code>mysql</code> وظيفة <code>createConnection()‎</code> الّتي تُعيد لنا نسخة من اتّصال بقاعدة البيانات الّتي حدّدناها، والذي يمكن بدؤه باستدعاء الوظيفة <code>connect()‎</code> ثم تّنفيذ الاستعلامات <code>query()‎</code> الّتي تتمّ بأسلوب غير متزامن (asynchronous) لتُعيد لنا الصّفوف النّاتجة عن الاستعلام ضمن المعامل الثّاني للدّالة (<code>function(err, posts) { ... }‎</code>) الّتي تُستدعى بعد انتهاء الاستعلام.</p><p>بهذه السّطور القليلة التي يمكن فهمها بالقليل من الجهد تمكنّنا من إنشاء مدوّنة بسيطة، وهنا يبرز جمال Node.js الذي يسمح للمبتدئين بتطبيق أفكار قد تبدو بعيدة المنال وجعلها واقعًا ملموسًا!</p><p>الآن حان وقت تجربة المشروع، نحتاج لإخبار Express بالإنصات إلى الطّلبات الّتي ترد على منفذ معيّن على جهازنا (<code>localhost</code>):</p><pre class="javascript ipsCode prettyprint">var express = require("express");
var mysql = require("mysql");

var connection = mysql.createConnection({ host: "localhost", user: "root", password: "", database: "myblog" });
connection.connect();

var app = express();

app.get("/", function(request, response) {
    connection.query( "SELECT * from `posts` ORDER BY date DESC LIMIT 10;", function(err, posts) {
        if (err) throw err;

        var html = "&lt;!DOCTYPE html&gt;&lt;html lang='ar'&gt;" +
        "&lt;head&gt;&lt;title&gt;مُدوّنتي!&lt;/title&gt;&lt;/head&gt;" +
        "&lt;body&gt;" + posts.map(function(post) { return "&lt;li&gt;" + post.title + "&lt;/li&gt;"; }).join("") + "&lt;/body&gt;&lt;/html&gt;";

        response.send(html);
    });

});

app.listen(3000);</pre><p>لبدء البرنامج، افتح الطّرفيّة وانتقل إلى مجلّد المشروع، ثم نفّذ الأمر التّالي:</p><pre><code class="lang-bash">node index.js
</code></pre><p>ستتوقّف المؤشّر في الطّرفيّة عن الاستجابة بسبب انشغال هذه الطّرفيّة بتنفيذ البرنامج، اذهب إلى المتصفّح وانتقل إلى الرابط <a rel="external nofollow" href="http://localhost:3000/%E2%80%8E">http://localhost:3000/‎</a> وشاهد النّتيجة:</p><p><a class="ipsAttachLink ipsAttachLink_image" rel="external nofollow" href="https://academy.hsoub.com/uploads/monthly_2015_03/home-page.jpg.767fb515db80b8f22a2d7c037036337b.jpg"><img class="ipsImage ipsImage_thumbnailed" data-fileid="68" alt="home-page.thumb.jpg.e3e3fe8ba940095f97c0" src="https://academy.hsoub.com/uploads/monthly_2015_03/home-page.thumb.jpg.e3e3fe8ba940095f97c098114877f2a6.jpg"></a></p><p>قد تبدو الصّفحة غاية في البساطة وخالية من أي عُنصر جماليّ، لكنّ ما يهمّنا الآن هو أنّنا قمنا بإنشاء خادوم يتواصل مع قاعدة بيانات ويعرض النّتائج على المستخدم... كلّ هذا في 16 سطرًا من JavaScript!</p><p>لإنهاء البرنامج عُد إلى الطّرفيّة ذاتها واضغط Ctrl+C.</p><h2>تعرّف على لغة القوالب Jade</h2><p>بعد أن تأكدنا من تنفيذ المكوّن الرئيسيّ لمشروعنا، سنعمل على تحسين شيفرتنا لجعلها أكثر بساطة وقابلة للتّطوير بسهولة فيما بعد. إذا ألقينا نظرةً على آخر ما كتبناه، سرعان ما نكتشف التّعقيد الذي ستصل إليه شيفرتنا إن أردنا إضافة المزيد من المزايا ضمن HTML، لأنّ هذا يعني إضافة المزيد من النّصّ إلى المتغيّر <code>html</code> بحيث يصبح طويلاً جدًّا وصعب القراءة؛ لا بدّ أن توجد طريقة أفضل من هذه!</p><p>تتوفّر في كلّ اللّغات طريقة لتوليد صفحات HTML ديناميكيّة على الخادوم، بمعنى أنّه يمكن تغيير بعض محتوياتها وإدخال محتوى مُتغيّر فيها قبل إرسالها إلى المستخدم، هل تساءلت يومًا كيف يعرض فيس بوك لكلّ مستخدم صفحةً خاصّة به؟ بحيث يكون هيكلها متماثلاً لكلّ المستخدمين ولكن محتواها من الأخبار مختلف من مستخدم لآخر، الجواب هو باستخدام القوالب؛ لن نقوم بإنشاء فيس بوك جديد الآن، لكنّنا سنستفيد من ميزات القوالب الدّيناميكيّة لتوليد HTML بدلًا من كتابتها يدويًّا ضمن شيفرتنا!</p><p>في عالم Node.js ستجد الكثير من لغات القولبة، لكنّ الامتداد الطّبيعيّ لاستخدام Express يكون باعتماد <a rel="external nofollow">Jade</a> كلغة قولبة كونها بدأت من المُطوّر ذاته، لنُعد كتابة HTML الصّفحة الرّئيسيّة لمدوّنتنا باستخدام Jade:</p><pre class="html ipsCode prettyprint">doctype html
html(lang="ar")
    head
        title "مُدوّنتي!"
    body
        for post in posts
            li #{ post.title }</pre><p>قارن بين نصّ HTML ونصّ Jade الأخير، أوّل ما نلاحظه في Jade هو بساطة صياغتها، فهي تلغي الوسوم النّهائيّة (مثل <code>&lt;/head&gt;</code> و<code>&lt;/body&gt;</code>) وتستعيض عن ذلك بكونها حسّاسة للمحاذاة، فكون الوسم <code>title</code> مُزاحًا إلى يمين <code>head</code> يعني أنّه محتوىً ضمنه، وكذلك الأمر بالنّسبة لـ<code>body</code>، نلاحظ كذلك دعم Jade للحلقات والمُتغيّرات، وهي من أبرز مزايا لغات القوالب، لأنها تسمح بتوليد عناصر متكرّرة دون الحاجة لكتابتها يدويًّا.</p><p>سنحتاج أوّلًا لتثبيت Jade وحفظه في متطلّبات المشروع:</p><pre><code class="lang-bash">npm install jade --save
</code></pre><p>احفظ شيفرة Jade السابقة في ملفّ <code>home.jade</code> ضمن مجلّد جديد سمّه <code>views</code> داخل مُجلّد المشروع، ثمّ عُد للملفّ <code>index.js</code>، ولنقم باستخدام Jade عوضًا عن الأسلوب السابق:</p><pre class="javascript ipsCode prettyprint">var express = require("express");
var mysql = require("mysql");

var connection = mysql.createConnection({ host: "localhost", user: "root", password: "", database: "myblog" });
connection.connect();

var app = express();

app.set("view engine", "jade");

app.get("/", function(request, response) {
    connection.query( "SELECT * from `posts` ORDER BY date DESC LIMIT 10;", function(err, posts) {
        response.render("home", { posts: posts });
    });

});

app.listen(3000);</pre><pre>ضبطنا الإعداد view engine في Express إلى القيمة "jade"، يستخدم Express هذا الإعداد عندما يُطلب منه عرض ملفّ ما باستخدام الوظيفة render التّابعة لكائن الجواب response، بحيث يبحث عن مُفسّر لغة القوالب (jade في حالتنا) ويطلب منه تحويل الملفّ "home" إلى HTML، مُمرّرًا له الكائن الذي يحوي المتغيّرات الّتي يحتاجها ({ posts: posts }). يبحث Express عن ملفّات العرض في المجلّد views بشكل افتراضيّ، وهو ما قمنا بإنشاءه للتّوّ.</pre><p>قم بتشغيل البرنامج مرّة أخرى باستخدام الأمر <code>node index.js</code> ثمّ زر الرّابط <a rel="external nofollow" href="http://localhost:3000/%E2%80%8E">http://localhost:3000/‎</a>. لم يتغيّر شيء ظاهر، لكنّنا انتقلنا إلى استخدام لغة قوالب وراء الكواليس، وسنستفيد من هذا بكتابة شيفرة أبسط وأكثر تنظيمًا.</p><p>لنقم الآن بتعديل القالب <code>home.jade</code> ليبدو بشكل أجمل:</p><pre class="html ipsCode prettyprint">doctype html
html(lang="ar", dir="rtl")
    head
        title "مُدوّنتي!"
    body
        style
            :css
                body {
                    font-family: Arial, sans-serif;
                }

        h1 مُدوّنتي
        hr
        for post in posts
            h2 #{ post.title }
            p #{ post.body }
            small بتاريخ #{ post.date }</pre><p>قمنا بتغيير اتّجاه النّص لجعله من اليمين إلى اليسار عبر الخاصة <code>"dir"</code>، ثمّ أدخلنا بعض التنسيق من خلال الوسم "<code>&lt;style&gt;</code>" في HTML، تسمح Jade بكتابة لغات أخرى ضمن القالب مثل كتابة CSS وCoffeeScript أو Markdown أو Sass عبر الصّياغة <code>:language</code> ليتم تحويلها إلى اللّغة المناسبة للمتصفّح إن تطلّب الأمر، وفي هذه الحالة أدخلنا CSS بسيط (الذي لا يحتاج للتّحويل) بكتابة <code>:css</code> قبل الشّيفرة. سنتعرّف على مزيد من مزايا Jade خلال عملنا.</p><p href="https://academy.hsoub.com/uploads/monthly_2015_03/home-page-jade.jpg.31a7f07087531af44685d117d7a4eda0.jpg"><img class="ipsImage ipsImage_thumbnailed" data-fileid="69" alt="home-page-jade.jpg.31a7f07087531af44685d" src="https://academy.hsoub.com/uploads/monthly_2015_03/home-page-jade.thumb.jpg.945c2eaaab5e4802a9edadf865191770.jpg"></p><p>تبدو مدوّنتنا بشكل أجمل الآن، لكنّها بالتأكيد تحتاج المزيد من العمل! يمكننا تحسين عرض صيغة التّاريخ باستخدام مكتبة <a rel="external nofollow" href="http://momentjs.com">moment‏</a> للتّعامل مع التّواريخ والوقت، سنحتاج أولاً إلى تثبيتها وحفظها في متطلّبات المشروع:</p><pre><code class="lang-bash">npm install --save moment
</code></pre><p>سنُدخل التّعديلات اللّازمة على الملفّين <code>index.js</code> و<code>home.jade</code>:</p><pre class="javascript ipsCode prettyprint">var express = require("express");
var mysql = require("mysql");

var moment = require("moment");
moment.locale("ar");

var formatDate = function(date) {
    return moment(new Date(date)).fromNow();
}

var connection = mysql.createConnection({ host: "localhost", user: "root", password: "", database: "myblog" });
connection.connect();

var app = express();

app.set("view engine", "jade");

app.get("/", function(request, response) {
    connection.query( "SELECT * from `posts` ORDER BY date DESC LIMIT 10;", function(err, posts) {
        response.render("home", { posts: posts, formatDate: formatDate });
    });

});

app.listen(3000);</pre><pre class="html ipsCode prettyprint">doctype html
html(lang="ar", dir="rtl")
    head
        title "مُدوّنتي!"
    body
        style
            :css
                body {
                    font-family: Arial, sans-serif;
                }

        h1 مُدوّنتي
        hr
        for post in posts
            h2 #{ post.title }
            p #{ post.body }
            small كُتِبَت #{ formatDate(post.date) }</pre><p>يمكن تمرير الدّوال (functions) إلى Jade كما نُمرّر المتغيّرات، وفي حالتنا قمنا بتعريف دالّة تقوم بتنسيق التّاريخ الذي تتلقاه بصياغة نسبيّة (منذ كذا يومًا، منذ ساعتين...) وذلك بالاستفادة من مكتبة moment التي استوردناها وعيّنّا لغة التّاريخ فيها إلى العربيّة. أجرينا التغييرات اللازمة في Jade مستخدمين الدّالة التي فرضناها وأصبحت متوفّرة ضمن القالب:</p><p href="https://academy.hsoub.com/uploads/monthly_2015_03/home-page-jade-moment.jpg.fd577f4d6006b5efbc4316175f006fd6.jpg"><img class="ipsImage ipsImage_thumbnailed" data-fileid="70" alt="home-page-jade-moment.jpg.fd577f4d6006b5" src="https://academy.hsoub.com/uploads/monthly_2015_03/home-page-jade-moment.thumb.jpg.b6d864890254331279c6ecd79f895e03.jpg"></p><h2>إنشاء صفحة التدوينة</h2><p>من المعتاد لصفحات التّدوينات أن تكون بهذه الهيئة: <a rel="external nofollow" href="http://myblog.com/posts/hello-world%D8%8C">http://myblog.com/posts/hello-world،</a> ويمكن أن نشاهد في مدوّنات أخرى روابط تحوي تاريخ كتابة التّدوينة أو رقمًا خاصًّا بها... إلخ، لكنّنا سندع الأمور بسيطة. لدينا حاليًّا 4 تدوينات، ستكون روابطها:</p><ul><li>‎/posts/hello-world</li><li>‎/posts/quotes-1</li><li>‎/posts/quotes-2</li><li>‎/posts/quotes-3</li></ul><p>الثّابت بين هذه الرّوابط هو اعتمادها على الحقل <code>slug</code> الّذي أدخلناه في كلّ سطر في جدول التّدوينات. من غير المنطقيّ أن نُسجّل رابطًا لكلّ تدوينة على حدة في Express، وسيصبح هذا مستحيلاً مع إنشاء تدوينات جديدة. يوفّر Express آليّة للإجابة على الطّلبات الواردة على الروابط التي <em>تطابق نمطًا معيّنًا</em>، وهو في حالتنا <code>/posts/‏</code> متبوعًا بحقل متغيّر <code>slug</code>، أو <code>‎/posts/:slug</code> بصياغة Express، سنضيف الشيفرة التالية إلى برنامجنا (قبل آخر سطر):</p><pre class="javascript ipsCode prettyprint">app.get("/posts/:slug", function(request, response) {

    var slug = request.params.slug;

    connection.query("SELECT * from `posts` WHERE slug = ?", [ slug ], function(err, rows) {
        var post = rows[];
        response.render("post", { post: post, formatDate: formatDate });
    });

})
</pre><p>نطلب من Express الاستجابة لأي رابط يطابق النمط <code>"‎/posts/:slug"</code> بالبحث عن التدوينة التي تملك القيمة <code>slug</code> ضمن العمود الموافق في جدول التّدوينات، نلاحظ أنّ Express يوفّر لنا هذه القيمة المتغيّرة من خلال الكائن <code>params</code> التابع لكائن الطّلب <code>request</code> (كائن الطّلب يحوي كذلك ترويسات الطّلب الّتي تحدّثنا عنها في الجزء السّابق). من المهمّ أنّ نحمي قاعدة بياناتنا من العبث وذلك بتجنب <a rel="external nofollow" href="https://www.youtube.com/watch?v=_jKylhJtPmI">هجمات حقن SQL‏</a>، ولهذا توفّر وحدة <code>mysql</code> دالّة <code>query()‎</code> ذاتها لكن مع 3 معاملات بدل اثنين فقط، حيث يكون الثاني مصفوفة تحوي القيم الّتي نريد التأكّد من سلامتها (escape) قبل إحلالها محلّ إشارات الاستفهام في استعلاماتنا. هذا أسلوب شائع جدًا في استعلامات SQL، وهو أقلّ ما يمكننا فعله لحماية قاعدة البيانات. لم نقم بعد بإنشاء قالب صفحة التّدوينة، لننشئ ملفًا جديدًا اسمه <code>post.jade</code> ضمن مجلّد <code>views</code>:</p><pre class="html ipsCode prettyprint">doctype html
html(lang="ar", dir="rtl")
    head
        title مُدوّنتي!
    body
        style
            :css
                body {
                    font-family: Arial, sans-serif;
                }

        h1 مُدوّنتي
        hr
        h2 #{ post.title }
        p #{ post.body }
        small كُتِبَت #{ formatDate(post.date) }</pre><p>لنبدأ برنامجنا، ونذهب إلى الصّفحة <a rel="external nofollow" href="http://localhost:3000/posts/hello-world">http://localhost:3000/posts/hello-world</a>:</p><p href="https://academy.hsoub.com/uploads/monthly_2015_03/post-page.jpg.ac09ff2a7ae8a096906c81318a3e2405.jpg"><img class="ipsImage ipsImage_thumbnailed" data-fileid="71" alt="post-page.jpg.ac09ff2a7ae8a096906c81318a" src="https://academy.hsoub.com/uploads/monthly_2015_03/post-page.thumb.jpg.bb29af579300962893c4030503aba155.jpg"></p><p>لدينا الآن بعض المشكلات، ماذا يحدث لو أدخلنا رابطًا لتدوينة غير موجودة؟ جرّب مثلاً <a rel="external nofollow" href="http://localhost:3000/posts/another-post">http://localhost:3000/posts/another-post</a>:</p><p href="https://academy.hsoub.com/uploads/monthly_2015_03/post-page-parse-error.jpg.857617a8a79d6a69659305bf0f9e5c62.jpg"><img class="ipsImage ipsImage_thumbnailed" data-fileid="74" alt="post-page-parse-error.jpg.857617a8a79d6a" src="https://academy.hsoub.com/uploads/monthly_2015_03/post-page-parse-error.thumb.jpg.6fb30585803a734e2b575cd7c4f22d53.jpg"></p><p>وقع خطأ في تفسير Jade سببه أن المتغيّر <code>post</code> الّذي وصله هو في الحقيقة غير معرّف <code>undefined</code>، لأنّه ما من تدوينة في قاعدة البيانات يطابق حقل <code>slug</code> فيها القيمة <code>another-post</code>، وعندما أجرينا الاستعلام أُعيدت لنا مصفوفة فارغة <code>rows</code>، وفي JavaScript فإنّ محاولة الوصول إلى خاصّة غير موجودة (<code>"0"</code>) في عنصر مُعرّف (المصفوفة <code>rows</code> في حالتنا) تُرجع <code>undefined</code>. ما الذي كان علينا فعله لتجنب هذا الخطأ؟</p><p>أولاً يجب التأكّد قبل كلّ شيء أنّ الخطأ الذي يقع في مرحلة الاستعلام يتم التّعامل معه (handled) قبل الانتقال لما بعده، انتبه إلى أنّ الاستعلام الذي يتم بنجاح ويعيد مصفوفة فارغة لا يعتبر خطأ، لذا يجب التّعامل مع هذه الحالة أيضًا؛ مبدئيًا سنكتفي بإيقاف تنفيذ الدّالة:</p><pre class="javascript ipsCode prettyprint">app.get("/posts/:slug", function(request, response) {

    var slug = request.params.slug;

    connection.query("SELECT * from `posts` WHERE slug = ?", [ slug ], function(err, rows) {
        if (err || rows.length == ) return;
        var post = rows[];
        response.render("post", { post: post, formatDate: formatDate });
    });

})
</pre><p>جرّب الآن إعادة تشغيل البرنامج وزيارة الصّفحة ذاتها... سيستمرّ المتصفّح بمحاولة تحميلها لوقت طويل قبل أن يفشل بسبب انتهاء مهلة الطّلب. لماذا يحدث هذا؟</p><p>علينا أن نفهم واحدًا من أهمّ المفاهيم في Express، وهو الكيفيّة التّي تسير بها عمليّة توجيه الرّوابط (routing)، في شيفرتنا الأخيرة سيتوقف Express عند <code>return</code> دون أن يعرف ما ينبغي فعله في <strong>الخطوة التّالية</strong>، وهذا يجعل البرنامج عالقًا في الفراغ، نحتاج لطريقة نخبر بها Express أن يتابع التّنفيذ ويفعل شيئًا ما عندما تنتهي إحدى وظائف التّعامل مع الرّوابط، ولهذا يعطينا Express دالّة <code>next</code> التي تتوفّر كمعامل ثالث للدالة التّي تتلقّى الرابط:<code class="lang-javascript"> </code></p><pre class="javascript ipsCode prettyprint">app.get("/posts/:slug", function(request, response, next) {

    var slug = request.params.slug;

    connection.query("SELECT * from `posts` WHERE slug = ?", [ slug ], function(err, rows) {
        if (err || rows.length == ) return next();
        var post = rows[];
        response.render("post", { post: post, formatDate: formatDate });
    });

})
</pre><p>أعد تشغيل البرنامج وزر الصّفحة مجدًّدا:</p><p href="https://academy.hsoub.com/uploads/monthly_2015_03/post-page-cannot-get.jpg.41e930de43ef8c856723449d6fb9e58f.jpg"><img class="ipsImage ipsImage_thumbnailed" data-fileid="72" alt="post-page-cannot-get.jpg.41e930de43ef8c8" src="https://academy.hsoub.com/uploads/monthly_2015_03/post-page-cannot-get.thumb.jpg.b9c7878aa4afef400b05dd5c246579c5.jpg"></p><p>هذا أفضل! لكن ما هي الدّالة التّالية التي استدعاها Express ليعرف أنّ صفحة على هذا الرّابط غير موجودة؟ الإجابة هي أنّ Express يحوي بشكل افتراضي دوالّ داخليّة يستدعيها عندما لا نزوّده بالدّالة التّالية، لكنّنا نستطيع فعل ذلك بسهولة:</p><pre class="javascript ipsCode prettyprint">app.get("/posts/:slug", function(request, response, next) {

    var slug = request.params.slug;

    connection.query("SELECT * from `posts` WHERE slug = ?", [ slug ], function(err, rows) {
        if (err || rows.length == ) return next();
        var post = rows[];
        response.render("post", { post: post, formatDate: formatDate });
        return;
    });

})

app.get("/posts/:slug", function(request, response) {
    response.send("التدوينة غير موجودة");
})
</pre><p>سجّلنا أكثر من دالّة تتعامل مع الرّابط ذاته، سينفّذها Express جميعًا بالتّرتيب ذاته، يمكن لكلّ دالّة أن تستدعي الدّالة التّالية أو أن توقف سلسلة الاستدعاءات بإرسال الطّلب للمتصفّح وإيقاف التّنفيذ. (إرسال الطّلب لا يعني بالضّرورة أنّ الدّوال التّالية لن تنفّذ، بل يجب إيقاف التّنفيذ صراحةً إن لم نرغب بهذا السّلوك). أعد تشغيل البرنامج ثم زُر الصّفحة ذاتها:</p><p href="https://academy.hsoub.com/uploads/monthly_2015_03/post-page-not-found.jpg.a672db954fec5413fb8a7e729060a746.jpg"><img class="ipsImage ipsImage_thumbnailed" data-fileid="73" alt="post-page-not-found.jpg.a672db954fec5413" src="https://academy.hsoub.com/uploads/monthly_2015_03/post-page-not-found.thumb.jpg.f5e7322a59569622d1c9a1ac7ff7eaa5.jpg"></p><p>حدث ما نتوقّعه بالضّبط، على سبيل التّأكد من كوننا لم نعبث بالوظيفة الرئيسيّة، جرّب زيارة تدوينة موجودة مثل <a rel="external nofollow" href="http://localhost:3000/posts/quotes-1">http://localhost:3000/posts/quotes-1</a>.</p><p>كاختبار لك، قم بتعديل صفحة "التّدوينة غير موجودة" مستخدمًا قالبًا خاصًّا ولتجعله جميلاً! تصرّف براحتك!</p><p>سأقوم بإدخال تعديل بسيط على الدّالة الثّانية، لجعلها ترسل الرّمز <code>404</code> (غير موجود) للمتصفّح بدل القيمة الافتراضيّة (<code>200</code>):</p><pre class="javascript ipsCode prettyprint">app.get("/posts/:slug", function(request, response) {
    response.status(404);
    response.send("التدوينة غير موجودة");
})</pre><p>لن يغيّر هذا شيئًا في الظّاهر، لكنّه العرف المتّفق عليه، يمكن لبعض المتصفّحات أن تتعامل مع خطأ كهذا بعرض صفحة نتائج البحث على Google مثلاً (مع أنّه لا يوجد متصفّح يفعل ذلك)، لكنّها طريقة HTTP في التّفاهم بين الخادوم والمتصفّح.</p><p>عظيم! لدينا الآن صفحة رئيسيّة منسّقة وصفحات مفردة للتّدوينات، في الدّرس القادم سنقوم بإنشاء نظام للمستخدمين تمهيدًا لإتاحة التّعليقات وكتابة تدوينات جديدة.</p>
]]></description><guid isPermaLink="false">20</guid><pubDate>Tue, 03 Mar 2015 18:45:00 +0000</pubDate></item><item><title>&#x625;&#x646;&#x634;&#x627;&#x621; &#x645;&#x62F;&#x648;&#x651;&#x646;&#x629; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; Node.js &#x648; Express (&#x627;&#x644;&#x62C;&#x632;&#x621; &#x627;&#x644;&#x623;&#x648;&#x644;)</title><link>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/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2015_03/express.png.851184f50d878419c784b589c533fdde.png" /></p>
<h2>
	التعريف بالمشروع
</h2>

<p>
	هذه سلسلة من الدّروس مُوجّهة للمبتدئين بتطوير الويب، تهدف إلى تعليم استخدام بيئة Node.js وإطار العمل Express من خلال بناء مدوّنة متكاملة تسمح للكُتَّاب بإضافة التّدوينات وتسمح للزوّار بإنشاء حسابات والتّعليق على التُدوينات. لن تقتصر هذه السّلسلة على شرح Express، بل ستقدّم شرحًا (نأمل أن يكون وافيًا) للعديد من المفاهيم المتعلّقة بتطوير الويب من جهة الخوادم (Server-side web development)، حيث سنتطرّق إلى تثبيت خادوم MySQL مع شرح استخدامه في Node.js بالإضافة إلى آليّة عمل بروتوكول HTTP ونظام إدارة المستخدمين وإنشاء الجلسات، في نهاية السّلسلة سنُلقي نظرة على مواضيع تحسين الأداء والأمان قبل نشر المشروع على الويب. في نهاية هذه السّلسلة من المفترض أن يكون المتعلّم قادرًا على التّعامل مع بيئة Node.js بسهولة ويمكنه إنشاء الخوادم وقواعد البيانات وإنشاء برامج تصل بين هذه الأجزاء. إذا كنت قادمًا من عالم PHP، فستكون هذه السّلسلة مناسبة لك أيضًا على حدٍّ سواء.
</p>

<p style="text-align:center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="136" href="https://academy.hsoub.com/uploads/monthly_2015_03/express.png.68bd4e96d0bcdef509553ecc2e0ca12c.png" rel=""><img alt="express.thumb.png.3de931e612d9a6ed8ff737" class="ipsImage ipsImage_thumbnailed" data-fileid="136" src="https://academy.hsoub.com/uploads/monthly_2015_03/express.thumb.png.3de931e612d9a6ed8ff7378333f13216.png"></a>
</p>

<h3>
	‏Node.js
</h3>

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

<h3>
	‏Express
</h3>

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

<p>
	يتولّى Express إدارة الرّوابط وتوجيهها فقط، ويمكن توسيعه باستخدام ما يُسمّى "البرامج الوسيطة" (middleware) التي تُشبه إلى حدّ ما إضافات المتصفّح التي تستخدمها، فهي تضيف المزيد من المزايا إلى الوظيفة الرئيسيّة لـExpress، وسنجد مثلاً برنامجًا وسيطًا يحفظ الجلسات باستخدام الكعكات (cookies) وآخر يخزّن نتائج الاستعلامات بشكل مؤقّت لتسريع استجابة الخادوم...
</p>

<p>
	من الواجب أن نذكر أنّ Express ليس إطار العمل الوحيد المتوفّر في Node.js، ولكنّه قد يكون الأشهر لبساطته الشّديدة وهيكليّته الممتازة. يبدو المستقبل واعدًا لمشاريع مثل <a href="http://koajs.com" rel="external nofollow">Koa‏</a> الّذي يستفيد من <a href="https://academy.hsoub.com/code/web-development/javascript/%D9%85%D8%A7-%D8%A7%D9%84%D8%AC%D8%AF%D9%8A%D8%AF-%D9%81%D9%8A-%D8%A7%D9%84%D8%A5%D8%B5%D8%AF%D8%A7%D8%B1-%D8%A7%D9%84%D9%82%D8%A7%D8%AF%D9%85-%D9%85%D9%86-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-ecmascript-6-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-r12/" rel="">المزايا القادمة إلى الإصدارات المستقبليّة من JavaScript</a> لتحسين كتابة الشّيفرة أكثر. يُشرف على تطوير Koa الفريق المطوّر لـExpress ذاته.
</p>

<h3>
	‏MySQL
</h3>

<p>
	سنستخدم لغة قواعد البيانات الشّهيرة SQL (بنكهة MySQL إن جاز التّعبير) لتخزين التّدوينات والتّعليقات ومعلومات المستخدمين، إن كنت لا تعرف الكثير عن SQL فلا بأس، لأنّ صياغتها غاية في السّهولة وتكاد تُشبه جمل اللغة الإنكليزيّة الحقيقيّة!
</p>

<h2>
	تثبيت Node.js
</h2>

<p>
	للحصول على آخر إصدار من Node.js:
</p>

<ul>
	<li style="text-align:center;">
		إذا كنت تستخدم Windows أو Mac فتوجّه إلى <a rel="">الصّفحة الرئيسية لموقع Node.js</a> سيقوم الموقع بالتّعرّف على نظامك وبنيته وانتقاء برنامج التّثبيت المناسب، ليس عليك سوى الضّغط على زر Install لتنزيل برنامج التّثبيت ثم فتحه ومتابعة الخطوات.<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="54" href="https://academy.hsoub.com/uploads/monthly_2015_03/node-home-page-windows.jpg.f0c1e55563c1266a210a1772102137a9.jpg" rel=""><img alt="node-home-page-windows.thumb.jpg.41c2139" class="ipsImage ipsImage_thumbnailed" data-fileid="54" src="https://academy.hsoub.com/uploads/monthly_2015_03/node-home-page-windows.thumb.jpg.41c2139007bf2464b84c7872dc0d9407.jpg"></a>
	</li>
</ul>

<p style="text-align:center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="56" href="https://academy.hsoub.com/uploads/monthly_2015_03/node-install-windows-1.jpg.0b2214e38f596468283a757321d4eda7.jpg" rel=""><img alt="node-install-windows-1.thumb.jpg.5aeff31" class="ipsImage ipsImage_thumbnailed" data-fileid="56" src="https://academy.hsoub.com/uploads/monthly_2015_03/node-install-windows-1.thumb.jpg.5aeff319d18f4bf23b77fefaeefcfd7d.jpg"></a>
</p>

<p style="text-align:center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="62" href="https://academy.hsoub.com/uploads/monthly_2015_03/node-install-windows-7.jpg.6f052abba8a605c4c789728ab9ccbbee.jpg" rel=""><img alt="node-install-windows-7.thumb.jpg.d2c7edf" class="ipsImage ipsImage_thumbnailed" data-fileid="62" src="https://academy.hsoub.com/uploads/monthly_2015_03/node-install-windows-7.thumb.jpg.d2c7edf4d68d7868fbb53ac44bfc6d78.jpg"></a>
</p>

<p style="text-align:center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="63" href="https://academy.hsoub.com/uploads/monthly_2015_03/node-install-windows-8.jpg.3352fc82b3843d6b8a61b40fec88ebbe.jpg" rel=""><img alt="node-install-windows-8.thumb.jpg.bcb6f2c" class="ipsImage ipsImage_thumbnailed" data-fileid="63" src="https://academy.hsoub.com/uploads/monthly_2015_03/node-install-windows-8.thumb.jpg.bcb6f2c6339b51911b8cf54a2bbc421d.jpg"></a>
</p>

<ul>
	<li>
		إذا كنت تستخدم Arch Linux، فستجد أحدث إصدار من Node.js ضمن مستودعات مستخدمي Arch (‏AUR). ويمكن تثبيته بالأمر التالي إذا كنت تستخدم yaourt (الأمر مشابه بالنّسبة لكلّ <a href="https://wiki.archlinux.org/index.php/AUR_helpers" rel="external nofollow">البرامج التي توفّر وصولاً إلى AUR‏</a><span class="ipsEmoji">?</span>
	</li>
</ul>

<pre class="ipsCode prettyprint prettyprinted" style=""><span class="pln">yaourt </span><span class="pun">-</span><span class="pln">S nodejs </span><span class="pun">--</span><span class="pln">noconfirm</span></pre>

<p>
	سيُطلب منك إدخال كلمة المرور إلى مستخدمك أو كلمة المرور إلى المستخدم الجذر (إن وُجدت). كما يمكن التّثبيت بالطّريقة المشروحة في الخيار التّالي.
</p>

<ul>
	<li>
		إذا كنت تستخدم توزيعة Linux أخرى، فقد تجد إصدارًا قديمًا من Node.js ضمن مستودعات توزيعتك، لذا يُنصح بتثبيت Node.js عن طريق مُدير إصدارات Node.js المتوفّر على GitHub، ويتمّ التثبيت بالطّريقة التالية:
	</li>
</ul>

<ol>
	<li>
		قم بتنزيل آخر نسخة من مدير إصدارات Node.js عن طريق زر Download ZIP في <a href="https://github.com/tj/n" rel="external nofollow">صفحة المشروع على GitHub‏</a>.
	</li>
	<li>
		فكّ ضغط الملفّ الذي قمت بتنزيله
	</li>
	<li>
		انتقل بالطّرفيّة إلى مسار المجلد الناتج عن العمليّة السابقة، مثلاً:
		<pre class="ipsCode prettyprint prettyprinted" style=""><span class="pln"> cd </span><span class="pun">~</span><span class="str">/Downloads/</span><span class="pln">n</span><span class="pun">-</span><span class="pln">master</span></pre>
	</li>
	<li>
		<p>
			ثم قم بتنفيذ أمر بناء المشروع:
		</p>

		<pre class="ipsCode prettyprint prettyprinted" style=""><span class="pln">sudo make install</span></pre>
	</li>
	<li>
		<p>
			قم بتثبيت آخر إصدار مستقرّ من Node.js مستخدمًا الأمر <code>n</code> الذي يوفّره مدير إصدارات Node.js:
		</p>

		<pre class="ipsCode prettyprint prettyprinted" style=""><span class="pln"> sudo n stable</span></pre>
	</li>
	<li>
		سيُطلب منك إدخال كلمة المرور إلى مستخدمك أو كلمة المرور إلى المستخدم الجذر إن وُجدت.
	</li>
</ol>

<p>
	من فوائد مُدير إصدارات Node.js إمكانيّة التبديل بشكل سريع بين عدّة إصدارات من Node.js، فقد ترغب أحيانًا بتجربة بعض المزايا المتوفّرة في إصدار غير مستقرّ (مثل <code>v0.11</code> الذي يتضمّن بعضًا من مُكوّنات ECMAScript 6) مُستخدمًا الأمر <code>n latest</code>، ولكنّك ترغب بالعودة للعمل على مشاريع جادّة ضمن إصدار مستقرّ. ولهذا يمكنك استخدام الأمر <code>n stable</code>.
</p>

<ol>
	<li>
		يمكن أيضًا تثبيت الإصدارات الحديثة من Node.js على Ubuntu والتّوزيعات الأخرى باتّباع <a href="https://github.com/joyent/node/wiki/installing-node.js-via-package-manager#debian-and-ubuntu-based-linux-distributions" rel="external nofollow">التّعليمات الرّسميّة المتوفّرة</a> على صفحة Node.js على GitHub.
	</li>
</ol>

<p>
	للتحقّق من تثبيت Node.js اكتب الأمر التالي في الطّرفيّة (أو سطر أوامر Windows):
</p>

<pre class="ipsCode prettyprint prettyprinted" style=""><span class="pln">node </span><span class="pun">-</span><span class="pln">v</span></pre>

<p>
	لتحصل على نتيجة برقم إصدار Node.js الذي قمت بتثبيته، مثل <code>v0.10.33</code>. إن كانت النتيجة تُفيد بعدم وجود الأمر مثل <code>bash: node: command not found</code> (على Linux) أو <code>node is not recognized as an internal or external command...</code> (على Windows)، فتحقّق من اتّباع الخطوات السّابقة مجدًدًا.
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="65" href="https://academy.hsoub.com/uploads/monthly_2015_03/node-install-windows-success.jpg.ebb1e93038c661b1bb884a88b56f2aff.jpg" rel=""><img alt="node-install-windows-success.thumb.jpg.a" class="ipsImage ipsImage_thumbnailed" data-fileid="65" src="https://academy.hsoub.com/uploads/monthly_2015_03/node-install-windows-success.thumb.jpg.a5c02d9b01f827af040d26117393e999.jpg"></a>
</p>

<h2>
	إنشاء المشروع
</h2>

<p>
	لنبدأ العمل بشكل نظيف، أنشئ مُجلّدًا جديدًا في مكان ما في جهازك وانتقل إليه باستخدام الطّرفيّة، سأقوم بإنشاء مجلّد ضمن مسار مُستخدمي <code>/home/f/‏</code> وأسمّيه <code>my-blog</code>، ثم سأنتقل إليه باستخدام الأمر <code>cd</code> (اختصارًا لـchange directory) (الذي يتطابق في Linux وMac وWindows):
</p>

<pre class="ipsCode prettyprint prettyprinted" style=""><span class="pln">cd </span><span class="pun">/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">f</span><span class="pun">/</span><span class="kwd">my</span><span class="pun">-</span><span class="pln">blog</span></pre>

<p>
	في Windows، قد يكون الأمر مُشابهًا لهذا:
</p>

<pre class="ipsCode prettyprint prettyprinted" style=""><span class="pln">cd C</span><span class="pun">:</span><span class="pln">\Users\f\my</span><span class="pun">-</span><span class="pln">blog</span></pre>

<p>
	سنستخدم الأمر <code>init</code> الذي يوفّره مُدير حزم Node‏ (npm) لإنشاء مشروع جديد، افتح الطّرفيّة (سطر الأوامر في Windows) واكتب الأمر التالي:
</p>

<pre class="ipsCode prettyprint prettyprinted" style=""><span class="pln">npm init</span></pre>

<p>
	سيطرح البرنامج عليك مجموعة من الحقول لتعبئتها:
</p>

<ol>
	<li>
		‏<code>name</code>: اسم المشروع، ويقترح npm اسم المجلد الحالي كاسم للمشروع، ويمكنك الأخذ بالاقتراح بترك الحقل فارغًا وضغط Enter.
	</li>
	<li>
		‏<code>version</code>: إصدار المشروع (يمكنك تركه كما هو).
	</li>
	<li>
		‏<code>description</code>: وصف للمشروع.
	</li>
	<li>
		‏<code>entry point</code>: الملفّ الرئيسيّ الذي ينطلق منه المشروع، يمكنك تركه كما هو وإنشاء الملف <code>index.js</code> لاحقًا.
	</li>
	<li>
		‏<code>test command</code>: الأمر الذي يجب أن ينفّذه npm عندما يطلب منه تنفيذ الاختبارات على المشروع، أي عندما ينفذ الأمر <code>npm test</code> ضمن مجلد المشروع الرئيسيّ. سنتركه فارغًا الآن.
	</li>
	<li>
		‏<code>git repository</code>: مسار مستودع git الذي ستستخدمه لإدارة المشروع، يمكن أن يكون رابط <code><a href="" rel="">http://‎</a></code> أو <code>git://‎</code> ويمكن تعديله لاحقًا.
	</li>
	<li>
		‏<code>keywords</code>: الكلمات المفتاحية للمشروع مفصولة بفاصلة لاتينية (<code>,</code>)، أمثلة: <code>blog, mysql, expressjs, tutorial</code>.
	</li>
	<li>
		‏<code>author</code>: كاتب المشروع.
	</li>
	<li>
		‏<code>license</code>: رخصة المشروع، يمكن استخدام أي رخصة مثل <code>GPLv2</code> أو <code>MIT</code>.
	</li>
</ol>

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

<h2>
	تثبيت متطلّبات المشروع
</h2>

<p>
	سيعتمد مشروعنا على إطار العمل Express كما هو واضح، بالإضافة إلى قواعد بيانات MySQL لتخزين التدوينات والمستخدمين وتعليقاتهم، سنحتاج أيضًا إلى بعض المتطلّبات الأخرى التي سنثبّتها في وقت الحاجة إليها. لتثبيت أحدث إصدار من <em><code>express</code></em> وحفظه كمتطلّب ضمن ملفّ <code>package.json</code>، نفّذ الأمر التّالي:
</p>

<pre class="ipsCode prettyprint prettyprinted" style=""><span class="pln">npm install express </span><span class="pun">--</span><span class="pln">save</span></pre>

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

<pre class="ipsCode prettyprint prettyprinted" style=""><span class="pln">npm install express@4</span><span class="pun">.</span><span class="lit">10.</span><span class="pun">*</span><span class="pln"> </span><span class="pun">--</span><span class="pln">save</span></pre>

<p>
	يحتاج تثبيت MySQL إلى خطوتين: الأولى تثبيت الخادوم الذي يوفّر قاعدة البيانات، ويُنجز بطريقة مختلفة لكل نظام تشغيل:
</p>

<ol>
	<li>
		في Windows وMac OS X، يمكن تثبيته <a href="https://dev.mysql.com/downloads/mysql" rel="external nofollow">بتنزيل برنامج التّثبيت المناسب</a> لإصدار النّظام وبنيته من الموقع الرّسمي ثمّ اتّباع خطوات التّثبيت كما في تثبيت أي برنامج آخر.
	</li>
	<li>
		في Arch Linux، أنصح باستخدام MariaDB، وهي بديل مطابق تمامًا لـMySQL ويحلّ محلّه بدون الاضطرار لتعديل أي جزء من الشيفرة، ويمكن تثبيته من خلال مستودعات مستخدمي Arch بالأمر التّالي:
	</li>
</ol>

<pre class="ipsCode prettyprint prettyprinted" style=""><span class="pln">yaourt </span><span class="pun">-</span><span class="pln">S mariadb </span><span class="pun">--</span><span class="pln">noconfirm</span></pre>

<ol>
	<li>
		في توزيعات Linux الأخرى مثل Ubuntu وFedora، فقد تتوفّر MariaDB وMySQL في المستودعات الرّسميّة ويمكن تثبيتها باستخدام <code>apt-get</code> و<code>yum</code>.
	</li>
</ol>

<p>
	الخطوة الأخرى تتضمّن تثبيت عميل MySQL (أو ما يسمى MySQL client)، وهو الجزء الذي سيتواصل مع الخادوم ليجلب نتائج الاستعلام من قاعدة البيانات ويوفّرها لمشروعنا، وفي حالتنا هذه ليس سوى وحدة Node.js يمكن تثبيتها بسهولة عبر <code>npm install</code> واستخدامها ضمن مشروعنا؛ تتوفّر العديد من الوحدات التي تقدّم إمكانية التواصل مع خادوم قواعد بيانات MySQL، ومن أفضلها الوحدة <em><a href="https://github.com/felixge/node-mysql" rel="external nofollow"><code>mysql</code>‏</a></em> التي يمكن تثبيتها بتنفيذ الأمر:
</p>

<pre class="ipsCode prettyprint prettyprinted" style=""><span class="pln">npm install mysql </span><span class="pun">--</span><span class="pln">save</span></pre>

<p>
	<em>نصيحة</em>: يمكن تثبيت الحزمتين بأمر واحد:
</p>

<pre class="html ipsCode prettyprint prettyprinted" style=""><span class="pln">npm install express mysql </span><span class="pun">--</span><span class="pln">save</span></pre>

<p>
	بعد تثبيت الحزمتين، سنلاحظ إضافة حقل جديد في ملفّ <code>package.json</code> يوضّح متطلّبات المشروع التي حفظناها إلى الآن (قد تختلف أرقام الإصدارات لديك):
</p>

<pre class="javascript ipsCode prettyprint prettyprinted" 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">"express"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^4.10.6"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"mysql"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"^2.5.4"</span><span class="pln">
</span><span class="pun">}</span></pre>

<h2>
	إنشاء قاعدة البيانات وإدخال بعض التّدوينات كعيّنة
</h2>

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

<p>
	لإنشاء قاعدة البيانات وإدخال التدوينات، سنقوم بالتواصل مع الخادوم عبر الطّرفيّة، مستخدمين البرنامج <code>mysql</code> الذي يتمّ تثبيته تلقائيًّا عند تثبيت خادوم MySQL أو MariaDB. افتح الطّرفية ونفّذ الأمر:
</p>

<pre class="ipsCode prettyprint prettyprinted" style=""><span class="pln">mysql </span><span class="pun">-</span><span class="pln">u root</span></pre>

<p>
	ملاحظة: إن اخترت اسمًا للمستخدم وكلمة مرور مختلفين أثناء التثبيت، فيمكن إدخالها بالطريقة الآتية (سيطلب منك إدخال كلمة المرور إلى هذا المستخدم):
</p>

<pre class="ipsCode prettyprint prettyprinted" style=""><span class="pln">mysql </span><span class="pun">-</span><span class="pln">u username </span><span class="pun">-</span><span class="pln">p</span></pre>

<p>
	ستظهر شاشة مشابهة لهذه:
</p>

<p>
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="67" href="https://academy.hsoub.com/uploads/monthly_2015_03/mysql-shell.jpg.959a9888eb0f771dd5e3fcc8c483798f.jpg" rel=""><img alt="mysql-shell.thumb.jpg.f478b7b60d4c35a2b7" class="ipsImage ipsImage_thumbnailed" data-fileid="67" src="https://academy.hsoub.com/uploads/monthly_2015_03/mysql-shell.thumb.jpg.f478b7b60d4c35a2b758c1400ef96e9e.jpg"></a>
</p>

<p>
	 
</p>

<p>
	ملاحظة: إن واجهتك مُشكلات في بدء البرنامج <code>mysql</code>، جرّب إعادة تشغيل النّظام. ملاحظة (2): يمكنك الاستغناء عن استعمال صدفة MySQL إذا كنت قد ثبّتت PHPMyAdmin على جهازك، حيث بإمكانك إدخال الأوامر ذاتها في مربّع الاستعلامات.
</p>

<p>
	في صدفة MySQL هذه يمكننا إدخال أوامر MySQL ليقوم الخادوم بتنفيذها على الفور. سنقوم بإنشاء قاعدة بيانات المدوّنة، وسنسمّيها <code>myblog</code>:
</p>

<pre class="ipsCode prettyprint prettyprinted" style=""><span class="pln">CREATE DATABASE myblog</span><span class="pun">;</span></pre>

<p>
	النتيجة التّالية دليل على نجاح التنفيذ:
</p>

<pre class="ipsCode prettyprint prettyprinted" style=""><span class="typ">Query</span><span class="pln"> OK</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> row affected </span><span class="pun">(</span><span class="lit">0.00</span><span class="pln"> sec</span><span class="pun">)</span></pre>

<p>
	اتّصل بقاعدة البيانات الجديدة بالأمر:
</p>

<pre class="ipsCode prettyprint prettyprinted" style=""><span class="pln">connect myblog</span></pre>

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

<pre class="ipsCode prettyprint prettyprinted" style=""><span class="pln">CREATE TABLE </span><span class="str">`users`</span><span class="pln"> </span><span class="pun">(</span><span class="pln">id INT NOT NULL PRIMARY KEY AUTO_INCREMENT</span><span class="pun">,</span><span class="pln"> username VARCHAR</span><span class="pun">(</span><span class="lit">50</span><span class="pun">),</span><span class="pln"> password VARCHAR</span><span class="pun">(</span><span class="lit">500</span><span class="pun">)</span><span class="pln"> NOT NULL</span><span class="pun">,</span><span class="pln"> full_name VARCHAR</span><span class="pun">(</span><span class="lit">50</span><span class="pun">),</span><span class="pln"> is_author BOOLEAN DEFAULT </span><span class="pun">,</span><span class="pln"> UNIQUE INDEX </span><span class="pun">(</span><span class="pln">username</span><span class="pun">));</span><span class="pln">

INSERT INTO </span><span class="str">`users`</span><span class="pln"> </span><span class="pun">(</span><span class="pln">username</span><span class="pun">,</span><span class="pln"> password</span><span class="pun">,</span><span class="pln"> full_name</span><span class="pun">,</span><span class="pln"> is_author</span><span class="pun">)</span><span class="pln"> VALUES </span><span class="pun">(</span><span class="str">"admin"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"$2a$08$Z3FpAQwRgj7W0i71TtizFO7QDjpsIRNJfHh6mLgRJRJBtheKJh1Tu"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"admin"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">);</span><span class="pln">

CREATE TABLE </span><span class="str">`posts`</span><span class="pln"> </span><span class="pun">(</span><span class="pln">id INT NOT NULL PRIMARY KEY AUTO_INCREMENT</span><span class="pun">,</span><span class="pln"> title VARCHAR</span><span class="pun">(</span><span class="lit">100</span><span class="pun">),</span><span class="pln"> body LONGTEXT</span><span class="pun">,</span><span class="pln"> date TIMESTAMP</span><span class="pun">,</span><span class="pln"> author_id INT</span><span class="pun">,</span><span class="pln"> slug VARCHAR</span><span class="pun">(</span><span class="lit">50</span><span class="pun">),</span><span class="pln"> UNIQUE INDEX </span><span class="pun">(</span><span class="pln">slug</span><span class="pun">),</span><span class="pln"> FOREIGN KEY </span><span class="pun">(</span><span class="pln">author_id</span><span class="pun">)</span><span class="pln"> REFERENCES </span><span class="str">`users`</span><span class="pln"> </span><span class="pun">(</span><span class="pln">id</span><span class="pun">));</span><span class="pln">

INSERT INTO </span><span class="str">`posts`</span><span class="pln"> </span><span class="pun">(</span><span class="pln">title</span><span class="pun">,</span><span class="pln"> body</span><span class="pun">,</span><span class="pln"> date</span><span class="pun">,</span><span class="pln"> author_id</span><span class="pun">,</span><span class="pln"> slug</span><span class="pun">)</span><span class="pln"> VALUES </span><span class="pun">(</span><span class="str">"مرحبًا بالعالم!"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"مرحبًا بكم في مدوّنتي المتواضعة!"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"2014-12-29"</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">"hello-world"</span><span class="pun">);</span><span class="pln">
INSERT INTO </span><span class="str">`posts`</span><span class="pln"> </span><span class="pun">(</span><span class="pln">title</span><span class="pun">,</span><span class="pln"> body</span><span class="pun">,</span><span class="pln"> date</span><span class="pun">,</span><span class="pln"> author_id</span><span class="pun">,</span><span class="pln"> slug</span><span class="pun">)</span><span class="pln"> VALUES </span><span class="pun">(</span><span class="str">"اقتباسات (1)"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"الغني لو سئل عن تحسين العمل والحياة فسوف يقول: نحن نعرف أن البؤس غيرمفرح والواقع أن البؤس مادام بعيداً عنا فإننا نتسلح بفكرة أنه غير مفرح. ولكن لا تتوقع منا أن نفعل أي شيء بصدده. نحن آسفون لطبقاتكم الدنيا مثل مانحن آسفون لقطة جرباء...غير أننا سنقاتل كالمردة ضد أي تحسين لظرفكم. نحن نشعر انكم مأمونون أكثر وأنتم في حالكم هذا. إن الواقع الراهن يناسبنا ولسنا مستعدين لمخاطرة تحريركم حتى بساعة إضافية في اليوم هكذا يا إخوتي الأعزاء إن كان عليكم ان تعرقوا لدفع رحلاتنا إلى إيطاليا فلتعرقوا ولتحل عليكم اللعنة ― جورج أورويل، متشردًا في باريس ولندن"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"2014-12-30"</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">"quotes-1"</span><span class="pun">);</span><span class="pln">
INSERT INTO </span><span class="str">`posts`</span><span class="pln"> </span><span class="pun">(</span><span class="pln">title</span><span class="pun">,</span><span class="pln"> body</span><span class="pun">,</span><span class="pln"> date</span><span class="pun">,</span><span class="pln"> author_id</span><span class="pun">,</span><span class="pln"> slug</span><span class="pun">)</span><span class="pln"> VALUES </span><span class="pun">(</span><span class="str">"اقتباسات (2)"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"التليفزيون يُغرقك في بحر من الأصوات والألوان بحيث لا تجد الوقت الكافي لتفكر أو تنتقد... إنه يقدم لك الأفكار الجاهزة ولا يسمح لك بالانتقاد الذي يسمح به الكتاب. ― راي برادبري، فهرنهايت 451"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"2014-12-31"</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">"quotes-2"</span><span class="pun">);</span><span class="pln">
INSERT INTO </span><span class="str">`posts`</span><span class="pln"> </span><span class="pun">(</span><span class="pln">title</span><span class="pun">,</span><span class="pln"> body</span><span class="pun">,</span><span class="pln"> date</span><span class="pun">,</span><span class="pln"> author_id</span><span class="pun">,</span><span class="pln"> slug</span><span class="pun">)</span><span class="pln"> VALUES </span><span class="pun">(</span><span class="str">"اقتباسات (3)"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"أستطيع أن أقول لك يا بنيّ إنّ السّعادة ينبوع يتفجّر من القلب، لا غيث يهطل من السّماء، وأنّ النّفس الكريمة الرّاضية البريئة من أدران الرّذائل وأقذارها، ومطامع الحياة وشهواتها، سعيدة حيثما حلّت. [...] فمن أراد السّعادة فلا يسأل عنها المال والنّسب، وبين الفضّة والذّهب، والقصور والبساتين، والأرواح والرّياحين، بل يسأل عنها نفسه الّتي بين جنبيه فهي ينبوع سعادته وهنائه إن شاء، ومصدر شقائه وبلائه إن أراد. ― مصطفى لطفي المنفلوطي، الفضيلة"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"2015-01-01"</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">"quotes-3"</span><span class="pun">);</span></pre>

<p>
	لكلّ سطر في جدول التّدوينات الحقول التالية: العنوان <code>title</code> ونص التدوينة <code>body</code> وتاريخها <code>date</code> ومُعرّف الكاتب <code>author_id</code> الذي يُشير إلى أحد الكُتّاب المُسجلّين في جدول المستخدمين <code>users</code>، ثمّ <code>slug</code> وهو العنوان بالإنكليزية الملائم لاستخدامه ضمن رابط التّدوينة مثل hello-world في <a href="http://myblog/posts/hello-world" rel="external nofollow">http://myblog/posts/hello-world</a>.
</p>

<p>
	نحن الآن جاهزون للعمل! في الدّرس القادم سنبدأ بإنشاء الصّفحة الرّئيسيّة لمدوّنتنا، والّتي ستعرض التّدوينات الّتي أضفناها لتوّنا.
</p>
]]></description><guid isPermaLink="false">19</guid><pubDate>Sun, 01 Mar 2015 13:34:00 +0000</pubDate></item></channel></rss>
