من الحاويات إلى kubernetes دمج قاعدة البيانات MongoDB في تطبيقك Node


عبد الصمد العماري

قد تجد نفسك، وأنت تستخدم node.js، أنك بصدد تطوير مشروع يُخزّن البيانات والاستعلام عنها. في هذه الحالة، ستحتاج إلى اختيار حلّ تقني لقاعدة البيانات يكون ملائما لأنواع البيانات والاستعلامات التي يستخدمها تطبيقك.

في هذا الدّرس، ستعمل على دمج قاعدة بيانات MongoDB مع تطبيق Node موجودٍ سلفًا. يمكن أن تكون قواعد البيانات NoSQL مثل MongoDB مفيدةً إذا كانت متطلبات بياناتك تتضمن قابلية التوسع والمرونة. ويتكامل MongoDB جيدًا مع Node لأنه مصمم للعمل بشكل غير متزامن مع كائنات JSON.

من أجل دمج MongoDB في مشروعك، سوف تستخدم Object Document Mapper) ODM) الخاص ب Mongoose لإنشاء مخططات ونماذج بيانات لتطبيقك. سيتيح لك ذلك تنظيم شيفرة التطبيق وفقًا للنمط الهيكلي MVC، والذي يسمح بفصل المنطق الذي يحكم كيفية معالجة التطبيق لإدخالات المستخدم عن ذلك الذي يتعلق بكيفية هيكلة البيانات وتقديمها إلى المستخدم. ويسهّل الاعتمادُ على هذا النمط الاختبار والتطوير في المستقبل عبر فصل المتعلّقات في قاعدة البيانات الخاصة بك.

في نهاية البرنامج التعليمي، سيكون لديك تطبيق جاهز خاصّ بمعلومات سمك القرش يعمل على أخذ مدخلات المستخدم حول أسماك القرش المفضلة لديه وعرض النتائج في المتصفح:

shark_added.png

المتطلبات الأساسية

الخطوة الأولى: إنشاء مستخدم Mongo

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

أولاً، تحقق أن MongoDB يشتغل على خادمك:

sudo systemctl status mongodb

يدلّ الإخراج التالي على أنّ MongoDB يشتغل:

● mongodb.service - An object/document-oriented database
   Loaded: loaded (/lib/systemd/system/mongodb.service; enabled; vendor preset: enabled)
   Active: active (running) since Thu 2019-01-31 21:07:25 UTC; 21min ago
...

بعد ذلك، افتح الصدفة الخاصة ب Mongo من أجل إنشاء مستخدمك:

Mongo

سينقلك هذا الأمر إلى صدفة إدارية:

MongoDB shell version v3.6.3
connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 3.6.3
...
>

ستظهر لك بعض التحذيرات الإدارية عند فتح الصدفة (shell) نظرًا لغياب قيود على وصولك إلى قاعدة بيانات الخاصة بالمشرف admin. لمعرفة المزيد حول تقييد هذا الوصول يمكنك قراءة كيفية تثبيت وتأمين MongoDB على أوبونتو 18.04، عندما تنتقل إلى إعدادات الإنتاج.

تستطيع الآن الاستفادة من وصولك إلى قاعدة بيانات المشرف admin لإنشاء مستخدم يمتلك الأذونات userAdminAnyDatabase التي تتيح الوصول المحمي بكلمة مرور لقواعد بيانات التطبيق.

حدّد في الصّدفة shell، أنك تريد استخدام قاعدة بيانات المشرف admin لإنشاء مستخدمك:

use admin

بعد ذلك، أنشئ دورًا (role) وكلمة مرور عبر إضافة اسم مستخدم وكلمة مرور باستخدام الأمر db.createUser. بعد كتابة هذا الأمر، ستُظهِر الصدفة ثلاث نقاط قبل كل سطر حتى يكتمل تنفيذ الأمر. تأكد من تغيير المستخدم وكلمة المرور المقدمين هنا باسم المستخدم وكلمة المرور الخاصين بك:

db.createUser(
  {
    user: "sammy",
    pwd: "your_password",
    roles: [ { role: "userAdminAnyDatabase", db: "admin" } ]
  }
)

سينشئ هذا الأمر إدخالًا للمستخدم sammy في قاعدة بيانات المشرف admin. وسيعمل اسم المستخدم الذي تحدده وقاعدة بيانات المشرف admin كمحدّدات للمستخدم.

وسيبدو إخراج العملية برمّتها كما يلي، بما في ذلك الرسالة التي تشير إلى نجاح الإدخال:

> db.createUser(
...  {
...    user: "sammy",
...    pwd: "your_password",
...    roles: [ { role: "userAdminAnyDatabase", db: "admin" } ]
...  }
...)
Successfully added user: {
        "user" : "sammy",
        "roles" : [
                {
                        "role" : "userAdminAnyDatabase",
                        "db" : "admin"
                }
        ]
}

يمكنك بعد إنشاء المستخدم وكلمة المرور، الخروج من الصّدفة الخاصة ب Mongo:

exit

وتستطيع كذلك الآن، بعد أن أنشأت مستخدم قاعدة البيانات الخاصة بك، الانتقال إلى استنساخ شيفرة المشروع البدئية وإضافة مكتبة Mongoose، والتي سوف تسمح لك بتنفيذ المخططات والنماذج للمجموعات (collections) في قواعد بياناتك.

