Kodax

التحقق من البيانات 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);
اختبر فهمك