الجزء 3: البرمجة من جهة الخادم

التحقق من الصحة وESLint

عادةً ما تكون هناك قيود نريد تطبيقها على البيانات المخزّنة في قاعدة بيانات تطبيقنا. لا ينبغي أن يقبل تطبيقنا ملاحظات تمتلك خاصية content مفقودة أو فارغة. يُتحقق من صلاحية الملاحظة في معالج المسار:

app.post('/api/notes', (request, response) => {
  const body = request.body
  // highlight-start
  if (!body.content) {
    return response.status(400).json({ error: 'content missing' })
  }
  // highlight-end

  // ...
})

إذا لم تمتلك الملاحظة خاصية content، نستجيب للطلب برمز الحالة 400 bad request.

من الطرق الأذكى للتحقق من صيغة البيانات قبل تخزينها في قاعدة البيانات استخدام وظيفة التحقق المتاحة في Mongoose.

يمكننا تعريف قواعد تحقق محددة لكل حقل في المخطط:

const noteSchema = new mongoose.Schema({
  // highlight-start
  content: {
    type: String,
    minLength: 5,
    required: true
  },
  // highlight-end
  important: Boolean
})

أصبح حقل content الآن مطلوباً أن يكون طوله خمسة أحرف على الأقل، وقد ضُبط كحقل مطلوب، أي أنه لا يمكن أن يكون مفقوداً. لم نضف أي قيود على حقل important، لذا لم يتغير تعريفه في المخطط.

المتحققان minLength وrequired مدمجان وتوفرهما Mongoose. تتيح لنا وظيفة المتحقق المخصص في Mongoose إنشاء متحققين جدد إذا لم يغطِّ أي من المتحققين المدمجين احتياجاتنا.

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

app.post('/api/notes', (request, response, next) => {
  const body = request.body

  const note = new Note({
    content: body.content,
    important: body.important || false,
  })

  note.save()
    .then(savedNote => {
      response.json(savedNote)
    })
    .catch(error => next(error))
})

لنوسّع معالج الأخطاء ليتعامل مع أخطاء التحقق هذه:

const errorHandler = (error, request, response, next) => {
  console.error(error.message)

  if (error.name === 'CastError') {
    return response.status(400).send({ error: 'malformatted id' })
  } else if (error.name === 'ValidationError') {
    return response.status(400).json({ error: error.message })
  }

  next(error)
}

عندما يفشل التحقق من كائن، نعيد رسالة الخطأ الافتراضية التالية من Mongoose:

postman يعرض رسالة خطأ

نشر الواجهة الخلفية لقاعدة البيانات إلى الإنتاج

ينبغي أن يعمل التطبيق كما هو تقريباً على Fly.io/Render. لا نحتاج إلى توليد بناء إنتاجي جديد للواجهة الأمامية لأن التغييرات حتى الآن كانت على واجهتنا الخلفية فقط.

لن تُستخدم متغيرات البيئة المعرّفة في dotenv إلا عندما لا تكون الواجهة الخلفية في وضع الإنتاج، أي Fly.io أو Render.

في الإنتاج، علينا ضبط رابط قاعدة البيانات في الخدمة التي تستضيف تطبيقنا.

في Fly.io يتم ذلك بالأمر fly secrets set:

fly secrets set MONGODB_URI='mongodb+srv://fullstack:thepasswordishere@cluster0.a5qfl.mongodb.net/noteApp?retryWrites=true&w=majority'

أثناء تطوير التطبيق، من المرجح جداً أن يفشل شيء ما. مثلاً، عندما نشرت تطبيقي لأول مرة مع قاعدة البيانات، لم تظهر أي ملاحظة على الإطلاق:

متصفح لا تظهر فيه أي ملاحظات

كشف تبويب الشبكة في وحدة تحكم المتصفح أن جلب الملاحظات لم ينجح، فقد بقي الطلب مدة طويلة في حالة pending حتى فشل برمز الحالة 502.

يجب أن تبقى وحدة تحكم المتصفح مفتوحة طوال الوقت!

من الضروري أيضاً متابعة سجلات الخادم باستمرار. أصبحت المشكلة واضحة عندما فُتحت السجلات بالأمر fly logs:

