التحقق من البيانات Validation — REST APIs
التحقق من البيانات (Validation)
قاعدة لا استثناء لها: الخادم لا يثق بأي مدخل. تحقق الواجهة لراحة المستخدم فقط؛ أي شخص يستطيع إرسال طلب مباشرة بـ curl متجاوزاً واجهتك.
ماذا نتحقق منه؟
| الفحص | مثال |
|---|---|
| الوجود | email مطلوب |
| النوع | age رقم لا نص |
| الصيغة | بريد صالح، تاريخ ISO |
| المدى/الطول | 1 ≤ الكمية ≤ 100، الاسم ≤ 50 حرفاً |
| القيم المسموحة | status ∈ {pending, paid} |
| الحقول المسموحة فقط | تجاهل/ارفض role و isAdmin إن أرسلها العميل |
الأخير مهم أمنياً: User.create(req.body) مع جسم فيه "role": "admin" = ترقية صلاحيات (Mass Assignment).
Zod: مخطط واحد للتحقق والأنواع
const { z } = require("zod");
const createUserSchema = z.object({
name: z.string().trim().min(2, "الاسم قصير").max(50),
email: z.string().email("بريد غير صالح").toLowerCase(),
age: z.coerce.number().int().min(13).max(120).optional(),
}).strict(); // ارفض أي حقل غير معرّف (مثل role)
function validate(schema) {
return (req, res, next) => {
const result = schema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({
error: { code: "VALIDATION_ERROR", details: result.error.issues.map((i) => ({ field: i.path.join("."), message: i.message })) },
});
}
req.body = result.data; // البيانات بعد التنظيف والتحويل
next();
};
}safeParse لا يرمي استثناء، و result.data يحتوي القيم بعد التحويل (أرقام بدل نصوص، أحرف صغيرة...).
express-validator: البديل الشائع
يعمل بسلسلة قواعد لكل حقل (body("email").isEmail().normalizeEmail()) ثم validationResult(req). المثال الكامل يعرضه. اختر أداة واحدة لمشروعك.
نصائح
- تحقق من params و query أيضاً، لا الجسم فقط (رقم الصفحة، المعرّفات).
- التحقق ليس بديلاً عن الأمان في الطبقات الأخرى: استعلامات معاملية في SQL، وتهريب المخرجات في HTML.
- الرسائل للعميل تصف المشكلة دون كشف تفاصيل داخلية.
- قيود قاعدة البيانات (NOT NULL، UNIQUE، CHECK) خط دفاع أخير.
تمرين
اكتب مخطط Zod لطلب "إنشاء طلبية": مصفوفة عناصر (1–20) كل عنصر productId نصي و quantity بين 1 و 10، وعنوان التوصيل اختياري. جرّب إرسال quantity: "3" و quantity: 0 و حقل إضافي discount.
// ===== تثبيت express-validator =====
// npm install express-validator
const { body, param, query, validationResult } = require('express-validator');
// ===== Validation Middleware =====
const validateUser = [
body('name')
.trim()
.notEmpty().withMessage('الاسم مطلوب')
.isLength({ min: 2, max: 50 }).withMessage('الاسم يجب أن يكون 2-50 حرف'),
body('email')
.trim()
.notEmpty().withMessage('البريد مطلوب')
.isEmail().withMessage('بريد إلكتروني غير صالح')
.normalizeEmail(),
body('password')
.notEmpty().withMessage('كلمة المرور مطلوبة')
.isLength({ min: 8 }).withMessage('كلمة المرور 8 أحرف على الأقل')
.matches(/\d/).withMessage('يجب أن تحتوي على رقم')
.matches(/[A-Z]/).withMessage('يجب أن تحتوي على حرف كبير'),
body('age')
.optional()
.isInt({ min: 1, max: 120 }).withMessage('عمر غير صالح'),
body('role')
.optional()
.isIn(['user', 'admin', 'moderator']).withMessage('دور غير صالح'),
];
// ===== Handle Validation Errors =====
const handleValidation = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
errors: errors.array().map(err => ({
field: err.path,
message: err.msg
}))
});
}
next();
};
// ===== استخدام في Route =====
app.post('/api/users', validateUser, handleValidation, async (req, res) => {
// البيانات صالحة هنا
const user = await createUser(req.body);
res.status(201).json({ success: true, data: user });
});
// ===== Joi Alternative =====
const Joi = require('joi');
const userSchema = Joi.object({
name: Joi.string().min(2).max(50).required(),
email: Joi.string().email().required(),
password: Joi.string().min(8).required(),
age: Joi.number().integer().min(1).max(120)
});
const validateWithJoi = (schema) => (req, res, next) => {
const { error } = schema.validate(req.body, { abortEarly: false });
if (error) {
return res.status(400).json({
success: false,
errors: error.details.map(d => ({ message: d.message }))
});
}
next();
};
app.post('/api/users', validateWithJoi(userSchema), createUser);اختبر فهمك