При работе с загрузкой данных в серверных приложениях на Node.js файлы редко валидируются напрямую через бинарное содержимое. Обычно используется промежуточное представление — объект метаданных, который формируется библиотеками вроде multer. Такой объект содержит информацию о имени файла, MIME-типе, размере и временном пути хранения.
В Joi файл рассматривается как обычный объект, структура которого подлежит строгому описанию через схему.
Типичное представление файла:
{
fieldname: 'avatar',
originalname: 'photo.png',
encoding: '7bit',
mimetype: 'image/png',
size: 34567,
destination: '/uploads',
filename: 'a1b2c3.png',
path: '/uploads/a1b2c3.png'
}
Каждое поле может участвовать в валидации через соответствующие правила Joi.
Основной подход заключается в описании структуры объекта файла через
Joi.object():
import Joi fr om 'joi';
const fileSchema = Joi.object({
fieldname: Joi.string().required(),
originalname: Joi.string().required(),
mimetype: Joi.string().required(),
size: Joi.number().max(2 * 1024 * 1024).required(),
path: Joi.string().required()
});
В данном случае задаются базовые ограничения:
size ограничивает максимальный размер файлаmimetype фиксирует допустимый тип содержимогоКонтроль формата файла часто реализуется через проверку MIME-типа.
Joi позволяет использовать valid() для ограничения
допустимых значений:
const imageFileSchema = Joi.object({
mimetype: Joi.string().valid(
'image/jpeg',
'image/png',
'image/webp'
).required()
});
Дополнительно может применяться проверка через регулярные выражения:
mimetype: Joi.string().pattern(/^image\/(jpeg|png|webp)$/)
Регулярные выражения обеспечивают гибкость при расширении списка допустимых типов.
Ограничение размера является одной из ключевых проверок при обработке загрузок:
size: Joi.number()
.max(5 * 1024 * 1024)
.required()
Такое ограничение предотвращает загрузку слишком больших файлов, которые могут привести к перегрузке файловой системы или памяти.
В большинстве серверных приложений Joi используется совместно с multer, который отвечает за получение файлов.
Пример интеграции:
import multer from 'multer';
const upload = multer({ dest: 'uploads/' });
const schema = Joi.object({
file: Joi.object({
originalname: Joi.string().required(),
mimetype: Joi.string().required(),
size: Joi.number().max(1024 * 1024).required()
}).required()
});
Обработка запроса:
app.post('/upload', upload.single('file'), (req, res) => {
const { error } = schema.validate({ file: req.file });
if (error) {
return res.status(400).json(error.details);
}
res.status(200).send('OK');
});
При множественной загрузке используется Joi.array():
const multiFileSchema = Joi.object({
files: Joi.array()
.items(
Joi.object({
originalname: Joi.string().required(),
mimetype: Joi.string().valid('image/png', 'image/jpeg').required(),
size: Joi.number().max(2 * 1024 * 1024).required()
})
)
.min(1)
.max(10)
});
Ограничения:
Joi поддерживает расширение логики через custom():
const schema = Joi.object({
file: Joi.object({
originalname: Joi.string().required(),
size: Joi.number().required()
}).custom((value, helpers) => {
if (!value.originalname.endsWith('.png')) {
return helpers.error('file.invalidExtension');
}
return value;
})
});
Кастомные правила позволяют реализовать:
Хотя MIME-тип считается более надёжным источником, расширение часто используется как дополнительный фильтр:
const schema = Joi.object({
originalname: Joi.string()
.pattern(/\.(jpg|jpeg|png|webp)$/i)
.required()
});
Такой подход применяется как дополнительный уровень защиты.
Имя файла может содержать опасные символы, пробелы или конструкции, влияющие на файловую систему.
Joi позволяет описывать базовую фильтрацию:
const schema = Joi.object({
originalname: Joi.string()
.pattern(/^[a-zA-Z0-9._-]+$/)
.required()
});
Дополнительно часто используется нормализация:
В сложных системах файл является частью более крупной структуры данных:
const userSchema = Joi.object({
username: Joi.string().required(),
avatar: Joi.object({
originalname: Joi.string().required(),
mimetype: Joi.string().valid('image/png').required(),
size: Joi.number().max(1024 * 1024).required()
}).required()
});
Композиция позволяет:
Ошибки Joi имеют стандартизированную структуру:
{
"message": "\"size\" must be less than or equal to 1048576",
"path": ["file", "size"],
"type": "number.max",
"context": {
"limit": 1048576,
"value": 2097152
}
}
При работе с файлами важные поля:
path — указывает конкретное поле файлаtype — тип нарушения правилаcontext.lim it — допустимое ограничениеJoi поддерживает условные правила через when():
const schema = Joi.object({
type: Joi.string().valid('image', 'document').required(),
file: Joi.object().when('type', {
is: 'image',
then: Joi.object({
mimetype: Joi.string().valid('image/png', 'image/jpeg').required(),
size: Joi.number().max(2 * 1024 * 1024).required()
}),
otherwise: Joi.object({
mimetype: Joi.string().valid('application/pdf').required(),
size: Joi.number().max(10 * 1024 * 1024).required()
})
})
});
Такой механизм позволяет динамически изменять правила в зависимости от контекста запроса.
При потоковой загрузке файл может отсутствовать как готовый объект и существовать только как метаданные. В таких случаях Joi применяется к промежуточному представлению:
const streamFileSchema = Joi.object({
path: Joi.string().required(),
size: Joi.number().positive().required(),
mimetype: Joi.string().required()
});
При необходимости строгого контроля возможно комбинирование нескольких уровней валидации:
Пример расширенной проверки:
const schema = Joi.object({
file: Joi.object({
originalname: Joi.string().required(),
size: Joi.number().required(),
mimetype: Joi.string().required(),
hash: Joi.string().length(64).required()
})
});
Добавление хэша позволяет контролировать целостность файла независимо от транспортного слоя.