Кодогенерация из OpenAPI: автоматические клиенты и SDK

← Назад к блогу

📑 Содержание

TL;DR: Кодогенерация из OpenAPI-спецификации избавляет от ручного написания HTTP-клиентов, типов и SDK. Вы описываете API один раз — инструменты генерируют типизированный код для TypeScript, Python, Go, Java и 50+ других языков. Это устраняет рассинхрон между спецификацией и кодом.

1. Что такое кодогенерация из OpenAPI

Кодогенерация — автоматическое создание исходного кода на основе OpenAPI/Swagger-спецификации. Спецификация описывает эндпоинты, параметры, тела запросов, ответы и модели данных — генератор превращает это описание в готовый к использованию код.

OpenAPI Spec
YAML / JSON
Генератор
CLI / плагин
Код
клиенты, типы, SDK

Что можно генерировать

Тип артефакта Описание Пример
Типы / интерфейсы TypeScript interfaces, Python dataclasses, Go structs interface User { id: string; name: string; }
API-клиент Типизированные функции для вызова API api.users.getById({ id: "123" })
SDK Полноценная библиотека с классами, ошибками, ретраями new UsersApi(config).getUser("123")
Серверные заглушки Скелет сервера с маршрутами и валидацией Express/FastAPI/Spring контроллеры
React Query хуки useQuery/useMutation хуки для каждого эндпоинта useGetUser({ id: "123" })
Mock-сервер Сервер с фейковыми данными на основе спеки Prism, MSW handlers

2. Зачем генерировать код из спецификации

✅ Преимущества

  • Единый источник правды — спецификация = документация = код
  • Типобезопасность — ошибки ловятся при компиляции
  • Скорость — не нужно писать HTTP-клиент руками
  • Консистентность — все клиенты работают одинаково
  • Автоматическое обновление — изменил спеку → перегенерировал
  • Breaking changes — компилятор покажет несовместимости

⚠️ Ограничения

  • Качество зависит от качества спецификации
  • Сгенерированный код не всегда идиоматичен
  • Нужна CI/CD интеграция для актуальности
  • Кастомизация может быть сложной
  • Размер сгенерированного кода (SDK)
  • Зависимость от инструмента
⚠️ Без OpenAPI — без кодогенерации: Кодогенерация работает только с формализованной спецификацией. Если ваше API не имеет OpenAPI-описания, начните с его создания. Подробнее — в нашей статье «Что такое Swagger и OpenAPI».

3. Обзор инструментов

openapi-typescript

TypeScript

Лёгкий генератор только типов из OpenAPI 3.x. Не генерирует runtime-код — только .d.ts / TypeScript-типы. В паре с openapi-fetch даёт type-safe HTTP-клиент с минимальным бандлом (~6 KB).

Лучший выбор для TypeScript-проектов, где нужны типы без лишнего кода.

Orval

TypeScript

Генератор клиентского кода с интеграциями для React Query, SWR, Angular, Vue Query. Генерирует хуки useQuery/useMutation для каждого эндпоинта. Поддерживает Zod-схемы и MSW-моки.

Лучший выбор для React/Vue проектов с TanStack Query.

openapi-generator

50+ языков

Универсальный open-source генератор. Поддерживает TypeScript, Python, Go, Java, C#, Rust, Kotlin, Swift и десятки других. Генерирует полноценные SDK с классами, моделями, конфигурацией и обработкой ошибок.

Лучший выбор для мультиязычных проектов и генерации серверных заглушек.

swagger-codegen

40+ языков

Оригинальный генератор от SmartBear (создатели Swagger). Предшественник openapi-generator. Поддерживает Swagger 2.0 и OpenAPI 3.0. Сейчас менее активно развивается — для новых проектов рекомендуется openapi-generator.

4. TypeScript: openapi-typescript + openapi-fetch

Самый лёгкий и type-safe подход для TypeScript. Генерируются только типы, а openapi-fetch использует их для типизации запросов.

Установка

npm install openapi-typescript openapi-fetch
# или
npx openapi-typescript ./openapi.yaml -o ./src/api/schema.d.ts

