Fastify schemas

В основе архитектуры Fastify лежит концепция строгих схем (schemas), которые описывают структуру входящих запросов и исходящих ответов. Схемы используются для нескольких целей одновременно:

  • валидация входных данных (body, querystring, params, headers)
  • сериализация ответов
  • генерация документации
  • оптимизация производительности через предкомпиляцию валидаторов

Fastify по умолчанию использует AJV (Another JSON Schema Validator), однако экосистема позволяет подключать альтернативные валидаторы, включая Joi.

Joi как система валидации

Joi представляет собой декларативную библиотеку для описания схем данных в JavaScript. Она позволяет строить строгие правила проверки объектов, массивов, строк и чисел с богатым набором операторов.

Базовые возможности Joi включают:

  • описание типов данных
  • цепочки валидации
  • кастомные сообщения об ошибках
  • преобразование данных (coercion)
  • вложенные структуры любой глубины

Пример базовой схемы:

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')
});

Интеграция Joi в Fastify

Fastify не использует Joi по умолчанию, поэтому требуется дополнительная интеграция. Существует несколько подходов, каждый из которых решает задачу валидации по-разному.

Использование type provider для 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

После настройки валидатора можно использовать 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

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 });

Преобразование данных (coercion)

Одной из сильных сторон 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()
  })
});

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

Fastify оптимизирован под AJV, который компилирует JSON Schema в высокопроизводительный код. Joi работает иначе — через интерпретацию правил.

Ключевые отличия:

  • AJV быстрее в высоконагруженных API
  • Joi гибче в выражении логики
  • AJV лучше интегрирован в Fastify
  • Joi удобнее для сложной бизнес-валидации

Кастомные правила и расширения 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 });

Применение в архитектуре Fastify

Схемы Joi в Fastify обычно применяются в следующих сценариях:

  • API с жёсткой бизнес-валидацией
  • микросервисы с изолированными контрактами
  • шлюзы (API Gateway)
  • формы сложной структуры
  • интеграции с внешними системами, где требуется трансформация данных

Использование Joi особенно оправдано там, где важнее выразительность и читаемость правил, чем максимальная производительность валидации.