سجل خادم fly.io يظهر الاتصال بـ undefined

كان رابط قاعدة البيانات undefined، لذا نُسي الأمر fly secrets set MONGODB_URI.

ستحتاج أيضاً إلى إدراج عنوان IP لتطبيق fly.io في القائمة البيضاء لدى MongoDB Atlas. إذا لم تفعل ذلك سترفض MongoDB الاتصال.

للأسف، لا يوفر fly.io عنوان IPv4 مخصصاً لتطبيقك، لذا ستحتاج إلى السماح لجميع عناوين IP في MongoDB Atlas.

عند استخدام Render، يُعطى رابط قاعدة البيانات عبر تعريف متغير البيئة المناسب في لوحة التحكم:

لوحة تحكم Render تعرض متغير البيئة MONGODB_URI

تعرض لوحة تحكم Render سجلات الخادم:

لوحة تحكم Render مع سهم يشير إلى خادم يعمل على المنفذ 10000

يمكنك العثور على شيفرة تطبيقنا الحالي كاملةً في فرع part3-6 من مستودع GitHub هذا.

تمارين 3.19.-3.21.

3.19*: قاعدة بيانات دليل الهاتف، الخطوة 7

وسّع التحقق بحيث يجب أن يكون الاسم المخزّن في قاعدة البيانات طوله ثلاثة أحرف على الأقل.

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

personService
    .create({ ... })
    .then(createdPerson => {
      // ...
    })
    .catch(error => {
      // هذه هي طريقة الوصول إلى رسالة الخطأ
      console.log(error.response.data.error)
    })

يمكنك عرض رسالة الخطأ الافتراضية التي تعيدها Mongoose، حتى وإن لم تكن مقروءة كما ينبغي:

لقطة شاشة لدليل الهاتف تظهر فشل التحقق من شخص

ملاحظة: في عمليات التحديث، تكون أدوات التحقق في mongoose معطّلة افتراضياً. اقرأ الوثائق لمعرفة كيفية تفعيلها.

3.20*: قاعدة بيانات دليل الهاتف، الخطوة 8

أضف تحققاً إلى تطبيق دليل الهاتف لديك، يضمن أن أرقام الهاتف بالصيغة الصحيحة. يجب أن يكون رقم الهاتف:

  • طوله 8 أو أكثر
  • مكوّناً من جزأين يفصل بينهما شرطة -، الجزء الأول يحتوي على رقمين أو ثلاثة أرقام والجزء الثاني يتكون أيضاً من أرقام
    • مثلاً 09-1234556 و040-22334455 أرقام هاتف صحيحة
    • مثلاً 1234556 و1-22334455 و10-22-334455 غير صحيحة

استخدم متحققاً مخصصاً لتنفيذ الجزء الثاني من التحقق.

إذا حاول طلب HTTP POST إضافة شخص برقم هاتف غير صحيح، فينبغي أن يستجيب الخادم برمز حالة ورسالة خطأ مناسبتين.

3.21 نشر الواجهة الخلفية لقاعدة البيانات إلى الإنتاج

أنشئ نسخة "full stack" جديدة من التطبيق عبر إنشاء بناء إنتاجي جديد للواجهة الأمامية، ونسخه إلى مجلد الواجهة الخلفية. تحقق من أن كل شيء يعمل محلياً باستخدام التطبيق بالكامل من العنوان http://localhost:3001/.

ادفع أحدث نسخة إلى Fly.io/Render وتحقق من أن كل شيء يعمل هناك أيضاً.

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

Lint

قبل أن ننتقل إلى الجزء التالي، سنلقي نظرة على أداة مهمة تُسمى lint. تقول ويكيبيديا ما يلي عن lint:

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

في اللغات المُصرَّفة ذات الأنواع الساكنة مثل Java، يمكن لبيئات التطوير المتكاملة مثل NetBeans الإشارة إلى الأخطاء في الشيفرة، حتى تلك التي تتجاوز مجرد أخطاء التصريف. ويمكن استخدام أدوات إضافية لإجراء التحليل الساكن مثل checkstyle، لتوسيع قدرات بيئة التطوير بحيث تشير أيضاً إلى المشكلات المتعلقة بالأسلوب، مثل الإزاحة.