Генерация типов

# Из локального файла
npx openapi-typescript ./openapi.yaml -o ./src/api/schema.d.ts

# Из URL
npx openapi-typescript https://api.example.com/openapi.json -o ./src/api/schema.d.ts

# С опциями
npx openapi-typescript ./openapi.yaml \
  --output ./src/api/schema.d.ts \
  --enum                           # генерировать TS enum вместо union

Пример OpenAPI-спецификации

# openapi.yaml
openapi: 3.1.0
info:
  title: Blog API
  version: 1.0.0
paths:
  /posts:
    get:
      operationId: listPosts
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
      responses:
        '200':
          description: Список постов
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Post'
                  total:
                    type: integer
    post:
      operationId: createPost
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreatePost'
      responses:
        '201':
          description: Пост создан
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Post'
  /posts/{id}:
    get:
      operationId: getPost
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Post'
        '404':
          description: Not found
components:
  schemas:
    Post:
      type: object
      required: [id, title, content, createdAt]
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
        content:
          type: string
        published:
          type: boolean
        createdAt:
          type: string
          format: date-time
    CreatePost:
      type: object
      required: [title, content]
      properties:
        title:
          type: string
          minLength: 1
          maxLength: 200
        content:
          type: string
        published:
          type: boolean
          default: false

Сгенерированные типы

Результат: schema.d.ts

openapi-typescript генерирует точные TypeScript-типы для всех paths, operations, components:

// src/api/schema.d.ts (сгенерировано)
export interface paths {
  "/posts": {
    get: {
      parameters: {
        query?: { page?: number; limit?: number; };
      };
      responses: {
        200: {
          content: {
            "application/json": {
              data: components["schemas"]["Post"][];
              total: number;
            };
          };
        };
      };
    };
    post: {
      requestBody: {
        content: {
          "application/json": components["schemas"]["CreatePost"];
        };
      };
      responses: {
        201: {
          content: {
            "application/json": components["schemas"]["Post"];
          };
        };
      };
    };
  };
  "/posts/{id}": {
    get: {
      parameters: { path: { id: string; }; };
      responses: {
        200: {
          content: {
            "application/json": components["schemas"]["Post"];
          };
        };
        404: { content: never; };
      };
    };
  };
}

export interface components {
  schemas: {
    Post: {
      id: string;
      title: string;
      content: string;
      published?: boolean;
      createdAt: string;
    };
    CreatePost: {
      title: string;
      content: string;
      published?: boolean;
    };
  };
}

Использование с openapi-fetch

// src/api/client.ts
import createClient from 'openapi-fetch';
import type { paths } from './schema';

const api = createClient<paths>({
  baseUrl: 'https://api.example.com',
  headers: {
    Authorization: `Bearer ${token}`,
  },
});

// Полная типобезопасность — автокомплит для путей, параметров, тела, ответа

// GET /posts?page=2&limit=10
const { data, error } = await api.GET('/posts', {
  params: { query: { page: 2, limit: 10 } },
});
// data.data → Post[]
// data.total → number

// POST /posts
const { data: newPost } = await api.POST('/posts', {
  body: {
    title: 'Новый пост',
    content: 'Содержимое...',
    published: true,
  },
});
// newPost → Post

// GET /posts/{id}
const { data: post, error: notFound } = await api.GET('/posts/{id}', {
  params: { path: { id: '550e8400-e29b-41d4-a716-446655440000' } },
});
// post → Post | undefined
// notFound → 404 error
💡 Бандл: openapi-fetch — всего ~6 KB (gzip). Это обёртка над fetch() с типизацией. Никакого лишнего runtime-кода, в отличие от полноценных SDK-генераторов.

5. Orval: React Query из OpenAPI

Orval — генерирует готовые React Query хуки, Zod-схемы и MSW-моки из OpenAPI.

Установка и конфигурация

npm install orval -D
// orval.config.ts
import { defineConfig } from 'orval';

