Skip to Content
BlogHey API: Auto-Generate a Type-Safe TypeScript SDK from OpenAPI

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

code-on-screen

πŸ“Έ

Pexels

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

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 add @hey-api/openapi-ts -D -E

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:

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

Create the config file

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

openapi-ts.config.ts
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
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
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
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:

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.

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.


Why This is a Big Deal ❀️

I want to be real with you here β€” the first time I ran pnpm generate:sdk and saw @tanstack/react-query.gen.ts appear with fully-typed hooks for every endpoint, I genuinely got excited. Like actually excited about a config file 😭.

Because think about what you’re eliminating:

  • Writing type interfaces that mirror what the backend already defined
  • Writing fetch wrappers for every endpoint
  • Setting up React Query queryFn and queryKey for each query
  • Forgetting to update types when the backend changes

All of that is just gone. You focus on your UI and your business logic. The boring plumbing is handled.


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 …

Comments

Leave a comment or reaction below!

Last updated on