في عالم JavaScript، الأداة الرائدة حالياً للتحليل الساكن (المعروف أيضاً بـ "linting") هي ESLint.

لنضف ESLint كـ اعتمادية تطوير للواجهة الخلفية. اعتماديات التطوير هي أدوات لا تلزم إلا أثناء تطوير التطبيق. على سبيل المثال، الأدوات المتعلقة بالاختبار هي من هذه الاعتماديات. عندما يعمل التطبيق في وضع الإنتاج، لا تكون اعتماديات التطوير مطلوبة.

ثبّت ESLint كاعتمادية تطوير للواجهة الخلفية بالأمر:

npm install eslint @eslint/js --save-dev

سيتغير محتوى ملف package.json كما يلي:

{
  //...
  "dependencies": {
    "dotenv": "^16.4.7",
    "express": "^5.1.0",
    "mongoose": "^8.11.0"
  },
  "devDependencies": {
    "@eslint/js": "^9.22.0",
    "eslint": "^9.22.0"
  }
}

أضاف الأمر قسم devDependencies إلى الملف وضمّن الحزمتين eslint و@eslint/js، وثبّت المكتبات المطلوبة في مجلد node_modules.

بعد ذلك يمكننا تهيئة إعداد افتراضي لـ ESLint بالأمر:

npx eslint --init

سنجيب عن جميع الأسئلة:

مخرجات الطرفية من تهيئة ESLint

سيُحفظ الإعداد في الملف المُنشأ eslint.config.mjs.

تنسيق ملف الإعداد

لنُعد تنسيق ملف الإعداد eslint.config.mjs من شكله الحالي إلى ما يلي:

import globals from 'globals'

export default [
  {
    files: ['**/*.js'],
    languageOptions: {
      sourceType: 'commonjs',
      globals: { ...globals.node },
      ecmaVersion: 'latest',
    },
  },
]

حتى الآن، يعرّف ملف إعداد ESLint لدينا خيار files بالقيمة ["**/*.js"]، ما يخبر ESLint بالنظر في جميع ملفات JavaScript في مجلد مشروعنا. تحدد خاصية languageOptions خيارات متعلقة بميزات اللغة التي ينبغي أن يتوقعها ESLint، وقد عرّفنا فيها خيار sourceType بالقيمة "commonjs". يشير هذا إلى أن شيفرة JavaScript في مشروعنا تستخدم نظام وحدات CommonJS، ما يسمح لـ ESLint بتحليل الشيفرة وفقاً لذلك.

تحدد خاصية globals المتغيرات العامة المعرّفة مسبقاً. يخبر عامل النشر (spread operator) المطبّق هنا ESLint بتضمين جميع المتغيرات العامة المعرّفة في إعدادات globals.node مثل process. وفي حالة شيفرة المتصفح نعرّف هنا globals.browser للسماح بالمتغيرات العامة الخاصة بالمتصفح مثل window و_document_.

أخيراً، خُصّصت خاصية ecmaVersion بالقيمة "latest". يضبط هذا إصدار ECMAScript على أحدث إصدار متاح، ما يعني أن ESLint سيفهم أحدث صيغ وميزات JavaScript ويفحصها بشكل صحيح.

نريد الاستفادة من إعدادات ESLint الموصى بها إلى جانب إعداداتنا الخاصة. تزوّدنا حزمة @eslint/js التي ثبّتناها سابقاً بإعدادات معرّفة مسبقاً لـ ESLint. سنستوردها ونفعّلها في ملف الإعداد:

import globals from 'globals'
import js from '@eslint/js'
// ...

export default [
  js.configs.recommended,
  {
    // ...
  },
]

أضفنا js.configs.recommended إلى أعلى مصفوفة الإعداد، وهذا يضمن تطبيق إعدادات ESLint الموصى بها أولاً قبل خياراتنا المخصصة.

لنواصل بناء ملف الإعداد. ثبّت إضافة تعرّف مجموعة من القواعد المتعلقة بأسلوب الشيفرة:

npm install --save-dev @stylistic/eslint-plugin

استورد الإضافة وفعّلها، وأضف قواعد أسلوب الشيفرة الأربع هذه:

import globals from 'globals'
import js from '@eslint/js'
import stylisticJs from '@stylistic/eslint-plugin'

export default [
  {
    // ...
    // highlight-start
    plugins: { 
      '@stylistic/js': stylisticJs,
    },
    rules: { 
      '@stylistic/js/indent': ['error', 2],
      '@stylistic/js/linebreak-style': ['error', 'unix'],
      '@stylistic/js/quotes': ['error', 'single'],
      '@stylistic/js/semi': ['error', 'never'],
    }, 
    // highlight-end
  },
]

توفر خاصية plugins طريقة لتوسيع وظائف ESLint عبر إضافة قواعد وإعدادات وقدرات أخرى مخصصة غير متاحة في مكتبة ESLint الأساسية. ثبّتنا وفعّلنا @stylistic/eslint-plugin، الذي يضيف قواعد أسلوبية لـ JavaScript إلى ESLint. بالإضافة إلى ذلك، أُضيفت قواعد للإزاحة وفواصل الأسطر والعلامات التنصيصية والفواصل المنقوطة. جميع هذه القواعد الأربع معرّفة في إضافة أنماط ESLint.

ملاحظة لمستخدمي Windows: ضُبط نمط فواصل الأسطر على unix في قواعد الأسلوب. يُوصى باستخدام فواصل أسطر بنمط Unix (\n) بغض النظر عن نظام التشغيل لديك، لأنها متوافقة مع معظم أنظمة التشغيل الحديثة وتسهّل التعاون عندما يعمل عدة أشخاص على الملفات نفسها. إذا كنت تستخدم فواصل أسطر بنمط Windows، فسينتج ESLint الأخطاء التالية: Expected linebreaks to be 'LF' but found 'CRLF'. في هذه الحالة، اضبط Visual Studio Code لاستخدام فواصل أسطر بنمط Unix باتباع هذا الدليل.

تشغيل أداة lint

يمكن فحص ملف مثل index.js والتحقق منه بالأمر التالي:

npx eslint index.js

يُوصى بإنشاء سكربت npm منفصل لعملية lint:

{
  // ...
  "scripts": {
    "start": "node index.js",
    "dev": "node --watch index.js",
    "test": "echo \"Error: no test specified\" && exit 1",
    "lint": "eslint ."
    // ...
  },
  // ...
}

الآن سيفحص الأمر npm run lint كل ملف في المشروع.

تُفحص أيضاً الملفات الموجودة في مجلد dist عند تشغيل الأمر. لا نريد أن يحدث هذا، ويمكننا تحقيق ذلك بإضافة كائن يحمل خاصية ignores التي تحدد مصفوفة بالمجلدات والملفات التي نريد تجاهلها.

// ...
export default [
  js.configs.recommended,
  {
    files: ['**/*.js'],
    // ...
  },
  // highlight-start
  { 
    ignores: ['dist/**'], 
  },
  // highlight-end
]

يؤدي هذا إلى عدم فحص مجلد dist بأكمله بواسطة ESLint.

لدى lint الكثير لتقوله عن شيفرتنا:

مخرجات الطرفية لأخطاء ESLint

بديل أفضل من تنفيذ أداة lint من سطر الأوامر هو ضبط eslint-plugin في المحرر، يعمل على تشغيل أداة lint باستمرار. باستخدام الإضافة سترى الأخطاء في شيفرتك فوراً. يمكنك العثور على مزيد من المعلومات عن إضافة ESLint لـ Visual Studio هنا.

ستضع إضافة ESLint لـ VS Code خطاً أحمر تحت مخالفات الأسلوب:

لقطة شاشة لإضافة ESLint في vscode تظهر الأخطاء

هذا يجعل اكتشاف الأخطاء وإصلاحها فوراً أمراً سهلاً.

إضافة المزيد من قواعد الأسلوب

لدى ESLint مجموعة واسعة من القواعد يسهل الأخذ بها عبر تعديل ملف eslint.config.mjs.

لنضف قاعدة eqeqeq التي تحذرنا إذا فُحصت المساواة بأي شيء غير المعامل الثلاثي يساوي. تُضاف القاعدة تحت حقل rules في ملف الإعداد.

export default [
  // ...
  rules: {
    // ...
   eqeqeq: 'error',
  },
  // ...
]

