Интеграция с базами данных

Интеграция библиотеки Validator.js с базами данных решает сразу несколько критически важных задач:

  • предотвращение записи некорректных данных;
  • защита от SQL-инъекций и мусорных значений;
  • унификация правил валидации;
  • подготовка данных перед сохранением;
  • снижение количества ошибок на уровне ORM и СУБД;
  • обеспечение согласованности между API и хранилищем данных.

Даже если база данных уже содержит ограничения (UNIQUE, NOT NULL, CHECK, типы колонок), прикладная валидация остаётся обязательной. Validator.js выполняет предварительную проверку ещё до обращения к БД.


Архитектура валидации перед сохранением данных

Типичный поток обработки выглядит следующим образом:

HTTP Request
    ↓
Парсинг данных
    ↓
Validator.js
    ↓
ORM / Query Builder
    ↓
База данных

Validator.js должен работать до выполнения SQL-запросов.

Пример:

import validator fr om 'validator'

function validateUser(data) {
    if (!validator.isEmail(data.email)) {
        throw new Error('Некорректный email')
    }

    if (!validator.isLength(data.password, { min: 8 })) {
        throw new Error('Слишком короткий пароль')
    }
}

Валидация перед INSERT

Проверка email перед сохранением

import validator fr om 'validator'
import db fr om './db.js'

async function createUser(email) {
    if (!validator.isEmail(email)) {
        throw new Error('Некорректный email')
    }

    await db.query(
        'INS ERT INTO users(email) VALUES($1)',
        [email]
    )
}

Здесь Validator.js:

  • предотвращает попадание неверного email;
  • уменьшает вероятность хранения мусорных данных;
  • снижает количество ошибок в бизнес-логике.

Нормализация данных перед записью

Validator.js содержит функции sanitization.

import validator fr om 'validator'

const email = validator.normalizeEmail(
    ' USER@GMAIL.COM '
)

console.log(email)

Результат:

user@gmail.com

Часто используется комбинация:

const email = validator.normalizeEmail(
    validator.trim(inputEmail)
)

Интеграция с PostgreSQL

Использование вместе с pg

import pg fr om 'pg'
import validator fr om 'validator'

const { Pool } = pg

const pool = new Pool({
    user: 'postgres',
    password: '123456',
    database: 'app'
})

async function registerUser(data) {
    if (!validator.isEmail(data.email)) {
        throw new Error('Неверный email')
    }

    if (!validator.isStrongPassword(data.password)) {
        throw new Error('Слабый пароль')
    }

    const query = `
        INS ERT INTO users(email, password)
        VALUES($1, $2)
    `

    await pool.query(query, [
        validator.normalizeEmail(data.email),
        data.password
    ])
}

Валидация UUID

PostgreSQL активно использует UUID.

if (!validator.isUUID(userId)) {
    throw new Error('Некорректный UUID')
}

Можно указать конкретную версию:

validator.isUUID(userId, 4)

Проверка JSON-полей

PostgreSQL поддерживает JSON и JSONB.

const metadata = '{"theme":"dark"}'

if (!validator.isJSON(metadata)) {
    throw new Error('Некорректный JSON')
}

Интеграция с MySQL

Проверка числовых значений

if (!validator.isInt(age + '', {
    min: 0,
    max: 120
})) {
    throw new Error('Некорректный возраст')
}

Валидация decimal-полей

if (!validator.isDecimal(price + '')) {
    throw new Error('Некорректная цена')
}

Проверка даты перед INSERT

if (!validator.isDate(birthDate)) {
    throw new Error('Некорректная дата')
}

Интеграция с MongoDB

Работа с ObjectId

Validator.js не содержит встроенной проверки MongoDB ObjectId, однако можно использовать регулярное выражение.

import validator from 'validator'

function isObjectId(id) {
    return validator.matches(
        id,
        /^[0-9a-fA-F]{24}$/
    )
}

Проверка URL перед сохранением

MongoDB часто используется для хранения документов с URL.

if (!validator.isURL(website)) {
    throw new Error('Некорректный URL')
}

Интеграция с ORM

