البرمجة متوسط 9 دقيقة

كيفية التحقق من مدخلات API في Node.js باستخدام Zod

كل قيمة في req.body غير موثوقة. يستطيع المستخدم إرسال حقول مفقودة، نوع خاطئ، سلسلة فارغة حيث تتوقع بريدًا إلكترونيًا، أو blob بحجم 10 ميغابايت حيث تتوقع اسمًا. بدون تحقق إما تنهار أو تُفسد قاعدة بياناتك.

Zod هي أفضل مكتبة تحقق لمشاريع Node.js التي تستخدم TypeScript — تعرّف schema مرة واحدة، تتحقق في وقت التشغيل، وتستنتج أنواع TypeScript الثابتة من نفس الـ schema فلا يوجد تكرار. يغطي هذا الدليل تعريف schemas، تحليل request bodies، إعادة أخطاء منظّمة، كتابة middleware قابل للإعادة للتحقق، واستخدام Zod للتنظيف أيضًا.

الخطوات

  1. 1

    ثبّت Zod

    Zod ليس لها تبعيات وقت تشغيل وتعمل مع JavaScript العادي أو TypeScript. ثبّتها مرة واحدة واستخدمها في كامل المشروع.

    bash
    npm install zod
  2. 2

    عرّف أول schema

    تصف Zod schema الشكل والقيود التي تتوقعها تمامًا. أعلنها في أعلى الملف (أو في مجلد schemas/ مخصص) حتى يمكن مشاركة نفس الـ schema بين معالج المسار وأي اختبارات.

    typescript
    // src/schemas/user.schema.ts (أو .js)
    import { z } from "zod"
    
    export const createUserSchema = z.object({
      name: z.string().min(2).max(100).trim(),
      email: z.string().email().toLowerCase(),
      age: z.number().int().min(18).max(120),
      role: z.enum(["admin", "user"]).default("user"),
    })
    
    // TypeScript: استنتج النوع — لا حاجة لـ interface منفصل
    export type CreateUserInput = z.infer<typeof createUserSchema>
    // { name: string; email: string; age: number; role: "admin" | "user" }
  3. 3

    استخدم safeParse للتحقق من طلب

    schema.parse() يرمي استثناءً عند الفشل. schema.safeParse() يُعيد كائن نتيجة بدلًا من ذلك — استخدمه في معالجات المسار حتى تتحكم في الاستجابة. عند النجاح، result.data هي القيمة المحللة المُنظّفة (سلاسل مُقلّصة، قيم افتراضية مُطبّقة). عند الفشل، result.error تحتوي القائمة الكاملة للمشكلات.

    typescript
    // src/controllers/usersController.ts
    import { createUserSchema } from "../schemas/user.schema"
    
    export const create = (req: Request, res: Response) => {
      const result = createUserSchema.safeParse(req.body)
    
      if (!result.success) {
        return res.status(422).json({
          error: "Validation failed",
          issues: result.error.issues.map((issue) => ({
            field: issue.path.join("."),
            message: issue.message,
          })),
        })
      }
    
      // result.data مكتوب بأنواع كاملة وآمن للاستخدام
      const { name, email, age, role } = result.data
      // ... احفظ في قاعدة البيانات
      res.status(201).json({ name, email, age, role })
    }
  4. 4

    أعد أخطاء تحقق منظّمة

    يحتاج العملاء تفاصيل أخطاء قابلة للقراءة آليًا، لا مجرد 422 عام. الصيغة أدناه تعطي كل مشكلة مسار حقلها ورسالة بشرية — سهلة العرض بجانب حقول النماذج مباشرةً.

    json
    // مثال على body الاستجابة عند فشل التحقق
    // POST /api/users  {"name":"A","email":"not-an-email","age":15}
    {
      "error": "Validation failed",
      "issues": [
        {
          "field": "name",
          "message": "String must contain at least 2 character(s)"
        },
        {
          "field": "email",
          "message": "Invalid email"
        },
        {
          "field": "age",
          "message": "Number must be greater than or equal to 18"
        }
      ]
    }
  5. 5

    استخرج middleware تحقق قابل للإعادة

    وضع استدعاءات safeParse مباشرةً في كل controller متكرر. اكتب مصنع middleware من الدرجة العليا يأخذ schema ويُعيد middleware — المسارات تبقى نظيفة والتحقق يصبح تصريحيًا.

    typescript
    // src/middleware/validate.ts
    import { ZodSchema } from "zod"
    import { Request, Response, NextFunction } from "express"
    
    export function validate(schema: ZodSchema) {
      return (req: Request, res: Response, next: NextFunction) => {
        const result = schema.safeParse(req.body)
        if (!result.success) {
          return res.status(422).json({
            error: "Validation failed",
            issues: result.error.issues.map((issue) => ({
              field: issue.path.join("."),
              message: issue.message,
            })),
          })
        }
        req.body = result.data // استبدل body الخام بالبيانات المحللة والمُنظّفة
        next()
      }
    }
    
    // الاستخدام في ملف route:
    import { validate } from "../middleware/validate"
    import { createUserSchema } from "../schemas/user.schema"
    
    router.post("/", validate(createUserSchema), usersController.create)
  6. 6

    نظّف السلاسل داخل الـ schema

    تعمل تحولات Zod أثناء التحليل، لذا التنظيف مدمج في الـ schema — لا مبعثر عبر controllers. قلّص المسافات البيضاء، وحدّد الحالة، وفرض حدود الطول في مكان واحد. القيمة في req.body بعد safeParse نظيفة بالفعل.

    typescript
    export const createPostSchema = z.object({
      title: z
        .string()
        .min(5, "Title must be at least 5 characters")
        .max(200, "Title cannot exceed 200 characters")
        .trim(),                    // أزل المسافات البيضاء من الطرفين
    
      slug: z
        .string()
        .toLowerCase()              // وحّد الحالة قبل التخزين
        .regex(/^[a-z0-9-]+$/, "Slug can only contain lowercase letters, numbers, and hyphens"),
    
      content: z
        .string()
        .min(20)
        .max(50_000),               // امنع payloads بحجم ميغابايتات
    
      published: z.boolean().default(false),
    
      tags: z
        .array(z.string().trim().max(50))
        .max(10, "No more than 10 tags allowed")
        .default([]),
    })
  7. 7

    تحقق من params المسار وquery strings

    Zod لا تقتصر على request bodies. تحقق من معاملات المسار (التي تكون دائمًا سلاسل في Express) وquery strings بنفس الطريقة. حوّل الأنواع صراحةً — req.params.id تأتي كسلسلة ويجب تحويلها إلى رقم.

    typescript
    // التحقق من params المسار
    const idParamSchema = z.object({
      id: z.coerce.number().int().positive(), // يحول "42" → 42
    })
    
    // التحقق من query strings
    const listQuerySchema = z.object({
      page: z.coerce.number().int().positive().default(1),
      limit: z.coerce.number().int().min(1).max(100).default(20),
      search: z.string().trim().max(100).optional(),
    })
    
    // middleware التحقق موسّع للـ params:
    export function validateParams(schema: ZodSchema) {
      return (req: Request, res: Response, next: NextFunction) => {
        const result = schema.safeParse(req.params)
        if (!result.success) {
          return res.status(400).json({ error: "Invalid URL parameter", issues: result.error.issues })
        }
        req.params = result.data
        next()
      }
    }

