5 min read

Hey API: Auto-Generate a Type-Safe TypeScript SDK from OpenAPI

typescript
openapi
react-query
zod
hey-api
openapi-ts
code-generation

Okay, let me ask you something. How many times have you written code like this?

typescript
const getUsers = async (): Promise<User[]> => {
  const res = await fetch('/api/users')
  if (!res.ok) throw new Error('Failed to fetch users')
  return res.json()
}

And then wrote a separate User type. And then wrapped it in a React Query hook. And then added Zod validation on top. For every. Single. Endpoint.

I was doing the exact same thing and honestly — it felt like copying the same pattern over and over while the backend team kept changing things and I had to hunt down every place I wrote that type and update it manually 😤.

Then I found Hey API and I have not looked back since.


What is Hey API? 🤔

Hey API (@hey-api/openapi-ts) is a code generation tool that reads your OpenAPI/Swagger spec and generates a fully type-safe SDK for you — complete with TypeScript types, React Query hooks, Axios client, and Zod validation schemas.

One command. Everything generated. Everything in sync with your backend.

The best part? Companies like Vercel, OpenCode, and PayPal are already using it in production. It's not some experimental side project — it's battle-tested and MIT licensed.

So now you must be wondering — what exactly does it generate?

  • types.gen.ts — all your TypeScript interfaces from the spec
  • sdk.gen.ts — type-safe API functions for every endpoint
  • @tanstack/react-query.gen.ts — ready-to-use React Query hooks
  • zod.gen.ts — Zod schemas for runtime validation
  • client.gen.ts — the configured HTTP client

Sounds amazing, right?


Let's Build It 🚀

I built a full working example — you can check it out here: github.com/7hourspg/open-api-sdk. Let me walk you through how to set it up yourself.

Install the package

pnpm

bash
pnpm add @hey-api/openapi-ts -D -E

npm

bash
npm add @hey-api/openapi-ts -D -E

yarn

bash
yarn add --dev @hey-api/openapi-ts

ℹ️ Note

The -E flag pins the exact version. Hey API recommends this since it's still in active development and minor updates can introduce changes.

Add the generate script

In your package.json, add a script to trigger generation:

json
{
  "scripts": {
    "generate:sdk": "openapi-ts"
  }
}

Create the config file

Create openapi-ts.config.ts in your project root:

typescript
import { defineConfig } from '@hey-api/openapi-ts'
 
export default defineConfig({
  input: './swagger.json', // your OpenAPI spec (local file or URL)
  output: 'src/client', // where to put the generated files
  plugins: [
    '@hey-api/client-axios', // use Axios as the HTTP client
    '@tanstack/react-query', // generate React Query hooks
    'zod', // generate Zod schemas
  ],
})

That's it. Run pnpm generate:sdk and watch the magic happen ✨.


Setting Up the Client 🔧

After generation, you need to configure the base URL and headers once. Do this in your main.tsx before your app renders:

src/main.tsx
// src/main.tsx
import { client } from './client/client.gen'
 
client.setConfig({
  baseURL: 'https://your-api-domain.com',
  headers: {
    'Content-Type': 'application/json',
  },
})

Now every generated function will use this config automatically. No need to pass the base URL anywhere else.


Using the Generated Hooks ✨

This is where it gets really fun. Here's what consuming a GET /users endpoint looks like after generation:

src/pages/users.tsx
// src/pages/users.tsx
import { useQuery } from '@tanstack/react-query'
import { getApiV1UsersOptions } from '../client/@tanstack/react-query.gen'
 
function UsersList() {
  const { data, isLoading, error } = useQuery(getApiV1UsersOptions())
 
  if (isLoading) return <p>Loading...</p>
  if (error) return <p>Something went wrong</p>
 
  return (
    <ul>
      {data?.map((user) => (
        <li key={user.id}>{user.userName}</li>
      ))}
    </ul>
  )
}

data is fully typed here. No casting, no guessing. The types come directly from your OpenAPI spec. If the backend changes the response shape and updates the spec, you just re-run pnpm generate:sdk and TypeScript will immediately tell you everywhere something broke. 🤯


What About Mutations? 🔄

Same story. POST, PUT, DELETE — all covered.

src/pages/create-user.tsx
// src/pages/create-user.tsx
import { useMutation } from '@tanstack/react-query'
import { postApiV1UsersMutation } from '../client/@tanstack/react-query.gen'
 
function CreateUser() {
  const { mutate, isPending } = useMutation(postApiV1UsersMutation())
 
  const handleSubmit = () => {
    mutate({
      body: { userName: 'Rajiv', password: 'secret123' },
    })
  }
 
  return (
    <button onClick={handleSubmit} disabled={isPending}>
      {isPending ? 'Creating...' : 'Create User'}
    </button>
  )
}

The naming convention is consistent and predictable — queries get Options as a suffix, mutations get Mutation. Once you see it once, you just know how to use every other endpoint.


The Generated Files Explained 📂

Let me quickly walk through what each generated file actually does:

text
src/client/
├── client.gen.ts                    # Axios instance + setConfig
├── types.gen.ts                     # All TypeScript interfaces
├── sdk.gen.ts                       # Raw API functions
├── zod.gen.ts                       # Zod validation schemas
└── @tanstack/
    └── react-query.gen.ts           # useQuery/useMutation wrappers

You never touch these files manually. They are generated output — treat them like node_modules. The source of truth is your swagger.json.

⚠️ Warning

Never edit the files inside src/client/ directly. Any manual changes will be overwritten the next time you run generate:sdk.


Keeping the SDK in Sync 🔄

When your backend team updates the API:

  1. Get the new swagger.json from them (or from a URL)
  2. Run pnpm generate:sdk
  3. Fix the TypeScript errors that pop up

That's the workflow. Step 3 is the key one — TypeScript errors are your changelogs now. No more "oh they changed the response format and now everything is undefined" moments at runtime.


Conclusion 🎉

If you're working with a REST API that has an OpenAPI spec, there is no reason to write your API client manually anymore. Hey API takes the spec and hands you back everything you need — types, functions, hooks, and validators — in seconds.

Try it out on your next project. Run that one command and see what appears. I promise you'll wonder how you lived without it.

Thanks for reading! ✨ Drop a comment if you have any questions or if you're already using something similar — would love to know what you think.

Bye for now .....