การตรวจสอบแบบฟอร์ม Smart Node.js

เผยแพร่แล้ว: 2022-03-11

งานพื้นฐานอย่างหนึ่งที่ต้องทำใน API คือการตรวจสอบความถูกต้องของข้อมูล ในบทความนี้ ฉันต้องการแสดงวิธีเพิ่มการตรวจสอบความถูกต้องของกระสุนสำหรับข้อมูลของคุณในลักษณะที่ส่งคืนข้อมูลที่มีรูปแบบสวยงาม

การตรวจสอบข้อมูลที่กำหนดเองใน Node.js นั้นไม่ใช่เรื่องง่ายหรือรวดเร็ว มีฟังก์ชันมากมายที่คุณต้องเขียนเพื่อให้ครอบคลุมข้อมูลทุกประเภท แม้ว่าฉันจะลองใช้ Node.js ในรูปแบบไลบรารีข้อมูลแล้ว—สำหรับทั้ง Express และ Koa— แต่ก็ไม่เคยตอบสนองความต้องการของโครงการของฉันเลย มีปัญหากับการขยายไลบรารีและไลบรารีไม่ทำงานกับโครงสร้างข้อมูลที่ซับซ้อนหรือการตรวจสอบความถูกต้องแบบอะซิงโครนัส

การตรวจสอบแบบฟอร์มใน Node.js ด้วย 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 เช่นกัน ไลบรารี datalize ยังมีตัวอย่างสำหรับการนำการตรวจสอบแบบฟอร์ม 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()

จากนั้น Datalize จะสร้างออบเจ็กต์ (มีให้ใช้งานในรูปแบบ . .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'); } });

และเราไม่ต้องตรวจสอบว่าข้อมูลถูกต้องหรือไม่ เนื่องจาก datalize จะทำเพื่อเรา หากข้อมูลไม่ถูกต้อง จะแสดงข้อความแสดงข้อผิดพลาดที่จัดรูปแบบพร้อมรายการฟิลด์ที่ไม่ถูกต้อง

การตรวจสอบความถูกต้องของแบบสอบถาม

ได้ คุณยังสามารถตรวจสอบพารามิเตอร์การสืบค้นของคุณได้อย่างง่ายดาย โดยไม่จำเป็นต้องใช้กับคำขอ 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() ข้อมูลแบบสอบถามและแบบฟอร์มสามารถตรวจสอบร่วมกันได้โดยส่งมิดเดิลแวร์ datalize สองตัวในเมธอด .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 .file() , .mime() และ .size( .size() ดังนั้นคุณจึงไม่ต้องจัดการไฟล์แยกกัน

เริ่มเขียน API ที่ดีขึ้นตอนนี้

ฉันเคยใช้ datalize สำหรับการตรวจสอบแบบฟอร์ม Node.js ในหลายโปรเจ็กต์ที่ใช้งานจริงมาแล้ว สำหรับ API ทั้งขนาดเล็กและขนาดใหญ่ มันช่วยให้ฉันนำเสนอโครงการดีๆ ได้ตรงเวลาและเครียดน้อยลง ขณะเดียวกันก็ทำให้อ่านและบำรุงรักษาได้ง่ายขึ้น ในโครงการหนึ่ง ฉันได้ใช้มันเพื่อตรวจสอบความถูกต้องของข้อมูลสำหรับข้อความ WebSocket โดยการเขียน wrapper แบบง่าย ๆ รอบ Socket.IO และการใช้งานก็ค่อนข้างเหมือนกับการกำหนดเส้นทางใน Koa ดังนั้นจึงเป็นเรื่องที่ดี หากมีความสนใจมากพอ ฉันอาจเขียนบทช่วยสอนสำหรับเรื่องนั้นด้วย

ฉันหวังว่าบทช่วยสอนนี้จะช่วยคุณและฉันสร้าง API ที่ดีขึ้นใน Node.js ด้วยข้อมูลที่ได้รับการตรวจสอบอย่างสมบูรณ์ โดยไม่มีปัญหาด้านความปลอดภัย หรือข้อผิดพลาดของเซิร์ฟเวอร์ภายใน และที่สำคัญที่สุด ฉันหวังว่าจะช่วยคุณประหยัดเวลาได้มาก โดยที่คุณไม่ต้องลงทุนเขียนฟังก์ชันเพิ่มเติมสำหรับการตรวจสอบความถูกต้องของแบบฟอร์มโดยใช้ JavaScript