export default defineConfig({
  blogApi: {
    input: {
      target: './openapi.yaml',
      // или URL: 'https://api.example.com/openapi.json'
    },
    output: {
      target: './src/api/generated.ts',
      client: 'react-query',
      mode: 'tags-split', // файл на каждый тег
      override: {
        mutator: {
          path: './src/api/custom-fetch.ts',
          name: 'customFetch',
        },
        query: {
          useQuery: true,
          useInfinite: true,
          useSuspenseQuery: true,
        },
      },
    },
  },
});
# Генерация
npx orval

# Watch-режим
npx orval --watch

Сгенерированные хуки

// src/api/generated.ts (сгенерировано Orval)

// Хуки для GET /posts
export const useListPosts = (
  params?: ListPostsParams,
  options?: UseQueryOptions<ListPosts200>,
) => {
  return useQuery({
    queryKey: getListPostsQueryKey(params),
    queryFn: () => listPosts(params),
    ...options,
  });
};

// Хук для POST /posts
export const useCreatePost = (
  options?: UseMutationOptions<Post, Error, CreatePost>,
) => {
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: createPost,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: getListPostsQueryKey() });
    },
    ...options,
  });
};

// Типы
export interface Post {
  id: string;
  title: string;
  content: string;
  published?: boolean;
  createdAt: string;
}

export interface CreatePost {
  title: string;
  content: string;
  published?: boolean;
}

Использование в компонентах

// components/Posts.tsx
import { useListPosts, useCreatePost } from '@/api/generated';

export function Posts() {
  const { data, isLoading } = useListPosts({ page: 1, limit: 20 });
  const createPost = useCreatePost();

  const handleCreate = () => {
    createPost.mutate({
      title: 'Новый пост',
      content: 'Содержимое',
    });
  };

  if (isLoading) return <div>Загрузка...</div>;

  return (
    <div>
      {data?.data.map((post) => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <time>{post.createdAt}</time>
        </article>
      ))}
      <button onClick={handleCreate}>Создать пост</button>
    </div>
  );
}

Генерация Zod-схем

// orval.config.ts
output: {
  client: 'zod',
  // Генерирует Zod-схемы для runtime-валидации
}

// Результат:
export const postSchema = z.object({
  id: z.string().uuid(),
  title: z.string(),
  content: z.string(),
  published: z.boolean().optional(),
  createdAt: z.string().datetime(),
});

Генерация MSW-моков

// orval.config.ts
output: {
  client: 'react-query',
  mock: true, // генерирует MSW handlers
}

// Результат: src/api/generated.msw.ts
import { http, HttpResponse } from 'msw';

export const getListPostsHandler = () =>
  http.get('*/posts', () => {
    return HttpResponse.json({
      data: [
        { id: '1', title: 'Mock Post', content: '...', createdAt: '2026-02-23' },
      ],
      total: 1,
    });
  });

6. openapi-generator: универсальный генератор

openapi-generator — самый мощный генератор с поддержкой 50+ языков. Генерирует полноценные SDK, серверные заглушки и документацию.

Установка

# NPM (рекомендуется)
npm install @openapitools/openapi-generator-cli -g

# Docker
docker pull openapitools/openapi-generator-cli

# Homebrew (macOS)
brew install openapi-generator

TypeScript (axios)

openapi-generator-cli generate \
  -i openapi.yaml \
  -g typescript-axios \
  -o ./src/api/generated \
  --additional-properties=supportsES6=true,npmName=blog-api-client

# Доступные TypeScript-генераторы:
# typescript-axios   — Axios-клиент
# typescript-fetch   — Fetch API
# typescript-node    — Node.js (http)
# typescript-angular — Angular HttpClient

Использование сгенерированного SDK

// TypeScript (axios)
import { Configuration, PostsApi } from './api/generated';

const config = new Configuration({
  basePath: 'https://api.example.com',
  accessToken: 'your-jwt-token',
});

const postsApi = new PostsApi(config);

// Типизированные вызовы
const { data: posts } = await postsApi.listPosts(1, 20);
// posts.data → Post[]