Validator.js + Sequelize

Sequelize поддерживает встроенные валидаторы на основе Validator.js.

Определение модели

import { DataTypes } from 'sequelize'

const User = sequelize.define('User', {
    email: {
        type: DataTypes.STRING,
        validate: {
            isEmail: true
        }
    },

    username: {
        type: DataTypes.STRING,
        validate: {
            isAlphanumeric: true,
            len: [3, 20]
        }
    }
})

Sequelize внутри использует Validator.js.


Кастомные валидаторы

const User = sequelize.define('User', {
    password: {
        type: DataTypes.STRING,

        validate: {
            strong(val ue) {
                if (!validator.isStrongPassword(value)) {
                    throw new Error('Слабый пароль')
                }
            }
        }
    }
})

Validator.js и Mongoose

Mongoose позволяет подключать Validator.js вручную.

Пример схемы

import mongoose from 'mongoose'
import validator from 'validator'

const UserSchema = new mongoose.Schema({
    email: {
        type: String,

        validate(value) {
            return validator.isEmail(value)
        }
    }
})

Проверка URL

const ArticleSchema = new mongoose.Schema({
    image: {
        type: String,

        validate(value) {
            return validator.isURL(value)
        }
    }
})

Validator.js и Prisma

Prisma не включает встроенную интеграцию с Validator.js, поэтому валидация обычно выполняется отдельно.

Пример

import validator from 'validator'
import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

async function createPost(data) {
    if (!validator.isSlug(data.slug)) {
        throw new Error('Некорректный slug')
    }

    return prisma.post.create({
        data
    })
}

Санитизация данных перед БД

Validator.js содержит функции очистки строк.

trim

const name = validator.trim(input)

Удаляет пробелы по краям.


escape

const text = validator.escape(comment)

Преобразует HTML-символы:

<script>

в:

&lt;script&gt;

stripLow

Удаление управляющих символов:

validator.stripLow(text)

Защита от SQL-инъекций

Validator.js не заменяет параметризованные запросы.

Неправильно:

const sql = `
    SEL ECT * FROM users
    WH ERE email = '${email}'
`

Правильно:

await db.query(
    'SELE CT * FR OM users WH ERE email = $1',
    [email]
)

Validator.js используется как дополнительный уровень защиты.


Проверка уникальности данных

Validator.js не умеет проверять уникальность напрямую, поскольку это задача базы данных.

Обычно используется комбинация:

  1. Validator.js;
  2. запрос в БД;
  3. UNIQUE INDEX.

Пример:

if (!validator.isEmail(email)) {
    throw new Error('Некорректный email')
}

const exists = await db.query(
    'SEL ECT id FR OM users WH ERE email = $1',
    [email]
)

if (exists.rows.length > 0) {
    throw new Error('Email уже существует')
}

Транзакции и валидация

Валидация выполняется до открытия транзакции.

if (!validator.isCurrency(amount + '')) {
    throw new Error('Некорректная сумма')
}

const client = await pool.connect()

try {
    await client.query('BEGIN')

    // SQL-запросы

    await client.query('COMMIT')
}
catch (err) {
    await client.query('ROLLBACK')
}

Массовая валидация данных

Валидация массива объектов

function validateUsers(users) {
    return users.every(user => {
        return validator.isEmail(user.email)
    })
}

Сбор ошибок

function validateUser(user) {
    const errors = []

    if (!validator.isEmail(user.email)) {
        errors.push('Некорректный email')
    }

    if (!validator.isMobilePhone(user.phone, 'any')) {
        errors.push('Некорректный телефон')
    }

    return errors
}

Middleware для Express

Express часто используется вместе с Validator.js.

Middleware валидации

import validator fr om 'validator'

export function validateUser(req, res, next) {
    const { email } = req.body

    if (!validator.isEmail(email)) {
        return res.status(400).json({
            error: 'Некорректный email'
        })
    }

    next()
}

Валидация перед UPDATE

Частичное обновление данных

