Claude Code for openapi-ts: Generate TypeScript from OpenAPI Specs — Claude Skills 360 Blog
Blog / Backend / Claude Code for openapi-ts: Generate TypeScript from OpenAPI Specs
Backend

Claude Code for openapi-ts: Generate TypeScript from OpenAPI Specs

Published: May 24, 2027
Read time: 6 min read
By: Claude Skills 360

openapi-ts (via @hey-api) generates fully typed TypeScript API clients from OpenAPI 3.x specs — npx @hey-api/openapi-ts -i openapi.yaml -o src/api generates services, models, and types. @hey-api/client-fetch generates a native fetch client; @hey-api/client-axios generates an Axios client. Generated services expose getUsers(), createUser({ body }), deleteUser({ path: { id } }) — all with inferred request/response types. Error types from OpenAPI components/responses are typed too. Configuration via openapi-ts.config.ts controls output directory, service naming, and plugins. openapi-zod-client generates Zod schemas from OpenAPI for runtime validation. @hey-api/sdk generates a modern SDK with tree-shakable functions. The watch flag regenerates on spec changes. --base overrides the base URL. CI/CD runs generation in pipelines with npx @hey-api/openapi-ts. Claude Code generates openapi-ts configs, re-export patterns, mock handlers, and CI workflows.

CLAUDE.md for openapi-ts

## openapi-ts Stack
- Version: @hey-api/openapi-ts >= 0.56, @hey-api/client-fetch >= 0.5
- Generate: npx @hey-api/openapi-ts -i ./openapi.yaml -o src/api/generated
- Config: openapi-ts.config.ts at project root with input/output/plugins
- Plugin fetch: plugins: ["@hey-api/client-fetch"] — native fetch, tree-shakable
- Plugin axios: plugins: ["@hey-api/client-axios"] — axios, interceptors
- Import: import { getUsers, createUser } from "@/api/generated"
- Base URL: client.setConfig({ baseUrl: process.env.NEXT_PUBLIC_API_URL })

Configuration

// openapi-ts.config.ts — openapi-ts configuration
import { defineConfig } from "@hey-api/openapi-ts"

export default defineConfig({
  // OpenAPI spec — URL or local file
  input: process.env.OPENAPI_URL ?? "./docs/openapi.yaml",

  // Output directory
  output: {
    path: "./src/api/generated",
    format: "prettier",
    lint: "eslint",
  },

  // Plugins determine the output shape
  plugins: [
    "@hey-api/schemas",     // JSON schemas from components
    "@hey-api/sdk",         // Tree-shakable SDK functions
    {
      name: "@hey-api/client-fetch",
      // Throw for non-2xx responses
      throwOnError: true,
    },
    {
      name: "@hey-api/typescript",
      enums: "typescript",  // Generate const enums
    },
  ],
})

Generated Client Usage

// lib/api/client.ts — configure the generated client
import { client } from "@/api/generated"
import { cookies } from "next/headers"

// Initialize once with base URL (server-side)
export function initApiClient(accessToken?: string) {
  client.setConfig({
    baseUrl: process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:8000",
    headers: accessToken
      ? { Authorization: `Bearer ${accessToken}` }
      : undefined,
  })
}

// Browser-side: auto-attach token from storage
if (typeof window !== "undefined") {
  client.interceptors.request.use((config) => {
    const token = localStorage.getItem("access_token")
    if (token) {
      config.headers.set("Authorization", `Bearer ${token}`)
    }
    return config
  })

  // Handle 401 — refresh or redirect
  client.interceptors.response.use(
    (response) => response,
    async (error) => {
      if (error.response?.status === 401) {
        // Try token refresh
        const refreshToken = localStorage.getItem("refresh_token")
        if (refreshToken) {
          try {
            const { data } = await fetch("/api/auth/refresh", {
              method: "POST",
              body: JSON.stringify({ refreshToken }),
            }).then(r => r.json())

            localStorage.setItem("access_token", data.accessToken)
            // Retry original request
            return client.request(error.config)
          } catch {
            localStorage.clear()
            window.location.href = "/sign-in"
          }
        }
      }
      throw error
    },
  )
}

Using Generated Services

// lib/api/users.ts — wrapper around generated services
import {
  getApiUsers,
  postApiUsers,
  getApiUsersId,
  putApiUsersId,
  deleteApiUsersId,
  type User,
  type CreateUserBody,
  type UpdateUserBody,
} from "@/api/generated"

// Typed response wrappers
export async function listUsers(params: { page?: number; per_page?: number } = {}) {
  const { data } = await getApiUsers({ query: params })
  return data  // Inferred as PaginatedUsers type from OpenAPI spec
}

export async function createUser(body: CreateUserBody) {
  const { data } = await postApiUsers({ body })
  return data  // Inferred as User
}

export async function getUserById(id: string) {
  const { data } = await getApiUsersId({ path: { id } })
  return data  // Inferred as User
}

