سنُعِدّ في هذا المقال الوجهات Routes (شيفرة معالجة عناوين URL، ويسميها البعض بالموجهات) باستخدام دوال معالجة وهمية dummy لجميع النقاط النهائية للموارد التي سنحتاجها في موقع المكتبة المحلية LocalLibrary. سيكون لدينا في النهاية بنية معيارية لشيفرة معالجة الوجهات، والتي يمكننا توسيعها باستخدام دوال معالجة حقيقية في المقالات اللاحقة، وسيصبح لدينا أيضًا فهم جيد لكيفية إنشاء وجهات معيارية باستخدام إطار عمل Express.
- المتطلبات الأساسية: الاطلاع على مقال مدخل إلى إطار عمل الويب Express، وإكمال المقالات السابقة من هذه السلسلة بما في ذلك مقال استخدام قاعدة البيانات.
- الهدف: فهم كيفية إنشاء وجهات بسيطة وإعداد جميع النقاط النهائية لعناوين URL.
تعرّفنا في المقال السابق على نماذج Mongoose للتفاعل مع قاعدة البيانات، واستخدمنا سكربتًا مستقلًا لإنشاء بعض سجلات المكتبة الأولية، ويمكننا الآن كتابة الشيفرة البرمجية لتقديم تلك المعلومات للمستخدمين. يجب أولًا تحديد المعلومات التي نريد عرضها في صفحاتنا، ثم تعريف عناوين URL المناسبة لإعادة تلك الموارد، ثم يجب إنشاء الوجهات (معالجات عناوين URL) والعروض Views (القوالب Templates) لعرض تلك الصفحات.
يوضح المخطط البياني الآتي التدفق الرئيسي للبيانات والأشياء التي يجب تقديمها عند التعامل مع طلب أو استجابة HTTP، ويُوضّح العروض والوجهات والمتحكّمات التي تمثل الدوال التي تفصل الشيفرة البرمجية لتوجيه الطلبات من هذه الشيفرة التي تعالج الطلبات فعليًا.
أنشأنا النماذج مسبقًا، فالأشياء الرئيسية التي يجب إنشاؤها هي:
- "الوجهات Routes" لتوجيه الطلبات المدعومة وأيّ معلومات مُشفَّرة في عناوين URL للطلب إلى دوال المتحكم المناسبة.
- دوال المتحكم للحصول على البيانات المطلوبة من النماذج وإنشاء صفحة HTML تعرض البيانات وإعادتها إلى المستخدم لعرضها في المتصفح.
- العروض (القوالب) التي تستخدمها المتحكمات لتقديم البيانات.
يمكن أن يكون لدينا في النهاية صفحات لإظهار القوائم والمعلومات التفصيلية للكتب وأنواعها والمؤلفين ونسخ الكتب، وصفحات لإنشاء السجلات وتحديثها وحذفها. تُعَد هذه المعلومات كثيرة لعرضها في مقال واحد، لذلك سيركز معظم هذا المقال على إعداد الوجهات والمتحكمات لإعادة محتوًى "وهمي"، وسنوسّع توابع المتحكم في مقالاتنا اللاحقة للعمل مع بيانات النموذج.
يوفّر القسم الأول التالي نظرةً عامةً موجزة حول كيفية استخدام برمجية Express الوسيطة Router، ثم سنستخدم ذلك في الأقسام اللاحقة عند إعداد وجهات موقع المكتبة المحلية LocalLibrary.
مقدمة إلى الوجهات
الوجهة هي قسم من شيفرة Express التي تربط بين فعل HTTP، مثل GET
و POST
و PUT
و DELETE
وإلخ مع مسار أو نمط URL ودالة تُستدعى لمعالجة هذا النمط.
هناك عدة طرق لإنشاء الوجهات، وسنستخدم في هذا المقال البرمجية الوسيطة express.Router
لأنها تسمح بتجميع معالجات الوجهات لجزء معين من الموقع مع بعضها البعض والوصول إليها باستخدام بادئة prefix وجهة مشتركة. سنحتفظ بجميع الوجهات المتعلقة بالمكتبة في وحدة "دليل catalog"، وإذا أضفنا وجهات لمعالجة حسابات المستخدمين أو لدوال أخرى، فيمكننا الاحتفاظ بها مُجمَّعةً بصورة منفصلة.
ملاحظة: ناقشنا سابقًا وجهات تطبيق Express باختصار في مقال سابق. يشبه استخدام كائن الموجّه Router إلى حدٍ كبير تعريف الوجهات مباشرةً في كائن تطبيق Express باستثناء تقديم دعم أفضل للتقسيم إلى وحدات Modularization (كما سننافش في القسم التالي).
يوفر الجزء المتبقي من هذا القسم نظرةً عامة حول كيفية استخدام الكائن Router
لتعريف الوجهات.
تعريف واستخدام وحدات وجهة منفصلة
تقدم الشيفرة البرمجية التالية مثالًا عن كيفية إنشاء وحدة وجهة ثم استخدامها في تطبيق Express.
أولًا، ننشئ وجهات لميزة الويكي wiki في وحدة بالاسم "wiki.js"، إذ تستورد هذه الشيفرة البرمجية كائن تطبيق Express، وتستخدمه للحصول على كائن Router
، ثم تضيف بعض الوجهات إليه باستخدام التابع get()
، ثم تصدّر الوحدةُ الكائنَ Router
.
// wiki.js: وحدة وجهة ويكي Wiki const express = require("express"); const router = express.Router(); // وجهة الصفحة الرئيسية router.get("/", function (req, res) { res.send("Wiki home page"); }); // وجهة صفحة About router.get("/about", function (req, res) { res.send("About this wiki"); }); module.exports = router;
ملاحظة: عرّفنا في الشيفرة السابقة دوال رد النداء Callbacks الخاصة بمعالج الوجهة مباشرةً في دوال وجهة، وسنعرّف في موقع المكتبة المحلية LocalLibrary دوال رد النداء هذه في متحكم وحدة منفصل.
يمكن استخدام وحدة وجهة في ملف تطبيقنا الرئيسي من خلال طلب وحدة الوجهة (wiki.js) باستخدام الدالة require()
، ثم استدعاء الدالة use()
في تطبيق Express لإضافة كائن Router
إلى مسار معالجة البرمجية الوسيطة مع تحديد مسار URL الخاص بالويكي "wiki".
const wiki = require("./wiki.js"); // … app.use("/wiki", wiki);
ويمكن بعد ذلك الوصول إلى الوجهتين المُعرَّفتين في وحدة وجهة wiki من /wiki/
و /wiki/about/
.
دوال الوجهة
تعرّف الوحدة السابقة بعضًا من دوال الوجهة، إذ تُعرَّف الوجهة "about" (التي سنعيد إنتاجها فيما يلي) باستخدام التابع Router.get()
، والذي يستجيب فقط لطلبات HTTP من النوع GET. الوسيط الأول لهذا التابع هو مسار URL والوسيط الثاني هو دالة رد نداء تُستدعَى عند تلقي طلب HTTP من النوع GET مع المسار.
router.get("/about", function (req, res) { res.send("About this wiki"); });
تأخذ دالة رد النداء ثلاثة وسائط تُسمَّى عادة req
و res
و next
، والتي ستحتوي على كائن طلب HTTP واستجابة HTTP والدالة التالية next في سلسلة البرمجيات الوسيطة.
ملاحظة: تُعَد دوال Router برمجيات Express وسيطة، مما يعني أنها يجب إما إكمال (أو الاستجابة) للطلب أو استدعاء الدالة next
في السلسلة، إذ نكمل في حالتنا الطلب باستخدام التابع send()
، لذلك لا يُستخدَم الوسيط next
ونختار عدم تحديده.
تأخذ دالة router السابقة دالة رد نداء واحدة، ولكن يمكنك تحديد عدة وسائط لدوال رد النداء أو مصفوفة من دوال رد النداء. تُعَد كل دالة جزءًا من سلسلة البرمجيات الوسيطة، وستُستدعَى بالترتيب نفسه لإضافتها إلى السلسلة (إلّا إذا أكملت الدالة السابقة الطلب).
تستدعي دالة رد النداء في مثالنا التابع send()
في الاستجابة لإعادة السلسلة النصية "About this wiki" عندما نتلقى طلب GET مع المسار ("/about
"). هناك عدد من توابع الاستجابة الأخرى لإنهاء دورة الطلب/الاستجابة، فمثلًا يمكنك استدعاء التابع res.json()
لإرسال استجابة JSON أو التابع res.sendFile()
لإرسال ملف. يُعَد التابع render()
تابع الاستجابة الذي سنستخدمه غالبًا أثناء بناء المكتبة، والذي ينشئ ويعيد ملفات HTML باستخدام القوالب والبيانات (سنتحدث عن ذلك في مقالٍ لاحق).
أفعال HTTP
تستخدم أمثلة الوجهات السابقة التابع Router.get()
للاستجابة على طلبات HTTP من النوع GET مع مسار معين.
يوفّر الكائن Router
أيضًا توابع وجهة لجميع أفعال HTTP الأخرى، والتي تُستخدَم غالبًا بنفس الطريقة تمامًا ومن هذه التوابع: post()
و put()
و delete()
و options()
و trace()
و copy()
و lock()
و mkcol()
و move()
و purge()
و propfind()
و proppatch()
و unlock()
و report()
و mkactivity()
و checkout()
و merge()
و m-search()
و notify()
و subscribe()
و unsubscribe()
و patch()
و search()
و connect()
.
تتصرف الشيفرة البرمجية التالية مثلًا مثل وجهة /about
السابقة، ولكنها تستجيب فقط لطلبات HTTP من النوع POST
:
router.post("/about", (req, res) => { res.send("About this wiki"); });
مسارات الوجهة
تعرِّف مساراتُ الوجهة النقاطَ النهائية التي يمكن إجراء الطلبات عندها، فالأمثلة التي رأيناها حتى الآن كانت مجرد سلاسل نصية، واُستخدِمت كما هو مكتوب تمامًا مثل: '/' و '/about' و '/book' و '/any-random.path'.
يمكن أن تكون مسارات الوجهة أيضًا أنماطًا من السلاسل النصية التي تستخدم شكلًا من صيغ التعابير النمطية لتعريف أنماط النقاط النهائية التي ستجري مطابقتها. نوضح فيما يلي هذه الصيغة (لاحظ تفسير الشَرطة الواصلة (-
) والنقطة (.
) حرفيًا باستخدام المسارات المستندة إلى السلاسل النصية):
-
?
: يجب أن تحتوي النقطة النهائية endpoint على 0 أو 1 من المحرف السابق (أو المجموعة)، فمثلًا سيتطابق مسار الوجهة'/ab?cd'
مع النقاط النهائيةacd
أوabcd
. -
+
: يجب أن تحتوي النقطة النهائية على واحد أو أكثر من المحرف السابق (أو المجموعة)، فمثلًا سيتطابق مسار الوجهة'/ab+cd'
مع النقاط النهائيةabcd
وabbcd
وabbbcd
وإلخ. -
*
: يمكن أن تحتوي النقطة النهائية على سلسلة نصية عشوائية في مكان المحرف*
، فمثلًا سيتطابق مسار الوجهة'/ab*cd'
مع النقاط النهائيةabcd
وabXcd
وabSOMErandomTEXTcd
وإلخ. -
()
: تجميع تطابق مجموعة من المحارف لإجراء عملية أخرى عليها، فمثلًا سينفّذ'/ab(cd)?e'
تطابق?
على المجموعة(cd)
، إذ سيتطابق معabe
وabcde
.
يمكن أن تكون مسارات الوجهة أيضًا تعابير نمطية Regular Expressions في لغة جافا سكريبت JavaScript، فمثلًا سيتطابق مسار الوجهة التالي مع catfish
و dogfish
، ولكن لن يتطابق مع catflap
و catfishhead
وما إلى ذلك. لاحظ أن مسار التعبير النمطي يستخدم صياغة التعابير النمطية، وهي ليست سلسلة نصية بين علامات اقتباس كما في الحالات السابقة.
app.get(/.*fish$/, function (req, res) { // … });
ملاحظة: ستستخدم معظم الوجهات في موقع المكتبة المحلية LocalLibrary سلاسلًا نصية ولن تستخدم تعابيرًا نمطية، وسنستخدم أيضًا معاملات الوجهات كما سنناقش في القسم التالي.
معاملات الوجهة
تُسمَّى معاملات الوجهة بأجزاء Segments عنوان URL المُستخدَمة لالتقاط القيم في مواضع محددة من عنوان URL، إذ تُسبَق هذه المقاطع بنقطتين ثم الاسم (مثل /:your_parameter_name/
). تُخزَّن القيم المُلتقَطة في كائن req.params
من خلال استخدام أسماء المعاملات بوصفها مفاتيحًا، مثل req.params.your_parameter_name
.
ليكن لدينا مثلًا عنوان URL مُشفَّر يحتوي على معلومات حول المستخدمين والكتب: http://localhost:3000/users/34/books/8989
، إذ يمكننا استخراج هذه المعلومات كما هو موضح فيما يلي باستخدام معاملات مسار userId
و bookId
:
app.get("/users/:userId/books/:bookId", (req, res) => { // الوصول إلى userId باستخدام req.params.userId // الوصول إلى bookId باستخدام req.params.bookId res.send(req.params); });
يجب أن تتكون أسماء معاملات الوجهة من "محارف كلمات"، أي A-Z و a-z و0-9 و _.
ملاحظة: ستجري مطابقة عنوان "/book/create" مع وجهة مثل الوجهة /book/:bookId
، والتي ستستخرج قيمة "bookId" الخاصة بالإنشاء 'create
'. ستُستخدَم الوجهة الأولى التي تطابق عنوان URL الوارد، لذلك إذا أردتَ معالجة عناوين /book/create
بصورة منفصلة، فيجب تعريف معالج الوجهة قبل الوجهة /book/:bookId
.
هذا كل ما تحتاجه لبدء استخدام الوجهات، ولكن يمكنك العثور على مزيد من المعلومات في مقال كيفية تحديد الوجهات وأنواع طلبات HTTP وفي توثيق Express: التوجيه الأساسي ودليل التوجيه. توضح الأقسام التالية كيفية إعداد الوجهات والمتحكمات لموقع المكتبة المحلية.
معالجة الأخطاء في دوال الوجهة
تحتوي جميع دوال الوجهة الموضحة سابقًا على وسائط req
و res
التي تمثل الطلب والاستجابة على التوالي. تُستدعَى دوال الوجهة أيضًا مع وسيط ثالث هو next
، والذي يمكن استخدامه لتمرير الأخطاء إلى سلسلة برمجيات Express الوسيطة. توضح الشيفرة التالية كيفية إنجاز ذلك باستخدام مثال استعلام قاعدة البيانات الذي يأخذ دالة رد نداء ويعيد إما خطأ err
أو بعض النتائج. تُستدعَى next
مع خطأ err
بوصفه قيمة معاملها الأول عند إعادة خطأ err
، وسينتقل هذا الخطأ في النهاية إلى شيفرة معالجة الأخطاء العامة، وتُعاد البيانات المطلوبة ثم تُستخدَم في الاستجابة في حالة النجاح.
router.get("/about", (req, res, next) => { About.find({}).exec((err, queryResults) => { if (err) { return next(err); } // اعرض شيئًا ما في حالة النجاح res.render("about_view", { title: "About", list: queryResults }); }); });
معالجة الاستثناءات في دوال الوجهة
يوضح القسم السابق كيف يتوقع إطار عمل Express أن تعيد دوال الوجهة أخطاءً، إذ صُمِّم إطار العمل للاستخدام مع الدوال غير المتزامنة التي تأخذ دالة رد نداء (مع خطأ وووسيط النتيجة)، والتي تُستدعَى عند اكتمال العملية. يُعَد ذلك مشكلة لأننا سنجري لاحقًا استعلامات قاعدة بيانات Mongoose التي تستخدم واجهات برمجة تطبيقات مستندة إلى الوعود promise، والتي يمكن أن تؤدي إلى رمي استثناءات في دوال الوجهة بدلًا من إعادة الأخطاء في دالة رد النداء.
يمكن أن يعالج إطار العمل الاستثناءات بصورة صحيحة من خلال اكتشافها ثم توجيهها بوصفها أخطاءً كما هو موضح في القسم السابق.
ملاحظة: من المتوقع أن يعالج Express 5 التجريبي استثناءات جافا سكريبت بطريقة أصيلة.
ليكن لدينا المثال البسيط من القسم السابق مع وجود About.find().exec()
بوصفه استعلام قاعدة بيانات يعيد وعدًا، إذ يمكن كتابة دالة الوجهة ضمن كتلة try...catch
كما يلي:
exports.get("/about", async function (req, res, next) { try { const successfulResult = await About.find({}).exec(); res.render("about_view", { title: "About", list: successfulResult }); } catch (error) { return next(error); } });
يُعَد ذلك كمًا كبيرًا جدًا من الشيفرة البرمجية المتداولة لإضافتها إلى كل دالة، لذا سنستخدم بدلًا من ذلك وحدة express-async-handler التي تعرّف دالة تغليف تخفي كتلة try...catch
والشيفرة البرمجية لتوجيه الخطأ، وبالتالي أصبح المثال نفسه الآن بسيطًا جدًا، لأننا نحتاج فقط إلى كتابة شيفرة برمجية للحالة التي نفترض فيها النجاح:
// استيراد الوحدة const asyncHandler = require("express-async-handler"); exports.get( "/about", asyncHandler(async (req, res, next) => { const successfulResult = await About.find({}).exec(); res.render("about_view", { title: "About", list: successfulResult }); }), );
الوجهات اللازمة لموقع المكتبة المحلية
سنعرض فيما يلي عناوين URL التي سنحتاجها لصفحاتنا، إذ نضع اسم كل نموذج من نماذجنا (الكتاب ونسخة الكتاب ونوع الكتاب والمؤلف) مكان الكائن object، والكائنات objects هي جمع كائن، والمعرّف id هو نسخة فريدة من الحقل (_id
) تُعطَى لكل نسخة من نموذج Mongoose افتراضيًا.
-
catalog/
: الصفحة الرئيسية أو صفحة الفهرس. -
catalog/<objects>/
: قائمة بجميع الكتب أو نسخها أو أنواعها أو المؤلفين، مثل/catalog/books/
و/catalog/genres/
وغير ذلك. -
catalog/<object>/<id>
: صفحة التفاصيل لكتاب أو نسخة أو نوع أو مؤلف معين مع قيمة الحقل_id
المُحدَّدة، مثل/catalog/book/584493c1f4887f06c0e67d37
. -
catalog/<object>/create
: استمارة إنشاء كتاب أو نسخة أو نوع أو مؤلف جديد، مثل/catalog/book/create
. -
catalog/<object>/<id>/update
: استمارة لتحديث كتاب أو نسخة أو نوع أو مؤلف معين مع قيمة الحقل_id
المُحدَّدة، مثل/catalog/book/584493c1f4887f06c0e67d37/update
. -
catalog/<object>/<id>/delete
: استمارة لحذف كتاب أو نسخة أو نوع أو مؤلف معين مع قيمة الحقل_id
المُحدَّدة، مثل/catalog/book/584493c1f4887f06c0e67d37/delete
.
لا تشفِّر الصفحة الرئيسية الأولى وصفحات القائمة أيّ معلومات إضافية، فبينما تعتمد النتائج المُعادة على نوع النموذج والمحتوى في قاعدة البيانات، ستبقى الاستعلامات المُنفَّذة للحصول على المعلومات نفسها دائمًا، وبالمثل، ستكون الشيفرة البرمجية المُنفَّذة لإنشاء الكائن نفسها دائمًا.
تُستخدَم عناوين URL الأخرى للعمل على نسخةٍ من مستند أو نموذج معين، وتشفّر هوية العنصر في عنوان URL (كما هو موضّح في العنصر <id>
سابقًا). سنستخدم معاملات المسار لاستخراج المعلومات المشفرة وتمريرها إلى معالج الوجهة، وسنستخدمها في مقال لاحق لتحديد المعلومات التي نحصل عليها من قاعدة البيانات ديناميكيًا. نحتاج فقط إلى وجهة واحدة لكل مورد من نوع معين من خلال تشفير المعلومات في عنوان URL، مثل استخدام وجهة واحدة للتعامل مع عرض عنصر كتاب واحد.
ملاحظة: يسمح إطار عمل Express بإنشاء عناوين URL الخاصة بك بالطريقة التي تريدها، إذ يمكنك تشفير المعلومات في متن عنوان URL أو استخدام معاملات URL من النوع GET
(مثل /book/?id=6
). يجب أن تظل عناوين URL نظيفة ومنطقية وقابلة للقراءة بغض النظر عن الطريقة التي تستخدمها.
سننشئ فيما يلي دوال رد نداء معالجة الوجهة وشيفرة الوجهة البرمجية لجميع عناوين URL السابقة.
إنشاء دوال رد النداء لمعالجة الوجهة
سننشئ أولًا جميع دوال رد النداء الوهمية أو الهيكلية التي ستُستدعَى لاحقًا قبل أن نعرّف الوجهات، وستُخزَّن دوال رد النداء في وحدات متحكمات منفصلة لكل من Book
و BookInstance
و Genre
و Author
، إذ يمكنك استخدام أي بنية ملفات أو وحدات، ولكن تبدو الطريقة التي سنستخدمها مناسبة ودقيقة لهذا المشروع.
ابدأ بإنشاء مجلدٍ للمتحكمات في جذر المشروع /controllers
، ثم أنشئ ملفات أو وحدات منفصلة للمتحكمات للتعامل مع كل نموذج كما يلي:
/express-locallibrary-tutorial //the project root /controllers authorController.js bookController.js bookinstanceController.js genreController.js
ستستخدم المتحكمات الوحدة express-async-handler
، لذا ثبّتها في المكتبة باستخدام npm
قبل المتابعة كما يلي:
npm install --save express-async-handler
متحكم المؤلف Author
افتح الملف "/controllers/authorController.js" واكتب فيه الشيفرة البرمجية التالية:
const Author = require("../models/author"); const asyncHandler = require("express-async-handler"); // عرض قائمة بجميع المؤلفين exports.author_list = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Author list"); }); // عرض صفحة التفاصيل لمؤلف معين exports.author_detail = asyncHandler(async (req, res, next) => { res.send(`NOT IMPLEMENTED: Author detail: ${req.params.id}`); }); // عرض استمارة إنشاء مؤلف باستخدام GET exports.author_create_get = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Author create GET"); }); // معالجة إنشاء مؤلف باستخدام POST exports.author_create_post = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Author create POST"); }); // عرض استمارة حذف المؤلف باستخدام GET exports.author_delete_get = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Author delete GET"); }); // معالجة حذف المؤلف باستخدام POST exports.author_delete_post = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Author delete POST"); }); // عرض استمارة تحديث المؤلف باستخدام GET exports.author_update_get = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Author update GET"); }); // معالجة تحديث المؤلف باستخدام POST exports.author_update_post = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Author update POST"); });
تطلب الوحدة أولًا نموذج المؤلف Author
الذي سنستخدمه لاحقًا للوصول إلى بياناتنا وتحديثها، والمغلِّف asyncHandler
الذي سنستخدمه لالتقاط الاستثناءات من دوال معالج الوجهة، ثم تصدِّر دوالًا لكل عنوان من عناوين URL التي نرغب في معالجتها. لاحظ أن عمليات الإنشاء والتحديث والحذف تستخدم الاستمارات Forms، وبالتالي تملك توابعًا إضافية لمعالجة طلبات الاستمارة من النوع Post، وسنناقش هذه التوابع في مقال لاحق.
تستخدم جميع الدوال الدالة المُغلِّفة Wrapper السابقة في معالجة الاستثناءات في دوال الوجهة مع وسائط للطلب والاستجابة والدالة التالية next، وتستجيب الدوال بسلسلة نصية تشير إلى أن الصفحة المرتبطة لم تُنشَأ بعد. إذا كان من المتوقع أن تتلقى دالة المتحكم معاملات المسار، فستكون هذه المعاملات هي الخرج الموجود في سلسلة الرسالة النصية (لاحظ req.params.id
).
لاحظ أنه يمكن ألّا تحتوي بعض دوال الوجهة بعد تقديمها على أي شيفرة برمجية يمكنها أن ترمي استثناءات، ولكن يمكننا تغيير تلك الدوال إلى دوال معالجة وجهة عادية عند الوصول إليها.
متحكم نسخة الكتاب BookInstance
افتح الملف /controllers/bookinstanceController.js وانسخ الشيفرة البرمجية التالية التي تتبع نمطًا مطابقًا لوحدة المتحكم Author
:
const BookInstance = require("../models/bookinstance"); const asyncHandler = require("express-async-handler"); // عرض قائمة بجميع نسخ الكتب BookInstance exports.bookinstance_list = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: BookInstance list"); }); // عرض صفحة تفاصيل نسخة كتاب BookInstance معينة exports.bookinstance_detail = asyncHandler(async (req, res, next) => { res.send(`NOT IMPLEMENTED: BookInstance detail: ${req.params.id}`); }); // عرض استمارة إنشاء نسخة الكتاب BookInstance باستخدام GET exports.bookinstance_create_get = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: BookInstance create GET"); }); // معالجة إنشاء نسخة كتاب BookInstance باستخدام POST exports.bookinstance_create_post = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: BookInstance create POST"); }); // عرض استمارة حذف نسخة كتاب BookInstance باستخدام GET exports.bookinstance_delete_get = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: BookInstance delete GET"); }); // معالجة حذف نسخة كتاب BookInstance باستخدام POST exports.bookinstance_delete_post = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: BookInstance delete POST"); }); // عرض استمارة تحديث نسخة كتاب BookInstance باستخدام GET exports.bookinstance_update_get = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: BookInstance update GET"); }); // معالجة تحديث نسخة الكتاب باستخدام POST exports.bookinstance_update_post = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: BookInstance update POST"); });
متحكم نوع الكتاب Genre
افتح الملف /controllers/genreController.js والصق فيه النص التالي الذي يتبع نمطًا مطابقًا لملفات Author
و BookInstance
:
const Genre = require("../models/genre"); const asyncHandler = require("express-async-handler"); // عرض قائمة بجميع أنواع الكتب Genre exports.genre_list = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Genre list"); }); // عرض صفحة تفاصيل نوع كتاب Genre معين exports.genre_detail = asyncHandler(async (req, res, next) => { res.send(`NOT IMPLEMENTED: Genre detail: ${req.params.id}`); }); // عرض استمارة إنشاء نوع الكتاب Genre باستخدام GET exports.genre_create_get = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Genre create GET"); }); // معالجة إنشاء نوع كتاب Genre باستخدام POST exports.genre_create_post = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Genre create POST"); }); // عرض استمارة حذف نوع كتاب Genre باستخدام GET exports.genre_delete_get = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Genre delete GET"); }); // معالجة حذف نوع كتاب Genre باستخدام POST exports.genre_delete_post = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Genre delete POST"); }); // عرض استمارة تحديث نوع كتاب Genre باستخدام GET exports.genre_update_get = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Genre update GET"); }); // معالجة تحديث نوع كتاب Genre باستخدام POST exports.genre_update_post = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Genre update POST"); });
متحكم الكتاب Book
افتح الملف /controllers/bookController.js وانسخ الشيفرة البرمجية التالية، إذ يتبع هذا الملف النمط المتبع نفسه في وحدات المتحكمات الأخرى، ولكنه يحتوي أيضًا على الدالة index()
لعرض صفحة الترحيب بالموقع:
const Book = require("../models/book"); const asyncHandler = require("express-async-handler"); exports.index = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Site Home Page"); }); // عرض قائمة بجميع الكتب exports.book_list = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Book list"); }); // عرض صفحة تفاصيل كتاب معين exports.book_detail = asyncHandler(async (req, res, next) => { res.send(`NOT IMPLEMENTED: Book detail: ${req.params.id}`); }); // عرض استمارة إنشاء كتاب باستخدام GET exports.book_create_get = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Book create GET"); }); // معالجة إنشاء كتاب باستخدام POST exports.book_create_post = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Book create POST"); }); // عرض استمارة حذف كتاب باستخدام GET exports.book_delete_get = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Book delete GET"); }); // معالجة حذف كتاب باستخدام POST exports.book_delete_post = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Book delete POST"); }); // عرض استمارة تحديث كتاب باستخدام GET exports.book_update_get = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Book update GET"); }); // معالجة تحديث كتاب باستخدام POST exports.book_update_post = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Book update POST"); });
إنشاء دليل وحدات الوجهة
سننشئ الآن وجهات لجميع عناوين URL التي يحتاجها موقع المكتبة المحلية LocalLibrary، والتي ستستدعي دوال المتحكمات التي عرّفناها في الأقسام السابقة.
تحتوي البنية الهيكلية للموقع مسبقًا على المجلد "./routes" الذي يحتوي على وجهات لصفحة الفهرس index والمستخدمين users. أنشئ ملف وجهة آخر هو catalog.js ضمن هذا المجلد كما يلي:
/express-locallibrary-tutorial //the project root /routes index.js users.js catalog.js
افتح الملف /routes/catalog.js وانسخ الشيفرة البرمجية التالية:
const express = require("express"); const router = express.Router(); // طلب وحدات المتحكمات const book_controller = require("../controllers/bookController"); const author_controller = require("../controllers/authorController"); const genre_controller = require("../controllers/genreController"); const book_instance_controller = require("../controllers/bookinstanceController"); /// وجهات الكتاب /// // الحصول على صفحة الدليل الرئيسية router.get("/", book_controller.index); // طلب GET لإنشاء كتاب. يجب أن يأتي هذا الطلب قبل الوجهات Routes التي تعرض الكتاب (يستخدم المعرّف id) router.get("/book/create", book_controller.book_create_get); // طلب POST لإنشاء كتاب router.post("/book/create", book_controller.book_create_post); // طلب GET لحذف كتاب router.get("/book/:id/delete", book_controller.book_delete_get); // طلب POST لحذف كتاب router.post("/book/:id/delete", book_controller.book_delete_post); // طلب GET لتحديث كتاب router.get("/book/:id/update", book_controller.book_update_get); // طلب POST لتحديث كتاب router.post("/book/:id/update", book_controller.book_update_post); // طلب GET لكتاب واحد router.get("/book/:id", book_controller.book_detail); // طلب GET لقائمة جميع عناصر الكتب router.get("/books", book_controller.book_list); /// وجهات المؤلف /// // طلب GET لإنشاء مؤلف Author. يجب أن يأتي هذا الطلب قبل وجهة المعرّف (مثل عرض مؤلف) router.get("/author/create", author_controller.author_create_get); // طلب POST لإنشاء مؤلف router.post("/author/create", author_controller.author_create_post); // طلب GET لحذف مؤلف router.get("/author/:id/delete", author_controller.author_delete_get); // طلب POST لحذف مؤلف router.post("/author/:id/delete", author_controller.author_delete_post); // طلب GET لتحديث مؤلف router.get("/author/:id/update", author_controller.author_update_get); // طلب POST لتحديث مؤلف router.post("/author/:id/update", author_controller.author_update_post); // طلب GET لمؤلف واحد router.get("/author/:id", author_controller.author_detail); // طلب GET لقائمة جميع المؤلفين router.get("/authors", author_controller.author_list); /// وجهات نوع الكتاب GENRE /// // طلب GET لإنشاء نوع كتاب. يجب أن يأتي هذا الطلب قبل الوجهة التي تعرض نوع الكتاب (يستخدم المعرّف) router.get("/genre/create", genre_controller.genre_create_get); // طلب POST لإنشاء نوع كتاب router.post("/genre/create", genre_controller.genre_create_post); // طلب GET لحذف نوع كتاب router.get("/genre/:id/delete", genre_controller.genre_delete_get); // طلب POST لحذف نوع كتاب router.post("/genre/:id/delete", genre_controller.genre_delete_post); // طلب GET لتحديث نوع كتاب router.get("/genre/:id/update", genre_controller.genre_update_get); // طلب POST لتحديث نوع كتاب router.post("/genre/:id/update", genre_controller.genre_update_post); // طلب GET لنوع كتاب واحد router.get("/genre/:id", genre_controller.genre_detail); // طلب GET لقائمة جميع أنواع الكتب router.get("/genres", genre_controller.genre_list); /// طرق نسخ الكتب BOOKINSTANCE /// // طلب GET لإنشاء نسخة كتاب BookInstance. يجب أن يأتي هذا الطلب قبل الوجهة التي تعرض نسخة الكتاب (يستخدم المعرّف) router.get( "/bookinstance/create", book_instance_controller.bookinstance_create_get, ); // طلب POST لإنشاء نسخة كتاب router.post( "/bookinstance/create", book_instance_controller.bookinstance_create_post, ); // طلب GET لحذف نسخة كتاب router.get( "/bookinstance/:id/delete", book_instance_controller.bookinstance_delete_get, ); // طلب POST لحذف نسخة كتاب router.post( "/bookinstance/:id/delete", book_instance_controller.bookinstance_delete_post, ); // طلب GET لتحديث نسخة الكتاب router.get( "/bookinstance/:id/update", book_instance_controller.bookinstance_update_get, ); // طلب POST لتحديث نسخة كتاب router.post( "/bookinstance/:id/update", book_instance_controller.bookinstance_update_post, ); // طلب GET لنسخة كتاب واحدة router.get("/bookinstance/:id", book_instance_controller.bookinstance_detail); // طلب GET لقائمة جميع نسخ الكتاب router.get("/bookinstances", book_instance_controller.bookinstance_list); module.exports = router;
تطلب الوحدة كائن Express ثم تستخدمه لإنشاء كائن Router
، إذ تُضبَط جميع الوجهات في هذا الكائن الذي يُصدَّر لاحقًا. تُعرّف الوجهات إما باستخدام تابع .get()
أو تابع .post()
للكائن Router
، وتُعرّف جميع المسارات باستخدام سلاسل نصية، إذ لا نستخدم أنماط سلاسل نصية أو تعابيرًا نمطية. تستخدم الوجهات التي تعمل على بعض الموارد المحددة (مثل الكتاب) معاملاتِ المسار للحصول على معرّف الكائن من عنوان URL، وتُستورَد جميع دوال المعالجة من وحدات المتحكمات التي أنشأناها في القسم السابق.
تحديث وحدة وجهة صفحة الفهرس
ضبطنا جميع وجهاتنا الجديدة، ولكن لا يزال لدينا الوجهة إلى الصفحة الأصلية، ولكن لنعيد التوجيه إلى صفحة الفهرس الجديدة التي أنشأناها في المسار '/catalog'
بدلًا من ذلك.
افتح الملف /routes/index.js وضع الدالة التالية بدلًا من الوجهة الحالية:
// الحصول على الصفحة الرئيسية router.get("/", function (req, res) { res.redirect("/catalog"); });
ملاحظة: استخدمنا تابع الاستجابة redirect()
الذي يؤدي إلى إعادة التوجيه إلى الصفحة المحددة، مع إرسال رمز حالة HTTP الذي هو "302 Found" افتراضيًا، ويمكنك تغيير رمز الحالة المُعاد إن لزم الأمر وتوفير مسارات مطلقة أو نسبية. يمكنك الاطلاع على مقال رموز الإجابة في HTTP والمقال كيفية استكشاف وإصلاح رموز أخطاء HTTP الشائعةعلى أكاديمية حسوب لمزيدٍ من المعلومات حول رموز حالة HTTP.
تحديث الملف app.js
تتمثل الخطوة الأخيرة في إضافة الوجهات إلى سلسلة البرمجيات الوسيطة في الملف app.js، لذا افتح هذا الملف، واطلب وجهة الدليل بعد الوجهات الأخرى من خلال إضافة السطر الثالث التالي بعد السطرين الآخرين الموجودين مسبقًا في الملف:
var indexRouter = require("./routes/index"); var usersRouter = require("./routes/users"); const catalogRouter = require("./routes/catalog"); // استيراد الوجهات لمنطقة "الدليل" في الموقع
ضِف بعد ذلك وجهة الدليل catalog إلى مكدس البرمجيات الوسيطة بعد الوجهات الأخرى من خلال إضافة السطر الثالث التالي بعد السطرين الآخرين الموجودين مسبقًا في الملف:
app.use("/", indexRouter); app.use("/users", usersRouter); app.use("/catalog", catalogRouter); // إضافة وجهات الدليل إلى سلسلة البرمجيات الوسيطة
ملاحظة: أضفنا وحدة دليلنا في المسار '/catalog'
الذي سيكون البادئة لجميع المسارات المحدَّدة في وحدة الدليل، فمثلًا سيكون عنوان URL هو /catalog/books/
للوصول إلى قائمة الكتب.
يجب أن يكون لدينا الآن مسارات ودوال هيكلية مُفعَّلة لجميع عناوين URL التي سندعمها في موقع المكتبة المحلية LocalLibrary.
اختبار الوجهات
أولًا، ابدأ تشغيل موقع الويب بالطريقة المعتادة، فالطريقة الافتراضية في أنظمة التشغيل هي:
// نظام ويندوز SET DEBUG=express-locallibrary-tutorial:* & npm start // ماك أو لينكس DEBUG=express-locallibrary-tutorial:* npm start
ولكن إذا سبق لك إعداد nodemon، فيمكنك بدلًا من ذلك استخدام ما يلي:
npm run serverstart
انتقل بعد ذلك إلى عدد من عناوين URL الخاصة بموقع المكتبة المحلية LocalLibrary وتحقق من عدم ظهور صفحة خطأ (HTTP 404). إليك مجموعة صغيرة من عناوين URL:
-
http://localhost:3000/
-
http://localhost:3000/catalog
-
http://localhost:3000/catalog/books
-
http://localhost:3000/catalog/bookinstances/
-
http://localhost:3000/catalog/authors/
-
http://localhost:3000/catalog/genres/
-
http://localhost:3000/catalog/book/5846437593935e2f8c2aa226
-
http://localhost:3000/catalog/book/create
الخلاصة
أنشأنا جميع الوجهات الخاصة بموقعنا، مع دوال المتحكمات الوهمية التي يمكننا ملؤها بتقديم كامل في مقالات لاحقة، وتعلمنا الكثير من المعلومات الأساسية حول وجهات Express والتعامل مع الاستثناءات وبعض الأساليب لبناء الوجهات والمتحكمات.
سننشئ في المقال التالي صفحة ترحيب مناسبة للموقع باستخدام العروض (القوالب) والمعلومات المخزنة في نماذجنا، وسننشئ استمارات HTML وشيفرة معالجة الاستمارات لبدء تعديل البيانات التي يخزنها الموقع.
ترجمة -وبتصرُّف- للمقال Express Tutorial Part 4: Routes and controllers.
أفضل التعليقات
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.