الخطوة الثانية: إضافة معلومات Mongoose وقاعدة البيانات إلى المشروع

ستكون خطوتنا التالية هي استنساخ شيفرة التطبيق البدئية وإضافة معلومات Mongoose وقاعدة بيانات MongoDB إلى المشروع.

في المجلّد الرئيسي للمستخدم غير الجذري، استنسخ مستودع nodejs-image-demo من حساب DigitalOcean Community GitHub. ويتضمن مستودع التخزين هذا الشيفرة الخاصة بالإعداد الموضح في كيفية إنشاء تطبيق Node.js باستخدام Docker.

انسخ المستودع في مجلّد يسمى node_project:

git clone https://github.com/do-community/nodejs-image-demo.git node_project

انتقل إلى المجلّد node_project:

cd  node_project

قبل تعديل شيفرة المشروع، دعنا نلقي نظرة على بنية المشروع باستخدام الأمر tree . ملحوظة: يعدّ الأمر tree مفيدًا لعرض بنيات الملفات والمجلّدات من سطر الأوامر. يمكنك تثبيته باستخدام الأمر التالي:

sudo apt install tree

ومن أجل استخدامه، انتقل بالأمر cd إلى مجلّد معين واكتب tree. يمكنك أيضًا أن تزوّد الأمر بكامل المسار إلى النقطة المحدّدة باستخدام أمر مثل:

tree /home/sammy/sammys-project

اكتب ما يلي لمعاينة المجلّد node_project:

tree

تبدو بنية المشروع الحالي كما يلي:

├── Dockerfile
├── README.md
├── app.js
├── package-lock.json
├── package.json
└── views
    ├── css
    │   └── styles.css
    ├── index.html
    └── sharks.html

سنضيف كلما تقدمنا في هذا البرنامج التعليمي بعض المجلّدات إلى هذا المشروع، وسيكون الأمر tree مفيدًا لمساعدتنا في تتبع تقدمنا.

بعد ذلك، أضف حزمة npm mongoose إلى المشروع باستخدام الأمر npm install:

npm install mongoose

سينشئ هذا الأمر مجلّد node_modules في مجلّد مشروعك، باستخدام الاعتماديات المدرجة في ملف package.json الخاص بالمشروع، وسيضيف كذلك mongoose إلى هذا المجلّد. سيضيف mongoose أيضًا إلى الاعتماديات المدرجة في ملفك package.json. للحصول على توضيح أكثر تفصيلا عن package.json، يرجى الاطلاع على الخطوة الأولى في كيفية إنشاء تطبيق Node.js باستخدام Docker.

قبل إنشاء أي مخططات أو نماذج لMongoose، سنضيف معلومات اتصال قاعدة البيانات حتى يتمكن التطبيق من الاتصال بقاعدة البيانات الخاصة بنا.

من أجل فصل العناصر المتعلّقة بالتطبيق قدر الإمكان، أنشئ ملفًا منفصلًا لمعلومات اتصال قاعدة البيانات باسم db.js. ويمكنك فتح هذا الملف باستخدام nano أو المحرر المفضل لديك:

nano db.js

ستستورد أولاً وحدة mongoose باستخدام الدالّة require:

  • الملف ‎~/node_project/db.js:
const mongoose = require('mongoose');

سوف يتيح لك ذلك الوصول إلى توابع Mongoose المدمجة، والتي ستستخدمها لإنشاء اتصال بقاعدة البيانات الخاصة بك.

بعد ذلك، أضف الثوابت التالية لتحديد معلومات الاتصال URI الخاصة بMongo. رغم أن اسم المستخدم وكلمة المرور اختياريان، ولكننا سندرجهما حتى نتمكن من طلب مصادقة الهوية على قاعدة البيانات. تأكد من استبدال اسم المستخدم وكلمة المرور بمعلوماتك الخاصة، وتبقى لك الحرية في طلب شيء آخر من قاعدة البيانات غير sharkinfo إذا كنت تفضل ذلك:

  • الملف ‎~/node_project/db.js:
const mongoose = require('mongoose');

const MONGO_USERNAME = 'sammy';
const MONGO_PASSWORD = 'your_password';
const MONGO_HOSTNAME = '127.0.0.1';
const MONGO_PORT = '27017';
const MONGO_DB = 'sharkinfo';

نظرًا لأننا نشغّل قاعدة البيانات محليًا، فقد استخدمنا 127.0.0.1 اسمًا للمضيف. قد يكون الأمر مغايرًا في سياقات تطوير أخرى: على سبيل المثال، إذا كنت تستخدم خادم قاعدة بيانات منفصل أو تعمل باستخدام عقد متعدّدة في إطار سير عمل يتضمن حاويات.

ختامًا، حدّد قيمة ثابتة لـ URI وأنشئ الاتصال باستخدام التابع ()mongoose.connect: الملف ‎~/node_project/db.js:

...
const url = `mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@${MONGO_HOSTNAME}:${MONGO_PORT}/${MONGO_DB}?authSource=admin`;

mongoose.connect(url, {useNewUrlParser: true});

لاحظ أننا في URI حدّدنا authSource لمستخدمنا كمشرف لقاعدة البيانات. ويعدّ هذا الأمر ضروريًا لأننا حددنا اسم مستخدم في سلسلة الاتصال connection string. ويحدّد استخدام الراية useNewUrlParser مع التابع ()mongoose.connect أننا نريد استخدام مفسّر URL الجديد الخاص بـ Mongo.

