У високопродуктивних мікросервісних архітектурах (AdTech, фінтех, онлайн-геймінг, обробка IoT-телеметрії) головним фактором масштабування бекенду є здатність обробляти десятки тисяч запитів за секунду (RPS) на одному ядрі процесора з мінімальною затримкою (P99 Latency < 5 мс). За таких умов використання інтерпретованих валідаторів на зразок Zod або Yup стає вузьким місцем (bottleneck), витрачаючи до 60–70% усього процесорного часу сервера на рекурсивний обхід структур даних та алокацію тимчасових об'єктів у купі V8.
Бібліотека TypeBox (автор Colin Sinclair) представляє фундаментально іншу парадигму валідації та генерації контрактів. Її ключова архітектурна відмінність:
TypeBox не створює пропрієтарних структур у пам'яті. У рантаймі схема TypeBox є чистим, валідним об'єктом специфікації JSON Schema (draft-7 або draft 2020-12).
При цьому на етапі статичної компіляції TypeBox забезпечує безкомпромісну строгу типізацію: утиліта Static<typeof Schema> вилучає чистий TypeScript-інтерфейс без генерації глибоких рекурсивних ланцюжків методів.
Спираючись на стандарт JSON Schema, TypeBox досягає максимальної продуктивності в індустрії завдяки безшовній інтеграції з двома спеціалізованими компіляторами:
TypeCompiler: компілюють структуру JSON Schema безпосередньо в оптимізований машинний байткод V8, випереджаючи стандартні валідатори у 50–100 разів.fast-json-stringify в екосистемі Fastify: замість повільного виклику JSON.stringify(), який виконує динамічний обхід властивостей об'єкта в рантаймі, Fastify використовує схему TypeBox для побудови спеціалізованого серіалізатора. Він формує підсумковий JSON-рядок зі швидкістю нативної конкатенації рядків, подвоюючи пропускну здатність сервера.Бібліотека розроблена за принципом Zero-Dependencies: її кодова база не містить сторонніх залежностей, а вага в бандлі становить лічені кілобайти.
В архітектурі TypeBox схема виступає Єдиним джерелом правди (Single Source of Truth) для всіх рівнів серверного застосунку:
+-------------------------------------------------------------------------+
| АРХІТЕКТУРА ЄДИНОГО ДЖЕРЕЛА TYPEBOX |
| const UserSchema = Type.Object({ ... }) |
+-------------------------------------------------------------------------+
| | |
| +-----------------------------+-----------------------------+ |
| | | | |
| v v v |
| [ TypeScript Type ] [ Чистий JSON Schema ] [ Специфікація ] |
| type User = { [ OpenAPI ] |
| Static<typeof Schema> "type": "object", Автоматична |
| Строга типізація IDE "properties": { ... } генерація Swagger |
| } документації |
| | |
| +---------------+---------------+ |
| | | |
| v v |
| [ JIT Валідатор: Ajv ] [ Серіалізатор Fastify ] |
| Компіляція схеми у V8 fast-json-stringify: |
| машинний код (100k+ RPS) Блискавична відповідь клієнту |
+-------------------------------------------------------------------------+На відміну від бібліотек-трансформерів, TypeBox суворо дотримується семантики JSON Schema: він перевіряє відповідність структури контракту, але ніколи не мутує вхідні дані (повна відсутність сайд-ефектів).
@fastify/swagger без ведення окремих YAML-документів.Fastify реєструє схему TypeBox для body, querystring, params та response. Ajv компілює валідатори під час старту.
Вхідний HTTP-пакет перевіряється скомпільованою функцією машинного коду. Невалідні дані відхиляються за субмікросекунди.
Контролер отримує req.body зі строгою типізацією Static<typeof Schema>, виконуючи операції без накладних витрат.
Вихідні дані серіалізуються скомпільованим шаблоном fast-json-stringify зі швидкістю конкатенації рядків C++.
Static<T>#Оголошення комплексної схеми замовлення з вкладеними структурами та суворими обмеженнями:
import { Type, Static } from '@sinclair/typebox';
// Перелік статусів замовлення
export const OrderStatusSchema = Type.Union([
Type.Literal('PENDING'),
Type.Literal('PROCESSING'),
Type.Literal('COMPLETED'),
Type.Literal('CANCELLED'),
]);
// Схема позиції замовлення
export const OrderItemSchema = Type.Object({
productId: Type.String({ format: 'uuid' }),
sku: Type.String({ minLength: 3, maxLength: 20 }),
quantity: Type.Integer({ minimum: 1, maximum: 100 }),
unitPriceCents: Type.Integer({ minimum: 0 }),
});
// Головна схема створення замовлення
export const CreateOrderSchema = Type.Object({
customerId: Type.String({ format: 'uuid' }),
items: Type.Array(OrderItemSchema, { minItems: 1, maxItems: 50 }),
discountCode: Type.Optional(Type.String({ pattern: '^[A-Z0-9]{4,12}$' })),
shippingAddress: Type.Object({
country: Type.String({ minLength: 2, maxLength: 2 }), // ISO-3166-1 alpha-2
city: Type.String({ minLength: 1 }),
postalCode: Type.String({ minLength: 3, maxLength: 10 }),
streetAddress: Type.String({ minLength: 5 }),
}),
metadata: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
});
// Статичний інференс чистих TypeScript-типів
export type OrderStatus = Static<typeof OrderStatusSchema>;
export type OrderItem = Static<typeof OrderItemSchema>;
export type CreateOrderPayload = Static<typeof CreateOrderSchema>;
// Демонстрація: у рантаймі CreateOrderSchema — це ЧИСТИЙ ОБ'ЄКТ JSON SCHEMA!
console.log(JSON.stringify(CreateOrderSchema, null, 2));
/*
Виведе стандартний JSON Schema:
{
"type": "object",
"properties": {
"customerId": { "type": "string", "format": "uuid" },
"items": {
"type": "array",
"items": { ... },
"minItems": 1
},
...
},
"required": ["customerId", "items", "shippingAddress"]
}
*/TypeCompiler#Для автономної перевірки даних за межами Fastify використовується вбудований модуль @sinclair/typebox/compiler:
import { TypeCompiler } from '@sinclair/typebox/compiler';
import { CreateOrderSchema, CreateOrderPayload } from './order-schema';
// КОМПІЛЯЦІЯ ВИКОНУЄТЬСЯ СТРОГО ОДИН РАЗ ПРИ СТАРТІ СЕРВЕРА
export const CompiledOrderValidator = TypeCompiler.Compile(CreateOrderSchema);
export function validateOrderPayload(rawInput: unknown): {
isValid: boolean;
data?: CreateOrderPayload;
errors?: string[];
} {
// 1. Ультрашвидка перевірка в машинному коді V8 (Check)
const isValid = CompiledOrderValidator.Check(rawInput);
if (!isValid) {
// 2. Якщо дані невалідні, вилучаємо деталі через генератор Errors
const errors: string[] = [];
for (const error of CompiledOrderValidator.Errors(rawInput)) {
errors.push(`${error.path}: ${error.message} (отримано: ${JSON.stringify(error.value)})`);
}
return {
isValid: false,
errors,
};
}
// 3. Дані валідні, повертаємо зі строгою типізацією
return {
isValid: true,
data: rawInput as CreateOrderPayload,
};
}Поєднаємо TypeBox із Fastify для наскрізної типізації тіла запиту, параметрів та кодів відповідей:
import Fastify from 'fastify';
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox';
import fastifySwagger from '@fastify/swagger';
import fastifySwaggerUi from '@fastify/swagger-ui';
import { CreateOrderSchema, CreateOrderPayload } from './order-schema';
import { Type } from '@sinclair/typebox';
// Ініціалізація сервера Fastify із провайдером типів TypeBox
const server = Fastify({
logger: true,
}).withTypeProvider<TypeBoxTypeProvider>();
// Реєстрація OpenAPI Swagger (напряму споживає схеми TypeBox!)
async function bootstrap() {
await server.register(fastifySwagger, {
openapi: {
info: {
title: 'High-Load Orders API',
version: '1.0.0',
},
},
});
await server.register(fastifySwaggerUi, {
routePrefix: '/docs',
});
// Реєстрація ендпоінта з подвійною схемою: вхідна валідація + fast-json-stringify для виводу
server.post(
'/api/v1/orders',
{
schema: {
summary: 'Створення нового замовлення',
body: CreateOrderSchema,
response: {
201: Type.Object({
orderId: Type.String({ format: 'uuid' }),
status: Type.Literal('CREATED'),
totalCents: Type.Integer(),
}),
400: Type.Object({
statusCode: Type.Integer(),
error: Type.String(),
message: Type.String(),
}),
},
},
},
async (request, reply) => {
// request.body СТРОГО типізований як CreateOrderPayload завдяки TypeBoxTypeProvider!
const { customerId, items } = request.body;
const totalCents = items.reduce((sum, item) => sum + item.unitPriceCents * item.quantity, 0);
const generatedOrderId = crypto.randomUUID();
// Відповідь серіалізується через fast-json-stringify зі схемою 201
return reply.status(201).send({
orderId: generatedOrderId,
status: 'CREATED',
totalCents,
});
}
);
await server.listen({ port: 8080, host: '0.0.0.0' });
console.log('High-Load Fastify сервер запущено на http://localhost:8080/docs');
}
bootstrap().catch(console.error);Розробимо високоефективний сервіс валідації пакетів телеметрії з перевіркою числових діапазонів і бітових прапорів:
import { Type, Static } from '@sinclair/typebox';
import { TypeCompiler } from '@sinclair/typebox/compiler';
// Схема пакета телеметрії IoT-пристрою
export const TelemetryPacketSchema = Type.Object({
deviceId: Type.String({ pattern: '^[A-Z]{3}-\\d{6}$' }),
timestampMs: Type.Integer({ minimum: 1700000000000 }),
batteryLevelPct: Type.Number({ minimum: 0, maximum: 100 }),
readings: Type.Array(
Type.Object({
sensorType: Type.Union([
Type.Literal('TEMP'),
Type.Literal('PRESSURE'),
Type.Literal('HUMIDITY'),
Type.Literal('VOLTAGE'),
]),
val: Type.Number(),
isValid: Type.Boolean(),
}),
{ minItems: 1, maxItems: 32 }
),
firmwareVersion: Type.String({ minLength: 5, maxLength: 15 }),
});
export type TelemetryPacket = Static<typeof TelemetryPacketSchema>;
// JIT-компіляція виконується один раз під час запуску сервісу
const compiledCheck = TypeCompiler.Compile(TelemetryPacketSchema);
export class HighLoadTelemetryValidator {
public static validate(rawPacket: unknown): rawPacket is TelemetryPacket {
return compiledCheck.Check(rawPacket);
}
public static inspectErrors(rawPacket: unknown): string[] {
const issues: string[] = [];
for (const error of compiledCheck.Errors(rawPacket)) {
issues.push(`${error.path}: ${error.message}`);
}
return issues;
}
}Розробники, звиклі до Zod, шукають у TypeBox методи .transform(), .trim() або .default(). TypeBox суворо дотримується принципів стандарту JSON Schema: схема є декларативним описом контракту, а не мутатором даних. Будь-яке очищення чи трансформація повинні відбуватися окремо в прикладному коді контролера.
У специфікації JSON Schema тип number дозволяє довільні числа з рухомою комою (12.345). Якщо поле є первинним ключем, кількістю товару чи номером порту, використання Type.Number() дозволить клієнту надіслати дробове значення, спричинивши помилку на рівні БД. Для цілих чисел завжди використовуйте Type.Integer().
// ❌ НЕБЕЗПЕЧНО: дозволяє дробові числа (наприклад, 2.5 одиниці товару)
const BadItem = Type.Object({ quantity: Type.Number({ minimum: 1 }) });
// ✅ ПРАВИЛЬНО: строге цілочисельне обмеження
const GoodItem = Type.Object({ quantity: Type.Integer({ minimum: 1 }) });Виклик TypeCompiler.Compile(Schema) генерує JavaScript-код і звертається до рушія V8 через new Function. Виклик компілятора всередині HTTP-маршруту знижує пропускну здатність у 200 разів. Схеми повинні компілюватися суворо один раз під час старту застосунку.
У TypeScript інженери звикли позначати необов'язкові поля знаком питання (field?: string). У TypeBox самого лише знака питання недостатньо: поле необхідно явно обгорнути в Type.Optional(Type.String()). Без цього поле потрапить у масив required специфікації JSON Schema, і запити без нього відхилятимуться валідатором.
Інженер описує схему запиту та відповіді через Type.Object(). Fastify передає її до Ajv.
Ajv створює оптимізовану функцію валідації, а fast-json-stringify формує шаблон серіалізації відповіді.
Тіло запиту перевіряється скомпільованим кодом V8 за долі мікросекунди. Невалідні запити відсікаються з HTTP 400.
Контролер опрацьовує req.body зі строгою типізацією Static<typeof Schema> без зайвих витрат пам'яті.
Відповідь надсилається клієнту через fast-json-stringify без використання повільного JSON.stringify.