التحقق من صحة نموذج Node.js الذكية
نشرت: 2022-03-11يعد التحقق من صحة البيانات إحدى المهام الأساسية التي يجب إجراؤها في واجهة برمجة التطبيقات. في هذه المقالة ، أود أن أوضح لك كيفية إضافة التحقق من صحة الرصاص لبياناتك بطريقة تعيدها أيضًا بتنسيق جيد.
إجراء التحقق من صحة البيانات المخصصة في Node.js ليس سهلاً ولا سريعًا. هناك الكثير من الوظائف التي تحتاج إلى كتابتها لتغطية أي نوع من البيانات. بينما جربت عددًا قليلاً من مكتبات بيانات النماذج Node.js - لكل من Express و Koa - لم يلبوا أبدًا احتياجات مشاريعي. كانت هناك مشاكل في توسيع المكتبات والمكتبات لا تعمل مع هياكل البيانات المعقدة أو التحقق غير المتزامن.
التحقق من صحة النموذج في Node.js مع Datalize
لهذا السبب قررت في النهاية أن أكتب مكتبة التحقق من صحة النموذج الصغيرة والفعالة الخاصة بي والتي تسمى datalize. إنه قابل للتمديد ، لذا يمكنك استخدامه في أي مشروع وتخصيصه وفقًا لمتطلباتك. يتحقق من صحة نص الطلب أو استعلامه أو معلماته. كما أنه يدعم عوامل التصفية غير async
JSON المعقدة مثل المصفوفات أو الكائنات المتداخلة .
اقامة
يمكن تثبيت Datalize عبر npm:
npm install --save datalize
لتحليل نص الطلب ، يجب عليك استخدام مكتبة منفصلة. إذا لم تكن تستخدم واحدًا بالفعل ، فإنني أوصي بـ koa-body لـ Koa أو body-parser لـ Express.
يمكنك تطبيق هذا البرنامج التعليمي على خادم HTTP API الذي تم إعداده بالفعل ، أو استخدام رمز خادم Koa HTTP البسيط التالي.
const Koa = require('koa'); const bodyParser = require('koa-body'); const app = new Koa(); const router = new (require('koa-router'))(); // helper for returning errors in routes app.context.error = function(code, obj) { this.status = code; this.body = obj; }; // add koa-body middleware to parse JSON and form-data body app.use(bodyParser({ enableTypes: ['json', 'form'], multipart: true, formidable: { maxFileSize: 32 * 1024 * 1024, } })); // Routes... // connect defined routes as middleware to Koa app.use(router.routes()); // our app will listen on port 3000 app.listen(3000); console.log(' API listening on 3000');
ومع ذلك ، هذا ليس إعداد إنتاج (يجب عليك استخدام التسجيل ، وفرض التفويض ، ومعالجة الأخطاء ، وما إلى ذلك) ، ولكن هذه الأسطر القليلة من التعليمات البرمجية ستعمل بشكل جيد مع الأمثلة التي سأعرضها لك.
ملاحظة: تستخدم جميع أمثلة الكود Koa ، لكن كود التحقق من صحة البيانات سيعمل مع Express أيضًا. تحتوي مكتبة البيانات أيضًا على مثال لتطبيق التحقق من صحة النموذج السريع.
مثال أساسي للتحقق من صحة نموذج Node.js
لنفترض أن لديك خادم ويب Koa أو Express ونقطة نهاية في واجهة برمجة التطبيقات (API) الخاصة بك والتي تنشئ مستخدمًا بعدة حقول في قاعدة البيانات. بعض الحقول مطلوبة ، وبعضها يمكن أن يحتوي فقط على قيم محددة أو يجب تنسيقه على النوع الصحيح.
يمكنك كتابة منطق بسيط مثل هذا:
/** * @api {post} / Create a user * ... */ router.post('/', (ctx) => { const data = ctx.request.body; const errors = {}; if (!String(data.name).trim()) { errors.name = ['Name is required']; } if (!(/^[\-0-9a-zA-Z\.\+_]+@[\-0-9a-zA-Z\.\+_]+\.[a-zA-Z]{2,}$/).test(String(data.email))) { errors.email = ['Email is not valid.']; } if (Object.keys(errors).length) { return ctx.error(400, {errors}); } const user = await User.create({ name: data.name, email: data.email, }); ctx.body = user.toJSON(); });
لنقم الآن بإعادة كتابة هذا الرمز والتحقق من صحة هذا الطلب باستخدام datalize:
const datalize = require('datalize'); const field = datalize.field; /** * @api {post} / Create a user * ... */ router.post('/', datalize([ field('name').trim().required(), field('email').required().email(), ]), (ctx) => { if (!ctx.form.isValid) { return ctx.error(400, {errors: ctx.form.errors}); } const user = await User.create(ctx.form); ctx.body = user.toJSON(); });
أقصر وأنظف وسهل القراءة. باستخدام datalize ، يمكنك تحديد قائمة من الحقول والتسلسل إليها مثل العديد من القواعد (الوظائف التي تتسبب في خطأ إذا كان الإدخال غير صالح) أو عوامل التصفية (الوظائف التي تنسق الإدخال) كما تريد.
يتم تنفيذ القواعد والمرشحات بنفس الترتيب الذي تم تحديده به ، لذلك إذا كنت تريد قص سلسلة للمسافة البيضاء أولاً ثم تحقق مما إذا كانت تحتوي على أي قيمة ، فعليك تحديد .trim()
قبل .required()
.
سيقوم .form
بعد ذلك بإنشاء كائن (متاح على هيئة نموذج في كائن السياق الأوسع) باستخدام الحقول التي حددتها فقط ، حتى لا تضطر إلى سردها مرة أخرى. تخبرك الخاصية .form.isValid
ما إذا كان التحقق من الصحة ناجحًا أم لا.
المعالجة التلقائية للخطأ
إذا لم نرغب في التحقق مما إذا كان النموذج صالحًا أم لا مع كل طلب ، فيمكننا إضافة برمجية وسيطة عالمية تلغي الطلب إذا لم تنجح البيانات في التحقق من الصحة.
للقيام بذلك ، نقوم فقط بإضافة هذا الجزء من الكود إلى ملف التمهيد الخاص بنا حيث نقوم بإنشاء مثيل تطبيق Koa / Express الخاص بنا.
const datalize = require('datalize'); // set datalize to throw an error if validation fails datalize.set('autoValidate', true); // only Koa // add to very beginning of Koa middleware chain app.use(async (ctx, next) => { try { await next(); } catch (err) { if (err instanceof datalize.Error) { ctx.status = 400; ctx.body = err.toJSON(); } else { ctx.status = 500; ctx.body = 'Internal server error'; } } }); // only Express // add to very end of Express middleware chain app.use(function(err, req, res, next) { if (err instanceof datalize.Error) { res.status(400).send(err.toJSON()); } else { res.send(500).send('Internal server error'); } });
ولا يتعين علينا التحقق مما إذا كانت البيانات صالحة بعد الآن ، حيث أن البيانات الرقمية ستفعل ذلك لنا. إذا كانت البيانات غير صالحة ، فسوف تقوم بإرجاع رسالة خطأ منسقة بقائمة من الحقول غير الصالحة.
التحقق من صحة الاستعلام
نعم ، يمكنك حتى التحقق من صحة معلمات الاستعلام الخاصة بك بسهولة شديدة - لا يلزم استخدامها مع طلبات POST
فقط. نستخدم فقط الأسلوب المساعد .query()
، والفرق الوحيد هو أن البيانات مخزنة في كائن .data
بدلاً من .form
.
const datalize = require('datalize'); const field = datalize.field; /** * @api {get} / List users * ... */ router.post('/', datalize.query([ field('keywords').trim(), field('page').default(1).number(), field('perPage').required().select([10, 30, 50]), ]), (ctx) => { const limit = ctx.data.perPage; const where = { }; if (ctx.data.keywords) { where.name = {[Op.like]: ctx.data.keywords + '%'}; } const users = await User.findAll({ where, limit, offset: (ctx.data.page - 1) * limit, }); ctx.body = users; });
هناك أيضًا طريقة مساعدة للتحقق من صحة المعلمة ، .params()
. يمكن التحقق من بيانات الاستعلام والنموذج معًا عن طريق تمرير اثنين من البرامج الوسيطة للتحويل في طريقة .post .post()
الخاصة بالموجه.