احفظ الملف وأغلقه عند الانتهاء من التحرير.

كخطوة أخيرة، أضف معلومات اتصال قاعدة البيانات إلى ملف app.js بحيث يمكن للتطبيق استخدامها. ثم افتح التطبيق.

nano app.js

ستبدو الأسطر الأولى من الملف كما يلي:

  • الملف ‎~/node_project/app.js:
const express = require('express');
const app = express();
const router = express.Router();

const path = __dirname + '/views/';
...

أسفل السّطر الذي يحدّد القيمة الثابتة للكائن router، الموجود قريبًا من أعلى الملف، أضف السطر التالي:

  • الملف ‎~/node_project/app.js:
...
const router = express.Router();
const db = require('./db');

const path = __dirname + '/views/';
...

يطلب هذا الأمر من التطبيق استخدام معلومات اتصال قاعدة البيانات المحددة في db.js.

احفظ الملف وأغلقه عند الانتهاء من التحرير.

بوجود معلومات قاعدة البيانات وبعد إضافتها إلى مشروعك، تكون مستعدًا لإنشاء المخططات والنماذج التي ستشكل البيانات في المجموعة sharks.

الخطوة الثالثة: إنشاء مخططات ونماذج Mongoose

ستكون خطوتنا التالية هي التفكير في بنية المجموعة sharks التي سينشئها المستخدمون في قاعدة بيانات sharkinfo بواسطة مدخلاتهم. فما هي البنية التي نريد أن تكون لهذه المستندات التي أُنشئت؟ تتضمن صفحة معلومات سمك القرش في تطبيقنا الحالي بعض التفاصيل حول أسماك القرش المختلفة وسلوكياتها:

shark.png

وفقًا لذلك، سيكون بإمكان المستخدمين أن يضيفوا أسماك قرش جديدة مرفقة بتفاصيل حول سلوكها العام. وسيحدّد هذا الهدف كيفية إنشاء مخططنا.

من أجل تمييز مخططاتك ونماذجك عن الأجزاء الأخرى لتطبيقك، أنشئ مجلّدا models في مجلّد المشروع الحالي:

mkdir models

بعد ذلك، افتح ملفًا باسم sharks.js لإنشاء مخططك ونموذجك:

nano models/sharks.js

استورد وحدة Mongose في الجزء العلوي من الملف:

  • الملف ‎~/node_project/models/sharks.js:
const mongoose = require('mongoose');

بعد ذلك، حدّد كائنًا Schema لاستخدامه أساسًا للمخطط الخاصّ بأسماك القرش:

  • الملف ‎~/node_project/models/sharks.js:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;

يمكنك الآن تحديد الحقول التي تريد تضمينها في مخطّطك. ونظرًا لأننا نريد إنشاء مجموعة تحتوي على أسماك قرش فردية ومعلومات حول سلوكياتها، فلنُضمِّن فيها مفتاحين واحدٌ للاسم name والآخر للسلوك character. أضف مخطط Shark التالي أسفل تعريفات الثوابت:

  • الملف ‎~/node_project/models/sharks.js:
...
const Shark = new Schema ({
        name: { type: String, required: true },
        character: { type: String, required: true },
});

يتضمن هذا التعريف معلومات حول نوع الإدخال الذي نتوقعه من المستخدمين، وهو في هذه الحالة، سلسلة نصّية، وهل هذا الإدخال ضروري أم لا.

أخيرًا ، أنشئ نموذج Shark باستخدام دالة Mongoose التالية model()‎. سيتيح لك هذا النموذج الاستعلام عن المستندات من خلال مجموعتك والتحقق من صحة المستندات الجديدة. أضف السطر التالي في أسفل الملف:

...
module.exports = mongoose.model('Shark', Shark)

يجعل هذا السطر الأخير النموذج Shark متاحًا كوحدة نمطية باستخدام خاصية module.exports. إذ تحدّد هذه الخاصية القيم التي ستصدرها الوحدة النمطية، مما يجعلها متاحة للاستخدام في أي مكان آخر في التطبيق.

سيبدو الملف النهائي models/sharks.js على هذا النحو:

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const Shark = new Schema ({
        name: { type: String, required: true },
        character: { type: String, required: true },
});

module.exports = mongoose.model('Shark', Shark)

احفظ الملف وأغلقه عند الانتهاء من التحرير.

بإنشائك لمخطط Shark ونموذجه، سيكون بإمكانك البدء في العمل على المنطق الذي تريده أن يحكم كيفية تعامل تطبيقك مع مدخلات المستخدم.

الخطوة الرابعة: إنشاء وحدات التحكم

ستكون خطوتنا التالية هي إنشاء وحدة التحكم التي تحدد كيفية حفظ مدخلات المستخدم في قاعدة البيانات وإعادتها إليه.

أنشئ أولاً مجلّدًا لوحدة التحكم:

mkdir controllers

بعد ذلك، افتح ملفًا في هذا المجلد باسم sharks.js:

nano controllers/sharks.js

في الجزء العلوي من الملف، سنستورد الوحدة مع النموذج Shark لكي نستطيع استخدامها في منطق وحدة التحكم لدينا. سنستورد أيضًا وحدة المسار path للوصول إلى الأدوات المساعدة التي ستتيح لنا تعيين المسار إلى الاستمارة التي سيدخل فيها المستخدمون معلوماتهم عن أسماك القرش.