const { data: newPost } = await postsApi.createPost({
  title: 'Hello',
  content: 'World',
});
// newPost → Post

// Обработка ошибок
try {
  await postsApi.getPost('non-existent-id');
} catch (error) {
  if (axios.isAxiosError(error) && error.response?.status === 404) {
    console.log('Пост не найден');
  }
}

7. Python-клиенты

openapi-generator (Python)

openapi-generator-cli generate \
  -i openapi.yaml \
  -g python \
  -o ./python-client \
  --additional-properties=packageName=blog_api_client
# Использование
from blog_api_client import ApiClient, Configuration
from blog_api_client.api import PostsApi

config = Configuration(
    host="https://api.example.com",
    access_token="your-jwt-token"
)

with ApiClient(config) as client:
    posts_api = PostsApi(client)

    # Типизированные вызовы
    posts = posts_api.list_posts(page=1, limit=20)
    print(posts.data)  # List[Post]

    new_post = posts_api.create_post(
        create_post={"title": "Hello", "content": "World"}
    )
    print(new_post.id)  # str (UUID)

openapi-python-client (альтернатива)

pip install openapi-python-client

# Генерация
openapi-python-client generate --url https://api.example.com/openapi.json

# Обновление существующего
openapi-python-client update --url https://api.example.com/openapi.json
# Более идиоматичный Python-клиент
from blog_api_client import Client, AuthenticatedClient
from blog_api_client.api.posts import list_posts, create_post
from blog_api_client.models import CreatePost

client = AuthenticatedClient(
    base_url="https://api.example.com",
    token="your-jwt-token",
)

# Каждый эндпоинт — функция (не класс)
posts = list_posts.sync(client=client, page=1, limit=20)

# Async-версия
posts = await list_posts.asyncio(client=client, page=1, limit=20)

# Создание поста
new_post = create_post.sync(
    client=client,
    body=CreatePost(title="Hello", content="World"),
)

8. Go-клиенты

oapi-codegen (рекомендуется для Go)

go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@latest

# Генерация
oapi-codegen -generate types,client -package api openapi.yaml > api/client.go
// api/client.go (сгенерировано)
package api

type Post struct {
    Id        string  `json:"id"`
    Title     string  `json:"title"`
    Content   string  `json:"content"`
    Published *bool   `json:"published,omitempty"`
    CreatedAt string  `json:"createdAt"`
}

type CreatePost struct {
    Title     string `json:"title"`
    Content   string `json:"content"`
    Published *bool  `json:"published,omitempty"`
}
// main.go — использование
package main

import (
    "context"
    "fmt"
    "github.com/yourproject/api"
)

func main() {
    client, err := api.NewClientWithResponses(
        "https://api.example.com",
        api.WithRequestEditorFn(func(ctx context.Context, req *http.Request) error {
            req.Header.Set("Authorization", "Bearer "+token)
            return nil
        }),
    )
    if err != nil {
        panic(err)
    }

    // Типизированные вызовы
    page, limit := 1, 20
    resp, err := client.ListPostsWithResponse(context.Background(), &api.ListPostsParams{
        Page:  &page,
        Limit: &limit,
    })

    if resp.StatusCode() == 200 {
        for _, post := range resp.JSON200.Data {
            fmt.Printf("Post: %s\n", post.Title)
        }
    }
}

openapi-generator (Go)

openapi-generator-cli generate \
  -i openapi.yaml \
  -g go \
  -o ./go-client \
  --additional-properties=packageName=blogapi

9. Генерация серверных заглушек

Кодогенерация работает и в обратном направлении: из OpenAPI-спецификации можно создать скелет сервера с маршрутами, валидацией и моделями.

Доступные серверные генераторы

Генератор Фреймворк Команда
nodejs-express-server Node.js + Express -g nodejs-express-server
python-fastapi Python + FastAPI -g python-fastapi
go-server Go + net/http -g go-server
spring Java + Spring Boot -g spring
php-laravel PHP + Laravel -g php-laravel
rust-server Rust + Hyper -g rust-server

Пример: FastAPI server stub