async function updateUser(id, data) {
    if (data.email) {
        if (!validator.isEmail(data.email)) {
            throw new Error('Некорректный email')
        }
    }

    if (data.website) {
        if (!validator.isURL(data.website)) {
            throw new Error('Некорректный URL')
        }
    }
}

Валидация поисковых параметров

LIMIT и OFFSET

if (!validator.isInt(lim it + '', {
    min: 1,
    max: 100
})) {
    throw new Error('Некорректный lim it')
}

Проверка сортировки

const allowed = ['name', 'created_at']

if (!allowed.includes(sort)) {
    throw new Error('Недопустимое поле сортировки')
}

Асинхронная валидация

Validator.js синхронен, но легко комбинируется с асинхронными проверками.

async function validate(data) {
    if (!validator.isEmail(data.email)) {
        throw new Error('Некорректный email')
    }

    const user = await db.query(
        'SEL ECT id FR OM users WH ERE email = $1',
        [data.email]
    )

    if (user.rows.length > 0) {
        throw new Error('Email занят')
    }
}

Централизация правил валидации

Отдельный модуль

// validators/user.js

import validator from 'validator'

export function validateEmail(email) {
    return validator.isEmail(email)
}

export function validatePassword(password) {
    return validator.isStrongPassword(password)
}

Повторное использование

import {
    validateEmail,
    validatePassword
} from './validators/user.js'

Валидация на уровне DTO

Data Transfer Object

class CreateUserDTO {
    constructor(data) {
        this.email = data.email
        this.password = data.password
    }

    validate() {
        const errors = []

        if (!validator.isEmail(this.email)) {
            errors.push('Неверный email')
        }

        if (!validator.isStrongPassword(this.password)) {
            errors.push('Слабый пароль')
        }

        return errors
    }
}

Производительность

Validator.js работает быстро, однако при массовой обработке данных следует:

  • избегать повторной нормализации;
  • не валидировать одинаковые поля несколько раз;
  • использовать батчевую обработку;
  • минимизировать регулярные выражения.

Типичные ошибки

Отсутствие trim

Неправильно:

validator.isEmail(email)

Правильно:

validator.isEmail(
    validator.trim(email)
)

Валидация после INSERT

Неправильно:

await saveToDB(data)

validate(data)

Слепое доверие frontend

Frontend-валидация не заменяет backend-проверки.


Комбинирование с Joi и Zod

Validator.js часто используется совместно с библиотеками схемной валидации.

Joi

Joi:

const schema = Joi.object({
    email: Joi.string().email()
})

Zod

Zod:

const schema = z.object({
    email: z.string().email()
})

Validator.js при этом может использоваться для дополнительной проверки и sanitization.


Практическая структура проекта

src/
├── validators/
│   ├── user.js
│   ├── post.js
│   └── common.js
│
├── services/
├── repositories/
├── database/
└── routes/

Рекомендации по интеграции

Оптимальная последовательность

  1. Получение данных;
  2. Sanitization;
  3. Validator.js;
  4. Бизнес-валидация;
  5. Проверки БД;
  6. SQL-запрос.

Что валидировать обязательно

Особенно важно проверять:

  • email;
  • URL;
  • UUID;
  • телефоны;
  • даты;
  • JSON;
  • slug;
  • числовые значения;
  • валюты;
  • IP-адреса;
  • домены;
  • JWT;
  • credit card;
  • locale;
  • MIME types.

Комплексный пример

import validator from 'validator'

async function createUser(data) {
    const errors = []

    const email = validator.normalizeEmail(
        validator.trim(data.email)
    )

    if (!validator.isEmail(email)) {
        errors.push('Некорректный email')
    }

    if (!validator.isStrongPassword(data.password)) {
        errors.push('Слабый пароль')
    }

    if (!validator.isMobilePhone(
        data.phone,
        'any'
    )) {
        errors.push('Некорректный телефон')
    }

    if (errors.length > 0) {
        throw new Error(errors.join(', '))
    }

    await db.query(
        `
        INS ERT IN TO users(
            email,
            password,
            phone
        )
        VALUES($1, $2, $3)
        `,
        [
            email,
            data.password,
            data.phone
        ]
    )
}