openapi-typescript для TypeScript: полное руководство с примерами
В современной веб-разработке API-интерфейсы играют ключевую роль, и их спецификация с помощью стандарта OpenAPI (ранее Swagger) стала де-факто стандартом. Однако ручное поддержание типов TypeScript в соответствии с OpenAPI-спецификацией — это трудоемкий и чреватый ошибками процесс. Библиотека openapi-typescript решает эту проблему, автоматически генерируя строгие типы TypeScript на основе вашей OpenAPI-схемы (v3.0 и v3.1).
Основная цель библиотеки — обеспечить type-safe взаимодействие с API. Вместо того чтобы вручную объявлять интерфейсы для каждого эндпоинта, вы запускаете генератор, который создает единый файл (или несколько) с типами для всех запросов, ответов, параметров и схем. Это не только экономит время, но и гарантирует, что ваши типы всегда синхронизированы с актуальной версией API. Если спецификация меняется, достаточно перегенерировать типы, и компилятор TypeScript сразу укажет на все несоответствия в коде.
Библиотека активно используется в проектах, где критична надежность и скорость разработки: от микросервисных архитектур до SPA-приложений. Она поддерживает генерацию как для клиентской стороны (например, для fetch или axios), так и для серверной (например, для Express или Fastify). В этой статье мы подробно разберем установку, основные методы, реальные примеры и типичные ошибки.
Установка
Для установки библиотеки используйте npm или yarn. Команда установит как сам генератор, так и необходимые зависимости.
npm install openapi-typescript typescript @types/node --save-dev
Или через yarn:
yarn add -D openapi-typescript typescript @types/node
Если вы планируете использовать сгенерированные типы в рантайме (например, для валидации), дополнительно установите openapi-typescript-helpers:
npm install openapi-typescript-helpers
Быстрый старт — минимальный рабочий пример
Предположим, у вас есть файл spec.yaml с OpenAPI-спецификацией. Создадим простой скрипт для генерации типов.
// generate.ts
import { createClient } from 'openapi-typescript';
import * as fs from 'fs';
async function main() {
const spec = fs.readFileSync('./spec.yaml', 'utf-8');
const result = await createClient(spec, { format: 'yaml' });
fs.writeFileSync('./api-types.d.ts', result);
console.log('Типы сгенерированы в api-types.d.ts');
}
main().catch(console.error);
Запустите скрипт:
npx ts-node generate.ts
После этого в файле api-types.d.ts появятся все типы, соответствующие вашей спецификации. Теперь вы можете импортировать их в проекте:
import type { paths, components } from './api-types';
// Использование типа для конкретного эндпоинта
type GetUsersResponse = paths['/users']['get']['responses'][200]['content']['application/json'];
Основные методы и функции
1. createClient(schema: string, options?: ClientOptions): Promise<string>
Что делает: Основной метод для генерации типов. Принимает строку со спецификацией (JSON или YAML) и возвращает строку с TypeScript-кодом.
import { createClient } from 'openapi-typescript';
import * as fs from 'fs';
const spec = fs.readFileSync('./spec.json', 'utf-8');
const types = await createClient(spec, { format: 'json' });
fs.writeFileSync('./types.d.ts', types);
2. createClientFromUrl(url: string, options?: ClientOptions): Promise<string>
Что делает: Асинхронно загружает спецификацию по URL и генерирует типы. Полезно для удаленных API.
import { createClientFromUrl } from 'openapi-typescript';
const types = await createClientFromUrl('https://api.example.com/openapi.json');
console.log(types); // Выведет сгенерированный TypeScript-код
3. createClientFromFile(path: string, options?: ClientOptions): Promise<string>
Что делает: Загружает спецификацию из локального файла и генерирует типы. Удобно для CI/CD.
import { createClientFromFile } from 'openapi-typescript';
const types = await createClientFromFile('./spec.yaml');
console.log(types);
4. ClientOptions — объект конфигурации
Что делает: Набор опций для тонкой настройки генерации. Поддерживает поля: format (json/yaml), version (3.0/3.1), exportType (type/interface), immutableTypes (boolean) и другие.
import { createClient } from 'openapi-typescript';
const options = {
format: 'yaml',
version: '3.1',
exportType: 'interface',
immutableTypes: true,
additionalProperties: false
};
const types = await createClient(spec, options);
5. transformSchema(schema: OpenAPIObject, options?: TransformOptions): string
Что делает: Низкоуровневый метод для трансформации уже загруженного объекта спецификации в TypeScript-код. Используется для кастомной обработки.
import { transformSchema } from 'openapi-typescript';
import * as yaml from 'js-yaml';
const specObject = yaml.load(fs.readFileSync('./spec.yaml', 'utf-8'));
const types = transformSchema(specObject, { exportType: 'type' });
console.log(types);
6. generateTypes(schema: string, options?: GenerateOptions): Promise<Record<string, string>>
Что делает: Генерирует типы для каждого эндпоинта отдельно, возвращая объект, где ключ — путь к эндпоинту, значение — строка с типами.
import { generateTypes } from 'openapi-typescript';
const result = await generateTypes(spec, { format: 'json' });
for (const [path, types] of Object.entries(result)) {
console.log(`Типы для ${path}:`, types);
}
7. validateSchema(schema: string, options?: ValidateOptions): Promise<boolean>
Что делает: Проверяет корректность OpenAPI-спецификации перед генерацией. Возвращает true, если схема валидна.
import { validateSchema } from 'openapi-typescript';
const isValid = await validateSchema(spec, { format: 'yaml' });
if (!isValid) {
throw new Error('Спецификация невалидна');
}
8. getSchemaErrors(schema: string, options?: ValidateOptions): Promise<string[]>
Что делает: Возвращает массив строк с описанием ошибок в спецификации. Полезно для отладки.
import { getSchemaErrors } from 'openapi-typescript';
const errors = await getSchemaErrors(spec);
if (errors.length > 0) {
errors.forEach(err => console.error('Ошибка:', err));
}
9. setGlobalOptions(options: GlobalOptions): void
Что делает: Устанавливает глобальные опции для всех последующих вызовов генерации. Удобно для настройки проекта.
import { setGlobalOptions } from 'openapi-typescript';
setGlobalOptions({
format: 'yaml',
exportType: 'interface',
immutableTypes: true
});
// Теперь все вызовы createClient будут использовать эти опции по умолчанию
10. resetGlobalOptions(): void
Что делает: Сбрасывает глобальные опции к значениям по умолчанию.
import { resetGlobalOptions } from 'openapi-typescript';
resetGlobalOptions();
Реальный практический пример (150+ строк кода)
Рассмотрим полноценный пример: у нас есть API для управления задачами (Todo API). Мы сгенерируем типы, создадим type-safe клиент и реализуем CRUD-операции.
// 1. Генерация типов (generate-types.ts)
import { createClient } from 'openapi-typescript';
import * as fs from 'fs';
import * as path from 'path';
async function generateTypes() {
const specPath = path.join(__dirname, 'todo-api.yaml');
const spec = fs.readFileSync(specPath, 'utf-8');
const types = await createClient(spec, {
format: 'yaml',
exportType: 'type',
immutableTypes: true
});
const outputPath = path.join(__dirname, 'generated', 'todo-api.d.ts');
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
fs.writeFileSync(outputPath, types);
console.log('Типы сгенерированы успешно');
}
generateTypes().catch(console.error);
// 2. Type-safe клиент (todo-client.ts)
import type { paths, components } from './generated/todo-api';
// Типы для удобства
type Todo = components['schemas']['Todo'];
type CreateTodoRequest = components['schemas']['CreateTodoRequest'];
type UpdateTodoRequest = components['schemas']['UpdateTodoRequest'];
class TodoClient {
private baseUrl: string;
private headers: Record;
constructor(baseUrl: string, apiKey?: string) {
this.baseUrl = baseUrl;
this.headers = {
'Content-Type': 'application/json',
...(apiKey ? { 'Authorization': `Bearer ${apiKey}` } : {})
};
}
// GET /todos — получение всех задач
async getTodos(): Promise {
const response = await fetch(`${this.baseUrl}/todos`, {
method: 'GET',
headers: this.headers
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data: paths['/todos']['get']['responses'][200]['content']['application/json'] = await response.json();
return data;
}
// GET /todos/{id} — получение задачи по ID
async getTodoById(id: number): Promise {
const response = await fetch(`${this.baseUrl}/todos/${id}`, {
method: 'GET',
headers: this.headers
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data: paths['/todos/{id}']['get']['responses'][200]['content']['application/json'] = await response.json();
return data;
}
// POST /todos — создание новой задачи
async createTodo(todo: CreateTodoRequest): Promise {
const response = await fetch(`${this.baseUrl}/todos`, {
method: 'POST',
headers: this.headers,
body: JSON.stringify(todo)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data: paths['/todos']['post']['responses'][201]['content']['application/json'] = await response.json();
return data;
}
// PUT /todos/{id} — обновление задачи
async updateTodo(id: number, todo: UpdateTodoRequest): Promise {
const response = await fetch(`${this.baseUrl}/todos/${id}`, {
method: 'PUT',
headers: this.headers,
body: JSON.stringify(todo)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data: paths['/todos/{id}']['put']['responses'][200]['content']['application/json'] = await response.json();
return data;
}
// DELETE /todos/{id} — удаление задачи
async deleteTodo(id: number): Promise {
const response = await fetch(`${this.baseUrl}/todos/${id}`, {
method: 'DELETE',
headers: this.headers
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
}
}
// 3. Использование клиента (app.ts)
async function main() {
const client = new TodoClient('https://api.example.com', 'your-api-key');
try {
// Создаем задачу
const newTodo = await client.createTodo({
title: 'Изучить openapi-typescript',
description: 'Прочитать документацию и написать примеры',
completed: false
});
console.log('Создана задача:', newTodo);
// Получаем все задачи
const allTodos = await client.getTodos();
console.log('Все задачи:', allTodos);
// Обновляем задачу
const updatedTodo = await client.updateTodo(newTodo.id, {
title: 'Изучить openapi-typescript (обновлено)',
completed: true
});
console.log('Обновленная задача:', updatedTodo);
// Удаляем задачу
await client.deleteTodo(newTodo.id);
console.log('Задача удалена');
} catch (error) {
console.error('Ошибка:', error);
}
}
main();
Советы по производительности и типичные ошибки
Советы по производительности
- Кэшируйте сгенерированные типы. Если спецификация меняется редко, генерируйте типы один раз и сохраняйте их в репозитории. Это ускорит сборку проекта.
- Используйте
immutableTypes: true. Это позволяет TypeScript лучше оптимизировать код, особенно при работе с большими схемами. - Разделяйте генерацию. Если спецификация очень большая (сотни эндпоинтов), используйте
generateTypesдля генерации типов по частям. - Избегайте
additionalProperties: true. Это замедляет генерацию и ухудшает читаемость типов. - Используйте
exportType: 'type'вместо'interface'. Типы работают быстрее в больших проектах.
Типичные ошибки
Также в библиотеке
Конфигурация приложений Kotlin: библиотеки и подходы
OAuth клиент для Kotlin: интеграция с провайдерами через ktor-client и kotlinx-serialization
Стандартная библиотека Go (Golang): полный обзор
ts-morph для TypeScript: полное руководство с примерами
Работа с изображениями в Go: resize и crop с библиотекой imaging