openapi-generator-cli generate \
  -i openapi.yaml \
  -g python-fastapi \
  -o ./server
# server/src/openapi_server/apis/posts_api.py (сгенерировано)
from fastapi import APIRouter, Path, Query
from ..models.post import Post
from ..models.create_post import CreatePost

router = APIRouter()

@router.get("/posts", response_model=ListPostsResponse)
async def list_posts(
    page: int = Query(1, ge=1),
    limit: int = Query(20, ge=1, le=100),
) -> ListPostsResponse:
    # TODO: реализовать логику
    ...

@router.post("/posts", response_model=Post, status_code=201)
async def create_post(body: CreatePost) -> Post:
    # TODO: реализовать логику
    ...

@router.get("/posts/{id}", response_model=Post)
async def get_post(id: str = Path(...)) -> Post:
    # TODO: реализовать логику
    ...
💡 API-first подход: Серверные заглушки позволяют реализовать design-first workflow: сначала проектируете API в OpenAPI, затем генерируете скелет сервера и клиентов. Это гарантирует, что реализация соответствует спецификации.

10. CI/CD: автоматизация генерации

Самое ценное в кодогенерации — автоматическое обновление при изменении спецификации. Интегрируйте в CI/CD.

GitHub Actions

# .github/workflows/codegen.yml
name: OpenAPI Codegen
on:
  push:
    paths:
      - 'openapi.yaml'
  schedule:
    - cron: '0 6 * * 1' # каждый понедельник

jobs:
  generate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Generate TypeScript types
        run: npx openapi-typescript openapi.yaml -o src/api/schema.d.ts

      - name: Generate React Query hooks
        run: npx orval

      - name: Check for changes
        id: diff
        run: |
          git diff --exit-code src/api/ || echo "changed=true" >> $GITHUB_OUTPUT

      - name: Create PR with updated types
        if: steps.diff.outputs.changed == 'true'
        uses: peter-evans/create-pull-request@v6
        with:
          title: 'chore: update generated API types'
          body: |
            Auto-generated from OpenAPI spec changes.

            **Review the type changes carefully — they may indicate breaking API changes.**
          branch: chore/update-api-types
          labels: automated, api

npm scripts

{
  "scripts": {
    "codegen": "openapi-typescript openapi.yaml -o src/api/schema.d.ts",
    "codegen:orval": "orval",
    "codegen:all": "npm run codegen && npm run codegen:orval",
    "codegen:watch": "orval --watch",
    "predev": "npm run codegen:all",
    "prebuild": "npm run codegen:all"
  }
}
1

Спецификация обновлена

Бэкенд-разработчик меняет openapi.yaml — добавляет поле, меняет тип, добавляет эндпоинт.

2

CI запускает генерацию

Автоматически генерируются новые типы и клиенты для всех языков.

3

Проверка breaking changes

Если типы изменились — TypeScript-компилятор покажет ошибки в клиентском коде. Breaking changes ловятся автоматически.

4

PR с изменениями

Создаётся Pull Request с обновлёнными типами. Ревьюер видит, что именно изменилось в API.

11. Best practices

1. Держите спецификацию актуальной

⚠️ Мусор на входе — мусор на выходе. Если OpenAPI-спецификация устарела или содержит ошибки, сгенерированный код будет бесполезен. Валидируйте спеку в CI:
# Валидация спецификации
npx @redocly/cli lint openapi.yaml

# Или с spectral
npx @stoplight/spectral-cli lint openapi.yaml

2. Не редактируйте сгенерированный код

# .gitignore (если генерируете при сборке)
src/api/generated/

# Или коммитьте, но добавьте заголовок:
# AUTO-GENERATED — do not edit manually
# Re-generate: npm run codegen

Если нужна кастомизация — используйте шаблоны (templates) генератора или обёртку поверх сгенерированного кода:

// src/api/posts.ts — ваш код поверх сгенерированного
import { useListPosts as _useListPosts } from './generated';