وبينما نحن في هذا السياق، لنجرِ بعض التغييرات الأخرى على القواعد.

لنمنع المسافات الزائدة غير الضرورية في نهايات الأسطر، ونطلب أن تكون هناك دائماً مسافة قبل الأقواس المعقوفة وبعدها، ونطالب أيضاً باستخدام متسق للمسافات البيضاء في معاملات دوال السهم.

export default [
  // ...
  rules: {
    // ...
    eqeqeq: 'error',
    // highlight-start
    'no-trailing-spaces': 'error',
    'object-curly-spacing': ['error', 'always'],
    'arrow-spacing': ['error', { before: true, after: true }],
    // highlight-end
  },
]

يأخذ إعدادنا الافتراضي مجموعة من القواعد المعرّفة مسبقاً من:

// ...

export default [
  js.configs.recommended,
  // ...
]

يتضمن هذا قاعدة تحذر بشأن أوامر console.log التي لا نريد استخدامها. يمكن تعطيل قاعدة بتعريف "قيمتها" على أنها 0 أو off في ملف الإعداد. لنفعل هذا بقاعدة no-console في الوقت الحالي.

[
  {
    // ...
    rules: {
      // ...
      eqeqeq: 'error',
      'no-trailing-spaces': 'error',
      'object-curly-spacing': ['error', 'always'],
      'arrow-spacing': ['error', { before: true, after: true }],
      'no-console': 'off',
    },
  },
]

سيسمح لنا تعطيل قاعدة no-console باستخدام عبارات console.log دون أن يشير إليها ESLint كمشكلات. يمكن أن يكون هذا مفيداً بشكل خاص أثناء التطوير عندما تحتاج إلى تصحيح أخطاء شيفرتك. إليك ملف الإعداد الكامل مع جميع التغييرات التي أجريناها حتى الآن:

import globals from 'globals'
import js from '@eslint/js'
import stylisticJs from '@stylistic/eslint-plugin'

export default [
  js.configs.recommended,
  {
    files: ['**/*.js'],
    languageOptions: {
      sourceType: 'commonjs',
      globals: { ...globals.node },
      ecmaVersion: 'latest',
    },
    plugins: {
      '@stylistic/js': stylisticJs,
    },
    rules: {
      '@stylistic/js/indent': ['error', 2],
      '@stylistic/js/linebreak-style': ['error', 'unix'],
      '@stylistic/js/quotes': ['error', 'single'],
      '@stylistic/js/semi': ['error', 'never'],
      eqeqeq: 'error',
      'no-trailing-spaces': 'error',
      'object-curly-spacing': ['error', 'always'],
      'arrow-spacing': ['error', { before: true, after: true }],
      'no-console': 'off',
    },
  },
  {
    ignores: ['dist/**'],
  },
]

ملاحظة عندما تجري تغييرات على ملف eslint.config.mjs، يُوصى بتشغيل أداة lint من سطر الأوامر. سيتحقق هذا من أن ملف الإعداد منسّق بشكل صحيح:

مخرجات الطرفية من npm run lint

إذا كان هناك خطأ ما في ملف إعدادك، فقد تتصرف إضافة lint بشكل غير منتظم تماماً.

تعرّف كثير من الشركات معايير للبرمجة تُفرض في جميع أنحاء المؤسسة عبر ملف إعداد ESLint. لا يُوصى بإعادة اختراع العجلة مراراً وتكراراً، وقد تكون فكرة جيدة تبنّي إعداد جاهز من مشروع شخص آخر في مشروعك. اعتمدت مشاريع كثيرة مؤخراً دليل أسلوب JavaScript من Airbnb بالأخذ بإعداد ESLint الخاص بـ Airbnb.

يمكنك العثور على شيفرة تطبيقنا الحالي كاملةً في فرع part3-7 من مستودع GitHub هذا.

تمرين 3.22.

3.22: إعداد Lint

أضف ESLint إلى تطبيقك وأصلح جميع التحذيرات.

كان هذا آخر تمرين في هذا الجزء من المقرر. حان الوقت لدفع شيفرتك إلى GitHub وتسجيل جميع تمارينك المنجزة في نظام تسليم التمارين.