Input Validation — zod, joi bilan so'rovni tekshirish

~20 daq · Express · Autentifikatsiya va Xavfsizlik

Muammodan boshlaylik

Foydalanuvchi ro'yxatdan o'tish formini to'ldirdi. Siz hech narsa tekshirmasdan saqladingiz:

app.post('/register', async (req, res) => {
  const { name, email, age } = req.body;
  await pool.query('INSERT INTO users (name, email, age) VALUES ($1, $2, $3)', [name, email, age]);
  res.status(201).json({ message: 'Yaratildi' });
});

Kimdir quyidagini yuborsa:

{ "name": "", "email": "bu-email-emas", "age": -999 }

Bo'sh nom, noto'g'ri email, manfiy yosh — barchasi ma'lumotlar bazasiga saqlanadi. Keyinchalik bu ma'lumotlar bilan ishlash qiyin, foydalanuvchiga noto'g'ri ma'lumot ko'rsatiladi.

Validation — foydalanuvchidan kelgan ma'lumotni qabul qilishdan oldin tekshirish.


Zod nima?

Zod — TypeScript va JavaScript uchun schema validation kutubxonasi. Siz "ma'lumot qanday ko'rinishi kerak" ni tasvirlaysiz — Zod tekshiradi.

Joi ham mashhur alternativa. Ikkalasi o'xshash, lekin Zod TypeScript bilan yaxshiroq ishlaydi va zamonaviyroq. Yangi loyihalarda Zod tavsiya etiladi.

npm install zod

Schema yaratish

const { z } = require('zod');

const registerSchema = z.object({
  name:  z.string().min(2, 'Ism kamida 2 ta harf').max(50),
  email: z.string().email('Email noto\'g\'ri formatda'),
  age:   z.number().int().min(18, 'Yosh kamida 18').max(120).optional(),
  role:  z.enum(['user', 'admin']).default('user'),
});

Schema — "bu ob'ekt qanday bo'lishi kerak" degan tavsif. Har bir maydon uchun tur va qoidalar belgilanadi.


Validation middleware

Schema ni route da to'g'ridan-to'g'ri ishlatish o'rniga — middleware yaratish yaxshiroq. Bir marta yoziladi, istalgan route da ishlatiladi:

function validate(schema) {
  return (req, res, next) => {
    const result = schema.safeParse(req.body);

    if (!result.success) {
      const errors = result.error.errors.map(e => ({
        field:   e.path.join('.'),
        message: e.message,
      }));
      return res.status(400).json({ errors });
    }

    req.body = result.data; // tozalangan va default qiymatlar bilan
    next();
  };
}

safeParse — xato chiqarmaydi, natijani { success, data, error } sifatida qaytaradi. parse esa xato chiqaradi — try/catch kerak.

req.body = result.data — muhim. Zod ma'lumotni tozalaydi: ortiqcha maydonlarni o'chiradi, default qiymatlarni qo'shadi, turlarni to'g'rilaydi.


Route da ishlatish

const registerSchema = z.object({
  name:     z.string().min(2).max(50),
  email:    z.string().email(),
  password: z.string().min(8, 'Parol kamida 8 ta belgi'),
});

router.post('/register', validate(registerSchema), async (req, res, next) => {
  try {
    // req.body allaqachon tekshirilgan va tozalangan
    const { name, email, password } = req.body;
    // ...
  } catch (err) {
    next(err);
  }
});

Validation muvaffaqiyatsiz bo'lsa — handler chaqirilmaydi. Foydalanuvchi xato xabar oladi:

{
  "errors": [
    { "field": "email", "message": "Invalid email" },
    { "field": "password", "message": "Parol kamida 8 ta belgi" }
  ]
}

Params va query validation

Body dan tashqari params va query ham tekshirilishi kerak:

function validateParams(schema) {
  return (req, res, next) => {
    const result = schema.safeParse(req.params);
    if (!result.success) return res.status(400).json({ message: 'Noto\'g\'ri parametr' });
    req.params = result.data;
    next();
  };
}

const idSchema = z.object({
  id: z.string().regex(/^\d+$/, 'ID raqam bo\'lishi kerak').transform(Number),
});

router.get('/users/:id', validateParams(idSchema), async (req, res, next) => {
  const id = req.params.id; // endi number
  // ...
});

transform(Number) — string ni raqamga aylantiradi. Params har doim string keladi — bu bilan avtomatik konvertatsiya.


Joi — alternativa

Joi ham keng tarqalgan. Sintaksis biroz boshqacha:

const Joi = require('joi');

const registerSchema = 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(18).max(120),
});

// Tekshirish:
const { error, value } = registerSchema.validate(req.body, { abortEarly: false });
if (error) {
  return res.status(400).json({
    errors: error.details.map(d => ({ field: d.path[0], message: d.message }))
  });
}

abortEarly: false — birinchi xatoda to'xtamaslik, barcha xatolarni topish.


Tez-tez uchraydigan xatolar

Validation ni faqat frontendda qilish:

// Frontend tekshirdi — backend tekshirmaydi
// Hacker frontendni o'tkazib, to'g'ridan-to'g'ri API ga yuboradi

Validation har doim backend da ham bo'lishi kerak. Frontend validation — foydalanuvchi qulayligi uchun. Backend validation — xavfsizlik uchun.

req.body = result.data unutish:

const result = schema.safeParse(req.body);
if (!result.success) return res.status(400).json(...);
// req.body = result.data; — unutildi
// req.body da hali eski, tozalanmagan ma'lumot

Xulosa

Validation — foydalanuvchidan kelgan ma'lumotni qabul qilishdan oldin tekshirish. Zod — zamonaviy, TypeScript bilan yaxshi. Joi — eski, lekin keng tarqalgan.

Middleware sifatida yoziladi — bir marta, istalgan route da ishlatiladi. req.body = result.data — tozalangan ma'lumot.

Frontend validation — qulaylik uchun. Backend validation — xavfsizlik uchun. Ikkalasi kerak.


Keyingi dars

30-dars: SQL Injection, XSS, CSRF — qanday himoyalanish