export function useListPosts(page = 1) {
  return _useListPosts({ page, limit: 20 }, {
    staleTime: 5 * 60 * 1000,
    select: (data) => data.data, // убираем обёртку
  });
}

3. Используйте operationId

# ❌ Без operationId — некрасивые имена функций
paths:
  /posts:
    get:
      summary: Get posts

# ✅ С operationId — читаемые имена
paths:
  /posts:
    get:
      operationId: listPosts  # → useListPosts(), list_posts(), ListPosts()
      summary: Get posts

4. Версионируйте спецификацию

# Структура
api/
├── openapi.yaml          # текущая версия
├── openapi.v1.yaml       # предыдущая (для обратной совместимости)
└── changelog.md          # что изменилось

5. Тестируйте совместимость

# openapi-diff — проверка breaking changes
npx openapi-diff openapi.v1.yaml openapi.yaml

# Пример вывода:
# BREAKING: Removed path /posts/{id}/comments
# BREAKING: Changed type of Post.id from integer to string
# NON-BREAKING: Added optional field Post.tags

12. Сравнительная таблица инструментов

Инструмент Языки Тип вывода Бандл React Query Рекомендация
openapi-typescript TypeScript Только типы ~6 KB Нет (через openapi-fetch) TS: типы + лёгкий клиент
Orval TypeScript Хуки + типы + моки Средний ✅ Из коробки React/Vue + TanStack Query
openapi-generator 50+ языков SDK (классы) Большой Нет Мультиязычные проекты
swagger-codegen 40+ языков SDK (классы) Большой Нет Legacy Swagger 2.0
oapi-codegen Go Типы + клиент Минимальный N/A Go-проекты
openapi-python-client Python Клиент (async) Средний N/A Python (идиоматичный)
💡 Рекомендации:
TypeScript (типы) → openapi-typescript + openapi-fetch
TypeScript (React Query) → Orval
Python → openapi-python-client
Go → oapi-codegen
Java / C# / Kotlin / 50+ языков → openapi-generator
Full-stack TS без OpenAPItRPC

FAQ

Что такое кодогенерация из OpenAPI?

Автоматическое создание клиентского кода (SDK), типов, серверных заглушек и моков из OpenAPI-спецификации. Вместо ручного написания HTTP-запросов вы получаете готовый типизированный клиент для любого языка.

Какой инструмент кодогенерации лучше для TypeScript?

openapi-typescript + openapi-fetch — для максимально лёгкого type-safe клиента (~6 KB). Orval — если нужны React Query хуки, Zod-валидация и MSW-моки из коробки. openapi-generator — для полноценного SDK с классами.

Можно ли генерировать код из Swagger 2.0?

Да. openapi-generator и swagger-codegen поддерживают обе версии. Рекомендуется мигрировать на OpenAPI 3.x для современных возможностей (oneOf, callbacks, links). Конвертация: swagger2openapi.

Стоит ли коммитить сгенерированный код?

Зависит от проекта. Коммитить — если важна воспроизводимость и не все разработчики знают про кодогенерацию. Генерировать при сборке — если CI/CD настроен и спецификация всегда доступна. Рекомендуемый подход — генерировать в prebuild скрипте.

Чем кодогенерация отличается от tRPC?

tRPC использует TypeScript inference — типы передаются напрямую, без спецификации и генерации. Кодогенерация требует OpenAPI-файл, но работает с любыми языками (не только TypeScript). tRPC — для монорепо, кодогенерация — для мультиязычных проектов.

Как обрабатывать аутентификацию в сгенерированном клиенте?

Большинство генераторов поддерживают securitySchemes из OpenAPI. Для openapi-fetch — передайте заголовок в конфигурации. Для openapi-generator — используйте Configuration с accessToken или apiKey. Для Orval — настройте mutator с кастомным fetch.

Полезные ссылки

🚀 Создавайте API со спецификацией

LightBox API — моковые API с автоматической OpenAPI-документацией. Генерируйте типизированные клиенты для вашего фронтенда.

Попробовать бесплатно →

Статья опубликована: 23 февраля 2026
Автор: LightBox API Team