أضف الدوالّ require التالية إلى بداية الملف:

  • الملف ‎~/node_project/controllers/sharks.js:
const path = require('path');
const Shark = require('../models/sharks');

بعد ذلك، سنكتب سلسلة من الدوال التي سنصدّرها مع وحدة التحكم باستخدام اختصارات Node للتصدير exports. ستشمل هذه الدوالّ المهام الثلاثة المتعلقة ببيانات سمك القرش الخاصة بمستخدمنا:

  • إرسال إستمارة إدخال سمك القرش للمستخدمين.
  • إنشاء إدخالٍ لقرش جديد.
  • إعادة عرض أسماك القرش للمستخدمين.

من أجل البدء، أنشئ دالةً index لعرض صفحة أسماك القرش مع إستمارة الإدخال. أضف هذه الدّالة أسفل الاستيرادات:

...
exports.index = function (req, res) {
    res.sendFile(path.resolve('views/sharks.html'));
};

بعد ذلك أضف، أسفل الدالة index، دالّة بالاسم create لإنشاء إدخال جديد لسمك القرش في المجموعة Sharks:

...
exports.create = function (req, res) {
    var newShark = new Shark(req.body);
    console.log(req.body);
    newShark.save(function (err) {
            if(err) {
            res.status(400).send('Unable to save shark to database');
        } else {
            res.redirect('/sharks/getshark');
        }
  });
               };

سوف تستدعى هذه الدّالة عندما يرسل المستخدم بيانات سمك القرش إلى الإستمارة على الصفحة sharks.html. سننشئ لاحقًا المسار route الخاصّ بهذا الإرسال POST في هذا البرنامج التعليمي عندما ننشئ مسارات التطبيق. باستخدام المحتوى body الخاصّ بالطلب POST، ستنشئ الدالة create كائن ملفّ سمك القرش الجديد، الذي يأخذ هنا الاسم newShark، اعتمادًا على النموذج Shark الذي استوردناه. ولقد أضفنا التابع console.log من أجل إظهار الإدخال الخاصّ بسمك القرش إلى الطرفية من أجل التحقق من أن طريقة POST تعمل على النحو المنشود، ولكن تبقى لك الحرّية في تجاوز هذا الأمر إذا كنت تفضل ذلك.

باستخدام الكائنnewShark ، ستستدعي الدالة create بعد ذلك تابع Mongoose المسمّى model.save()‎ من أجل إنشاء ملفّ سمك قرش جديد اعتمادًا على المفاتيح التي حدّدتها في النموذج Shark.

تتبع دالّة ردّ النداء هذه نموذج ردّ النداء المعياري في Node وهو: callback(error, results)‎. في حالة وجود خطأ، نرسل رسالة تُبلغ المستخدمين عن الخطأ، وفي حالة نجاح العمليّة، نستخدم التابع res.redirect()‎ لتوجيه المستخدمين إلى نقطة النهاية التي ستعيد معلومات القرش إليهم على المتصفح.

في الختام، ستعرض الدّالة list محتويات المجموعة مرة أخرى للمستخدم. أضف الشيفرة التالية أسفل الدالة create:

...
exports.list = function (req, res) {
        Shark.find({}).exec(function (err, sharks) {
                if (err) {
                        return res.send(500, err);
                }
                res.render('getshark', {
                        sharks: sharks
             });
        });
};

تستخدم هذه الدالّة النموذج Shark بالاعتماد على التابع ()model.find الخاصّ بMongoose من أجل إرجاع معلومات أسماك القرش التي أُدخِلت في المجموعة sharks. يتمّ هذا الأمر عبر إرجاع كائن الاستعلام query object، ويمثل في هذه الحالة جميع الإدخالات في المجموعة sharks، باستخدام الدالّة ()exec الخاصّة ب Mongoose. في حالة وجود خطأ، سترسل دالّة رد النّداء خطأً 500.

سوف يُعرض كائن الاستعلام الذي أرجعته الدّالة مع المجموعة sharks في الصفحة getshark التي سننشئها في الخطوة التالية باستخدام لغة القوالب EJS.

سيبدو الملف النهائي ‎~/node_project/controllers/sharks.js على هذا النحو:

const path = require('path');
const Shark = require('../models/sharks');

exports.index = function (req, res) {
    res.sendFile(path.resolve('views/sharks.html'));
};

exports.create = function (req, res) {
    var newShark = new Shark(req.body);
    console.log(req.body);
    newShark.save(function (err) {
            if(err) {
            res.status(400).send('Unable to save shark to database');
        } else {
            res.redirect('/sharks/getshark');
        }
  });
               };

exports.list = function (req, res) {
        Shark.find({}).exec(function (err, sharks) {
                if (err) {
                        return res.send(500, err);
                }
                res.render('getshark', {
                        sharks: sharks
             });
        });
};

ضع في اعتبارك أنه رغم أننا لا نستخدم الدّوال السهمية هنا، فقد ترغب في تضمينها أثناء تكرارك لهذه الشيفرة في عمليتك التطويرية.

احفظ الملف وأغلقه عند الانتهاء من التحرير.

قبل الانتقال إلى الخطوة التالية، يمكنك تنفيذ الأمر tree مرة أخرى من المجلّد nodeproject لعرض بنية المشروع في هذه المرحلة. هذه المرة، ومن أجل الإيجاز، سنطلب من tree استبعاد المجلّد nodemodules عبر الراية ‎-I:

tree -I node_modules

مع الإضافات التي أجريتها، ستبدو بنية مشروعك على النحو التالي:

├── Dockerfile
├── README.md
├── app.js
├── controllers
│   └── sharks.js
├── db.js
├── models
│   └── sharks.js
├── package-lock.json
├── package.json
└── views
    ├── css
    │   └── styles.css
    ├── index.html
    └── sharks.html

الآن بعد أن أصبحت لديك وحدة تحكم لتوجيه كيفية حفظ مدخلات المستخدم وإعادتها إليه، يمكنك الانتقال إلى إنشاء طرق العرض التي ستبلور منطق وحدة التحكم الخاصة بك.

الخطوة الخامسة: استخدام برمجيات EJS و Express الوسيطة لجمع البيانات وعرضها

من أجل تمكين التطبيق من العمل ببيانات المستخدم، سنقوم بأمرين مهمّين: سنضمّن أولاً دالة وسيطة Express تسمى ()urlencoded، والتي ستمكن تطبيقنا من تحليل بيانات المستخدم المُدخلَة. ثم سنضيف ثانيا علامات القوالب (template tags) إلى واجهة العرض views لتمكين التفاعل الديناميكي مع بيانات المستخدم في الشيفرة.

من أجل استخدام الدالة ()urlencoded الخاصة بـ Express، افتح أولاً الملف app.js:

nano app.js

أضف السطر التالي فوق الدالة ()express.static في الملف ‎~/node_project/app.js:

...
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path));
...

ستمكّن إضافة هذه الدّالة الوصول إلى بيانات POST المحلّلة (parsed) في استمارة معلومات سمك القرش. نحدّد القيمة true في الخيار extended لإتاحة قدر أكبر من المرونة في نوع البيانات التي سيحللها التطبيق (بما في ذلك الكائنات مثل الكائنات المتداخلة). يرجى الاطلاع على توثيق الدّالة لمزيد من المعلومات حول الخيارات.

احفظ الملف وأغلقه عند الانتهاء من التحرير.

بعد ذلك، سنضيف إمكانية استخدام القالب إلى واجهات العرض. ثبّت أولاً حزمة ejs باستخدام instaill npm:

npm install ejs

بعد ذلك ، افتح ملف sharks.html في المجلد views:

nano views/sharks.html

لقد ألقينا نظرة من قبل في الخطوة الثالثة على هذه الصفحة لتحديد كيفية كتابة المخطط والنموذج Mongoose:

sharko.png

الآن ، بدلاً من وجود تنسيق بعمودين، سنضيف عمودًا ثالثًا يحتوي على استمارة يمكن للمستخدمين إدخال معلومات حول أسماك القرش فيها.

كخطوة أولى، عدّل أبعاد الأعمدة الموجودة إلى 4 لإنشاء ثلاثة أعمدة متساوية الحجم. لاحظ أنك ستحتاج إلى إجراء هذا التعديل على السطرين اللذين يقرآن حاليًا <div class="col-lg-6"‎>. سيصبح كلاهما <div class="col-lg-4"‎>: الملف ‎~/node_project/views/sharks.html:

...
<div class="container">
    <div class="row">
        <div class="col-lg-4">
            <p>
                <div class="caption">Some sharks are known to be dangerous to humans, though many more are not. The sawshark, for example, is not considered a threat to humans.
                </div>
                <img src="https://assets.digitalocean.com/articles/docker_node_image/sawshark.jpg" alt="Sawshark">
            </p>
        </div>
        <div class="col-lg-4">
            <p>
                <div class="caption">Other sharks are known to be friendly and welcoming!</div>
                <img src="https://assets.digitalocean.com/articles/docker_node_image/sammy.png" alt="Sammy the Shark">
            </p>
        </div>
    </div>
  </div>

 </html>

للاطلاع على توضيح أكثر لنظام الشبكة في Bootstrap، بما في ذلك تخطيطات الصفوف والأعمدة، يرجى الاطلاع على هذه المقدمة إلى إطار Bootstrap.

بعد ذلك، أضف عمودًا آخر يتضمن نقطة النهاية المحددة للطلب POST مع بيانات المستخدم عن سمك القرش ووسوم القالب EJS التي ستلتقط تلك البيانات. سيكون هذا العمود أسفل وسمي الإغلاق <‎/p> و <‎/div> للعمود السابق وفوق وسوم الإغلاق للصف والحاوية والصفحة HTML. وتوجد وسوم الإغلاق هذه بالفعل في الشّيفرة. اتركها في مكانها وأنت تضيف الشيفرة التالية لإنشاء العمود الجديد:

...
       </p> <!-- closing p from previous column -->
   </div> <!-- closing div from previous column -->
<div class="col-lg-4">
            <p>
                <form action="/sharks/addshark" method="post">
                    <div class="caption">Enter Your Shark</div>
                    <input type="text" placeholder="Shark Name" name="name" <%=sharks[i].name; %>
                    <input type="text" placeholder="Shark Character" name="character" <%=sharks[i].character; %>
                    <button type="submit">Submit</button>
                </form>
            </p>
        </div> 
    </div> <!-- closing div for row -->
</div> <!-- closing div for container -->

</html> <!-- closing html tag -->

