لوحة المتصدرين
المحتوى الأكثر حصولًا على سمعة جيدة
المحتوى الأعلى تقييمًا في 12/17/21 in مقالات البرمجة
-
التعريف بالمشروع هذه سلسلة من الدّروس مُوجّهة للمبتدئين بتطوير الويب، تهدف إلى تعليم استخدام بيئة Node.js وإطار العمل Express من خلال بناء مدوّنة متكاملة تسمح للكُتَّاب بإضافة التّدوينات وتسمح للزوّار بإنشاء حسابات والتّعليق على التُدوينات. لن تقتصر هذه السّلسلة على شرح Express، بل ستقدّم شرحًا (نأمل أن يكون وافيًا) للعديد من المفاهيم المتعلّقة بتطوير الويب من جهة الخوادم (Server-side web development)، حيث سنتطرّق إلى تثبيت خادوم MySQL مع شرح استخدامه في Node.js بالإضافة إلى آليّة عمل بروتوكول HTTP ونظام إدارة المستخدمين وإنشاء الجلسات، في نهاية السّلسلة سنُلقي نظرة على مواضيع تحسين الأداء والأمان قبل نشر المشروع على الويب. في نهاية هذه السّلسلة من المفترض أن يكون المتعلّم قادرًا على التّعامل مع بيئة Node.js بسهولة ويمكنه إنشاء الخوادم وقواعد البيانات وإنشاء برامج تصل بين هذه الأجزاء. إذا كنت قادمًا من عالم PHP، فستكون هذه السّلسلة مناسبة لك أيضًا على حدٍّ سواء. Node.js لعلّك سمعت من قبل بـNode.js، لكنّها ما تزال غامضةً بشكل أو بآخر خصوصًا بين المطوّرين العرب، والسبب ربّما يكون ضعف الرّغبة في التّغيير أو صعوبة تأمين استضافة مشاريعها مقارنةً باستضافة مشاريع PHP أو غير ذلك من الأسباب. Node.js هي بيئة تطوير تسمح لنا بكتابة البرامج وتنفيذها باستخدام JavaScript، اللّغة الّتي كانت حتى وقت قريب حبيسة المتصفّح؛ لكنّها لم تعد كذلك بل أصبحت تُستخدم في كتابة مشاريع الويب وتطبيقات سطح المكتب وحتى التّطبيقات الّتي تعمل في الطّرفيّة، وربّما يعود الفضل في ذلك إلى Node.js ذاتها. إن كنت استخدمت PHP مع Apache لإنشاء موقع من قبل، فستجد أن Node.js تستطيع القيام بالمهمّة ذاتها وأكثر، وبأسلوب أبسط وأكثر تنظيمًا وتوفيرًا للوقت. Express لعلّ Express أهمّ مكوّنات مشروعنا، وعليه سينصبّ معظم شرحنا. Express باختصار هو إطار عمل لمشاريع الويب يعمل في بيئة Node.js، فهو بالنّسبة لـNode.js يقابل Laravel بالنّسبة لـPHP، وإن كان يختلف عنه كثيرًا في الفكرة الّتي يقوم عليها، إذ أنّ Express يلتزم بفلسفة Node.js الّتي تهدف إلى تجزئة المشاريع الضّخمة إلى وحدات، لن تجد في Express ذاته وحدات تتعامل مع قواعد البيانات أو تتولّى إدارة الجلسات وحماية كلمات المرور وما إلى ذلك، بل عليك أن تعتاد أنّ لكلّ شيء في Node.js وحدة مستقلّة يمكن استيرادها واستخدامها مع الوحدات الأخرى لنحصل على مشروع يسهل تطويره وصيانته دون الاعتماد على مكوّن ضخم من جهة مُطوّرة واحدة قد يكون مصير تطويره إلى الإهمال في المستقبل. يتولّى Express إدارة الرّوابط وتوجيهها فقط، ويمكن توسيعه باستخدام ما يُسمّى "البرامج الوسيطة" (middleware) التي تُشبه إلى حدّ ما إضافات المتصفّح التي تستخدمها، فهي تضيف المزيد من المزايا إلى الوظيفة الرئيسيّة لـExpress، وسنجد مثلاً برنامجًا وسيطًا يحفظ الجلسات باستخدام الكعكات (cookies) وآخر يخزّن نتائج الاستعلامات بشكل مؤقّت لتسريع استجابة الخادوم... من الواجب أن نذكر أنّ Express ليس إطار العمل الوحيد المتوفّر في Node.js، ولكنّه قد يكون الأشهر لبساطته الشّديدة وهيكليّته الممتازة. يبدو المستقبل واعدًا لمشاريع مثل Koa الّذي يستفيد من المزايا القادمة إلى الإصدارات المستقبليّة من JavaScript لتحسين كتابة الشّيفرة أكثر. يُشرف على تطوير Koa الفريق المطوّر لـExpress ذاته. MySQL سنستخدم لغة قواعد البيانات الشّهيرة SQL (بنكهة MySQL إن جاز التّعبير) لتخزين التّدوينات والتّعليقات ومعلومات المستخدمين، إن كنت لا تعرف الكثير عن SQL فلا بأس، لأنّ صياغتها غاية في السّهولة وتكاد تُشبه جمل اللغة الإنكليزيّة الحقيقيّة! تثبيت Node.js للحصول على آخر إصدار من Node.js: إذا كنت تستخدم Windows أو Mac فتوجّه إلى الصّفحة الرئيسية لموقع Node.js سيقوم الموقع بالتّعرّف على نظامك وبنيته وانتقاء برنامج التّثبيت المناسب، ليس عليك سوى الضّغط على زر Install لتنزيل برنامج التّثبيت ثم فتحه ومتابعة الخطوات. إذا كنت تستخدم Arch Linux، فستجد أحدث إصدار من Node.js ضمن مستودعات مستخدمي Arch (AUR). ويمكن تثبيته بالأمر التالي إذا كنت تستخدم yaourt (الأمر مشابه بالنّسبة لكلّ البرامج التي توفّر وصولاً إلى AUR? yaourt -S nodejs --noconfirm سيُطلب منك إدخال كلمة المرور إلى مستخدمك أو كلمة المرور إلى المستخدم الجذر (إن وُجدت). كما يمكن التّثبيت بالطّريقة المشروحة في الخيار التّالي. إذا كنت تستخدم توزيعة Linux أخرى، فقد تجد إصدارًا قديمًا من Node.js ضمن مستودعات توزيعتك، لذا يُنصح بتثبيت Node.js عن طريق مُدير إصدارات Node.js المتوفّر على GitHub، ويتمّ التثبيت بالطّريقة التالية: قم بتنزيل آخر نسخة من مدير إصدارات Node.js عن طريق زر Download ZIP في صفحة المشروع على GitHub. فكّ ضغط الملفّ الذي قمت بتنزيله انتقل بالطّرفيّة إلى مسار المجلد الناتج عن العمليّة السابقة، مثلاً: cd ~/Downloads/n-master ثم قم بتنفيذ أمر بناء المشروع: sudo make install قم بتثبيت آخر إصدار مستقرّ من Node.js مستخدمًا الأمر n الذي يوفّره مدير إصدارات Node.js: sudo n stable سيُطلب منك إدخال كلمة المرور إلى مستخدمك أو كلمة المرور إلى المستخدم الجذر إن وُجدت. من فوائد مُدير إصدارات Node.js إمكانيّة التبديل بشكل سريع بين عدّة إصدارات من Node.js، فقد ترغب أحيانًا بتجربة بعض المزايا المتوفّرة في إصدار غير مستقرّ (مثل v0.11 الذي يتضمّن بعضًا من مُكوّنات ECMAScript 6) مُستخدمًا الأمر n latest، ولكنّك ترغب بالعودة للعمل على مشاريع جادّة ضمن إصدار مستقرّ. ولهذا يمكنك استخدام الأمر n stable. يمكن أيضًا تثبيت الإصدارات الحديثة من Node.js على Ubuntu والتّوزيعات الأخرى باتّباع التّعليمات الرّسميّة المتوفّرة على صفحة Node.js على GitHub. للتحقّق من تثبيت Node.js اكتب الأمر التالي في الطّرفيّة (أو سطر أوامر Windows): node -v لتحصل على نتيجة برقم إصدار Node.js الذي قمت بتثبيته، مثل v0.10.33. إن كانت النتيجة تُفيد بعدم وجود الأمر مثل bash: node: command not found (على Linux) أو node is not recognized as an internal or external command... (على Windows)، فتحقّق من اتّباع الخطوات السّابقة مجدًدًا. إنشاء المشروع لنبدأ العمل بشكل نظيف، أنشئ مُجلّدًا جديدًا في مكان ما في جهازك وانتقل إليه باستخدام الطّرفيّة، سأقوم بإنشاء مجلّد ضمن مسار مُستخدمي /home/f/ وأسمّيه my-blog، ثم سأنتقل إليه باستخدام الأمر cd (اختصارًا لـchange directory) (الذي يتطابق في Linux وMac وWindows): cd /home/f/my-blog في Windows، قد يكون الأمر مُشابهًا لهذا: cd C:\Users\f\my-blog سنستخدم الأمر init الذي يوفّره مُدير حزم Node (npm) لإنشاء مشروع جديد، افتح الطّرفيّة (سطر الأوامر في Windows) واكتب الأمر التالي: npm init سيطرح البرنامج عليك مجموعة من الحقول لتعبئتها: name: اسم المشروع، ويقترح npm اسم المجلد الحالي كاسم للمشروع، ويمكنك الأخذ بالاقتراح بترك الحقل فارغًا وضغط Enter. version: إصدار المشروع (يمكنك تركه كما هو). description: وصف للمشروع. entry point: الملفّ الرئيسيّ الذي ينطلق منه المشروع، يمكنك تركه كما هو وإنشاء الملف index.js لاحقًا. test command: الأمر الذي يجب أن ينفّذه npm عندما يطلب منه تنفيذ الاختبارات على المشروع، أي عندما ينفذ الأمر npm test ضمن مجلد المشروع الرئيسيّ. سنتركه فارغًا الآن. git repository: مسار مستودع git الذي ستستخدمه لإدارة المشروع، يمكن أن يكون رابط http:// أو git:// ويمكن تعديله لاحقًا. keywords: الكلمات المفتاحية للمشروع مفصولة بفاصلة لاتينية (,)، أمثلة: blog, mysql, expressjs, tutorial. author: كاتب المشروع. license: رخصة المشروع، يمكن استخدام أي رخصة مثل GPLv2 أو MIT. بعد الانتهاء سيعرض البرنامج عليك المعلومات التي أدخلتها وهي جاهزة للكتابة إلى ملفّ package.json، اكتب yes لكتابة الملفّ. الملفّ package.json هو نقطة الانطلاق في كلّ مشاريع Node.js، ويُستخدم للتعريف بالمشروع ووصفه عند نشره في سجلّ حزم npm، والأهم من ذلك أنّه يستخدم لحفظ ما يعتمد عليه المشروع من حزم مع أرقام الإصدارات المطلوبة لكلّ حزم؛ ومن خلال هذا الملفّ يمكن إستنساخ المشروع وإعادة تثبيت المتطلّبات للتعديل عليه في وقت آخر أو من قبل أشخاص آخرين. تثبيت متطلّبات المشروع سيعتمد مشروعنا على إطار العمل Express كما هو واضح، بالإضافة إلى قواعد بيانات MySQL لتخزين التدوينات والمستخدمين وتعليقاتهم، سنحتاج أيضًا إلى بعض المتطلّبات الأخرى التي سنثبّتها في وقت الحاجة إليها. لتثبيت أحدث إصدار من express وحفظه كمتطلّب ضمن ملفّ package.json، نفّذ الأمر التّالي: npm install express --save ملاحظة: سنعتمد الإصدار الرّابع في هذه السلسّلة لكونه أحدث إصدار في وقت كتابتها، للتأكّد من تثبيت الإصدار الرّابع حتّى بعد صدور إصدارات أحدث، يمكنك استخدام الأمر: npm install express@4.10.* --save يحتاج تثبيت MySQL إلى خطوتين: الأولى تثبيت الخادوم الذي يوفّر قاعدة البيانات، ويُنجز بطريقة مختلفة لكل نظام تشغيل: في Windows وMac OS X، يمكن تثبيته بتنزيل برنامج التّثبيت المناسب لإصدار النّظام وبنيته من الموقع الرّسمي ثمّ اتّباع خطوات التّثبيت كما في تثبيت أي برنامج آخر. في Arch Linux، أنصح باستخدام MariaDB، وهي بديل مطابق تمامًا لـMySQL ويحلّ محلّه بدون الاضطرار لتعديل أي جزء من الشيفرة، ويمكن تثبيته من خلال مستودعات مستخدمي Arch بالأمر التّالي: yaourt -S mariadb --noconfirm في توزيعات Linux الأخرى مثل Ubuntu وFedora، فقد تتوفّر MariaDB وMySQL في المستودعات الرّسميّة ويمكن تثبيتها باستخدام apt-get وyum. الخطوة الأخرى تتضمّن تثبيت عميل MySQL (أو ما يسمى MySQL client)، وهو الجزء الذي سيتواصل مع الخادوم ليجلب نتائج الاستعلام من قاعدة البيانات ويوفّرها لمشروعنا، وفي حالتنا هذه ليس سوى وحدة Node.js يمكن تثبيتها بسهولة عبر npm install واستخدامها ضمن مشروعنا؛ تتوفّر العديد من الوحدات التي تقدّم إمكانية التواصل مع خادوم قواعد بيانات MySQL، ومن أفضلها الوحدة mysql التي يمكن تثبيتها بتنفيذ الأمر: npm install mysql --save نصيحة: يمكن تثبيت الحزمتين بأمر واحد: npm install express mysql --save بعد تثبيت الحزمتين، سنلاحظ إضافة حقل جديد في ملفّ package.json يوضّح متطلّبات المشروع التي حفظناها إلى الآن (قد تختلف أرقام الإصدارات لديك): "dependencies": { "express": "^4.10.6", "mysql": "^2.5.4" } إنشاء قاعدة البيانات وإدخال بعض التّدوينات كعيّنة وجود بعض التدوينات سيساعدنا في بناء الواجهة ورؤية التغييرات بسهولة أكبر، لذلك سنقوم بإنشاء قاعدة البيانات وإدخال بعض التّدوينات إليها قبل كلّ شيء. لإنشاء قاعدة البيانات وإدخال التدوينات، سنقوم بالتواصل مع الخادوم عبر الطّرفيّة، مستخدمين البرنامج mysql الذي يتمّ تثبيته تلقائيًّا عند تثبيت خادوم MySQL أو MariaDB. افتح الطّرفية ونفّذ الأمر: mysql -u root ملاحظة: إن اخترت اسمًا للمستخدم وكلمة مرور مختلفين أثناء التثبيت، فيمكن إدخالها بالطريقة الآتية (سيطلب منك إدخال كلمة المرور إلى هذا المستخدم): mysql -u username -p ستظهر شاشة مشابهة لهذه: ملاحظة: إن واجهتك مُشكلات في بدء البرنامج mysql، جرّب إعادة تشغيل النّظام. ملاحظة (2): يمكنك الاستغناء عن استعمال صدفة MySQL إذا كنت قد ثبّتت PHPMyAdmin على جهازك، حيث بإمكانك إدخال الأوامر ذاتها في مربّع الاستعلامات. في صدفة MySQL هذه يمكننا إدخال أوامر MySQL ليقوم الخادوم بتنفيذها على الفور. سنقوم بإنشاء قاعدة بيانات المدوّنة، وسنسمّيها myblog: CREATE DATABASE myblog; النتيجة التّالية دليل على نجاح التنفيذ: Query OK, 1 row affected (0.00 sec) اتّصل بقاعدة البيانات الجديدة بالأمر: connect myblog لنقم بإنشاء جدول للمستخدمين وآخر للتدوينات ثم لندخل مستخدمًا مع 4 تدوينات كتبها في أيام مختلفة، يمكنك نسخها ولصقها في صدفة MySQL فحسب: CREATE TABLE `users` (id INT NOT NULL PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50), password VARCHAR(500) NOT NULL, full_name VARCHAR(50), is_author BOOLEAN DEFAULT , UNIQUE INDEX (username)); INSERT INTO `users` (username, password, full_name, is_author) VALUES ("admin", "$2a$08$Z3FpAQwRgj7W0i71TtizFO7QDjpsIRNJfHh6mLgRJRJBtheKJh1Tu", "admin", 1); CREATE TABLE `posts` (id INT NOT NULL PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100), body LONGTEXT, date TIMESTAMP, author_id INT, slug VARCHAR(50), UNIQUE INDEX (slug), FOREIGN KEY (author_id) REFERENCES `users` (id)); INSERT INTO `posts` (title, body, date, author_id, slug) VALUES ("مرحبًا بالعالم!", "مرحبًا بكم في مدوّنتي المتواضعة!", "2014-12-29", 1, "hello-world"); INSERT INTO `posts` (title, body, date, author_id, slug) VALUES ("اقتباسات (1)", "الغني لو سئل عن تحسين العمل والحياة فسوف يقول: نحن نعرف أن البؤس غيرمفرح والواقع أن البؤس مادام بعيداً عنا فإننا نتسلح بفكرة أنه غير مفرح. ولكن لا تتوقع منا أن نفعل أي شيء بصدده. نحن آسفون لطبقاتكم الدنيا مثل مانحن آسفون لقطة جرباء...غير أننا سنقاتل كالمردة ضد أي تحسين لظرفكم. نحن نشعر انكم مأمونون أكثر وأنتم في حالكم هذا. إن الواقع الراهن يناسبنا ولسنا مستعدين لمخاطرة تحريركم حتى بساعة إضافية في اليوم هكذا يا إخوتي الأعزاء إن كان عليكم ان تعرقوا لدفع رحلاتنا إلى إيطاليا فلتعرقوا ولتحل عليكم اللعنة ― جورج أورويل، متشردًا في باريس ولندن", "2014-12-30", 1, "quotes-1"); INSERT INTO `posts` (title, body, date, author_id, slug) VALUES ("اقتباسات (2)", "التليفزيون يُغرقك في بحر من الأصوات والألوان بحيث لا تجد الوقت الكافي لتفكر أو تنتقد... إنه يقدم لك الأفكار الجاهزة ولا يسمح لك بالانتقاد الذي يسمح به الكتاب. ― راي برادبري، فهرنهايت 451", "2014-12-31", 1, "quotes-2"); INSERT INTO `posts` (title, body, date, author_id, slug) VALUES ("اقتباسات (3)", "أستطيع أن أقول لك يا بنيّ إنّ السّعادة ينبوع يتفجّر من القلب، لا غيث يهطل من السّماء، وأنّ النّفس الكريمة الرّاضية البريئة من أدران الرّذائل وأقذارها، ومطامع الحياة وشهواتها، سعيدة حيثما حلّت. [...] فمن أراد السّعادة فلا يسأل عنها المال والنّسب، وبين الفضّة والذّهب، والقصور والبساتين، والأرواح والرّياحين، بل يسأل عنها نفسه الّتي بين جنبيه فهي ينبوع سعادته وهنائه إن شاء، ومصدر شقائه وبلائه إن أراد. ― مصطفى لطفي المنفلوطي، الفضيلة", "2015-01-01", 1, "quotes-3"); لكلّ سطر في جدول التّدوينات الحقول التالية: العنوان title ونص التدوينة body وتاريخها date ومُعرّف الكاتب author_id الذي يُشير إلى أحد الكُتّاب المُسجلّين في جدول المستخدمين users، ثمّ slug وهو العنوان بالإنكليزية الملائم لاستخدامه ضمن رابط التّدوينة مثل hello-world في http://myblog/posts/hello-world. نحن الآن جاهزون للعمل! في الدّرس القادم سنبدأ بإنشاء الصّفحة الرّئيسيّة لمدوّنتنا، والّتي ستعرض التّدوينات الّتي أضفناها لتوّنا.1 نقطة
-
في الدّرس السّابق قمنا بثتبيت Node.js وخادوم MySQL وبقيّة متطلّبات المشروع، حان الوقت لنبدأ العمل الحقيقيّ! إنشاء صفحة المدوّنة الرئيسيّةأهمّ ما تعرضه الصّفحة الرئيسيّة لكلّ مدوّنة عادةً آخر التّدوينات بتاريخ كتابتها من الأحدث للأقدم، وسنركّز الآن على تطبيق هذا الجزء على أنّ نتوسّع في إضافة الميّزات في وقتٍ لاحق. أنشئ الملفّ index.js الّذي يُمثّل نقطة انطلاق مشروعنا، ولنبدأ باستيراد Express ضمنه: var express = require("express");لنُنشئ الآن تطبيق Express جديد، وهو يمثّل الخادوم الذي يُدير مدوّنتنا بالكامل، يتمّ إنشاء تطبيق Express ببساطة باستدعاء دالّة express الّتي أنشأناها لتوّنا: var express = require("express"); var app = express();تكون الصّفحة الرئيسيّة للمدوّنة على الرّابط الجذر للموقع عادةً، وهو ما نُعبّر عنه بـ/، سنطلب من تطبيقنا الاستجابة للطّلبات التي تصل إلى هذا الرّابط بعرض صفحة HTML تحوي آخر 10 تدوينات مرتّبة وفق تاريخ كتابتها من الأحدث إلى الأقدم: var express = require("express"); var app = express(); app.get("/", function(request, response) { // أرسل HTML });لندع إرسال الصّفحة جانبًا ولنفهم أسلوب استخدام Express، لكلّ تطبيق Express وظائف أربعة تُستخدم في استقبال وتوجيه الطّلبات، وهي get وpost وput وdel، وهذه الوظائف توافق أفعال HTTP الشّائعة. ولكن ما هي أفعال HTTP؟ كيف يعمل HTTP؟في كلّ مرّة تزور صفحة على الويب فإنّ متصفّحك يرسل للخادوم الذي يستضيف الموقع طلبًا بالحصول (GET) على المحتوى في الرابط الذي كتبته، يكون طلب HTTP هذا مشابهًا للمثال التّالي (المُبسَّط عمدًا): 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تُسمّى الحقول Accept وAccept-Language... بترويسات الطّلب (Request Headings)، ولكلّ ترويسة معنىً بالنّسبة للخادوم الذي يستقبل الطّلب، فمثلاً يقوم المتصفّح في الحقل User-Agent بالتّعريف عن نفسه، وهو ما يسمح للخادوم بإرسال جواب مخصّص لكلّ متصفّح مثلاً (إن شاء)، وفي الحقل Accept-Language يُرسِل المتصفّح اللّغات الّتي يرغب المستخدم برؤية الجواب بها، فيقوم الخادوم بإرسالة الصّفحة بالعربيّة (سوريا) ar-sy في حالتنا إن توفّرت لديه، أو بالعربية ar كخيار ثانٍ... وهكذا. يردّ الخادوم على الطّلب بجواب HTTP (HTTP Response) الذي يُشبه مثالنا هذا: HTTP/1.1 200 OK Content-Type: text/xml; charset=utf-8 Content-Length: 3918 <!DOCTYPE html> <html lang="ar"> <head> <title>مُدوّنتي - مرحبًا بالعالم!</title> </head> <body> مرحبًا بكم في مدوّنتي المتواضعة! </body> </html>السّطر الأوّل في الجواب يُسمّى سطر الحالة، ويتضمّن حالة الطّلب (حيث الرّقم 200 يعني أن الخادوم تلقّى الطّلب وردّ عليه بما هو متوقّع)، بقيّة السّطور هي ترويسات الجواب (Response Headings) الّتي تعني كلّ واحدة منها شيئًا ما لمستقبل الجواب (المتصفّح). يلي الترويسات متن الجواب (Response Body) الذي يحوي في حالتنا صفحة HTML الّتي سيقوم المتصفّح بعرضها على المستخدم. فعل GET الذي استخدمناه ليس وحيدًا، فهناك أفعال أخرى مثل POST الذي يُستخدم في المتصفّح لإرسال الحقول التي يُعبّئها المستخدم (كتعبئة حقل تسجيل الدّخول)، والفعل DELETE الذّي يستخدم ليطلب من الخادوم حذف محتوى ما (مثل حذف تدوينة من قبل المستخدم). الجدير بالذّكر أن الخادوم حرّ التّصرّف بالطّلبات التي يتلقّاها، والطّريقة التي شرحناها بهذه الأفعال مبنيّة على التّقاليد الشّائعة لاستخدامها، فلا شيء في الحقيقة يمنع الخادوم من حذف تدوينة عندما يتلقّى طلب GET بدلاً من DELETE وإنّما هو عُرف متّفق عليه. لنعد الآن إلى مثالنا السّابق، تقبل الوظيفة get مُعاملين أولهما الرّابط المطلوب التّعامل معه، والأخرى دالّة تقرأ الطّلب وتعدّل جوابه قبل إرسال الجواب للمُتصفّح، يمكن إرسال متن الجواب للمتصفّح من خلال الوظيفة send() للكائن response: var express = require("express"); var app = express(); app.get("/", function(request, response) { var html = "<!DOCTYPE html><html lang='ar'>" + "<head><title>مُدوّنتي!</title></head>" + "<body>" + posts.map(function(post) { return "<li>" + post.title + "</li>"; }).join("") + "</body></html>" response.send(html); });في الحالة الافتراضية سيكون جواب هذا الطّلب بالرّمز 200 OK مع متن يطابق محتوى المُتغيّر html. سنتعرّف فيما بعد على كيفيّة تغيير رموز الحالة بحيث نُرسل الرّمز الشّهير 404 Not Found عندما لا نجد تدوينة على الرّابط المطلوب. سيتوقّف البرنامج في هذه الحالة مُعطيًا خطأ بسبب كون posts غير معرّف، كلّ ما علينا الآن هو جلب التّدوينات من خلال قاعدة البيانات وتخزينها ضمن المُتغيّر posts، نحتاج إذًا لتنفيذ استعلام MySQL لجلب أحدث التدوينات، ولهذا سنقوم باستيراد وحدة mysql التي قمنا بتثبيتها وتأمين الاتصال بقاعدة البيانات: 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 = "<!DOCTYPE html><html lang='ar'>" + "<head><title>مُدوّنتي!</title></head>" + "<body>" + posts.map(function(post) { return "<li>" + post.title + "</li>"; }).join("") + "</body></html>"; response.send(html); }); }); ملاحظة: لا تنسَ تغيير اسم المستخدم وكلمة المرور ليتوافقا مع ما اخترته أثناء تثبيت MySQL. تمتلك وحدة mysql وظيفة createConnection() الّتي تُعيد لنا نسخة من اتّصال بقاعدة البيانات الّتي حدّدناها، والذي يمكن بدؤه باستدعاء الوظيفة connect() ثم تّنفيذ الاستعلامات query() الّتي تتمّ بأسلوب غير متزامن (asynchronous) لتُعيد لنا الصّفوف النّاتجة عن الاستعلام ضمن المعامل الثّاني للدّالة (function(err, posts) { ... }) الّتي تُستدعى بعد انتهاء الاستعلام. بهذه السّطور القليلة التي يمكن فهمها بالقليل من الجهد تمكنّنا من إنشاء مدوّنة بسيطة، وهنا يبرز جمال Node.js الذي يسمح للمبتدئين بتطبيق أفكار قد تبدو بعيدة المنال وجعلها واقعًا ملموسًا! الآن حان وقت تجربة المشروع، نحتاج لإخبار Express بالإنصات إلى الطّلبات الّتي ترد على منفذ معيّن على جهازنا (localhost): 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 = "<!DOCTYPE html><html lang='ar'>" + "<head><title>مُدوّنتي!</title></head>" + "<body>" + posts.map(function(post) { return "<li>" + post.title + "</li>"; }).join("") + "</body></html>"; response.send(html); }); }); app.listen(3000);لبدء البرنامج، افتح الطّرفيّة وانتقل إلى مجلّد المشروع، ثم نفّذ الأمر التّالي: node index.js ستتوقّف المؤشّر في الطّرفيّة عن الاستجابة بسبب انشغال هذه الطّرفيّة بتنفيذ البرنامج، اذهب إلى المتصفّح وانتقل إلى الرابط http://localhost:3000/ وشاهد النّتيجة: قد تبدو الصّفحة غاية في البساطة وخالية من أي عُنصر جماليّ، لكنّ ما يهمّنا الآن هو أنّنا قمنا بإنشاء خادوم يتواصل مع قاعدة بيانات ويعرض النّتائج على المستخدم... كلّ هذا في 16 سطرًا من JavaScript! لإنهاء البرنامج عُد إلى الطّرفيّة ذاتها واضغط Ctrl+C. تعرّف على لغة القوالب Jadeبعد أن تأكدنا من تنفيذ المكوّن الرئيسيّ لمشروعنا، سنعمل على تحسين شيفرتنا لجعلها أكثر بساطة وقابلة للتّطوير بسهولة فيما بعد. إذا ألقينا نظرةً على آخر ما كتبناه، سرعان ما نكتشف التّعقيد الذي ستصل إليه شيفرتنا إن أردنا إضافة المزيد من المزايا ضمن HTML، لأنّ هذا يعني إضافة المزيد من النّصّ إلى المتغيّر html بحيث يصبح طويلاً جدًّا وصعب القراءة؛ لا بدّ أن توجد طريقة أفضل من هذه! تتوفّر في كلّ اللّغات طريقة لتوليد صفحات HTML ديناميكيّة على الخادوم، بمعنى أنّه يمكن تغيير بعض محتوياتها وإدخال محتوى مُتغيّر فيها قبل إرسالها إلى المستخدم، هل تساءلت يومًا كيف يعرض فيس بوك لكلّ مستخدم صفحةً خاصّة به؟ بحيث يكون هيكلها متماثلاً لكلّ المستخدمين ولكن محتواها من الأخبار مختلف من مستخدم لآخر، الجواب هو باستخدام القوالب؛ لن نقوم بإنشاء فيس بوك جديد الآن، لكنّنا سنستفيد من ميزات القوالب الدّيناميكيّة لتوليد HTML بدلًا من كتابتها يدويًّا ضمن شيفرتنا! في عالم Node.js ستجد الكثير من لغات القولبة، لكنّ الامتداد الطّبيعيّ لاستخدام Express يكون باعتماد Jade كلغة قولبة كونها بدأت من المُطوّر ذاته، لنُعد كتابة HTML الصّفحة الرّئيسيّة لمدوّنتنا باستخدام Jade: doctype html html(lang="ar") head title "مُدوّنتي!" body for post in posts li #{ post.title }قارن بين نصّ HTML ونصّ Jade الأخير، أوّل ما نلاحظه في Jade هو بساطة صياغتها، فهي تلغي الوسوم النّهائيّة (مثل </head> و</body>) وتستعيض عن ذلك بكونها حسّاسة للمحاذاة، فكون الوسم title مُزاحًا إلى يمين head يعني أنّه محتوىً ضمنه، وكذلك الأمر بالنّسبة لـbody، نلاحظ كذلك دعم Jade للحلقات والمُتغيّرات، وهي من أبرز مزايا لغات القوالب، لأنها تسمح بتوليد عناصر متكرّرة دون الحاجة لكتابتها يدويًّا. سنحتاج أوّلًا لتثبيت Jade وحفظه في متطلّبات المشروع: npm install jade --save احفظ شيفرة Jade السابقة في ملفّ home.jade ضمن مجلّد جديد سمّه views داخل مُجلّد المشروع، ثمّ عُد للملفّ index.js، ولنقم باستخدام Jade عوضًا عن الأسلوب السابق: 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);ضبطنا الإعداد view engine في Express إلى القيمة "jade"، يستخدم Express هذا الإعداد عندما يُطلب منه عرض ملفّ ما باستخدام الوظيفة render التّابعة لكائن الجواب response، بحيث يبحث عن مُفسّر لغة القوالب (jade في حالتنا) ويطلب منه تحويل الملفّ "home" إلى HTML، مُمرّرًا له الكائن الذي يحوي المتغيّرات الّتي يحتاجها ({ posts: posts }). يبحث Express عن ملفّات العرض في المجلّد views بشكل افتراضيّ، وهو ما قمنا بإنشاءه للتّوّ.قم بتشغيل البرنامج مرّة أخرى باستخدام الأمر node index.js ثمّ زر الرّابط http://localhost:3000/. لم يتغيّر شيء ظاهر، لكنّنا انتقلنا إلى استخدام لغة قوالب وراء الكواليس، وسنستفيد من هذا بكتابة شيفرة أبسط وأكثر تنظيمًا. لنقم الآن بتعديل القالب home.jade ليبدو بشكل أجمل: 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 }قمنا بتغيير اتّجاه النّص لجعله من اليمين إلى اليسار عبر الخاصة "dir"، ثمّ أدخلنا بعض التنسيق من خلال الوسم "<style>" في HTML، تسمح Jade بكتابة لغات أخرى ضمن القالب مثل كتابة CSS وCoffeeScript أو Markdown أو Sass عبر الصّياغة :language ليتم تحويلها إلى اللّغة المناسبة للمتصفّح إن تطلّب الأمر، وفي هذه الحالة أدخلنا CSS بسيط (الذي لا يحتاج للتّحويل) بكتابة :css قبل الشّيفرة. سنتعرّف على مزيد من مزايا Jade خلال عملنا. تبدو مدوّنتنا بشكل أجمل الآن، لكنّها بالتأكيد تحتاج المزيد من العمل! يمكننا تحسين عرض صيغة التّاريخ باستخدام مكتبة moment للتّعامل مع التّواريخ والوقت، سنحتاج أولاً إلى تثبيتها وحفظها في متطلّبات المشروع: npm install --save moment سنُدخل التّعديلات اللّازمة على الملفّين index.js وhome.jade: 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);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) }يمكن تمرير الدّوال (functions) إلى Jade كما نُمرّر المتغيّرات، وفي حالتنا قمنا بتعريف دالّة تقوم بتنسيق التّاريخ الذي تتلقاه بصياغة نسبيّة (منذ كذا يومًا، منذ ساعتين...) وذلك بالاستفادة من مكتبة moment التي استوردناها وعيّنّا لغة التّاريخ فيها إلى العربيّة. أجرينا التغييرات اللازمة في Jade مستخدمين الدّالة التي فرضناها وأصبحت متوفّرة ضمن القالب: إنشاء صفحة التدوينةمن المعتاد لصفحات التّدوينات أن تكون بهذه الهيئة: http://myblog.com/posts/hello-world، ويمكن أن نشاهد في مدوّنات أخرى روابط تحوي تاريخ كتابة التّدوينة أو رقمًا خاصًّا بها... إلخ، لكنّنا سندع الأمور بسيطة. لدينا حاليًّا 4 تدوينات، ستكون روابطها: /posts/hello-world/posts/quotes-1/posts/quotes-2/posts/quotes-3الثّابت بين هذه الرّوابط هو اعتمادها على الحقل slug الّذي أدخلناه في كلّ سطر في جدول التّدوينات. من غير المنطقيّ أن نُسجّل رابطًا لكلّ تدوينة على حدة في Express، وسيصبح هذا مستحيلاً مع إنشاء تدوينات جديدة. يوفّر Express آليّة للإجابة على الطّلبات الواردة على الروابط التي تطابق نمطًا معيّنًا، وهو في حالتنا /posts/ متبوعًا بحقل متغيّر slug، أو /posts/:slug بصياغة Express، سنضيف الشيفرة التالية إلى برنامجنا (قبل آخر سطر): 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 }); }); }) نطلب من Express الاستجابة لأي رابط يطابق النمط "/posts/:slug" بالبحث عن التدوينة التي تملك القيمة slug ضمن العمود الموافق في جدول التّدوينات، نلاحظ أنّ Express يوفّر لنا هذه القيمة المتغيّرة من خلال الكائن params التابع لكائن الطّلب request (كائن الطّلب يحوي كذلك ترويسات الطّلب الّتي تحدّثنا عنها في الجزء السّابق). من المهمّ أنّ نحمي قاعدة بياناتنا من العبث وذلك بتجنب هجمات حقن SQL، ولهذا توفّر وحدة mysql دالّة query() ذاتها لكن مع 3 معاملات بدل اثنين فقط، حيث يكون الثاني مصفوفة تحوي القيم الّتي نريد التأكّد من سلامتها (escape) قبل إحلالها محلّ إشارات الاستفهام في استعلاماتنا. هذا أسلوب شائع جدًا في استعلامات SQL، وهو أقلّ ما يمكننا فعله لحماية قاعدة البيانات. لم نقم بعد بإنشاء قالب صفحة التّدوينة، لننشئ ملفًا جديدًا اسمه post.jade ضمن مجلّد views: 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) }لنبدأ برنامجنا، ونذهب إلى الصّفحة http://localhost:3000/posts/hello-world: لدينا الآن بعض المشكلات، ماذا يحدث لو أدخلنا رابطًا لتدوينة غير موجودة؟ جرّب مثلاً http://localhost:3000/posts/another-post: وقع خطأ في تفسير Jade سببه أن المتغيّر post الّذي وصله هو في الحقيقة غير معرّف undefined، لأنّه ما من تدوينة في قاعدة البيانات يطابق حقل slug فيها القيمة another-post، وعندما أجرينا الاستعلام أُعيدت لنا مصفوفة فارغة rows، وفي JavaScript فإنّ محاولة الوصول إلى خاصّة غير موجودة ("0") في عنصر مُعرّف (المصفوفة rows في حالتنا) تُرجع undefined. ما الذي كان علينا فعله لتجنب هذا الخطأ؟ أولاً يجب التأكّد قبل كلّ شيء أنّ الخطأ الذي يقع في مرحلة الاستعلام يتم التّعامل معه (handled) قبل الانتقال لما بعده، انتبه إلى أنّ الاستعلام الذي يتم بنجاح ويعيد مصفوفة فارغة لا يعتبر خطأ، لذا يجب التّعامل مع هذه الحالة أيضًا؛ مبدئيًا سنكتفي بإيقاف تنفيذ الدّالة: 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 }); }); }) جرّب الآن إعادة تشغيل البرنامج وزيارة الصّفحة ذاتها... سيستمرّ المتصفّح بمحاولة تحميلها لوقت طويل قبل أن يفشل بسبب انتهاء مهلة الطّلب. لماذا يحدث هذا؟ علينا أن نفهم واحدًا من أهمّ المفاهيم في Express، وهو الكيفيّة التّي تسير بها عمليّة توجيه الرّوابط (routing)، في شيفرتنا الأخيرة سيتوقف Express عند return دون أن يعرف ما ينبغي فعله في الخطوة التّالية، وهذا يجعل البرنامج عالقًا في الفراغ، نحتاج لطريقة نخبر بها Express أن يتابع التّنفيذ ويفعل شيئًا ما عندما تنتهي إحدى وظائف التّعامل مع الرّوابط، ولهذا يعطينا Express دالّة next التي تتوفّر كمعامل ثالث للدالة التّي تتلقّى الرابط: 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 }); }); }) أعد تشغيل البرنامج وزر الصّفحة مجدًّدا: هذا أفضل! لكن ما هي الدّالة التّالية التي استدعاها Express ليعرف أنّ صفحة على هذا الرّابط غير موجودة؟ الإجابة هي أنّ Express يحوي بشكل افتراضي دوالّ داخليّة يستدعيها عندما لا نزوّده بالدّالة التّالية، لكنّنا نستطيع فعل ذلك بسهولة: 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("التدوينة غير موجودة"); }) سجّلنا أكثر من دالّة تتعامل مع الرّابط ذاته، سينفّذها Express جميعًا بالتّرتيب ذاته، يمكن لكلّ دالّة أن تستدعي الدّالة التّالية أو أن توقف سلسلة الاستدعاءات بإرسال الطّلب للمتصفّح وإيقاف التّنفيذ. (إرسال الطّلب لا يعني بالضّرورة أنّ الدّوال التّالية لن تنفّذ، بل يجب إيقاف التّنفيذ صراحةً إن لم نرغب بهذا السّلوك). أعد تشغيل البرنامج ثم زُر الصّفحة ذاتها: حدث ما نتوقّعه بالضّبط، على سبيل التّأكد من كوننا لم نعبث بالوظيفة الرئيسيّة، جرّب زيارة تدوينة موجودة مثل http://localhost:3000/posts/quotes-1. كاختبار لك، قم بتعديل صفحة "التّدوينة غير موجودة" مستخدمًا قالبًا خاصًّا ولتجعله جميلاً! تصرّف براحتك! سأقوم بإدخال تعديل بسيط على الدّالة الثّانية، لجعلها ترسل الرّمز 404 (غير موجود) للمتصفّح بدل القيمة الافتراضيّة (200): app.get("/posts/:slug", function(request, response) { response.status(404); response.send("التدوينة غير موجودة"); })لن يغيّر هذا شيئًا في الظّاهر، لكنّه العرف المتّفق عليه، يمكن لبعض المتصفّحات أن تتعامل مع خطأ كهذا بعرض صفحة نتائج البحث على Google مثلاً (مع أنّه لا يوجد متصفّح يفعل ذلك)، لكنّها طريقة HTTP في التّفاهم بين الخادوم والمتصفّح. عظيم! لدينا الآن صفحة رئيسيّة منسّقة وصفحات مفردة للتّدوينات، في الدّرس القادم سنقوم بإنشاء نظام للمستخدمين تمهيدًا لإتاحة التّعليقات وكتابة تدوينات جديدة.1 نقطة