export async function updateUser(id: string, body: UpdateUserBody) {
  const { data } = await putApiUsersId({ path: { id }, body })
  return data
}

export async function deleteUser(id: string) {
  await deleteApiUsersId({ path: { id } })
}

Zod Validator Generation

// scripts/generate-validators.ts — generate Zod from OpenAPI
// Run: npx openapi-zod-client openapi.yaml -o src/api/validators.ts

// Example generated output (openapi-zod-client):
// export const UserSchema = z.object({
//   id: z.string().uuid(),
//   email: z.string().email(),
//   name: z.string().min(1).max(100),
//   role: z.enum(["user", "admin"]),
//   createdAt: z.string().datetime(),
// })

// Custom config for openapi-zod-client
// package.json script:
// "generate:validators": "openapi-zod-client openapi.yaml -o src/api/validators.ts --export-schemas"

CI/CD Integration

# .github/workflows/generate-api.yml — auto-regenerate on spec change
# name: Regenerate API Client
# on:
#   push:
#     paths:
#       - "docs/openapi.yaml"
# jobs:
#   generate:
#     runs-on: ubuntu-latest
#     steps:
#       - uses: actions/checkout@v4
#       - uses: actions/setup-node@v4
#         with: { node-version: '20', cache: 'npm' }
#       - run: npm ci
#       - run: npx @hey-api/openapi-ts
#       - name: Commit generated files
#         uses: stefanzweifel/git-auto-commit-action@v5
#         with:
#           commit_message: "chore: regenerate API client from OpenAPI spec"
#           file_pattern: "src/api/generated/**"
// openapi-ts.config.ts — remote spec from running backend
import { defineConfig } from "@hey-api/openapi-ts"

// Optionally fetch spec from running dev server
export default defineConfig({
  input: process.env.CI
    ? "./docs/openapi.yaml"          // CI: use committed spec
    : "http://localhost:8000/openapi.json",  // Local: live spec from API

  output: {
    path: "./src/api/generated",
    format: "prettier",
  },

  plugins: [
    "@hey-api/sdk",
    {
      name: "@hey-api/client-fetch",
      throwOnError: true,
    },
    {
      name: "@hey-api/typescript",
      enums: "typescript",
      dates: "types+transform",  // Auto-transform ISO strings to Date objects
    },
  ],
})

MSW Mocks from OpenAPI

// scripts/generate-mocks.ts — generate MSW handlers from OpenAPI
// package.json: "generate:mocks": "openapi-msw openapi.yaml -o src/mocks/handlers.ts"

// Usage in tests:
// import { handlers } from "@/mocks/handlers"
// import { setupServer } from "msw/node"
// const server = setupServer(...handlers)
// beforeAll(() => server.listen())
// afterEach(() => server.resetHandlers())
// afterAll(() => server.close())

For the Orval alternative when generating React Query hooks, Axios clients, Angular services, or Zod schemas directly from OpenAPI with zero manual wrapping — Orval has more generator plugins and is more opinionated about the output shape, while openapi-ts is more flexible and has better TypeScript quality, see the Orval guide. For the Zodios alternative when a hand-authored, type-safe HTTP client with Zod schema validation at the boundary is preferred over code generation — Zodios lets you define API contracts in TypeScript code rather than generating from a YAML spec, useful when there’s no existing OpenAPI document, see the Zodios guide. The Claude Skills 360 bundle includes openapi-ts skill sets covering codegen, client configuration, and CI integration. Start with the free tier to try API client generation.

Keep Reading

Backend

Claude Code for Bun: Fast JavaScript Runtime and Toolkit

Build with Bun and Claude Code — Bun.serve for HTTP servers, Bun.file for fast file I/O, Bun.$ for shell commands, Bun.sql for SQLite and PostgreSQL, Bun.build for bundling, bun:test for testing, Bun.hash for hashing, bun.lock for deterministic installs, bun run for package.json scripts, hot reloading with --hot, bun init for project scaffolding, and compatibility with Node.js modules.

6 min read Jun 13, 2027
Backend

Claude Code for Express.js Advanced: Patterns for Production APIs

Advanced Express.js patterns with Claude Code — typed request handlers with RequestHandler generics, async error handling middleware, Zod validation middleware factory, rate limiting with express-rate-limit and Redis store, helmet security middleware, compression, dependency injection with tsyringe, file upload with multer and S3, pagination utilities, JWT middleware, and structured logging with pino.

6 min read Jun 8, 2027
Backend

Claude Code for KeystoneJS: Node.js CMS and App Framework

Build full-stack apps with KeystoneJS and Claude Code — config with lists, fields.text and fields.relationship for schema definition, access control with isAuthenticated and isAdmin functions, hooks with beforeOperation and afterOperation, GraphQL API auto-generation from schema, AdminUI for content management, session with statelessSessions, Prisma adapter for database, file storage with images and files fields, and custom REST endpoints.

6 min read Jun 7, 2027

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free