تضيف هنا في الوسم form نقطة نهاية "‎/sharks/addhark" لبيانات سمك القرش الخاصة بالمستخدم وتحدّد الطريقة POST لإرسالها. كما تحدّد في حقول الإدخال، الحقلين "Shark Name" و"Shark Character" ، وفقًا للنموذج Shark الذي حدّدته سابقًا.

لإضافة مُدخلات المستخدم إلى المجموعة sharks، فأنت تستخدم وسوم قالب:EJS (<%=, %>) بناءً على قواعد لغة جافاسكربت لتوجيه مدخلات المستخدم إلى الحقول المناسبة في المستند الذي أنشئ حديثًا. لمزيد من المعلومات حول كائنات جافاسكربت، يرجى مراجعة توثيق جافاسكربت في الموسوعة. ولمعرفة المزيد عن وسوم قالبEJS ، يرجى الاطلاع على التوثيق الرسمي لEJS.

ستبدو الحاوية بأكملها مع جميع الأعمدة الثلاثة، بما في ذلك العمود الذي يحتوي على استمارة إدخال سمك القرش، على هذا النحو عند الانتهاء: الملف ‎~/node_project/views/sharks.html:

...
<div class="container">
    <div class="row">
        <div class="col-lg-4">
            <p>
                <div class="caption">Some sharks are known to be dangerous to humans, though many more are not. The sawshark, for example, is not considered a threat to humans.
                </div>
                <img src="https://assets.digitalocean.com/articles/docker_node_image/sawshark.jpg" alt="Sawshark">
            </p>
        </div>
        <div class="col-lg-4">
            <p>
                <div class="caption">Other sharks are known to be friendly and welcoming!</div>
                <img src="https://assets.digitalocean.com/articles/docker_node_image/sammy.png" alt="Sammy the Shark">
            </p>
        </div>
    <div class="col-lg-4">
            <p>
                <form action="/sharks/addshark" method="post">
                    <div class="caption">Enter Your Shark</div>
                    <input type="text" placeholder="Shark Name" name="name" <%=sharks[i].name; %>
                    <input type="text" placeholder="Shark Character" name="character" <%=sharks[i].character; %>
                    <button type="submit">Submit</button>
                </form>
            </p>
        </div>
    </div>
  </div>

</html>

احفظ الملف وأغلقه عند الانتهاء من التحرير.

الآن بعد أن أصبح لديك طريقة لجمع مدخلات المستخدم، يمكنك إنشاء نقطة نهاية (endpoint) لعرض أسماك القرش المُعادة وكذلك معلومات السلوك المرتبطة بها.

انسخ ملف sharks.html المعدّل حديثًا إلى ملف يسمى getshark.html:

cp views/sharks.html views/getshark.html

افتح الملف getshark.html:

nano views/getshark.html

داخل الملف، سنعدّل العمود الذي استخدمناه لإنشاء استمارة إدخال أسماك القرش من خلال استبداله بعمود سيعرض أسماك القرش في المجموعة sharks. مرة أخرى، ستمتدّ الشفرة بين وسمي <p/> و <‎/div> للعمود السابق ووسوم الإغلاق للصف والحاوية والصفحة HTML. احرص على ترك هذه الوسوم في مكانها وأنت تضيف الشيفرة التالية لإنشاء العمود:

...
       </p> <!-- closing p from previous column -->
   </div> <!-- closing div from previous column -->
<div class="col-lg-4">
           <p>
              <div class="caption">Your Sharks</div>
                  <ul>
                     <% sharks.forEach(function(shark) { %>
                        <p>Name: <%= shark.name %></p>
                        <p>Character: <%= shark.character %></p>
                     <% }); %>
                  </ul>
            </p>
        </div>
    </div> <!-- closing div for row -->
</div> <!-- closing div for container -->

</html> <!-- closing html tag -->

هنا تستخدم وسوم قالب EJS والدالّة forEach()‎ لإخراج كل قيمة في المجموعة sharks ، بما في ذلك معلومات حول أحدث أسماك القرش المضافة.

ستبدو الحاوية بأكملها التي تحتوي على جميع الأعمدة الثلاثة، بما في ذلك العمود الذي يتضمن المجموعة sharks، على هذا النحو عند الانتهاء:

...
<div class="container">
    <div class="row">
        <div class="col-lg-4">
            <p>
                <div class="caption">Some sharks are known to be dangerous to humans, though many more are not. The sawshark, for example, is not considered a threat to humans.
                </div>
                <img src="https://assets.digitalocean.com/articles/docker_node_image/sawshark.jpg" alt="Sawshark">
            </p>
        </div>
        <div class="col-lg-4">
            <p>
                <div class="caption">Other sharks are known to be friendly and welcoming!</div>
                <img src="https://assets.digitalocean.com/articles/docker_node_image/sammy.png" alt="Sammy the Shark">
            </p>
        </div>
    <div class="col-lg-4">
            <p>
              <div class="caption">Your Sharks</div>
                  <ul>
                     <% sharks.forEach(function(shark) { %>
                        <p>Name: <%= shark.name %></p>
                        <p>Character: <%= shark.character %></p>
                     <% }); %>
                  </ul>
            </p>
        </div>
    </div>
  </div>

</html>

احفظ الملف وأغلقه عند الانتهاء من التحرير.

لكي يستخدم التطبيق القوالب التي أنشأتها، ستحتاج إلى إضافة بضعة أسطر إلى ملفّك app.js. لذا افتحه مرة أخرى:

