كيفية إعداد مشروع Express API Backend باستخدام PostgreSQL
نشرت: 2022-03-10سنتخذ نهج التطوير المستند إلى الاختبار (TDD) ووظيفة التكامل المستمر (CI) لإجراء اختباراتنا تلقائيًا على Travis CI و AppVeyor ، كاملة مع جودة الكود وتقارير التغطية. سنتعرف على وحدات التحكم ، والنماذج (مع PostgreSQL) ، ومعالجة الأخطاء ، والبرمجيات الوسيطة Express غير المتزامن. أخيرًا ، سنكمل خط أنابيب CI / CD من خلال تكوين النشر التلقائي على Heroku.
يبدو الأمر كثيرًا ، لكن هذا البرنامج التعليمي يستهدف المبتدئين المستعدين لتجربة أيديهم في مشروع خلفي بمستوى معين من التعقيد ، والذين قد لا يزالون محتارًا بشأن كيفية ملائمة جميع القطع معًا في مشروع حقيقي .
إنه قوي دون أن يكون مرهقًا ويتم تقسيمه إلى أقسام يمكنك إكمالها في فترة زمنية معقولة.
ابدء
الخطوة الأولى هي إنشاء دليل جديد للمشروع وبدء مشروع عقدة جديد. العقدة مطلوبة لمتابعة هذا البرنامج التعليمي. إذا لم يكن مثبتًا لديك ، فانتقل إلى الموقع الرسمي ، وقم بتنزيله وتثبيته قبل المتابعة.
سأستخدم الغزل كمدير للحزم الخاص بي لهذا المشروع. توجد تعليمات التثبيت لنظام التشغيل الخاص بك هنا. لا تتردد في استخدام npm إذا أردت.
افتح الجهاز الطرفي وأنشئ دليلًا جديدًا وابدأ مشروع Node.js.
# create a new directory mkdir express-api-template # change to the newly-created directory cd express-api-template # initialize a new Node.js project npm init
أجب عن الأسئلة التالية لإنشاء ملف package.json . يحتوي هذا الملف على معلومات حول مشروعك. تتضمن أمثلة هذه المعلومات التبعيات التي تستخدمها ، والأمر لبدء المشروع ، وما إلى ذلك.
يمكنك الآن فتح مجلد المشروع في المحرر الذي تختاره. أنا أستخدم كود الاستوديو المرئي. إنه IDE مجاني يحتوي على الكثير من المكونات الإضافية لجعل حياتك أسهل ، وهو متاح لجميع الأنظمة الأساسية الرئيسية. يمكنك تنزيله من الموقع الرسمي.
أنشئ الملفات التالية في مجلد المشروع:
- README.md
- .editorconfig
فيما يلي وصف لما يفعله .editorconfig من موقع EditorConfig على الويب. (ربما لا تحتاج إليه إذا كنت تعمل بمفردك ، لكنه لا يضر ، لذلك سأتركه هنا.)
"يساعد EditorConfig في الحفاظ على أنماط تشفير متسقة للعديد من المطورين الذين يعملون في نفس المشروع عبر العديد من المحررين و IDEs."
افتح .editorconfig
والصق الكود التالي:
root = true [*] indent_style = space indent_size = 2 charset = utf-8 trim_trailing_whitespace = false insert_final_newline = true
تعني [*]
أننا نريد تطبيق القواعد التي تندرج تحته على كل ملف في المشروع. نريد حجم مسافة بادئة لمسافتين ومجموعة أحرف UTF-8
. نريد أيضًا قطع المسافة البيضاء الزائدة وإدخال سطر فارغ نهائي في ملفنا.
افتح README.md وأضف اسم المشروع كعنصر من المستوى الأول.
# Express API template
دعنا نضيف التحكم في الإصدار على الفور.
# initialize the project folder as a git repository git init
قم بإنشاء ملف .gitignore وأدخل الأسطر التالية:
node_modules/ yarn-error.log .env .nyc_output coverage build/
هذه هي جميع الملفات والمجلدات التي لا نريد تتبعها. ليست لدينا في مشروعنا بعد ، لكننا سنراهم ونحن نمضي قدمًا.
في هذه المرحلة ، يجب أن يكون لديك بنية المجلد التالية.
EXPRESS-API-TEMPLATE ├── .editorconfig ├── .gitignore ├── package.json └── README.md
أعتبر هذه نقطة جيدة لارتكاب تغييراتي ودفعها إلى GitHub.
بدء مشروع جديد سريع
Express هو إطار عمل Node.js لبناء تطبيقات الويب. وفقًا للموقع الرسمي ، فهو عبارة عن ملف
إطار عمل ويب بسيط وسريع وغير معلن عنه لـ Node.js.
هناك أطر تطبيقات ويب رائعة أخرى لـ Node.js ، لكن Express تحظى بشعبية كبيرة ، مع أكثر من 47 ألف نجم على GitHub في وقت كتابة هذا التقرير.
في هذه المقالة ، لن نجري الكثير من المناقشات حول جميع الأجزاء التي يتكون منها Express. لهذه المناقشة ، أوصيك بمراجعة سلسلة Jamie. الجزء الأول هنا ، والجزء الثاني هنا.
قم بتثبيت Express وابدأ مشروع Express جديد. من الممكن إعداد خادم Express يدويًا من البداية ، ولكن لتسهيل حياتنا ، سنستخدم المولد السريع لإعداد هيكل التطبيق.
# install the express generator globally yarn global add express-generator # install express yarn add express # generate the express project in the current folder express -f
تفرض العلامة -f
على Express إنشاء المشروع في الدليل الحالي.
سنقوم الآن ببعض عمليات تنظيف المنزل.
- احذف فهرس الملف / users.js .
- حذف المجلدات
public/
views/
. - أعد تسمية الملف bin / www إلى bin / www.js.
- قم بإلغاء تثبيت
jade
باستخدام الأمرyarn remove jade
. - أنشئ مجلدًا جديدًا باسم
src/
وانقل ما يلي بداخله: 1. ملف app.js 2.bin/
folder 3.routes/
المجلد بالداخل. - افتح package.json وقم بتحديث نص
start
ليبدو كما هو موضح أدناه.
"start": "node ./src/bin/www"
في هذه المرحلة ، يبدو هيكل مجلد مشروعك كما يلي. يمكنك أن ترى كيف يسلط VS Code الضوء على تغييرات الملف التي حدثت.
EXPRESS-API-TEMPLATE ├── node_modules ├── src | ├── bin │ │ ├── www.js │ ├── routes │ | ├── index.js │ └── app.js ├── .editorconfig ├── .gitignore ├── package.json ├── README.md └── yarn.lock
افتح src / app.js واستبدل المحتوى بالرمز أدناه.
var logger = require('morgan'); var express = require('express'); var cookieParser = require('cookie-parser'); var indexRouter = require('./routes/index'); var app = express(); app.use(logger('dev')); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use(cookieParser()); app.use('/v1', indexRouter); module.exports = app;
بعد طلب بعض المكتبات ، نطلب من Express التعامل مع كل طلب قادم إلى /v1
باستخدام indexRouter
.
استبدل محتوى المسارات / index.js بالكود أدناه:
var express = require('express'); var router = express.Router(); router.get('/', function(req, res, next) { return res.status(200).json({ message: 'Welcome to Express API template' }); }); module.exports = router;
نحن نحصل على Express ، وننشئ جهاز توجيه منه ونخدم المسار /
، الذي يُرجع رمز الحالة 200
ورسالة JSON.
ابدأ التطبيق بالأمر أدناه:
# start the app yarn start
إذا قمت بإعداد كل شيء بشكل صحيح ، يجب أن ترى فقط $ node ./src/bin/www
في جهازك الطرفي.
قم بزيارة https://localhost:3000/v1
في متصفحك. يجب أن ترى الرسالة التالية:
{ "message": "Welcome to Express API template" }
هذه نقطة جيدة لارتكاب تغييراتنا.
- الفرع المقابل في الريبو الخاص بي هو 01-install-express.
تحويل الكود الخاص بنا إلى ES6
الكود الذي تم إنشاؤه بواسطة express-generator
موجود في ES5
، ولكن في هذه المقالة ، سنكتب كل الكود الخاص بنا في بناء جملة ES6
. لذا ، دعنا نحول الكود الموجود لدينا إلى ES6
.
استبدل محتوى المسارات / index.js بالكود أدناه:
import express from 'express'; const indexRouter = express.Router(); indexRouter.get('/', (req, res) => res.status(200).json({ message: 'Welcome to Express API template' }) ); export default indexRouter;
إنه نفس الكود كما رأينا أعلاه ، ولكن مع عبارة الاستيراد ووظيفة السهم في معالج التوجيه /
.
استبدل محتوى src / app.js بالرمز أدناه:
import logger from 'morgan'; import express from 'express'; import cookieParser from 'cookie-parser'; import indexRouter from './routes/index'; const app = express(); app.use(logger('dev')); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use(cookieParser()); app.use('/v1', indexRouter); export default app;
دعنا الآن نلقي نظرة على محتوى src / bin / www.js. سوف نبنيها تدريجياً. احذف محتوى src/bin/www.js
والصقه في كتلة التعليمات البرمجية أدناه.
#!/usr/bin/env node /** * Module dependencies. */ import debug from 'debug'; import http from 'http'; import app from '../app'; /** * Normalize a port into a number, string, or false. */ const normalizePort = val => { const port = parseInt(val, 10); if (Number.isNaN(port)) { // named pipe return val; } if (port >= 0) { // port number return port; } return false; }; /** * Get port from environment and store in Express. */ const port = normalizePort(process.env.PORT || '3000'); app.set('port', port); /** * Create HTTP server. */ const server = http.createServer(app); // next code block goes here
يتحقق هذا الرمز من تحديد منفذ مخصص في متغيرات البيئة. إذا لم يتم تعيين أي شيء ، يتم تعيين قيمة المنفذ الافتراضي 3000
على مثيل التطبيق ، بعد أن يتم تطبيعه إما إلى سلسلة أو رقم بواسطة normalizePort
. ثم يتم إنشاء الخادم من وحدة http
، مع app
كوظيفة رد الاتصال.
سطر #!/usr/bin/env node
اختياري لأننا سنحدد العقدة عندما نريد تنفيذ هذا الملف. ولكن تأكد من وجوده في السطر 1 من ملف src / bin / www.js أو إزالته بالكامل.
دعنا نلقي نظرة على وظيفة معالجة الأخطاء. انسخ والصق مقطع التعليمات البرمجية هذا بعد السطر الذي تم إنشاء الخادم فيه.
/** * Event listener for HTTP server "error" event. */ const onError = error => { if (error.syscall !== 'listen') { throw error; } const bind = typeof port === 'string' ? `Pipe ${port}` : `Port ${port}`; // handle specific listen errors with friendly messages switch (error.code) { case 'EACCES': alert(`${bind} requires elevated privileges`); process.exit(1); break; case 'EADDRINUSE': alert(`${bind} is already in use`); process.exit(1); break; default: throw error; } }; /** * Event listener for HTTP server "listening" event. */ const onListening = () => { const addr = server.address(); const bind = typeof addr === 'string' ? `pipe ${addr}` : `port ${addr.port}`; debug(`Listening on ${bind}`); }; /** * Listen on provided port, on all network interfaces. */ server.listen(port); server.on('error', onError); server.on('listening', onListening);
تستمع وظيفة onError
إلى وجود أخطاء في خادم http وتعرض رسائل الخطأ المناسبة. تقوم وظيفة onListening
ببساطة بإخراج المنفذ الذي يستمع إليه الخادم إلى وحدة التحكم. أخيرًا ، يستمع الخادم للطلبات الواردة على العنوان والمنفذ المحددين.
في هذه المرحلة ، تكون جميع الكودات الموجودة لدينا في صيغة ES6
. أوقف الخادم الخاص بك (استخدم Ctrl + C ) وقم بتشغيل yarn start
. ستحصل على خطأ SyntaxError: Invalid or unexpected token
. يحدث هذا لأن Node (في وقت كتابة هذا التقرير) لا تدعم بعض البنية التي استخدمناها في الكود الخاص بنا.
سنقوم الآن بإصلاح ذلك في القسم التالي.
تكوين تبعيات التنمية: babel
، nodemon
، eslint
، prettier
حان الوقت لإعداد معظم البرامج النصية التي سنحتاجها في هذه المرحلة من المشروع.
قم بتثبيت المكتبات المطلوبة باستخدام الأوامر أدناه. يمكنك فقط نسخ كل شيء ولصقه في جهازك. سيتم تخطي سطور التعليق.
# install babel scripts yarn add @babel/cli @babel/core @babel/plugin-transform-runtime @babel/preset-env @babel/register @babel/runtime @babel/node --dev
يؤدي هذا إلى تثبيت جميع نصوص بابل المدرجة باعتبارها تبعيات تطوير. تحقق من ملف package.json وسترى قسم devDependencies
. سيتم سرد جميع البرامج النصية المثبتة هناك.
نصوص بابل التي نستخدمها موضحة أدناه:
@babel/cli | التثبيت المطلوب لاستخدام babel . يسمح باستخدام Babel من المحطة وهو متاح كـ ./node_modules/.bin/babel . |
@babel/core | وظائف Core Babel. هذا هو التثبيت المطلوب. |
@babel/node | يعمل هذا تمامًا مثل Node.js CLI ، مع ميزة إضافية تتمثل في التجميع باستخدام إعدادات babel المسبقة والمكونات الإضافية. هذا مطلوب للاستخدام مع nodemon . |
@babel/plugin-transform-runtime | هذا يساعد على تجنب الازدواجية في الإخراج المترجمة. |
@babel/preset-env | مجموعة من المكونات الإضافية المسؤولة عن إجراء تحويلات التعليمات البرمجية. |
@babel/register | يقوم هذا بتجميع الملفات على الفور ويتم تحديده كشرط أثناء الاختبارات. |
@babel/runtime | يعمل هذا جنبًا إلى جنب مع @babel/plugin-transform-runtime . |
أنشئ ملفًا باسم .babelrc في جذر مشروعك وأضف الكود التالي:
{ "presets": ["@babel/preset-env"], "plugins": ["@babel/transform-runtime"] }
لنقم بتثبيت nodemon
# install nodemon yarn add nodemon --dev
nodemon
هي مكتبة تراقب الكود المصدري لمشروعنا وتعيد تشغيل خادمنا تلقائيًا كلما لاحظت أي تغييرات.
أنشئ ملفًا باسم nodemon.json في جذر مشروعك وأضف الكود أدناه:
{ "watch": [ "package.json", "nodemon.json", ".eslintrc.json", ".babelrc", ".prettierrc", "src/" ], "verbose": true, "ignore": ["*.test.js", "*.spec.js"] }
يخبر مفتاح watch
nodemon
بالملفات والمجلدات التي يجب مراقبتها من أجل التغييرات. لذلك ، عندما يتغير أي من هذه الملفات ، يقوم nodemon بإعادة تشغيل الخادم. يخبرها مفتاح ignore
أن الملفات لا تراقب التغييرات.
الآن قم بتحديث قسم scripts
في ملف package.json ليبدو كما يلي:
# build the content of the src folder "prestart": "babel ./src --out-dir build" # start server from the build folder "start": "node ./build/bin/www" # start server in development mode "startdev": "nodemon --exec babel-node ./src/bin/www"
-
prestart
البرامج النصية مسبقة التشغيل محتوى المجلدsrc/
في المجلدbuild/
الإنشاء. عند إصدار أمرyarn start
، يتم تشغيل هذا البرنامج النصي أولاً قبلstart
البرنامج النصي. - يعرض برنامج
start
النصي الآن محتوىbuild/
المجلد بدلاً منsrc/
المجلد الذي كنا نخدمه سابقًا. هذا هو النص الذي ستستخدمه عند تقديم الملف في الإنتاج. في الواقع ، تقوم خدمات مثل Heroku بتشغيل هذا البرنامج النصي تلقائيًا عند النشر. - يتم استخدام بداية
yarn startdev
لبدء تشغيل الخادم أثناء التطوير. من الآن فصاعدًا ، سنستخدم هذا البرنامج النصي أثناء تطوير التطبيق. لاحظ أننا نستخدم الآنbabel-node
لتشغيل التطبيق بدلاً منnode
العادية. تفرض العلامة--exec
babel-node
أن تخدم المجلدsrc/
. بالنسبة إلى سكربتstart
، نستخدمnode
حيث تم تجميع الملفات الموجودة فيbuild/
المجلد إلى ES5.
قم بتشغيل yarn startdev
وقم بزيارة https: // localhost: 3000 / v1. يجب أن يتم تشغيل الخادم الخاص بك مرة أخرى.
الخطوة الأخيرة في هذا القسم هي تكوين ESLint
prettier
. تساعد ESLint في فرض قواعد بناء الجملة بينما تساعد الأجمل في تنسيق التعليمات البرمجية الخاصة بنا بشكل صحيح لسهولة القراءة.
أضف كلاهما بالأمر أدناه. يجب عليك تشغيل هذا على محطة منفصلة أثناء مراقبة المحطة حيث يعمل خادمنا. يجب أن ترى إعادة تشغيل الخادم. هذا لأننا نراقب ملف package.json لمعرفة التغييرات.
# install elsint and prettier yarn add eslint eslint-config-airbnb-base eslint-plugin-import prettier --dev
الآن قم بإنشاء ملف .eslintrc.json في root
المشروع وأضف الكود أدناه:
{ "env": { "browser": true, "es6": true, "node": true, "mocha": true }, "extends": ["airbnb-base"], "globals": { "Atomics": "readonly", "SharedArrayBuffer": "readonly" }, "parserOptions": { "ecmaVersion": 2018, "sourceType": "module" }, "rules": { "indent": ["warn", 2], "linebreak-style": ["error", "unix"], "quotes": ["error", "single"], "semi": ["error", "always"], "no-console": 1, "comma-dangle": [0], "arrow-parens": [0], "object-curly-spacing": ["warn", "always"], "array-bracket-spacing": ["warn", "always"], "import/prefer-default-export": [0] } }
يحدد هذا الملف في الغالب بعض القواعد التي eslint
من الكود وفقًا لها. يمكنك أن ترى أننا نوسع قواعد الأسلوب التي تستخدمها Airbnb.
في قسم "rules"
، نحدد ما إذا كان يجب على eslint
إظهار تحذير أو خطأ عند مواجهة انتهاكات معينة. على سبيل المثال ، يُظهر رسالة تحذير على الجهاز الخاص بنا لأي مسافة بادئة لا تستخدم مسافتين. تؤدي القيمة [0]
إلى إيقاف تشغيل القاعدة ، مما يعني أننا لن نحصل على تحذير أو خطأ إذا انتهكنا هذه القاعدة.
قم بإنشاء ملف باسم .prettierrc وأضف الكود أدناه:
{ "trailingComma": "es5", "tabWidth": 2, "semi": true, "singleQuote": true }
نحن نضع علامة تبويب بعرض 2
ونفرض استخدام علامات الاقتباس المفردة في جميع أنحاء تطبيقنا. تحقق من دليل أجمل لمزيد من خيارات التصميم.
أضف الآن البرامج النصية التالية إلى package.json الخاص بك:
# add these one after the other "lint": "./node_modules/.bin/eslint ./src" "pretty": "prettier --write '**/*.{js,json}' '!node_modules/**'" "postpretty": "yarn lint --fix"
تشغيل yarn lint
. يجب أن ترى عددًا من الأخطاء والتحذيرات في وحدة التحكم.
الأمر pretty
يجمل الكود الخاص بنا. يتم تشغيل الأمر postpretty
مباشرة بعد ذلك. يقوم بتشغيل الأمر lint
مع إلحاق العلامة --fix
. تخبر هذه العلامة ESLint
بإصلاح مشكلات الفحص الشائعة تلقائيًا. بهذه الطريقة ، أقوم في الغالب بتشغيل أمر yarn pretty
دون القلق بشأن أمر lint
.
تشغيل yarn pretty
. يجب أن ترى أن لدينا تحذيرين فقط حول وجود alert
في ملف bin / www.js.
هذا ما يبدو عليه هيكل مشروعنا في هذه المرحلة.
EXPRESS-API-TEMPLATE ├── build ├── node_modules ├── src | ├── bin │ │ ├── www.js │ ├── routes │ | ├── index.js │ └── app.js ├── .babelrc ├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── .prettierrc ├── nodemon.json ├── package.json ├── README.md └── yarn.lock
قد تجد أن لديك ملفًا إضافيًا ، yarn-error.log
في جذر مشروعك. قم بإضافته إلى ملف .gitignore
. التزم بتغييراتك.
- الفرع المقابل في هذه المرحلة في الريبو الخاص بي هو تبعيات 02-dev.
الإعدادات ومتغيرات البيئة في ملف env الخاص بنا
في كل مشروع تقريبًا ، ستحتاج إلى مكان ما لتخزين الإعدادات التي سيتم استخدامها في جميع أنحاء تطبيقك ، مثل مفتاح AWS السري. نقوم بتخزين مثل هذه الإعدادات مثل متغيرات البيئة. هذا يبقيهم بعيدًا عن أعين المتطفلين ، ويمكننا استخدامها في تطبيقنا حسب الحاجة.
أحب الحصول على ملف settings.js أقرأ به جميع متغيرات بيئتي. بعد ذلك ، يمكنني الرجوع إلى ملف الإعدادات من أي مكان داخل تطبيقي. أنت حر في تسمية هذا الملف بأي شيء تريده ، ولكن هناك نوع من الإجماع حول تسمية مثل هذه الملفات settings.js أو config.js .
بالنسبة لمتغيرات بيئتنا ، سنحتفظ بها في ملف .env
في ملف settings
الخاص بنا من هناك.
أنشئ ملف .env في جذر مشروعك وأدخل السطر التالي:
TEST_ENV_VARIABLE="Environment variable is coming across"
لتتمكن من قراءة متغيرات البيئة في مشروعنا ، توجد مكتبة رائعة ، dotenv
تقرأ ملف .env
بنا وتمنحنا الوصول إلى متغيرات البيئة المحددة بالداخل. لنقم بتثبيته.
# install dotenv yarn add dotenv
أضف ملف .env إلى قائمة الملفات التي يشاهدها nodemon
.
الآن ، قم بإنشاء ملف settings.js داخل المجلد src/
وأضف الكود أدناه:
import dotenv from 'dotenv'; dotenv.config(); export const testEnvironmentVariable = process.env.TEST_ENV_VARIABLE;
نقوم باستيراد حزمة dotenv
واستدعاء طريقة التكوين الخاصة بها. ثم نقوم بتصدير testEnvironmentVariable
الذي حددناه في ملف .env
بنا.
افتح src / route / index.js واستبدل الكود بالرمز أدناه.
import express from 'express'; import { testEnvironmentVariable } from '../settings'; const indexRouter = express.Router(); indexRouter.get('/', (req, res) => res.status(200).json({ message: testEnvironmentVariable })); export default indexRouter;
التغيير الوحيد الذي أجريناه هنا هو أننا نستورد testEnvironmentVariable
من ملف settings
لدينا واستخدامه كرسالة إرجاع لطلب من المسار /
.
قم بزيارة https: // localhost: 3000 / v1 وسترى الرسالة ، كما هو موضح أدناه.
{ "message": "Environment variable is coming across." }
وهذا كل شيء. من الآن فصاعدًا ، يمكننا إضافة العديد من متغيرات البيئة كما نريد ويمكننا تصديرها من ملف settings.js .
هذه نقطة جيدة لارتكاب التعليمات البرمجية الخاصة بك. تذكر أن تقوم بتجميل وتنقيح الكود الخاص بك.
- الفرع المقابل في الريبو الخاص بي هو 03-env-variables.
كتابة اختبارنا الأول
حان الوقت لدمج الاختبار في تطبيقنا. أحد الأشياء التي تمنح المطور الثقة في الكود هو الاختبارات. أنا متأكد من أنك رأيت عددًا لا يحصى من المقالات على الويب تدعو إلى تطوير يحركها الاختبار (TDD). لا يمكن التأكيد بما فيه الكفاية على أن الكود الخاص بك يحتاج إلى قدر من الاختبار. من السهل جدًا متابعة TDD عند العمل مع Express.js.
في اختباراتنا ، سنقوم بإجراء مكالمات إلى نقاط نهاية API الخاصة بنا والتحقق لمعرفة ما إذا كان ما يتم إرجاعه هو ما نتوقعه.
قم بتثبيت التبعيات المطلوبة:
# install dependencies yarn add mocha chai nyc sinon-chai supertest coveralls --dev
كل من هذه المكتبات لها دورها الخاص لتلعبه في اختباراتنا.
mocha | عداء اختبار |
chai | تستخدم لعمل تأكيدات |
nyc | جمع تقرير تغطية الاختبار |
sinon-chai | يمتد تأكيدات شاي |
supertest | تستخدم لإجراء مكالمات HTTP إلى نقاط نهاية API الخاصة بنا |
coveralls | لتحميل تغطية الاختبار على المآزر |
قم بإنشاء test/
مجلد جديد في جذر مشروعك. قم بإنشاء ملفين داخل هذا المجلد:
- اختبار / setup.js
- اختبار / index.test.js
سيجد Mocha test/
المجلد تلقائيًا.
افتح test / setup.js والصق الكود أدناه. هذا مجرد ملف مساعد يساعدنا في تنظيم جميع عمليات الاستيراد التي نحتاجها في ملفات الاختبار الخاصة بنا.
import supertest from 'supertest'; import chai from 'chai'; import sinonChai from 'sinon-chai'; import app from '../src/app'; chai.use(sinonChai); export const { expect } = chai; export const server = supertest.agent(app); export const BASE_URL = '/v1';
هذا مثل ملف الإعدادات ، ولكن لاختباراتنا. بهذه الطريقة لا يتعين علينا تهيئة كل شيء داخل كل ملف من ملفات الاختبار الخاصة بنا. لذلك نقوم باستيراد الحزم الضرورية وتصدير ما قمنا بتهيئته - والذي يمكننا بعد ذلك استيراده في الملفات التي تحتاجها.
افتح index.test.js والصق كود الاختبار التالي.
import { expect, server, BASE_URL } from './setup'; describe('Index page test', () => { it('gets base url', done => { server .get(`${BASE_URL}/`) .expect(200) .end((err, res) => { expect(res.status).to.equal(200); expect(res.body.message).to.equal( 'Environment variable is coming across.' ); done(); }); }); });
هنا نقدم طلبًا للحصول على نقطة النهاية الأساسية ، وهي /
ونؤكد أن res.
body
الكائن لديه مفتاح message
مع قيمة Environment variable is coming across.
إذا لم تكن معتادًا على describe
، it
النمط ، فأنا أشجعك على إلقاء نظرة سريعة على مستند Mocha "Getting Started".
أضف أمر الاختبار إلى قسم scripts
في package.json .
"test": "nyc --reporter=html --reporter=text --reporter=lcov mocha -r @babel/register"
ينفذ هذا البرنامج النصي اختبارنا مع مدينة nyc
ثلاثة أنواع من تقارير التغطية: تقرير HTML ، يتم إخراجه إلى المجلد coverage/
؛ تم إخراج تقرير نصي إلى الجهاز وتقرير lcov إلى المجلد .nyc_output/
.
الآن قم بتشغيل yarn test
. يجب أن ترى تقريرًا نصيًا في جهازك تمامًا مثل ذلك الموجود في الصورة أدناه.
لاحظ أنه تم إنشاء مجلدين إضافيين:
-
.nyc_output/
-
coverage/
انظر داخل .gitignore
أننا نتجاهل كليهما بالفعل. أنا أشجعك على فتح coverage/index.html
في متصفح وعرض تقرير الاختبار لكل ملف.
هذه نقطة جيدة لإجراء تغييراتك.
- الفرع المقابل في الريبو الخاص بي هو 04-first-test.
التكامل المستمر (CD) والشارات: Travis ، المعاطف ، Code Climate ، AppVeyor
حان الوقت الآن لتكوين أدوات التكامل والنشر المستمرة (CI / CD). سنقوم بتكوين خدمات مشتركة مثل travis-ci
، AppVeyor
، و coveralls
، و codeclimate
وإضافة شارات إلى ملف README الخاص بنا.
هيا بنا نبدأ.
ترافيس سي
Travis CI هي أداة تدير اختباراتنا تلقائيًا في كل مرة نضغط فيها على التزام بـ GitHub (ومؤخراً ، Bitbucket) وفي كل مرة نقوم بإنشاء طلب سحب. يكون هذا مفيدًا في الغالب عند إجراء طلبات سحب من خلال إظهار ما إذا كان الكود الجديد الخاص بنا قد كسر أيًا من اختباراتنا.
- قم بزيارة travis-ci.com أو travis-ci.org وأنشئ حسابًا إذا لم يكن لديك حساب. يجب عليك التسجيل باستخدام حساب GitHub الخاص بك.
- مرر مؤشر الماوس فوق سهم القائمة المنسدلة بجوار صورة ملفك الشخصي وانقر على
settings
. - ضمن علامة التبويب
Repositories
، انقر فوقManage repositories on Github
لإعادة توجيهك إلى Github. - في صفحة GitHub ، قم بالتمرير لأسفل وصولاً إلى
Repository access
وانقر فوق خانة الاختيار الموجودة بجوارOnly select repositories
. - انقر فوق القائمة المنسدلة
Select repositories
وابحث عن repoexpress-api-template
repo. انقر فوقه لإضافته إلى قائمة المستودعات التي تريد إضافتها إلىtravis-ci
. - انقر فوق
Approve and install
وانتظر حتى يتم إعادة توجيهك مرة أخرى إلىtravis-ci
. - في الجزء العلوي من صفحة الريبو ، بالقرب من اسم الريبو ، انقر على أيقونة
build unknown
. من حالة صورة الحالة ، حدد علامة التخفيض من القائمة المنسدلة للتنسيق. - انسخ الكود الناتج والصقه في ملف README.md .
- في صفحة المشروع ، انقر فوق
More options
>Settings
. ضمن قسمEnvironment Variables
، أضف متغير envTEST_ENV_VARIABLE
. عند إدخال قيمته ، تأكد من وضعه ضمن علامات اقتباس مزدوجة مثل"Environment variable is coming across."
- قم بإنشاء ملف .travis.yml في جذر مشروعك والصقه في الكود أدناه (سنقوم بتعيين قيمة
CC_TEST_REPORTER_ID
في قسم Code Climate).
language: node_js env: global: - CC_TEST_REPORTER_ID=get-this-from-code-climate-repo-page matrix: include: - node_js: '12' cache: directories: [node_modules] install: yarn after_success: yarn coverage before_script: - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter - chmod +x ./cc-test-reporter - ./cc-test-reporter before-build script: - yarn test after_script: - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESUL
أولاً ، نطلب من Travis إجراء اختبارنا مع Node.js ، ثم قم بتعيين متغير البيئة العالمية CC_TEST_REPORTER_ID
(سنصل إلى هذا في قسم Code Climate). في قسم matrix
، نطلب من Travis تشغيل اختباراتنا باستخدام Node.js v12. نريد أيضًا تخزين الدليل node_modules/
بحيث لا يتعين إعادة إنشائه في كل مرة.
نقوم بتثبيت تبعياتنا باستخدام أمر yarn
وهو اختصار yarn install
. يتم استخدام الأمرين before_script
و after_script
لتحميل نتائج التغطية إلى codeclimate
. سنقوم بتكوين codeclimate
قريبًا. بعد تشغيل yarn test
بنجاح ، نريد أيضًا تشغيل yarn coverage
التي سترفع تقرير التغطية الخاص بنا إلى المآزر.io.
معاطف
تقوم المعاطف بتحميل بيانات تغطية الاختبار لتسهيل التصور. يمكننا عرض تغطية الاختبار على أجهزتنا المحلية من مجلد التغطية ، لكن الأغطية تجعلها متاحة خارج أجهزتنا المحلية.
- قم بزيارة coveralls.io وإما تسجيل الدخول أو التسجيل باستخدام حساب Github الخاص بك.
- قم بالتمرير فوق الجانب الأيسر من الشاشة للكشف عن قائمة التنقل. انقر فوق
ADD REPOS
. - ابحث عن repo
express-api-template
وقم بتشغيل التغطية باستخدام زر التبديل على الجانب الأيسر. إذا لم تتمكن من العثور عليه ، فانقر فوقSYNC REPOS
في الزاوية اليمنى العليا وحاول مرة أخرى. لاحظ أن الريبو الخاص بك يجب أن يكون عامًا ، ما لم يكن لديك حساب PRO. - انقر فوق التفاصيل للذهاب إلى صفحة تفاصيل الريبو.
- قم بإنشاء ملف .coveralls.yml في جذر مشروعك وأدخل الكود أدناه. للحصول على
repo_token
، انقر فوق تفاصيل الريبو. سوف تجدها بسهولة على تلك الصفحة. يمكنك فقط إجراء بحث في المتصفح عنrepo_token
.
repo_token: get-this-from-repo-settings-on-coveralls.io
يرسم هذا الرمز المميز بيانات التغطية الخاصة بك إلى الريبو على المعاطف. الآن ، أضف أمر coverage
إلى قسم scripts
في ملف package.json الخاص بك:
"coverage": "nyc report --reporter=text-lcov | coveralls"
يقوم هذا الأمر بتحميل تقرير التغطية في المجلد .nyc_output
إلى coveralls.io. قم بتشغيل اتصال الإنترنت الخاص بك وقم بتشغيل:
yarn coverage
يجب أن يقوم هذا بتحميل تقرير التغطية الحالي إلى المعاطف. قم بتحديث صفحة الريبو على المعاطف لرؤية التقرير الكامل.
في صفحة التفاصيل ، قم بالتمرير لأسفل للعثور على قسم BADGE YOUR REPO
. انقر فوق القائمة المنسدلة EMBED
وانسخ رمز التحديد والصقه في ملف README .
كود المناخ
Code Climate هي أداة تساعدنا في قياس جودة الكود. يوضح لنا مقاييس الصيانة عن طريق التحقق من الكود الخاص بنا مقابل بعض الأنماط المحددة. يكتشف أشياء مثل التكرار غير الضروري وحلقات for المتداخلة بعمق. كما أنه يجمع بيانات تغطية الاختبار تمامًا مثل المآزر.io.
- قم بزيارة codeclimate.com وانقر فوق "التسجيل باستخدام GitHub". تسجيل الدخول إذا كان لديك بالفعل حساب.
- بمجرد دخولك إلى لوحة التحكم ، انقر فوق
Add a repository
. - ابحث عن نموذج
express-api-template
repo من القائمة وانقر علىAdd Repo
. - انتظر حتى يكتمل البناء وأعد التوجيه إلى لوحة معلومات الريبو.
- ضمن
Codebase Summary
، انقر فوقTest Coverage
. ضمن قائمةTest coverage
، انسخTEST REPORTER ID
والصقه في .travis.yml كقيمةCC_TEST_REPORTER_ID
. - لا يزال في نفس الصفحة ، في شريط التنقل الأيمن ، ضمن
EXTRAS
، انقر فوق الشارات. انسخ قابليةmaintainability
test coverage
بتنسيق markdown والصقها في ملف README.md .
من المهم ملاحظة أن هناك طريقتين لتكوين فحوصات قابلية الصيانة. هناك الإعدادات الافتراضية التي يتم تطبيقها على كل ريبو ، ولكن إذا أردت ، يمكنك توفير ملف .codeclimate.yml في جذر مشروعك. سأستخدم الإعدادات الافتراضية ، والتي يمكنك العثور عليها ضمن علامة التبويب " Maintainability
" في صفحة إعدادات الريبو. أنا أشجعك على إلقاء نظرة على الأقل. إذا كنت لا تزال ترغب في تكوين الإعدادات الخاصة بك ، فسوف يوفر لك هذا الدليل جميع المعلومات التي تحتاجها.
AppVeyor
AppVeyor و Travis CI كلاهما من المتسابقين الآليين للاختبار. يتمثل الاختلاف الرئيسي في أن travis-ci يجري اختبارات في بيئة Linux بينما يجري AppVeyor اختبارات في بيئة Windows. تم تضمين هذا القسم لإظهار كيفية بدء استخدام AppVeyor.
- قم بزيارة AppVeyor وقم بتسجيل الدخول أو التسجيل.
- في الصفحة التالية ، انقر فوق
NEW PROJECT
. - من قائمة الريبو ، ابحث عن نموذج
express-api-template
repo. قم بالمرور فوقه وانقر فوقADD
. - انقر فوق علامة التبويب
Settings
. انقر فوقEnvironment
في شريط التنقل الأيمن. أضفTEST_ENV_VARIABLE
وقيمته. انقر فوق "حفظ" في أسفل الصفحة. - أنشئ ملف appveyor.yml في جذر مشروعك والصقه في الكود أدناه.
environment: matrix: - nodejs_version: "12" install: - yarn test_script: - yarn test build: off
يوجه هذا الرمز AppVeyor لإجراء اختباراتنا باستخدام Node.js v12. ثم نقوم بتثبيت تبعيات مشروعنا باستخدام أمر yarn
. test_script
يحدد الأمر لتشغيل الاختبار الخاص بنا. يخبر السطر الأخير AppVeyor بعدم إنشاء مجلد بناء.
انقر فوق علامة التبويب Settings
. في شريط التنقل الأيمن ، انقر فوق الشارات. انسخ رمز علامة التخفيض والصقه في ملف README.md .
قم بإيداع التعليمات البرمجية الخاصة بك وادفع إلى GitHub. إذا كنت قد فعلت كل شيء وفقًا للتعليمات ، فيجب أن تجتاز جميع الاختبارات ويجب أن ترى شاراتك الجديدة اللامعة كما هو موضح أدناه. تحقق مرة أخرى من أنك قمت بتعيين متغيرات البيئة على Travis و AppVeyor.
الآن هو الوقت المناسب لإجراء تغييراتنا.
- الفرع المقابل في الريبو الخاص بي هو 05-ci.
إضافة وحدة تحكم
حاليًا ، نتعامل مع طلب GET
إلى عنوان URL الجذر ، /v1
، داخل src / route / index.js . هذا يعمل كما هو متوقع ولا بأس به. ومع ذلك ، مع نمو التطبيق الخاص بك ، فإنك تريد إبقاء الأمور مرتبة. تريد فصل المخاوف - تريد فصلًا واضحًا بين الكود الذي يتعامل مع الطلب والكود الذي ينشئ الاستجابة التي سيتم إرسالها مرة أخرى إلى العميل. لتحقيق ذلك ، نكتب controllers
. أدوات التحكم هي ببساطة وظائف تتعامل مع الطلبات الواردة من خلال عنوان URL معين.
للبدء ، قم بإنشاء controllers/
مجلد داخل المجلد src/
. تنشئ controllers
الداخلية ملفين: index.js و home.js. سنقوم بتصدير وظائفنا من داخل index.js . يمكنك تسمية home.js بأي شيء تريده ، ولكنك تريد عادةً تسمية وحدات التحكم على اسم ما يتحكمون فيه. على سبيل المثال ، قد يكون لديك ملف usersController.js لاستيعاب كل وظيفة متعلقة بالمستخدمين في تطبيقك.
افتح src / Controllers / home.js وأدخل الرمز أدناه:
import { testEnvironmentVariable } from '../settings'; export const indexPage = (req, res) => res.status(200).json({ message: testEnvironmentVariable });
ستلاحظ أننا نقلنا الوظيفة التي تتعامل مع طلب المسار /
.
افتح src / controllers / index.js وأدخل الكود أدناه.
// export everything from home.js export * from './home';
نقوم بتصدير كل شيء من ملف home.js. هذا يسمح لنا بتقصير عبارات الاستيراد الخاصة بنا import { indexPage } from '../controllers';
افتح src / route / index.js واستبدل الكود الموجود هناك بالرمز أدناه:
import express from 'express'; import { indexPage } from '../controllers'; const indexRouter = express.Router(); indexRouter.get('/', indexPage); export default indexRouter;
التغيير الوحيد هنا هو أننا قدمنا وظيفة للتعامل مع الطلب إلى المسار /
.
لقد كتبت للتو وحدة تحكمك الأولى بنجاح. من هنا يتعلق الأمر بإضافة المزيد من الملفات والوظائف حسب الحاجة.
انطلق والعب مع التطبيق عن طريق إضافة المزيد من المسارات وأجهزة التحكم. يمكنك إضافة مسار ووحدة تحكم للصفحة حول. تذكر تحديث الاختبار الخاص بك ، رغم ذلك.
yarn test
للتأكد من أننا لم نكسر أي شيء. هل ينجح اختبارك؟ هذا بارد.
هذه نقطة جيدة لارتكاب تغييراتنا.
- الفرع المقابل في الريبو الخاص بي هو 06 وحدات تحكم.
ربط قاعدة بيانات PostgreSQL
وكتابة نموذج
تقوم وحدة التحكم الخاصة بنا حاليًا بإرجاع الرسائل النصية المشفرة. في تطبيق العالم الحقيقي ، نحتاج غالبًا إلى تخزين المعلومات واستردادها من قاعدة بيانات. في هذا القسم ، سنربط تطبيقنا بقاعدة بيانات PostgreSQL.
سنقوم بتنفيذ تخزين واسترجاع الرسائل النصية البسيطة باستخدام قاعدة بيانات. لدينا خياران لإعداد قاعدة بيانات: يمكننا توفير أحدهما من خادم سحابي ، أو يمكننا إعداد قاعدة بيانات خاصة بنا محليًا.
أوصي بتوفير قاعدة بيانات من خادم سحابي. ElephantSQL has a free plan that gives 20MB of free storage which is sufficient for this tutorial. Visit the site and click on Get a managed database today
. Create an account (if you don't have one) and follow the instructions to create a free plan. Take note of the URL on the database details page. We'll be needing it soon.
If you would rather set up a database locally, you should visit the PostgreSQL and PgAdmin sites for further instructions.
Once we have a database set up, we need to find a way to allow our Express app to communicate with our database. Node.js by default doesn't support reading and writing to PostgreSQL
database, so we'll be using an excellent library, appropriately named, node-postgres.
node-postgres
executes SQL
queries in node and returns the result as an object, from which we can grab items from the rows key.
Let's connect node-postgres
to our application.
# install node-postgres yarn add pg
Open settings.js and add the line below:
export const connectionString = process.env.CONNECTION_STRING;
Open your .env
file and add the CONNECTION_STRING
variable. This is the connection string we'll be using to establish a connection to our database. The general form of the connection string is shown below.
CONNECTION_STRING="postgresql://dbuser:dbpassword@localhost:5432/dbname"
If you're using elephantSQL you should copy the URL from the database details page.
Inside your /src
folder, create a new folder called models/
. Inside this folder, create two files:
- pool.js
- model.js
Open pools.js and paste the following code:
import { Pool } from 'pg'; import dotenv from 'dotenv'; import { connectionString } from '../settings'; dotenv.config(); export const pool = new Pool({ connectionString });
First, we import the Pool
and dotenv
from the pg
and dotenv
packages, and then import the settings we created for our postgres database before initializing dotenv
. We establish a connection to our database with the Pool
object. In node-postgres
, every query is executed by a client. A Pool is a collection of clients for communicating with the database.
To create the connection, the pool constructor takes a config object. You can read more about all the possible configurations here. It also accepts a single connection string, which I will use here.
Open model.js and paste the following code:
import { pool } from './pool'; class Model { constructor(table) { this.pool = pool; this.table = table; this.pool.on('error', (err, client) => `Error, ${err}, on idle client${client}`); } async select(columns, clause) { let query = `SELECT ${columns} FROM ${this.table}`; if (clause) query += clause; return this.pool.query(query); } } export default Model;
We create a model class whose constructor accepts the database table we wish to operate on. We'll be using a single pool for all our models.
We then create a select
method which we will use to retrieve items from our database. This method accepts the columns we want to retrieve and a clause, such as a WHERE
clause. It returns the result of the query, which is a Promise
. Remember we said earlier that every query is executed by a client, but here we execute the query with pool. This is because, when we use pool.query
, node-postgres
executes the query using the first available idle client.
The query you write is entirely up to you, provided it is a valid SQL
statement that can be executed by a Postgres engine.
The next step is to actually create an API endpoint to utilize our newly connected database. Before we do that, I'd like us to create some utility functions. The goal is for us to have a way to perform common database operations from the command line.
Create a folder, utils/
inside the src/
folder. Create three files inside this folder:
- queries.js
- queryFunctions.js
- runQuery.js
We're going to create functions to create a table in our database, insert seed data in the table, and to delete the table.
افتح queries.js والصق الكود التالي:
export const createMessageTable = ` DROP TABLE IF EXISTS messages; CREATE TABLE IF NOT EXISTS messages ( id SERIAL PRIMARY KEY, name VARCHAR DEFAULT '', message VARCHAR NOT NULL ) `; export const insertMessages = ` INSERT INTO messages(name, message) VALUES ('chidimo', 'first message'), ('orji', 'second message') `; export const dropMessagesTable = 'DROP TABLE messages';
في هذا الملف ، نحدد ثلاث سلاسل استعلام SQL. يقوم الاستعلام الأول بحذف جدول messages
وإعادة إنشائه. يقوم الاستعلام الثاني بإدراج صفين في جدول messages
. لا تتردد في إضافة المزيد من العناصر هنا. آخر استعلام يسقط / يحذف جدول messages
.
افتح queryFunctions.js والصق الكود التالي:
import { pool } from '../models/pool'; import { insertMessages, dropMessagesTable, createMessageTable, } from './queries'; export const executeQueryArray = async arr => new Promise(resolve => { const stop = arr.length; arr.forEach(async (q, index) => { await pool.query(q); if (index + 1 === stop) resolve(); }); }); export const dropTables = () => executeQueryArray([ dropMessagesTable ]); export const createTables = () => executeQueryArray([ createMessageTable ]); export const insertIntoTables = () => executeQueryArray([ insertMessages ]);
هنا ، نقوم بإنشاء وظائف لتنفيذ الاستعلامات التي حددناها مسبقًا. لاحظ أن الدالة executeQueryArray
تنفذ مصفوفة من الاستعلامات وتنتظر حتى يكتمل كل واحد منها داخل الحلقة. (لا تفعل شيئًا كهذا في كود الإنتاج). بعد ذلك ، لا نحسم الوعد إلا بعد تنفيذ آخر طلب بحث في القائمة. سبب استخدام المصفوفة هو أن عدد هذه الاستعلامات سيزداد مع زيادة عدد الجداول في قاعدة البيانات الخاصة بنا.
افتح runQuery.js والصق الكود التالي:
import { createTables, insertIntoTables } from './queryFunctions'; (async () => { await createTables(); await insertIntoTables(); })();
هذا هو المكان الذي نقوم فيه بتنفيذ الوظائف لإنشاء الجدول وإدراج الرسائل في الجدول. دعنا نضيف أمرًا في قسم scripts
في package.json لدينا لتنفيذ هذا الملف.
"runQuery": "babel-node ./src/utils/runQuery"
شغّل الآن:
yarn runQuery
إذا قمت بفحص قاعدة البيانات الخاصة بك ، فسترى أنه تم إنشاء جدول messages
وأنه تم إدراج الرسائل في الجدول.
إذا كنت تستخدم ElephantSQL ، في صفحة تفاصيل قاعدة البيانات ، انقر فوق BROWSER
من قائمة التنقل اليمنى. حدد جدول messages
وانقر فوق Execute
. يجب أن ترى الرسائل من ملف queries.js .
لنقم بإنشاء وحدة تحكم وطريق لعرض الرسائل من قاعدة البيانات الخاصة بنا.
قم بإنشاء ملف تحكم جديد src / controllers / messages.js والصق الكود التالي:
import Model from '../models/model'; const messagesModel = new Model('messages'); export const messagesPage = async (req, res) => { try { const data = await messagesModel.select('name, message'); res.status(200).json({ messages: data.rows }); } catch (err) { res.status(200).json({ messages: err.stack }); } };
نقوم باستيراد فئة Model
الخاصة بنا وإنشاء مثيل جديد لهذا النموذج. هذا يمثل جدول messages
في قاعدة بياناتنا. ثم نستخدم طريقة select
النموذج للاستعلام عن قاعدة البيانات الخاصة بنا. يتم إرسال البيانات ( name
message
) التي نحصل عليها في شكل JSON في الرد.
نحدد وحدة تحكم messagesPage
كوظيفة غير async
. نظرًا لأن استعلامات node-postgres
تعيد وعدًا ، فإننا await
نتيجة هذا الاستعلام. إذا واجهنا خطأ أثناء الاستعلام ، فإننا نلاحظه ونعرض المكدس للمستخدم. يجب أن تقرر كيفية اختيار معالجة الخطأ.
أضف نقطة نهاية الحصول على الرسائل إلى src / route / index.js وقم بتحديث سطر الاستيراد.
# update the import line import { indexPage, messagesPage } from '../controllers'; # add the get messages endpoint indexRouter.get('/messages', messagesPage)
قم بزيارة https: // localhost: 3000 / v1 / messages وسترى الرسائل المعروضة كما هو موضح أدناه.
الآن ، دعنا نقوم بتحديث ملف الاختبار الخاص بنا. عند إجراء TDD ، عادة ما تكتب اختباراتك قبل تنفيذ الكود الذي يجعل الاختبار يجتاز. أنا أتخذ النهج المعاكس هنا لأننا ما زلنا نعمل على إنشاء قاعدة البيانات.
قم بإنشاء ملف جديد ، hooks.js في test/
المجلد وأدخل الكود أدناه:
import { dropTables, createTables, insertIntoTables, } from '../src/utils/queryFunctions'; before(async () => { await createTables(); await insertIntoTables(); }); after(async () => { await dropTables(); });
عندما يبدأ اختبارنا ، يجد Mocha هذا الملف وينفذه قبل تشغيل أي ملف اختبار. يقوم بتنفيذ الخطاف before
لإنشاء قاعدة البيانات وإدراج بعض العناصر فيها. ثم يتم تشغيل ملفات الاختبار بعد ذلك. بمجرد الانتهاء من الاختبار ، يقوم Mocha بتشغيل الخطاف after
الذي نسقط فيه قاعدة البيانات. هذا يضمن أنه في كل مرة نجري فيها اختباراتنا ، نقوم بذلك باستخدام سجلات نظيفة وجديدة في قاعدة بياناتنا.
قم بإنشاء ملف اختبار جديد / messages.test.js وأضف الكود أدناه:
import { expect, server, BASE_URL } from './setup'; describe('Messages', () => { it('get messages page', done => { server .get(`${BASE_URL}/messages`) .expect(200) .end((err, res) => { expect(res.status).to.equal(200); expect(res.body.messages).to.be.instanceOf(Array); res.body.messages.forEach(m => { expect(m).to.have.property('name'); expect(m).to.have.property('message'); }); done(); }); }); });
نؤكد أن نتيجة الاستدعاء /messages
هي مصفوفة. لكل كائن رسالة ، نؤكد أن له name
وخاصية message
.
الخطوة الأخيرة في هذا القسم هي تحديث ملفات CI.
أضف الأقسام التالية إلى ملف .travis.yml :
services: - postgresql addons: postgresql: "10" apt: packages: - postgresql-10 - postgresql-client-10 before_install: - sudo cp /etc/postgresql/{9.6,10}/main/pg_hba.conf - sudo /etc/init.d/postgresql restart
يوجه هذا ترافيس إلى إنشاء قاعدة بيانات PostgreSQL 10 قبل إجراء اختباراتنا.
أضف الأمر لإنشاء قاعدة البيانات كأول إدخال في قسم before_script
:
# add this as the first line in the before_script section - psql -c 'create database testdb;' -U postgres
أنشئ متغير البيئة CONNECTION_STRING
على ترافيس ، واستخدم القيمة التالية:
CONNECTION_STRING="postgresql://postgres:postgres@localhost:5432/testdb"
أضف الأقسام التالية إلى ملف .appveyor.yml :
before_test: - SET PGUSER=postgres - SET PGPASSWORD=Password12! - PATH=C:\Program Files\PostgreSQL\10\bin\;%PATH% - createdb testdb services: - postgresql101
أضف متغير بيئة سلسلة الاتصال إلى appveyor. استخدم السطر أدناه:
CONNECTION_STRING=postgresql://postgres:Password12!@localhost:5432/testdb
الآن قم بإجراء تغييراتك وادفع إلى GitHub. يجب أن تمر اختباراتك على كل من Travis CI و AppVeyor.
- الفرع المقابل في الريبو الخاص بي هو 07-connect-postgres.
ملاحظة : آمل أن يعمل كل شيء بشكل جيد من جانبك ، ولكن في حالة مواجهة مشكلة لسبب ما ، يمكنك دائمًا التحقق من الكود الخاص بي في الريبو!
الآن ، دعنا نرى كيف يمكننا إضافة رسالة إلى قاعدة البيانات الخاصة بنا. بالنسبة لهذه الخطوة ، سنحتاج إلى طريقة لإرسال طلبات POST
إلى عنوان URL الخاص بنا. سأستخدم ساعي البريد لإرسال طلبات POST
.
دعنا نذهب إلى مسار TDD ونحدث اختبارنا ليعكس ما نتوقع تحقيقه.
افتح test / message.test.js وأضف حالة الاختبار التالية:
it('posts messages', done => { const data = { name: 'some name', message: 'new message' }; server .post(`${BASE_URL}/messages`) .send(data) .expect(200) .end((err, res) => { expect(res.status).to.equal(200); expect(res.body.messages).to.be.instanceOf(Array); res.body.messages.forEach(m => { expect(m).to.have.property('id'); expect(m).to.have.property('name', data.name); expect(m).to.have.property('message', data.message); }); done(); }); });
يقوم هذا الاختبار بإجراء طلب POST إلى نقطة نهاية /v1/messages
ونتوقع إرجاع مصفوفة. نتحقق أيضًا من خصائص id
name
message
في المصفوفة.
قم بإجراء الاختبارات الخاصة بك لمعرفة فشل هذه الحالة. دعنا الآن نصلحه.
لإرسال طلبات النشر ، نستخدم طريقة النشر الخاصة بالخادم. نرسل أيضًا الاسم والرسالة التي نريد إدراجها. نتوقع أن تكون الاستجابة عبارة عن مصفوفة ، مع id
الخاصية والمعلومات الأخرى التي يتكون منها الاستعلام. id
هو دليل على أنه تم إدراج سجل في قاعدة البيانات.
افتح src / Models / model.js وأضف طريقة insert
:
async insertWithReturn(columns, values) { const query = ` INSERT INTO ${this.table}(${columns}) VALUES (${values}) RETURNING id, ${columns} `; return this.pool.query(query); }
هذه هي الطريقة التي تسمح لنا بإدراج الرسائل في قاعدة البيانات. بعد إدخال العنصر ، يقوم بإرجاع id
name
message
.
افتح src / Controllers / messages.js وأضف وحدة التحكم التالية:
export const addMessage = async (req, res) => { const { name, message } = req.body; const columns = 'name, message'; const values = `'${name}', '${message}'`; try { const data = await messagesModel.insertWithReturn(columns, values); res.status(200).json({ messages: data.rows }); } catch (err) { res.status(200).json({ messages: err.stack }); } };
نقوم بتدمير جسم الطلب للحصول على الاسم والرسالة. ثم نستخدم القيم لتشكيل سلسلة استعلام SQL والتي ننفذها بعد ذلك باستخدام طريقة insertWithReturn
في نموذجنا.
أضف نقطة نهاية POST
أدناه إلى /src/routes/index.js وقم بتحديث سطر الاستيراد.
import { indexPage, messagesPage, addMessage } from '../controllers'; indexRouter.post('/messages', addMessage);
قم بإجراء الاختبارات الخاصة بك لمعرفة ما إذا كانت قد نجحت.
افتح Postman وأرسل طلب POST
إلى نقطة نهاية messages
. إذا كنت قد أجريت الاختبار للتو ، فتذكر تشغيل yarn query
لإعادة إنشاء جدول messages
.
yarn query
التزم بتغييراتك وادفع إلى GitHub. يجب أن تمر اختباراتك على كل من Travis و AppVeyor. ستنخفض تغطية الاختبار الخاصة بك ببضع نقاط ، لكن لا بأس بذلك.
- الفرع المقابل في الريبو الخاص بي هو 08-post-to-db.
الوسيطة
لن تكتمل مناقشتنا حول Express دون الحديث عن البرامج الوسيطة. يصف التوثيق السريع البرامج الوسيطة على النحو التالي:
وظائف "[...] التي لها حق الوصول إلى كائن الطلب (req
) ، وكائن الاستجابة (res
) ، ووظيفة البرامج الوسيطة التالية في دورة الطلب والاستجابة للتطبيق. عادةً ما يتم الإشارة إلى وظيفة البرامج الوسيطة التالية بواسطة متغير يسمىnext
".
يمكن أن تؤدي البرامج الوسيطة أي عدد من الوظائف مثل المصادقة وتعديل نص الطلب وما إلى ذلك. راجع توثيق Express حول استخدام البرامج الوسيطة.
سنقوم بكتابة برمجية وسيطة بسيطة تعدل نص الطلب. ستلحق البرمجيات الوسيطة الخاصة بنا بالكلمة SAYS:
بالرسالة الواردة قبل حفظها في قاعدة البيانات.
قبل أن نبدأ ، دعنا نعدل اختبارنا ليعكس ما نريد تحقيقه.
افتح test / messages.test.js وقم بتعديل آخر سطر توقع في حالة اختبار posts message
المنشورات:
it('posts messages', done => { ... expect(m).to.have.property('message', `SAYS: ${data.message}`); # update this line ... });
نحن نؤكد أن SAYS:
string قد تم إلحاقه بالرسالة. قم بإجراء الاختبارات الخاصة بك للتأكد من فشل حالة الاختبار هذه.
الآن ، دعنا نكتب الكود لإجراء الاختبار بنجاح.
قم بإنشاء middleware/
مجلد جديد داخل src/
folder. قم بإنشاء ملفين داخل هذا المجلد:
- middleware.js
- index.js
أدخل الكود أدناه في middleware.js :
export const modifyMessage = (req, res, next) => { req.body.message = `SAYS: ${req.body.message}`; next(); };
هنا ، نلحق السلسلة SAYS:
بالرسالة في نص الطلب. بعد القيام بذلك ، يجب علينا استدعاء الدالة next()
لتمرير التنفيذ إلى الوظيفة التالية في سلسلة استجابة الطلب. يجب على كل برمجية وسيطة استدعاء الوظيفة next
لتمرير التنفيذ إلى البرنامج الوسيط التالي في دورة الطلب والاستجابة.
أدخل الرمز أدناه في index.js :
# export everything from the middleware file export * from './middleware';
يقوم هذا بتصدير البرامج الوسيطة الموجودة لدينا في ملف /middleware.js . في الوقت الحالي ، لدينا فقط البرامج الوسيطة modifyMessage
.
افتح src / route / index.js وأضف البرمجيّات الوسيطة إلى سلسلة طلبات واستجابة الرسائل المنشورة.
import { modifyMessage } from '../middleware'; indexRouter.post('/messages', modifyMessage, addMessage);
يمكننا أن نرى أن وظيفة modifyMessage
تأتي قبل وظيفة addMessage
. نستدعي وظيفة addMessage
من خلال استدعاء next
في البرنامج modifyMessage
. كتجربة ، قم بالتعليق على السطر next()
في منتصف modifyMessage
وشاهد تعليق الطلب.
افتح Postman وأنشئ رسالة جديدة. يجب أن تشاهد السلسلة الملحقة.
هذه نقطة جيدة لارتكاب تغييراتنا.
- الفرع المقابل في الريبو الخاص بي هو 09-middleware.
معالجة الأخطاء والبرمجيات الوسيطة غير المتزامنة
الأخطاء لا مفر منها في أي تطبيق. المهمة المطروحة أمام المطور هي كيفية التعامل مع الأخطاء بأمان قدر الإمكان.
في اكسبرس:
تشير معالجة الخطأ إلى كيفية قيام Express بإمساك الأخطاء التي تحدث بشكل متزامن وغير متزامن ومعالجتها.
إذا كنا نكتب وظائف متزامنة فقط ، فقد لا داعي للقلق كثيرًا بشأن معالجة الأخطاء لأن Express يقوم بالفعل بعمل ممتاز في التعامل مع هذه الوظائف. وبحسب المستندات:
"الأخطاء التي تحدث في التعليمات البرمجية المتزامنة داخل معالجات التوجيه والبرمجيات الوسيطة لا تتطلب أي عمل إضافي."
ولكن بمجرد أن نبدأ في كتابة معالجات التوجيه غير المتزامنة والبرمجيات الوسيطة ، يتعين علينا القيام ببعض معالجة الأخطاء.
إن modifyMessage
الوسيطة editMessage هي وظيفة متزامنة. إذا حدث خطأ في هذه الوظيفة ، فسيقوم Express بالتعامل معها بشكل جيد. لنرى كيف نتعامل مع الأخطاء في البرامج الوسيطة غير المتزامنة.
لنفترض ، قبل إنشاء رسالة ، أننا نريد الحصول على صورة من Lorem Picsum API باستخدام عنوان URL هذا https://picsum.photos/id/0/info
. هذه عملية غير متزامنة يمكن أن تنجح أو تفشل ، وهذا يمثل حالة لنا للتعامل معها.
ابدأ بتثبيت Axios.
# install axios yarn add axios
افتح src / middleware / middleware.js وأضف الوظيفة التالية:
export const performAsyncAction = async (req, res, next) => { try { await axios.get('https://picsum.photos/id/0/info'); next(); } catch (err) { next(err); } };
في هذه الوظيفة غير async
، await
استدعاءًا لواجهة برمجة التطبيقات (لا نحتاج في الواقع إلى البيانات المرتجعة) وبعد ذلك نستدعي الوظيفة next
في سلسلة الطلب. إذا فشل الطلب ، فإننا نكتشف الخطأ ونمرره إلى next
. بمجرد أن يرى Express هذا الخطأ ، فإنه يتخطى جميع البرامج الوسيطة الأخرى في السلسلة. إذا لم نتصل next(err)
، فسيتم تعليق الطلب. إذا استدعينا next()
فقط دون err
، فسيتم متابعة الطلب كما لو لم يحدث شيء ولن يتم اكتشاف الخطأ.
قم باستيراد هذه الوظيفة وإضافتها إلى سلسلة البرامج الوسيطة لمسار رسائل البريد:
import { modifyMessage, performAsyncAction } from '../middleware'; indexRouter.post('/messages', modifyMessage, performAsyncAction, addMessage);
افتح src / app.js وأضف الكود أدناه قبل export default app
مباشرةً.
app.use((err, req, res, next) => { res.status(400).json({ error: err.stack }); }); export default app;
هذا هو معالج الأخطاء لدينا. وفقًا للوثيقة الخاصة بمعالجة الخطأ السريع:
"[...] وظائف معالجة الأخطاء لها أربع وسيطات بدلاً من ثلاث: (err, req, res, next)
."
لاحظ أن معالج الأخطاء هذا يجب أن يأتي أخيرًا ، بعد كل app.use()
. بمجرد أن نواجه خطأً ، نعيد تتبع المكدس برمز الحالة 400
. يمكنك أن تفعل ما تريد مع الخطأ. قد ترغب في تسجيله أو إرساله إلى مكان ما.
هذا مكان جيد لإجراء تغييراتك.
- الفرع المقابل في الريبو الخاص بي هو 10-async-middleware.
نشر في Heroku
- للبدء ، انتقل إلى https://www.heroku.com/ وقم بتسجيل الدخول أو التسجيل.
- قم بتنزيل Heroku CLI وتثبيته من هنا.
- افتح محطة طرفية في مجلد المشروع لتشغيل الأمر.
# login to heroku on command line heroku login
سيؤدي هذا إلى فتح نافذة متصفح ويطلب منك تسجيل الدخول إلى حساب Heroku الخاص بك.
سجّل الدخول لمنح جهازك الوصول إلى حساب Heroku الخاص بك ، وأنشئ تطبيق heroku جديدًا عن طريق تشغيل:
#app name is up to you heroku create app-name
سيؤدي هذا إلى إنشاء التطبيق على Heroku وإرجاع عنواني URL.
# app production url and git url https://app-name.herokuapp.com/ | https://git.heroku.com/app-name.git
انسخ عنوان URL على اليمين وقم بتشغيل الأمر أدناه. لاحظ أن هذه الخطوة اختيارية حيث قد تجد أن Heroku قد أضاف بالفعل عنوان URL البعيد.
# add heroku remote url git remote add heroku https://git.heroku.com/my-shiny-new-app.git
افتح محطة جانبية وقم بتشغيل الأمر أدناه. يُظهر لك هذا سجل التطبيق في الوقت الفعلي كما هو موضح في الصورة.
# see process logs heroku logs --tail
قم بتشغيل الأوامر الثلاثة التالية لتعيين متغيرات البيئة المطلوبة:
heroku config:set TEST_ENV_VARIABLE="Environment variable is coming across." heroku config:set CONNECTION_STRING=your-db-connection-string-here. heroku config:set NPM_CONFIG_PRODUCTION=false
تذكر في نصوصنا ، وضعنا:
"prestart": "babel ./src --out-dir build", "start": "node ./build/bin/www",
لبدء التطبيق ، يجب تجميعه وصولاً إلى prestart
باستخدام babel في الخطوة السابقة لأن babel موجودة فقط في تبعيات التطوير لدينا. يجب علينا ضبط NPM_CONFIG_PRODUCTION
على false
حتى نتمكن من تثبيتها أيضًا.
لتأكيد تعيين كل شيء بشكل صحيح ، قم بتشغيل الأمر أدناه. يمكنك أيضًا زيارة علامة تبويب settings
في صفحة التطبيق والنقر فوق Reveal Config Vars
.
# check configuration variables heroku config
الآن قم بتشغيل git push heroku
.
لفتح التطبيق ، قم بتشغيل:
# open /v1 route heroku open /v1 # open /v1/messages route heroku open /v1/messages
إذا كنت مثلي ، فأنت تستخدم نفس قاعدة بيانات PostgresSQL للتطوير والإنتاج ، فقد تجد أنه في كل مرة تقوم فيها بإجراء اختباراتك ، يتم حذف قاعدة البيانات. لإعادة إنشائه ، يمكنك تشغيل أحد الأمرين التاليين:
# run script locally yarn runQuery # run script with heroku heroku run yarn runQuery
النشر المستمر (CD) مع ترافيس
دعنا الآن نضيف النشر المستمر (CD) لإكمال تدفق CI / CD. سنقوم بالنشر من ترافيس بعد كل تجربة تشغيل ناجحة.
الخطوة الأولى هي تثبيت Travis CI. (يمكنك العثور على تعليمات التثبيت هنا.) بعد تثبيت Travis CI بنجاح ، قم بتسجيل الدخول عن طريق تشغيل الأمر أدناه. (لاحظ أنه يجب القيام بذلك في مستودع مشروعك.)
# login to travis travis login --pro # use this if you're using two factor authentication travis login --pro --github-token enter-github-token-here
إذا كان مشروعك مستضافًا على travis-ci.org ، فقم بإزالة علامة --pro
. للحصول على رمز GitHub المميز ، قم بزيارة صفحة إعدادات المطور الخاصة بحسابك وقم بإنشاء واحد. ينطبق هذا فقط إذا كان حسابك مؤمنًا باستخدام المصادقة الثنائية (2FA).
افتح ملف .travis.yml وأضف قسمًا للنشر:
deploy: provider: heroku app: master: app-name
هنا ، نحدد أننا نريد النشر إلى Heroku. يحدد القسم الفرعي للتطبيق أننا نريد نشر الفرع master
من الريبو الخاص بنا إلى app-name
التطبيق على Heroku. من الممكن نشر فروع مختلفة لتطبيقات مختلفة. يمكنك قراءة المزيد عن الخيارات المتاحة هنا.
قم بتشغيل الأمر أدناه لتشفير مفتاح Heroku API وإضافته إلى قسم النشر:
# encrypt heroku API key and add to .travis.yml travis encrypt $(heroku auth:token) --add deploy.api_key --pro
سيؤدي هذا إلى إضافة القسم الفرعي أدناه إلى قسم النشر.
api_key: secure: very-long-encrypted-api-key-string
الآن قم بإجراء تغييراتك وادفع إلى GitHub أثناء مراقبة سجلاتك. سترى البناء يتم تشغيله بمجرد الانتهاء من اختبار ترافيس. بهذه الطريقة ، إذا كان لدينا اختبار فاشل ، فلن يتم نشر التغييرات أبدًا. وبالمثل ، إذا فشل الإصدار ، فسيفشل التشغيل التجريبي بأكمله. هذا يكمل تدفق CI / CD.
- الفرع المقابل في الريبو الخاص بي هو 11 قرص مضغوط.
خاتمة
إذا كنت قد وصلت إلى هذا الحد ، فأقول ، "ممتاز!" في هذا البرنامج التعليمي ، قمنا بإعداد مشروع Express جديد بنجاح. لقد تقدمنا في تكوين تبعيات التطوير بالإضافة إلى التكامل المستمر (CI). قمنا بعد ذلك بكتابة وظائف غير متزامنة للتعامل مع الطلبات لنقاط نهاية API الخاصة بنا - والتي اكتملت بالاختبارات. ثم نظرنا بإيجاز إلى معالجة الأخطاء. أخيرًا ، نشرنا مشروعنا في Heroku وقمنا بتكوين النشر المستمر.
لديك الآن نموذج لمشروعك الخلفي التالي. لقد فعلنا ما يكفي فقط لتبدأ ، ولكن يجب أن تستمر في التعلم للاستمرار. تأكد من إطلاعك على مستندات Express.js أيضًا. إذا كنت تفضل استخدام MongoDB
بدلاً من PostgreSQL
، فلدي نموذج هنا يفعل ذلك بالضبط. يمكنك التحقق من الإعداد. لديها فقط بضع نقاط من الاختلاف.
موارد
- "إنشاء Express API Backend مع MongoDB ،" Orji Chidi Matthew ، GitHub
- "دليل قصير لتوصيل البرامج الوسيطة ،" ستيفن سوغدن
- "نموذج Express API ،" جيثب
- "AppVeyor vs Travis CI ،" StackShare
- "The Heroku CLI ،" مركز تطوير Heroku
- "انتشار Heroku ،" ترافيس سي آي
- "استخدام البرامج الوسيطة" ، Express.js
- "معالجة الخطأ" ، Express.js
- "الابتداء" ، موكا
-
nyc
(جيثب) - الفيل
- ساعي البريد
- يعبر
- ترافيس سي
- كود المناخ
- PostgreSQL
- pgAdmin