TypeBox для TypeScript: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Что такое TypeBox и зачем он нужен?

TypeBox — это мощная библиотека для TypeScript, которая позволяет определять схемы данных, совместимые со стандартом JSON Schema, и одновременно выводить из них корректные статические типы TypeScript. Простыми словами, это мост между runtime-валидацией данных (когда ваше приложение работает и проверяет данные на лету) и compile-time типами (когда TypeScript проверяет корректность кода до его выполнения).

Зачем это нужно? В современной веб-разработке данные часто приходят из ненадёжных источников: API-запросы, формы на сайте, файлы конфигурации. Без валидации вы рискуете получить ошибки времени выполнения, которые сложно отловить. TypeBox решает эту проблему элегантно: вы один раз описываете схему, и получаете:

  • Runtime-валидатор — проверяет, соответствует ли объект схеме во время работы программы.
  • Статический тип — TypeScript автоматически выводит тип, который можно использовать для аннотаций.
  • JSON Schema — схему можно сериализовать в JSON и использовать в других инструментах (например, для генерации UI-форм или документации).

TypeBox идеально подходит для: создания API-эндпоинтов с валидацией тела запроса, работы с конфигурационными файлами, построения сложных форм с проверкой полей, и любого сценария, где важна безопасность типов на всех этапах.

Установка TypeBox

TypeBox — это библиотека для Node.js/TypeScript, поэтому устанавливается через npm (или yarn/pnpm).

npm install @sinclair/typebox

После установки вы можете импортировать её в свой проект:

import { Type } from '@sinclair/typebox';

Для работы с валидацией вам также понадобится сам валидатор. TypeBox не включает его по умолчанию, чтобы оставить выбор за разработчиком. Рекомендуется использовать @sinclair/typebox/compiler (встроенный компилятор) или ajv (популярный валидатор JSON Schema). Установим компилятор:

npm install @sinclair/typebox-compiler

Или, если предпочитаете AJV:

npm install ajv ajv-formats

Обратите внимание: библиотека называется @sinclair/typebox, а не просто typebox. Это важно для корректной установки.

Быстрый старт с примером кода

Давайте создадим простую схему для пользователя и проверим её.

import { Type } from '@sinclair/typebox';
import { TypeCompiler } from '@sinclair/typebox/compiler';

// 1. Определяем схему пользователя
const UserSchema = Type.Object({
    id: Type.Number(),
    name: Type.String({ minLength: 2 }),
    email: Type.String({ format: 'email' }),
    age: Type.Optional(Type.Number({ minimum: 0, maximum: 120 })),
});

// 2. Создаём компилятор для валидации
const validator = TypeCompiler.Compile(UserSchema);

// 3. Тестовые данные
const validUser = {
    id: 1,
    name: 'Alice',
    email: 'alice@example.com',
    age: 30,
};

const invalidUser = {
    id: 'not-a-number',
    name: 'A',
    email: 'invalid-email',
};

// 4. Валидация
console.log(validator.Check(validUser));   // true
console.log(validator.Check(invalidUser)); // false

// 5. Получаем статический тип
type UserType = Static<typeof UserSchema>;
// Теперь UserType — это { id: number; name: string; email: string; age?: number; }

В этом примере мы определили схему, скомпилировали её в валидатор и проверили два объекта. Обратите внимание на использование Type.Optional для поля age — оно может отсутствовать. Также мы использовали format: 'email' для проверки формата email.

Основные методы и функции TypeBox

TypeBox предоставляет богатый набор функций для создания схем. Рассмотрим самые важные из них.

1. Type.String()

Что делает: Создаёт схему для строкового типа. Позволяет задавать дополнительные ограничения: минимальная/максимальная длина, регулярное выражение, формат (email, uri, date и др.).

Параметры: Принимает объект с опциями (все опциональны): minLength, maxLength, pattern (регулярное выражение), format (стандартный формат), default, examples и другие.

const emailSchema = Type.String({ format: 'email' });
const passwordSchema = Type.String({ minLength: 8, pattern: '^(?=.*[A-Za-z])(?=.*\\d)' });
const nameSchema = Type.String({ minLength: 2, maxLength: 50 });

2. Type.Number()

Что делает: Создаёт схему для числового типа. Поддерживает ограничения: минимум, максимум, исключение границ (exclusiveMinimum/exclusiveMaximum), множественные значения (enum).

Параметры: Объект с опциями: minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf, default.

const ageSchema = Type.Number({ minimum: 0, maximum: 150 });
const priceSchema = Type.Number({ multipleOf: 0.01 }); // цены с двумя знаками после запятой
const scoreSchema = Type.Number({ exclusiveMinimum: 0, exclusiveMaximum: 100 });

3. Type.Boolean()

Что делает: Создаёт схему для булевого типа. Опции минимальны, так как булево значение может быть только true или false.

Параметры: Принимает только стандартные опции JSON Schema: default, examples.

const isActiveSchema = Type.Boolean({ default: true });
const isDeletedSchema = Type.Boolean();

