В основе архитектуры Fastify лежит концепция строгих схем (schemas), которые описывают структуру входящих запросов и исходящих ответов. Схемы используются для нескольких целей одновременно:
body,
querystring, params,
headers)Fastify по умолчанию использует AJV (Another JSON Schema Validator), однако экосистема позволяет подключать альтернативные валидаторы, включая Joi.
Joi представляет собой декларативную библиотеку для описания схем данных в JavaScript. Она позволяет строить строгие правила проверки объектов, массивов, строк и чисел с богатым набором операторов.
Базовые возможности Joi включают:
Пример базовой схемы:
import Joi from 'joi';
const userSchema = Joi.object({
id: Joi.number().integer().positive().required(),
name: Joi.string().min(3).max(30).required(),
email: Joi.string().email().required(),
role: Joi.string().valid('admin', 'user').default('user')
});
Fastify не использует Joi по умолчанию, поэтому требуется дополнительная интеграция. Существует несколько подходов, каждый из которых решает задачу валидации по-разному.
Современный подход в Fastify заключается в использовании type providers. Для Joi существует пакет:
@fastify/type-provider-joiОн позволяет использовать Joi как источник типов и схем одновременно.
Пример подключения:
import Fastify from 'fastify';
import Joi from 'joi';
import { serializerCompiler, validatorCompiler, ZodTypeProvider } from '@fastify/type-provider-joi';
const fastify = Fastify().withTypeProvider();
Далее подключается компилятор валидатора:
fastify.setValidatorCompiler(({ schema }) => {
return (data) => schema.validate(data, { abortEarly: false });
});
И сериализатор:
fastify.setSerializerCompiler(({ schema }) => {
return (data) => schema.validate(data);
});
После настройки валидатора можно использовать Joi напрямую в схемах маршрутов.
fastify.route({
method: 'POST',
url: '/users',
schema: {
body: Joi.object({
username: Joi.string().alphanum().min(3).max(20).required(),
password: Joi.string().min(8).required(),
age: Joi.number().integer().min(18)
}),
response: {
200: Joi.object({
id: Joi.number(),
username: Joi.string()
})
}
},
handler: async (request, reply) => {
const user = request.body;
return {
id: 1,
username: user.username
};
}
});
Fastify разделяет схему на несколько логических частей:
body — тело запросаquerystring — параметры строки запросаparams — параметры маршрутаheaders — заголовкиresponse — схема ответаПример комплексной схемы:
schema: {
params: Joi.object({
id: Joi.number().required()
}),
querystring: Joi.object({
verbose: Joi.boolean().default(false)
}),
headers: Joi.object({
authorization: Joi.string().required()
}).unknown(true),
body: Joi.object({
title: Joi.string().required(),
content: Joi.string().required()
})
}
Joi предоставляет детализированные ошибки валидации. Fastify может перехватывать их и преобразовывать в стандартный HTTP-ответ.
Пример структуры ошибки:
{
"statusCode": 400,
"error": "Bad Request",
"message": "\"email\" is not allowed to be empty"
}
Настройка позволяет агрегировать ошибки:
Joi.object({
name: Joi.string().required()
}).validate(data, { abortEarly: false });
Одной из сильных сторон Joi является автоматическое приведение типов.
const schema = Joi.object({
age: Joi.number().integer(),
isActive: Joi.boolean()
});
Входные данные:
{
"age": "25",
"isActive": "true"
}
После валидации:
{
"age": 25,
"isActive": true
}
В крупных приложениях схемы выносятся в отдельные модули.
export const userBaseSchema = Joi.object({
username: Joi.string().min(3).required()
});
Далее используются через расширение:
const createUserSchema = userBaseSchema.keys({
password: Joi.string().min(8).required()
});
Joi поддерживает сложные структуры данных:
const schema = Joi.object({
users: Joi.array().items(
Joi.object({
id: Joi.number().required(),
email: Joi.string().email()
})
)
});
Joi позволяет задавать зависимости между полями:
const schema = Joi.object({
password: Joi.string().required(),
confirmPassword: Joi.string().valid(Joi.ref('password')).required()
});
Условные конструкции:
Joi.object({
role: Joi.string().valid('admin', 'user'),
permissions: Joi.when('role', {
is: 'admin',
then: Joi.array().items(Joi.string()),
otherwise: Joi.forbidden()
})
});
Fastify оптимизирован под AJV, который компилирует JSON Schema в высокопроизводительный код. Joi работает иначе — через интерпретацию правил.
Ключевые отличия:
Joi позволяет создавать собственные валидаторы:
const customJoi = Joi.extend((joi) => ({
type: 'positiveInteger',
base: joi.number().integer().min(1),
messages: {
'positiveInteger.base': 'Value must be a positive integer'
}
}));
Использование:
const schema = customJoi.object({
score: customJoi.positiveInteger().required()
});
Joi по умолчанию может отклонять или игнорировать лишние поля:
Joi.object({
name: Joi.string()
}).unknown(true);
Или наоборот:
Joi.object({
name: Joi.string()
}).options({ stripUnknown: true });
Схемы Joi в Fastify обычно применяются в следующих сценариях:
Использование Joi особенно оправдано там, где важнее выразительность и читаемость правил, чем максимальная производительность валидации.