nano app.js

فوق المكان الذي أضفت فيه الدالة express.urlencoded()‎، أضف الآن الأسطر التالية:

...
app.engine('html', require('ejs').renderFile);
app.set('view engine', 'html');
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path));

...

تطلب الدّالة app.engine من التطبيق توجيه محرك قوالب EJS إلى ملفات HTML، بينما تحدّد app.set محرك العرض الافتراضي.

يجب أن يبدو ملف app.js الآن على النحو التالي:

  • الملف ‎~/node_project/app.js:
const express = require('express');
const app = express();
const router = express.Router();
const db = require('./db');

const path = __dirname + '/views/';
const port = 8080;

router.use(function (req,res,next) {
  console.log('/' + req.method);
  next();
});

router.get('/',function(req,res){
  res.sendFile(path + 'index.html');
});

router.get('/sharks',function(req,res){
  res.sendFile(path + 'sharks.html');
});

app.engine('html', require('ejs').renderFile);
app.set('view engine', 'html');
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path));
app.use('/', router);

app.listen(port, function () {
  console.log('Example app listening on port 8080!')
})

الآن وبعد أن أنشأت واجهات عرض يمكن أن تعمل ديناميكيًا مع بيانات المستخدم، فقد حان الوقت لإنشاء المسارات لمشروعك من أجل الجمع بين واجهات العرض ومنطق وحدة التحكم الذي تعمل به.

الخطوة السادسة: إنشاء المسارات (routes)

الخطوة الأخيرة في الجمع بين مكونات التطبيق هي إنشاء المسارات. سنحاول فصل المسارات حسب وظيفتها، بما في ذلك المسار إلى صفحة الهبوط لتطبيقنا ومسار آخر إلى صفحة أسماك القرش. سيكون المسار sharks هو المكان الذي ندمج فيه منطق وحدة التحكم مع واجهات العرض التي أنشأناها في الخطوة السابقة.

أنشئ في البداية مجلّدًا باسم routes:

mkdir routes

بعد ذلك، أنشئ ملفًا يسمى index.js في هذا المجلّد وافتحه:

nano routes/index.js

سيستورد هذا الملف في البداية كائنات express وrouter وpath، مما يتيح لنا تحديد المسارات التي نريد تصديرها باستخدام الكائن router، وهذا يتيح بدوره العمل ديناميكيًا بمسارات الملفات (file paths). أضف الشيفرة التالية في أعلى الملف:

  • الملف ‎~/node_project/routes/index.js:
const express = require('express');
const router = express.Router();
const path = require('path');

بعد ذلك، أضف الدّالة router.use التالية، والتي تُحمّل الدالّة الوسيطة التي ستسجل طلبات الموجه وتمررها إلى مسار التطبيق:

...

router.use (function (req,res,next) {
  console.log('/' + req.method);
  next();
});

ستُوجَّه الطلبات الخاصّة بجذر التطبيق إلى هنا في البداية، ومن ثم سيُوجّه المستخدمون إلى صفحة الهبوط لتطبيقنا، وهو المسار الذي سنحدّده بعد ذلك. أضف الشيفرة التالية أسفل الدّالة router.use لتحديد المسار نحو صفحة الهبوط:

...

router.get('/',function(req,res){
  res.sendFile(path.resolve('views/index.html'));
});

إن أول ما نريد إرساله إليه المستخدم عندما يزور التطبيق هو صفحة الهبوط index.html الموجودة في المجلّد views.

ختامًا، لجعل هذه المسارات قابلة للوصول كوحدات يمكن استيرادها في مكان آخر من التطبيق، أضف عبارة ختامية إلى نهاية الملف لتصدير الكائن router:

...

module.exports = router;

سوف يبدو الملف النهائي على هذا النحو:

const express = require('express');
const router = express.Router();
const path = require('path');

router.use (function (req,res,next) {
  console.log('/' + req.method);
  next();
});

router.get('/',function(req,res){
  res.sendFile(path.resolve('views/index.html'));
});

module.exports = router;

احفظ هذا الملف وأغلقه عندما تنتهي من التحرير.

بعد ذلك، افتح ملفًا باسم sharks.js لتحديد كيفية استخدام التطبيق لنقاط النهاية وواجهات العرض المختلفة التي أنشأناها للتعامل مع مدخلات المستخدم لأسماك القرش:

nano routes/sharks.js

في الجزء العلوي من الملف، استورد الكائنات epress وrouter:

const express = require('express');
const router = express.Router();

بعد ذلك، استورد وحدة نمطية تسمى shark لتتيح لك العمل بالدّوال المصدرة التي حددتها في وحدة التحكم:

const express = require('express');
const router = express.Router();
const shark = require('../controllers/sharks');

يمكنك الآن إنشاء مسارات باستخدام الدّوال index وcreate وlist التي حدّدتها في ملف وحدة التحكم sharks. سيُربط كل مسار بطريقة HTTP المناسبة والتي تكون إما GET في حالة عرض صفحة الهبوط لمعلومات أسماك القرش الرئيسية وإعادة قائمة أسماك القرش إلى المستخدم، وإما POST في حالة إنشاء إدخال جديد لسمك قرش:

...

router.get('/', function(req, res){
    shark.index(req,res);
});

router.post('/addshark', function(req, res) {
    shark.create(req,res);
});

router.get('/getshark', function(req, res) {
    shark.list(req,res);
});

