Smart Node.js Form Doğrulaması
Yayınlanan: 2022-03-11Bir API'de gerçekleştirilecek temel görevlerden biri veri doğrulamadır. Bu makalede, verileriniz için nasıl kurşun geçirmez doğrulama ekleyeceğinizi, aynı zamanda onları güzel bir şekilde biçimlendirecek şekilde nasıl ekleyeceğinizi göstermek istiyorum.
Node.js'de özel veri doğrulaması yapmak ne kolay ne de hızlıdır. Her türlü veriyi kapsamak için yazmanız gereken birçok işlevsellik var. Hem Express hem de Koa için birkaç Node.js form veri kitaplığı denememe rağmen, projelerimin ihtiyaçlarını hiçbir zaman karşılamadılar. Kitaplıkları genişletme ve kitaplıkların karmaşık veri yapıları veya eşzamansız doğrulama ile çalışmaması ile ilgili sorunlar vardı.
Datalize ile Node.js'de Form Doğrulama
Bu yüzden sonunda datalize adlı kendi küçük ama güçlü form doğrulama kitaplığımı yazmaya karar verdim. Uzatılabilir, böylece herhangi bir projede kullanabilir ve gereksinimlerinize göre özelleştirebilirsiniz. Bir isteğin gövdesini, sorgusunu veya paragraflarını doğrular. Ayrıca zaman async
filtreleri ve diziler veya iç içe nesneler gibi karmaşık JSON yapılarını da destekler.
Kurmak
Datalize, npm ile kurulabilir:
npm install --save datalize
Bir isteğin gövdesini ayrıştırmak için ayrı bir kitaplık kullanmalısınız. Henüz kullanmadıysanız, Koa için koa-body'yi veya Express için body-parser'ı öneririm.
Bu öğreticiyi önceden kurulmuş HTTP API sunucunuza uygulayabilir veya aşağıdaki basit Koa HTTP sunucu kodunu kullanabilirsiniz.
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');
Ancak, bu bir üretim kurulumu değil (günlüğe kaydetme, yetkilendirmeyi zorlama, hataları işleme vb.)
Not: Tüm kod örnekleri Koa'yı kullanır, ancak veri doğrulama kodu Express için de çalışır. datalize kitaplığında ayrıca Express form doğrulamasını uygulamak için bir örnek vardır.
Temel Node.js Form Doğrulama Örneği
Diyelim ki bir Koa veya Express web sunucunuz ve API'nizde, veritabanında çeşitli alanlara sahip bir kullanıcı oluşturan bir uç noktanız var. Bazı alanlar zorunludur ve bazıları yalnızca belirli değerlere sahip olabilir veya türü düzeltmek için biçimlendirilmelidir.
Bunun gibi basit bir mantık yazabilirsiniz:
/** * @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(); });
Şimdi bu kodu yeniden yazalım ve datalize kullanarak bu isteği doğrulayalım:
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(); });
Daha kısa, daha temiz, okunması çok kolay. Datalize ile bir alan listesi belirleyip bunlara istediğiniz kadar kural (giriş geçersizse hata veren işlevler) veya filtreler (girişi biçimlendiren işlevler) zincirleyebilirsiniz.
Kurallar ve filtreler, tanımlandıkları sırayla yürütülür, bu nedenle önce boşluk için bir dize kırpmak ve ardından herhangi bir değeri olup olmadığını kontrol etmek istiyorsanız, .trim() .required()
.trim()
öğesinden önce tanımlamanız gerekir.
Daha sonra Datalize, yalnızca belirttiğiniz alanlarla bir nesne (daha geniş bağlam nesnesinde .form
olarak bulunur) oluşturur, böylece onları tekrar listelemeniz gerekmez. .form.isValid
özelliği, doğrulamanın başarılı olup olmadığını size söyler.
Otomatik Hata İşleme
Her istekte formun geçerli olup olmadığını kontrol etmek istemiyorsak, veriler doğrulamayı geçmezse talebi iptal eden global bir ara katman ekleyebiliriz.
Bunu yapmak için, bu kod parçasını, Koa/Express uygulama örneğimizi oluşturduğumuz önyükleme dosyamıza eklememiz yeterlidir.
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'); } });
Ve datalize bunu bizim için yapacağından, verilerin artık geçerli olup olmadığını kontrol etmemize gerek yok. Veriler geçersizse, geçersiz alanların listesiyle biçimlendirilmiş bir hata mesajı döndürür.
Sorgu Doğrulama
Evet, sorgu parametrelerinizi çok kolay bir şekilde doğrulayabilirsiniz; yalnızca POST
istekleriyle kullanılması gerekmez. Biz sadece .query()
helper yöntemini kullanıyoruz ve tek fark, verilerin .form
yerine .data
nesnesinde saklanmasıdır.
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; });
Parametre doğrulama için bir yardımcı yöntem de vardır, .params()
. Sorgu ve form verileri, yönlendiricinin .post .post()
yönteminde iki verili ara katman yazılımı geçirilerek birlikte doğrulanabilir.
Daha Fazla Filtre, Dizi ve İç İçe Nesne
Şimdiye kadar Node.js form doğrulamamızda gerçekten basit veriler kullandık. Şimdi diziler, iç içe nesneler vb. gibi daha karmaşık alanları deneyelim:

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, )); });
Doğrulamamız gereken veriler için yerleşik bir kural yoksa, .custom()
yöntemiyle özel bir veri doğrulama kuralı oluşturabilir (harika isim, değil mi?) ve gerekli mantığı buraya yazabiliriz. İç içe nesneler için, datalize()
işlevinde olduğu gibi bir alan listesi belirtebileceğiniz .container( .container()
yöntemi vardır. Kapsayıcıları kapların içine yerleştirebilir veya bunları değerleri dizilere dönüştüren .array()
filtreleriyle destekleyebilirsiniz. .array()
filtresi kapsayıcı olmadan kullanıldığında, dizideki her değere belirtilen kurallar veya filtreler uygulanır.
Böylece .array().select(['read', 'write'])
dizideki her değerin 'read'
veya 'write'
olup olmadığını kontrol eder ve eğer değilse, tüm indekslerin bir listesini döndürür. hatalar. Oldukça havalı, ha?
PUT
/ PATCH
Verilerinizi PUT
/ PATCH
(veya POST
) ile güncellemeye gelince, tüm mantığınızı, kurallarınızı ve filtrelerinizi yeniden yazmanız gerekmez. İsteğe bağlı olarak tanımlanmamışsa, bağlam nesnesinden herhangi bir alanı kaldıracak .optional()
veya .patch()
gibi fazladan bir filtre eklemeniz yeterlidir. ( .optional()
bunu her zaman isteğe bağlı yapacak, .patch()
ise yalnızca HTTP isteğinin yöntemi PATCH
ise isteğe bağlı yapacaktır.) Bu ekstra filtreyi, veritabanınızda hem veri oluşturmak hem de veri güncellemek için çalışacak şekilde ekleyebilirsiniz.
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(); });
İki basit ara katman yazılımı ile tüm POST
/ PUT
/ PATCH
yöntemleri için çoğu mantığı yazabiliriz. userEditMiddleware()
işlevi, düzenlemek istediğimiz kaydın var olup olmadığını doğrular ve aksi takdirde bir hata verir. Ardından userValidator()
, tüm uç noktalar için doğrulamayı yapar. Son olarak, .patch()
filtresi, tanımlı değilse ve isteğin yöntemi PATCH
ise, .form
nesnesindeki herhangi bir alanı kaldırır.
Node.js Form Doğrulama Ekstraları
Özel filtrelerde diğer alanların değerlerini alabilir ve buna göre doğrulama yapabilirsiniz. Ayrıca, tümü özel işlev geri çağırma parametrelerinde sağlandığı için, istek veya kullanıcı bilgileri gibi bağlam nesnesinden herhangi bir veri alabilirsiniz.
Kitaplık, bir dizi temel kural ve filtreyi kapsar, ancak herhangi bir alanla kullanabileceğiniz özel global filtreleri kaydedebilirsiniz, böylece aynı kodu tekrar tekrar yazmak zorunda kalmazsınız:
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); };
Bu iki özel filtreyle, tarih girişini doğrulamak için alanlarınızı .date()
veya .dateTime()
filtreleriyle zincirleyebilirsiniz.
Dosyalar ayrıca datalize kullanılarak da doğrulanabilir: Yalnızca .file()
, .mime()
ve .size()
gibi dosyalar için özel filtreler vardır, böylece dosyaları ayrı ayrı işlemeniz gerekmez.
Şimdi Daha İyi API'ler Yazmaya Başlayın
Hem küçük hem de büyük API'ler için halihazırda birkaç üretim projesinde Node.js form doğrulaması için datalize kullanıyorum. Harika projeleri zamanında ve daha az stresle teslim ederken onları daha okunaklı ve sürdürülebilir hale getirmeme yardımcı oldu. Bir projede, Socket.IO'nun etrafına basit bir sarmalayıcı yazarak WebSocket mesajları için verileri doğrulamak için bile kullandım ve kullanım Koa'daki rotaları tanımlamakla hemen hemen aynıydı, yani bu güzeldi. Yeterli ilgi varsa, bunun için de bir eğitim yazabilirim.
Umarım bu öğretici size yardımcı olur ve Node.js'de güvenlik sorunları veya dahili sunucu hataları olmadan mükemmel şekilde doğrulanmış verilerle daha iyi API'ler oluştururum. Ve en önemlisi, umarım size JavaScript kullanarak form doğrulama için ekstra işlevler yazmaya yatırım yapmak zorunda kalacağınız bir ton zaman kazandırır.