المزيد من عوامل التصفية والصفائف والكائنات المتداخلة
لقد استخدمنا حتى الآن بيانات بسيطة حقًا في عملية التحقق من صحة النموذج Node.js. الآن دعنا نجرب بعض الحقول الأكثر تعقيدًا مثل المصفوفات والكائنات المتداخلة وما إلى ذلك:
const datalize = require('datalize'); const field = datalize.field; const DOMAIN_ERROR = "Email's domain does not have a valid MX (mail) entry in its DNS record"; /** * @api {post} / Create a user * ... */ router.post('/', datalize([ field('name').trim().required(), field('email').required().email().custom((value) => { return new Promise((resolve, reject) => { dns.resolve(value.split('@')[1], 'MX', function(err, addresses) { if (err || !addresses || !addresses.length) { return reject(new Error(DOMAIN_ERROR)); } resolve(); }); }); }), field('type').required().select(['admin', 'user']), field('languages').array().container([ field('id').required().id(), field('level').required().select(['beginner', 'intermediate', 'advanced']) ]), field('groups').array().id(), ]), async (ctx) => { const {languages, groups} = ctx.form; delete ctx.form.languages; delete ctx.form.groups; const user = await User.create(ctx.form); await UserGroup.bulkCreate(groups.map(groupId => ({ groupId, userId: user.id, }))); await UserLanguage.bulkCreate(languages.map(item => ({ languageId: item.id, userId: user.id, level: item.level, )); });
إذا لم تكن هناك قاعدة مضمنة للبيانات التي نحتاج إلى التحقق من صحتها ، فيمكننا إنشاء قاعدة مخصصة للتحقق من صحة البيانات باستخدام طريقة .custom()
(اسم رائع ، أليس كذلك؟) وكتابة المنطق الضروري هناك. بالنسبة للكائنات المتداخلة ، توجد طريقة .container()
التي يمكنك من خلالها تحديد قائمة الحقول بنفس الطريقة كما في وظيفة datalize()
. يمكنك تداخل الحاويات داخل الحاويات أو استكمالها .array()
، والتي تحول القيم إلى مصفوفات. عند استخدام عامل التصفية .array()
بدون حاوية ، يتم تطبيق القواعد أو عوامل التصفية المحددة على كل قيمة في المصفوفة.
إذاً .array().select(['read', 'write'])
سيتحقق مما إذا كانت كل قيمة في المصفوفة إما 'read'
أو 'write'
وإذا لم تكن هناك أي قيمة ، فسيعيد قائمة بجميع الفهارس التي تحتوي على أخطاء. رائع جدا ، أليس كذلك؟
PUT
/ PATCH
عندما يتعلق الأمر بتحديث بياناتك باستخدام PUT
/ PATCH
(أو POST
) ، فلن تضطر إلى إعادة كتابة كل منطقك وقواعدك وعوامل التصفية. ما عليك سوى إضافة عامل تصفية إضافي مثل .optional()
أو .patch()
، والذي سيزيل أي حقل من كائن السياق إذا لم يتم تحديده في الطلب. ( .optional()
سيجعلها اختيارية دائمًا ، بينما .patch()
ستجعلها اختيارية فقط إذا كانت طريقة طلب HTTP هي PATCH
.) يمكنك إضافة عامل التصفية الإضافي هذا بحيث يعمل من أجل إنشاء وتحديث البيانات في قاعدة البيانات الخاصة بك.
const datalize = require('datalize'); const field = datalize.field; const userValidator = datalize([ field('name').patch().trim().required(), field('email').patch().required().email(), field('type').patch().required().select(['admin', 'user']), ]); const userEditMiddleware = async (ctx, next) => { const user = await User.findByPk(ctx.params.id); // cancel request here if user was not found if (!user) { throw new Error('User was not found.'); } // store user instance in the request so we can use it later ctx.user = user; return next(); }; /** * @api {post} / Create a user * ... */ router.post('/', userValidator, async (ctx) => { const user = await User.create(ctx.form); ctx.body = user.toJSON(); }); /** * @api {put} / Update a user * ... */ router.put('/:id', userEditMiddleware, userValidator, async (ctx) => { await ctx.user.update(ctx.form); ctx.body = ctx.user.toJSON(); }); /** * @api {patch} / Patch a user * ... */ router.patch('/:id', userEditMiddleware, userValidator, async (ctx) => { if (!Object.keys(ctx.form).length) { return ctx.error(400, {message: 'Nothing to update.'}); } await ctx.user.update(ctx.form); ctx.body = ctx.user.toJSON(); });
باستخدام اثنين من البرامج الوسيطة البسيطة ، يمكننا كتابة معظم المنطق لجميع طرق POST
/ PUT
/ PATCH
. تتحقق الوظيفة userEditMiddleware()
مما إذا كان السجل الذي نريد تحريره موجودًا وتلقي بخطأ بخلاف ذلك. ثم userValidator()
بالتحقق من صحة جميع نقاط النهاية. أخيرًا ، سيزيل عامل التصفية .patch()
أي حقل من كائن .form
إذا لم يتم تعريفه وإذا كانت طريقة الطلب هي PATCH
.
إضافات التحقق من صحة النموذج Node.js
في المرشحات المخصصة ، يمكنك الحصول على قيم الحقول الأخرى وإجراء التحقق بناءً على ذلك. يمكنك أيضًا الحصول على أي بيانات من كائن السياق ، مثل الطلب أو معلومات المستخدم ، حيث يتم توفيرها جميعًا في معلمات رد الاتصال للوظيفة المخصصة.
تغطي المكتبة مجموعة أساسية من القواعد والفلاتر ، ولكن يمكنك تسجيل عوامل تصفية عامة مخصصة يمكنك استخدامها مع أي حقول ، حتى لا تضطر إلى كتابة نفس الرمز مرارًا وتكرارًا:
const datalize = require('datalize'); const Field = datalize.Field; Field.prototype.date = function(format = 'YYYY-MM-DD') { return this.add(function(value) { const date = value ? moment(value, format) : null; if (!date || !date.isValid()) { throw new Error('%s is not a valid date.'); } return date.format(format); }); }; Field.prototype.dateTime = function(format = 'YYYY-MM-DD HH:mm') { return this.date(format); };
باستخدام هذين الفلترين المخصصين ، يمكنك ربط الحقول .date()
أو .dateTime()
للتحقق من صحة إدخال التاريخ.
يمكن أيضًا التحقق من صحة الملفات باستخدام datalize: هناك عوامل تصفية خاصة لملفات مثل .file()
و .mime()
و .size()
حتى لا تضطر إلى التعامل مع الملفات بشكل منفصل.
ابدأ في كتابة واجهات برمجة تطبيقات أفضل الآن
لقد كنت أستخدم datalize للتحقق من صحة نموذج Node.js في العديد من مشاريع الإنتاج بالفعل ، لكل من واجهات برمجة التطبيقات الصغيرة والكبيرة. لقد ساعدني ذلك في تقديم مشاريع رائعة في الوقت المحدد وبضغط أقل مع جعلها أكثر قابلية للقراءة والصيانة. حتى أنني استخدمته في أحد المشروعات للتحقق من صحة البيانات الخاصة برسائل WebSocket عن طريق كتابة غلاف بسيط حول Socket.IO وكان الاستخدام مشابهًا إلى حد كبير لتعريف المسارات في Koa ، لذلك كان ذلك رائعًا. إذا كان هناك اهتمام كافٍ ، فقد أكتب درسًا تعليميًا لذلك أيضًا.
آمل أن يساعدك هذا البرنامج التعليمي في إنشاء واجهات برمجة تطبيقات أفضل في Node.js ، مع بيانات تم التحقق من صحتها تمامًا دون مشاكل أمنية أو أخطاء داخلية في الخادم. والأهم من ذلك ، آمل أن يوفر لك الكثير من الوقت الذي قد تضطر إلى الاستثمار فيه في كتابة وظائف إضافية للتحقق من صحة النموذج باستخدام JavaScript.