نصائح ومحاذير

  • استخدم `z.coerce.number()` بدلًا من `z.number()` لأي قيمة قد تصل كسلسلة (params المسار، query strings). يحول `"42"` إلى `42` قبل التحقق.
  • ضع schemas في مجلد `src/schemas/` واستوردها في كل من معالجات المسار والاختبارات. الـ schema تُضاعف كتوثيق لعقد API المتوقع.
  • استخدم `z.discriminatedUnion()` عندما يمكن أن يكون لـ request body أشكال مختلفة حسب حقل `type` — أسرع ويعطي رسائل خطأ أفضل من `z.union()` العادي.
  • `result.error.flatten()` بديل مناسب لـ `.issues` يجمع الأخطاء حسب الحقل: `{ fieldErrors: { email: ["Invalid email"] }, formErrors: [] }` — مفيد لاستجابات التحقق من النماذج.

خاتمة

يحوّل Zod التحقق من المدخلات من فكرة لاحقة إلى جزء أساسي من عقد API. عرّف الـ schema مرة واحدة، احصل على الأمان في وقت التشغيل وأنواع TypeScript مجانًا، وعالج الأخطاء في middleware واحد بدلًا من تبعثر الفحوصات عبر كل معالج. مقترنًا بنمط middleware التحقق، كل مسار في المشروع يحصل على استجابات أخطاء متسقة ومنظّمة بأقل كود ممكن لكل مسار.

#Node.js #Zod #Validation
العودة إلى جميع الأدلة

هل تحتاج مساعدة في مشروعك؟

احجز استشارة مجانية لمدة 30 دقيقة لمناقشة تحدياتك التقنية واستكشاف الحلول معًا.