4. Type.Object()

Что делает: Создаёт схему для объекта с заданными свойствами. Это, пожалуй, самый часто используемый метод. Позволяет определять обязательные и опциональные поля.

Параметры: Первый аргумент — объект, где ключи — имена свойств, а значения — схемы. Второй аргумент (опционально) — дополнительные опции: additionalProperties (разрешать ли дополнительные поля), minProperties, maxProperties.

const userSchema = Type.Object({
    id: Type.Number(),
    name: Type.String(),
    email: Type.Optional(Type.String({ format: 'email' })),
}, { additionalProperties: false });

5. Type.Array()

Что делает: Создаёт схему для массива. Можно задать тип элементов, минимальную/максимальную длину, уникальность элементов.

Параметры: Первый аргумент — схема элемента. Второй (опционально) — объект с опциями: minItems, maxItems, uniqueItems.

const stringArraySchema = Type.Array(Type.String());
const numberArraySchema = Type.Array(Type.Number(), { minItems: 1, maxItems: 10 });
const uniqueIdArraySchema = Type.Array(Type.Number(), { uniqueItems: true });

6. Type.Union()

Что делает: Создаёт объединённый тип (anyOf в терминах JSON Schema). Значение должно соответствовать хотя бы одной из переданных схем.

Параметры: Принимает массив схем (минимум две).

const idSchema = Type.Union([Type.Number(), Type.String()]);
// id может быть числом или строкой

const statusSchema = Type.Union([
    Type.Literal('active'),
    Type.Literal('inactive'),
    Type.Literal('pending'),
]);

7. Type.Literal()

Что делает: Создаёт схему для точного литерального значения. Полезно для создания перечислений (enum) или констант.

Параметры: Принимает конкретное значение: строку, число, булево значение.

const roleSchema = Type.Literal('admin');
const portSchema = Type.Literal(8080);
const flagSchema = Type.Literal(true);

8. Type.Enum()

Что делает: Создаёт схему из TypeScript-перечисления (enum). Удобно, когда у вас уже есть enum в коде.

Параметры: Принимает сам enum (не строку, а объект enum).

enum Color {
    Red = 'RED',
    Green = 'GREEN',
    Blue = 'BLUE',
}

const colorSchema = Type.Enum(Color);
// Принимает только 'RED', 'GREEN', 'BLUE'

9. Type.Partial()

Что делает: Делает все поля объекта опциональными. Аналог TypeScript-утилиты Partial<T>.

Параметры: Принимает схему объекта.

const fullUserSchema = Type.Object({
    id: Type.Number(),
    name: Type.String(),
    email: Type.String(),
});

const partialUserSchema = Type.Partial(fullUserSchema);
// Теперь все поля необязательны

10. Type.Pick() и Type.Omit()

Что делает: Type.Pick создаёт схему, включающую только указанные поля из исходной схемы. Type.Omit — наоборот, исключает указанные поля. Аналоги одноимённым TypeScript-утилитам.

Параметры: Первый аргумент — исходная схема объекта. Второй — массив ключей (строк).

const userSchema = Type.Object({
    id: Type.Number(),
    name: Type.String(),
    email: Type.String(),
    password: Type.String(),
});

const publicUserSchema = Type.Pick(userSchema, ['id', 'name', 'email']);
const userWithoutPasswordSchema = Type.Omit(userSchema, ['password']);

11. Type.KeyOf()

Что делает: Создаёт объединение литералов из ключей объекта. Полезно, когда нужно проверить, является ли значение одним из ключей схемы.

Параметры: Принимает схему объекта.

const userSchema = Type.Object({
    id: Type.Number(),
    name: Type.String(),
    email: Type.String(),
});

const userKeySchema = Type.KeyOf(userSchema);
// Принимает только 'id' | 'name' | 'email'

12. Type.Record()

Что делает: Создаёт схему для объекта, где все значения имеют один тип, а ключи — другой (обычно строки). Аналог TypeScript Record<K, V>.

Параметры: Первый аргумент — схема для ключей (обычно Type.String()). Второй — схема для значений.

const stringMapSchema = Type.Record(Type.String(), Type.String());
// { [key: string]: string }

const numberMapSchema = Type.Record(Type.String(), Type.Number());
// { [key: string]: number }

Реальный практический пример использования

Рассмотрим создание простого REST API на Express.js с валидацией тела запроса с помощью TypeBox.

import express from 'express';
import { Type } from '@sinclair/typebox';
import { TypeCompiler } from '@sinclair/typebox/compiler';

const app = express();
app.use(express.json());

// Определяем схему для создания пользователя
const CreateUserSchema = Type.Object({
    username: Type.String({ minLength: 3, maxLength: 20 }),
    email: Type.String({ format: 'email' }),
    password: Type.String({ minLength: 8 }),
    age: Type.Optional(Type.Number({ minimum: 18 })),
}, { additionalProperties: false });

// Компилируем валида                    

Рекомендаций