Валидация объектов — одна из центральных задач библиотеки Joi. При работе со структурами данных необходимо контролировать:
null;Базовый механизм реализуется через методы:
required()optional()forbidden()presence()default()exist()Если ключ описан в схеме, но не помечен как обязательный, он считается необязательным.
const Joi = require('joi');
const schema = Joi.object({
username: Joi.string(),
age: Joi.number()
});
Корректны оба варианта:
{}
{
username: 'alex'
}
{
username: 'alex',
age: 25
}
Ошибка возникнет только при несоответствии типа:
{
age: '25'
}
Результат:
"age" must be a number
Метод required() делает поле обязательным.
const schema = Joi.object({
username: Joi.string().required(),
password: Joi.string().required()
});
Теперь отсутствие ключей вызывает ошибку:
{}
Результат:
"username" is required
Если отсутствует только один ключ:
{
username: 'alex'
}
Результат:
"password" is required
Метод required() имеет короткий псевдоним —
required.
Joi.string().required()
Также допустимо:
Joi.string().presence('required')
Важно понимать разницу между:
undefined;null;{}
{
username: undefined
}
Для required() оба варианта считаются ошибкой.
const schema = Joi.object({
username: Joi.string().required()
});
Проверка:
schema.validate({
username: undefined
});
Результат:
"username" is required
null не считается отсутствующим значением. Это отдельный
тип.
schema.validate({
username: null
});
Результат:
"username" must be a string
Чтобы разрешить null, используется
allow(null).
const schema = Joi.object({
username: Joi.string()
.allow(null)
.required()
});
Теперь допустимо:
{
username: null
}
optional() явно помечает поле как необязательное.
const schema = Joi.object({
middleName: Joi.string().optional()
});
Практической разницы с обычным объявлением нет:
Joi.string()
и
Joi.string().optional()
работают одинаково.
Явное указание необязательности делает схему более читаемой.
const schema = Joi.object({
login: Joi.string().required(),
password: Joi.string().required(),
avatar: Joi.string().optional(),
bio: Joi.string().optional(),
website: Joi.string().optional()
});
Такая схема визуально разделяет обязательные и дополнительные данные.
forbidden() запрещает присутствие ключа.
const schema = Joi.object({
id: Joi.number().forbidden(),
username: Joi.string().required()
});
Корректно:
{
username: 'alex'
}
Ошибка:
{
id: 10,
username: 'alex'
}
Результат:
"id" is not allowed
const createUserSchema = Joi.object({
id: Joi.forbidden(),
createdAt: Joi.forbidden(),
username: Joi.string().required(),
email: Joi.string().email().required()
});
Клиент не сможет подменить системные значения.
Метод presence() задаёт режим присутствия поля.
Допустимые значения:
'required''optional''forbidden'Пример:
Joi.string().presence('required')
Эквивалент:
Joi.string().required()
Режим можно назначить всей схеме.
const schema = Joi.object({
username: Joi.string(),
email: Joi.string(),
age: Joi.number()
}).prefs({
presence: 'required'
});
Теперь все поля обязательны.
Корректно:
{
username: 'alex',
email: 'alex@mail.com',
age: 30
}
Ошибка:
{
username: 'alex'
}
Результат:
"email" is required
Даже при глобальном required отдельные поля можно
сделать необязательными.
const schema = Joi.object({
username: Joi.string(),
email: Joi.string(),
avatar: Joi.string().optional()
}).prefs({
presence: 'required'
});
Теперь avatar можно не передавать.
exist() проверяет только наличие значения.
const schema = Joi.object({
token: Joi.exist()
});
Допустимо:
{
token: 123
}
{
token: false
}
{
token: null
}
Ошибка:
{}
Проверяет:
Joi.string().required()
Проверяет только наличие.
Joi.exist()
Метод default() задаёт значение по умолчанию.
const schema = Joi.object({
role: Joi.string().default('user')
});
Проверка:
schema.validate({});
Результат:
{
value: {
role: 'user'
}
}
Интересная особенность:
const schema = Joi.object({
role: Joi.string()
.required()
.default('user')
});
Несмотря на required(), ошибка не возникнет.
default() автоматически подставит значение.
const schema = Joi.object({
createdAt: Joi.date().default(() => new Date())
});
Функция вызывается во время валидации.
const schema = Joi.object({
user: Joi.object({
name: Joi.string().required(),
email: Joi.string().required()
}).required()
});
Теперь обязательны:
user;name и email внутри него.Проверка:
{
user: {}
}
Результат:
"user.name" is required
const schema = Joi.object({
profile: Joi.object({
bio: Joi.string(),
website: Joi.string()
}).optional()
});
Допустимо:
{}
и
{
profile: {
bio: 'Developer'
}
}
Частая схема:
const schema = Joi.object({
avatar: Joi.string()
.allow(null)
.required()
});
Поле обязательно должно присутствовать, но может содержать
null.
Допустимо:
{
avatar: null
}
Ошибка:
{}
По умолчанию пустая строка не считается валидной.
const schema = Joi.object({
username: Joi.string().required()
});
Проверка:
{
username: ''
}
Результат:
"username" is not allowed to be empty
const schema = Joi.object({
username: Joi.string()
.allow('')
.required()
});
Теперь допустимо:
{
username: ''
}
Метод empty() преобразует указанные значения в
undefined.
const schema = Joi.object({
username: Joi.string()
.empty('')
.required()
});
Теперь пустая строка будет считаться отсутствующим значением.
Проверка:
{
username: ''
}
Результат:
"username" is required
Часто обязательность поля зависит от других полей.
const schema = Joi.object({
isCompany: Joi.boolean(),
companyName: Joi.string().when('isCompany', {
is: true,
then: Joi.required(),
otherwise: Joi.optional()
})
});
Если:
{
isCompany: true
}
Результат:
"companyName" is required
Поле должно сопровождаться другим полем.
const schema = Joi.object({
password: Joi.string(),
repeatPassword: Joi.string()
}).with('password', 'repeatPassword');
Если есть password, должен быть и
repeatPassword.
Запрещает совместное присутствие.
const schema = Joi.object({
accessToken: Joi.string(),
password: Joi.string()
}).without('accessToken', 'password');
Требует присутствия только одного поля.
const schema = Joi.object({
email: Joi.string(),
phone: Joi.string()
}).xor('email', 'phone');
Допустимо:
{
email: 'test@mail.com'
}
или:
{
phone: '+77001234567'
}
Ошибка:
{}
и:
{
email: 'test@mail.com',
phone: '+77001234567'
}
Требует хотя бы одно поле.
const schema = Joi.object({
email: Joi.string(),
phone: Joi.string(),
telegram: Joi.string()
}).or('email', 'phone', 'telegram');
Требует совместного присутствия всех полей.
const schema = Joi.object({
startDate: Joi.date(),
endDate: Joi.date()
}).and('startDate', 'endDate');
Запрещает совместное присутствие группы полей.
const schema = Joi.object({
password: Joi.string(),
oauth: Joi.boolean()
}).nand('password', 'oauth');
По умолчанию лишние ключи запрещены.
const schema = Joi.object({
username: Joi.string()
});
Проверка:
{
username: 'alex',
role: 'admin'
}
Результат:
"role" is not allowed
const schema = Joi.object({
username: Joi.string()
}).unknown(true);
Теперь дополнительные поля допускаются.
const schema = Joi.object({
username: Joi.string()
}).prefs({
stripUnknown: true
});
Проверка:
{
username: 'alex',
role: 'admin'
}
Результат:
{
value: {
username: 'alex'
}
}
Иногда удобно сделать все поля обязательными автоматически.
const baseSchema = {
username: Joi.string(),
email: Joi.string(),
password: Joi.string()
};
const schema = Joi.object(baseSchema)
.fork(
['username', 'email', 'password'],
field => field.required()
);
Для PATCH обычно используются необязательные поля.
const updateUserSchema = Joi.object({
username: Joi.string(),
email: Joi.string().email(),
avatar: Joi.string()
}).min(1);
min(1) требует наличие хотя бы одного поля.
const createSchema = Joi.object({
username: Joi.string().required(),
email: Joi.string().required(),
password: Joi.string().required()
});
const updateSchema = Joi.object({
username: Joi.string(),
email: Joi.string(),
password: Joi.string()
}).min(1);
Joi.string().required()
null не разрешён автоматически.
Joi.string().optional()
Разрешает отсутствие поля, но не null.
Joi.string().required()
Пустая строка вызовет ошибку.
const schema = Joi.object({
profile: Joi.object({
bio: Joi.string().required()
}).optional()
});
Если profile отсутствует — ошибки нет.
Но если объект передан:
{
profile: {}
}
ошибка появится:
"profile.bio" is required
username: Joi.string().required()
Повышает читаемость схемы.
Лучше заранее определить:
null.Это упрощает поддержку API.
createdAt: Joi.date().default(Date.now)
Для публичных API особенно важно:
stripUnknown: true
или:
unknown(false)
Позволяет защититься от лишних данных и неожиданных свойств объектов.