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

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

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

Начать курс

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

Советы по производительности и типичные ошибки

Советы по производительности

  1. Кэшируйте сгенерированные типы. Если спецификация меняется редко, генерируйте типы один раз и сохраняйте их в репозитории. Это ускорит сборку проекта.
  2. Используйте immutableTypes: true. Это позволяет TypeScript лучше оптимизировать код, особенно при работе с большими схемами.
  3. Разделяйте генерацию. Если спецификация очень большая (сотни эндпоинтов), используйте generateTypes для генерации типов по частям.
  4. Избегайте additionalProperties: true. Это замедляет генерацию и ухудшает читаемость типов.
  5. Используйте exportType: 'type' вместо 'interface'. Типы работают быстрее в больших проектах.

Типичные ошибки

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