يستخدم كل مسار الدّالة المرتبطة به في controllers/sharks.js، لأننا جعلنا هذه الوحدة قابلة للوصول عن طريق استيرادها في الجزء العلوي من هذا الملف.

ختامًا، أغلق الملف بربط هذه المسارات بالكائن routes وتصديرها:

...

module.exports = router;

سيبدو الملف النهائي على هذا النحو:

const express = require('express');
const router = express.Router();
const shark = require('../controllers/sharks');

router.get('/', function(req, res){
    shark.index(req,res);
});

router.post('/addshark', function(req, res) {
    shark.create(req,res);
});

router.get('/getshark', function(req, res) {
    shark.list(req,res);
});

module.exports = router;

احفظ الملف وأغلقه عند الانتهاء من التحرير.

ستكون الخطوة الأخيرة في جعل هذه المسارات متاحةً للتطبيق هي إضافتها إلى app.js. افتح هذا الملف مرة أخرى:

nano app.js

أسفل الثابتة db، أضف الاستيراد التالي لمساراتك:

...
const db = require('./db');
const sharks = require('./routes/sharks');

بعد ذلك، عوّض الدّالة app.use التي تُركّب الكائن router بالسطر التالي الذي سيركّب وحدة التوجيه sharks:

...
app.use(express.static(path));
app.use('/sharks', sharks);

app.listen(port, function () {
        console.log("Example app listening on port 8080!")
})

تستطيع الآن حذف المسارات التي حدّدتها مسبقًا في هذا الملف، نظرًا لأنك تستورد مسارات تطبيقك باستخدام وحدة التوجيه sharks.

ستبدو النسخة النهائية للملف app.js على النحو التالي:

const express = require('express');
const app = express();
const router = express.Router();
const db = require('./db');
const sharks = require('./routes/sharks');

const path = __dirname + '/views/';
const port = 8080;

app.engine('html', require('ejs').renderFile);
app.set('view engine', 'html');
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path));
app.use('/sharks', sharks);

app.listen(port, function () {
  console.log('Example app listening on port 8080!')
})

احفظ الملف وأغلقه عند الانتهاء من التحرير.

يمكنك الآن إعادة تنفيذ الأمر tree مرة أخرى لرؤية البنية النهائية لمشروعك:

tree -I node_modules

ستبدو بنية مشروعك الآن على هذا النحو:

├── Dockerfile
├── README.md
├── app.js
├── controllers
│   └── sharks.js
├── db.js
├── models
│   └── sharks.js
├── package-lock.json
├── package.json
├── routes
│   ├── index.js
│   └── sharks.js
└── views
    ├── css
    │   └── styles.css
    ├── getshark.html
    ├── index.html
    └── sharks.html

بإنشائك لجميع مكونات تطبيقك ووضعها في المكان المناسب، فأنت الآن مستعدّ لإضافة سمك قرش للاختبار إلى قاعدة بياناتك!

إذا تتبعت درس إعداد الخادم الأولي في المتطلبات الأساسية، فستحتاج إلى تعديل جدار الحماية الخاص بك، لأنه لا يسمح حاليًا سوى بحركة المرور SSH. لذا نفّذ هذا الأمر من أجل السماح بحركة المرور إلى منفذ 8080:

sudo ufw allow 8080

شغّل التطبيق:

node app.js

بعد ذلك، انتقل بمتصفّحك إلى http://yourserverip:8080. ستظهر لك صفحة الهبوط التالية:

landing_pages.png

انقر على زر الحصول على معلومات القرش. ستظهر لك صفحة المعلومات التالية، مع استمارة إدخال سمك القرش المضافة:

shark_form.png

أضف في الاستمارة، سمكة قرش من اختيارك. لأغراض هذا العرض التوضيحي، سنضيف Megalodon Shark إلى حقل اسم القرش، وAncient إلى حقل سلوك القرش:

shark_filled.png

انقر على زر الإرسال. ستظهر لك صفحة بها معلومات القرش معروضة لك:

shark_added.png

سترى أيضًا مخرجاتٍ في وحدة التحكم الخاصة بك تشير إلى أنّ سمك القرش أضيف إلى مجموعتك:

Example app listening on port 8080!
{ name: 'Megalodon Shark', character: 'Ancient' }

إذا كنت ترغب في إنشاء إدخال جديد لسمك القرش، فارجع إلى صفحة أسماك القرش وكرر عملية إضافة سمكة قرش.

لديك الآن تطبيق لمعلومات سمك القرش يتيح للمستخدمين بإضافة معلومات حول أسماك القرش المفضلة لديهم.

خاتمة

لقد أنشأت في هذا البرنامج التعليمي تطبيق Node من خلال دمج قاعدة بيانات MongoDB وإعادة كتابة منطق التطبيق باستخدام النظام الهيكلي MVC. ويمكن أن يكون هذا التطبيق بمثابة نقطة انطلاق جيدة لتطبيق CRUD مكتمل.

لمزيد من التفصيل حول النظام الهيكلي MVC في سياقات أخرى، يرجى الاطلاع على سلسلة مقالات تطوير Django.

لمزيد من المعلومات حول العمل على MongoDB، يرجى الاطلاع على سلسلة المقالات حول MongoDB.

ترجمة -وبتصرف- للمقال How To Integrate MongoDB with Your Node Application لصاحبته Kathleen Juell





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن