<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: D.S.</title>
    <description>The latest articles on DEV Community by D.S. (@gunzip_).</description>
    <link>https://dev.to/gunzip_</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3490643%2Fe0f11b1e-0cd0-4fe2-8ed0-cba0dd3b3958.png</url>
      <title>DEV Community: D.S.</title>
      <link>https://dev.to/gunzip_</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gunzip_"/>
    <language>en</language>
    <item>
      <title>Generate TanStack Query Hooks from OpenAPI: Own the Last Mile</title>
      <dc:creator>D.S.</dc:creator>
      <pubDate>Mon, 11 May 2026 15:03:49 +0000</pubDate>
      <link>https://dev.to/gunzip_/generate-tanstack-query-hooks-from-openapi-own-the-last-mile-3h1k</link>
      <guid>https://dev.to/gunzip_/generate-tanstack-query-hooks-from-openapi-own-the-last-mile-3h1k</guid>
      <description>&lt;p&gt;When you go &lt;strong&gt;API-First&lt;/strong&gt;, your OpenAPI contract should be the single source of truth across your entire stack.&lt;/p&gt;

&lt;p&gt;Unfortunately, most community-managed generators TanStack Query generators force you into an uncomfortable tradeoff: either live with rigid, inflexible hooks, or write everything by hand and lose the benefits of type safety.&lt;/p&gt;

&lt;p&gt;This article shows a more flexible approach.&lt;/p&gt;

&lt;p&gt;Use &lt;a href="https://gunzip.github.io/apical-ts/" rel="noopener noreferrer"&gt;&lt;code&gt;@apical-ts/craft&lt;/code&gt;&lt;/a&gt; to handle all the heavy, error-prone OpenAPI work: parsing, schema generation, response unions, parameter serialization, and type inference. Then keep a thin, custom generator in your own repository for the TanStack Query layer.&lt;/p&gt;

&lt;p&gt;The result: fully typed, clean, and highly ergonomic hooks that you fully own and can evolve as your application needs — without upstream dependencies or duplicated contract logic.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This article is the frontend counterpart to&lt;br&gt;&lt;br&gt;
&lt;a href="https://dev.to/gunzip_/api-first-with-hono-openapi-to-typed-routes-without-lock-in-22ad"&gt;API-First with Hono: OpenAPI to Typed Routes Without Lock-In&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When working with TanStack Query and you want to generate hooks from an OpenAPI contract, you have a few options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;implement hooks by hand trying to match the contract types&lt;/li&gt;
&lt;li&gt;use a community-managed open source generator for the hooks layer&lt;/li&gt;
&lt;li&gt;or keep a small custom generator in your own repository&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This article shows the last option, using &lt;a href="https://gunzip.github.io/apical-ts/" rel="noopener noreferrer"&gt;&lt;code&gt;@apical-ts/craft&lt;/code&gt;&lt;/a&gt; to generate the contract-sensitive pieces and a lightweight local generator to emit TanStack Query hooks, as in &lt;a href="https://github.com/gunzip/apical-ts/tree/main/examples/tanstack-query-hooks" rel="noopener noreferrer"&gt;&lt;code&gt;examples/tanstack-query-hooks/&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Tradeoff Is Ownership
&lt;/h2&gt;

&lt;p&gt;Both the second and third approaches are still code generation. Both start from OpenAPI. What changes is who owns the last mile of the frontend integration.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Shared input&lt;/th&gt;
&lt;th&gt;Who owns hook ergonomics&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Community-managed generator&lt;/td&gt;
&lt;td&gt;OpenAPI spec&lt;/td&gt;
&lt;td&gt;upstream open source project&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom local generator&lt;/td&gt;
&lt;td&gt;OpenAPI spec&lt;/td&gt;
&lt;td&gt;your repository&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That difference matters because TanStack Query is intentionally flexible. Query keys, invalidation, mutation side effects, pagination, SSR conventions, and wrapper APIs often end up being application-specific.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Sweet Spot: Delegate the Hard Part, Own the Hook Layer
&lt;/h2&gt;

&lt;p&gt;With &lt;a href="https://gunzip.github.io/apical-ts/" rel="noopener noreferrer"&gt;&lt;code&gt;@apical-ts/craft&lt;/code&gt;&lt;/a&gt; you can generate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;shared Zod schemas in &lt;code&gt;generated/schemas/*&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;route metadata in &lt;code&gt;generated/routes/*&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;typed operation functions in &lt;code&gt;generated/client/*&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means your custom hook generator does &lt;strong&gt;not&lt;/strong&gt; need to solve the hardest OpenAPI problems itself. &lt;code&gt;@apical-ts/craft&lt;/code&gt; already handles the complex, correctness-sensitive work.&lt;/p&gt;

&lt;p&gt;At that point, the TanStack Query layer becomes a much thinner problem: read the generated client and route metadata, then wrap them in &lt;code&gt;useQuery&lt;/code&gt; and &lt;code&gt;useMutation&lt;/code&gt; the way your application wants.&lt;/p&gt;

&lt;p&gt;This is also exactly the kind of layer that can be implemented or evolved very quickly with an AI coding agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Generate the typed client, route metadata, and schemas&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @apical-ts/craft generate &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--client&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-i&lt;/span&gt; ./swagger.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; ./generated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the example package, this is exposed as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm run generate:client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Run a local generator for the TanStack Query layer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The local generator in &lt;code&gt;scripts/generate-hooks.ts&lt;/code&gt; reads the generated client and routes, then emits hook modules under &lt;code&gt;generated/tanstack-query-hooks/*&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Run it in the example template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm run generate:hooks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or by yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tsx ./scripts/generate-hooks.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Example Usage
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFindPetsByStatus&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;available&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addPet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAddPetMutation&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;addPet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mutateAsync&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Rex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;photoUrls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generated hooks stay simple and fully typed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useFindPetsByStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Parameters&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;ops&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findPetsByStatus&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Awaited&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;ReturnType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;ops&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findPetsByStatus&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;useQuery&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;queryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;findPetsByStatus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;queryFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;ops&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findPetsByStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why Owning the Hook Layer Matters
&lt;/h2&gt;

&lt;p&gt;Most feature requests for community generators are not about OpenAPI parsing: they’re about customization. Custom query key factories, selective invalidation, pagination helpers, optimistic updates, SSR patterns, etc.&lt;/p&gt;

&lt;p&gt;These are exactly the kinds of changes you can implement &lt;em&gt;quickly&lt;/em&gt; when you own the layer instead of opening an issue upstream and hoping the customization gets accepted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Take
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;@apical-ts/craft&lt;/code&gt; lets you delegate the hard OpenAPI work while keeping full control over your TanStack Query experience. It’s the best of both worlds: strong types and maximum flexibility.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ready-to-use example&lt;/strong&gt;:  &lt;/p&gt;

&lt;p&gt;You may want to try it yourself:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/gunzip/apical-ts/tree/main/examples/tanstack-query-hooks" rel="noopener noreferrer"&gt;https://github.com/gunzip/apical-ts/tree/main/examples/tanstack-query-hooks&lt;/a&gt;&lt;/p&gt;

</description>
      <category>tanstack</category>
      <category>react</category>
      <category>openapi</category>
      <category>zod</category>
    </item>
    <item>
      <title>API-First with Hono: OpenAPI to Typed Routes Without Lock-in</title>
      <dc:creator>D.S.</dc:creator>
      <pubDate>Sun, 10 May 2026 22:04:03 +0000</pubDate>
      <link>https://dev.to/gunzip_/api-first-with-hono-openapi-to-typed-routes-without-lock-in-22ad</link>
      <guid>https://dev.to/gunzip_/api-first-with-hono-openapi-to-typed-routes-without-lock-in-22ad</guid>
      <description>&lt;p&gt;When working with &lt;a href="https://hono.dev" rel="noopener noreferrer"&gt;Hono&lt;/a&gt;, both &lt;strong&gt;code-first&lt;/strong&gt; and &lt;strong&gt;API-first&lt;/strong&gt; approaches have valid use cases.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;@hono/zod-openapi&lt;/code&gt; is an excellent choice for many teams that prefer a code-first workflow. However, some teams deliberately choose (or are required) to adopt an &lt;strong&gt;API-first&lt;/strong&gt; strategy, where the OpenAPI specification is the Single Source of Truth.&lt;/p&gt;

&lt;p&gt;This article shows a simple, flexible way to implement &lt;strong&gt;API-first&lt;/strong&gt; with Hono using &lt;a href="https://gunzip.github.io/apical-ts/" rel="noopener noreferrer"&gt;&lt;code&gt;@apical-ts/craft&lt;/code&gt;&lt;/a&gt; and a lightweight custom generator.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Some Teams Choose API-First
&lt;/h2&gt;

&lt;p&gt;While code-first is often faster for small-to-medium projects, API-first becomes valuable when you want to keep the door open to potentially switch frameworks in the future.&lt;/p&gt;

&lt;p&gt;Generally speaking, &lt;strong&gt;code-first&lt;/strong&gt; typically delivers the best developer experience as your consumers are also in the TypeScript ecosystem, but it introduces framework lock-in. &lt;strong&gt;API-first&lt;/strong&gt; requires a code generation step, but gives you more flexibility and independence in the long term.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Sweet Spot: Generate the Hard Part, Keep the Framework Layer Thin
&lt;/h2&gt;

&lt;p&gt;You don't have to sacrifice DX for flexibility. &lt;/p&gt;

&lt;p&gt;With &lt;a href="https://gunzip.github.io/apical-ts/" rel="noopener noreferrer"&gt;&lt;code&gt;@apical-ts/craft&lt;/code&gt;&lt;/a&gt; you can generate Zod v4 schemas and "agnostic" rich route metadata directly from your &lt;code&gt;openapi.yaml&lt;/code&gt;, then use a small custom generator to create Hono routes exactly as you want them.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Generate Zod schemas and route metadata&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @apical-ts/craft generate &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-i&lt;/span&gt; ./openapi.yaml &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; ./src/generated &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--routes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Transform the metadata with your own generator&lt;/strong&gt; into clean, idiomatic Hono code.&lt;/p&gt;

&lt;p&gt;You can bootstrap this generator quickly by describing the desired architecture to an AI coding agent or refer to the &lt;a href="https://github.com/gunzip/apical-ts/tree/main/examples/hono" rel="noopener noreferrer"&gt;existing template in the @apical-ts documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here’s an example of a prompt you can use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;Goal: build type safe Hono handlers by first generating route metadata with @apical-ts/craft and then writing a generator that emits the Hono integration.

Process:
&lt;span class="p"&gt;1.&lt;/span&gt; Run &lt;span class="sb"&gt;`npx @apical-ts/craft generate --routes -i openapi.yaml -o generated`&lt;/span&gt;.
&lt;span class="p"&gt;2.&lt;/span&gt; Inspect &lt;span class="sb"&gt;`generated/routes/*`&lt;/span&gt; and use them as the only source of truth for &lt;span class="sb"&gt;`operationId`&lt;/span&gt;, &lt;span class="sb"&gt;`path`&lt;/span&gt;, &lt;span class="sb"&gt;`method`&lt;/span&gt;, &lt;span class="sb"&gt;`params`&lt;/span&gt;, &lt;span class="sb"&gt;`requestMap`&lt;/span&gt;, and &lt;span class="sb"&gt;`responseMap`&lt;/span&gt;.
&lt;span class="p"&gt;3.&lt;/span&gt; Implement the generator entrypoint in &lt;span class="sb"&gt;`scripts/generate-hono-server.ts`&lt;/span&gt;.
&lt;span class="p"&gt;4.&lt;/span&gt; Implement the generator modules under &lt;span class="sb"&gt;`scripts/hono-generator/*`&lt;/span&gt; so they read generated route metadata and emit &lt;span class="sb"&gt;`generated/hono/*`&lt;/span&gt;.
&lt;span class="p"&gt;5.&lt;/span&gt; The generator should produce:
&lt;span class="p"&gt;   -&lt;/span&gt; one generated Hono operation module per route
&lt;span class="p"&gt;   -&lt;/span&gt; one handler file per operation for userland code
&lt;span class="p"&gt;   -&lt;/span&gt; a register-routes module that mounts routes without a central handlers object
&lt;span class="p"&gt;   -&lt;/span&gt; shared runtime helpers
&lt;span class="p"&gt;6.&lt;/span&gt; Add a runnable Hono server that imports the generated registration layer.

Rules:
&lt;span class="p"&gt;-&lt;/span&gt; do not hand-write &lt;span class="sb"&gt;`generated/hono/*`&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; do not redefine endpoints or payload types outside @apical-ts/craft output
&lt;span class="p"&gt;-&lt;/span&gt; request validation must be driven by generated schemas and metadata
&lt;span class="p"&gt;-&lt;/span&gt; use &lt;span class="sb"&gt;`@hono/zod-validator`&lt;/span&gt; where request validation is needed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there, use the local generator to emit the Hono-specific layer, i.e.:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tsx scripts/generate-hono-server.ts &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--routes&lt;/span&gt; generated/routes &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; generated/hono &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--handlers&lt;/span&gt; handlers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generator can stay very opinionated about your project structure without becoming the source of truth for the contract.&lt;/p&gt;

&lt;p&gt;Typical project layout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;src/
├── handlers/                 &lt;span class="c"&gt;# ← Your business logic (not overwritten)&lt;/span&gt;
│   ├── pets/
│   │   ├── addPet.ts
│   │   └── getPetById.ts
├── generated/
│   └── hono/
│       ├── operations/
│       └── register-routes.ts
├── openapi.yaml
└── generators/
    └── hono-generator.ts     &lt;span class="c"&gt;# ← Your custom generator&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why not let the AI generate the Zod schemas too?
&lt;/h3&gt;

&lt;p&gt;Generating high-fidelity Zod schemas from OpenAPI is a surprisingly complex task. It requires deep knowledge of OpenAPI edge cases (discriminators, advanced &lt;code&gt;oneOf&lt;/code&gt;/&lt;code&gt;anyOf&lt;/code&gt;, nullable + required combinations, complex &lt;code&gt;additionalProperties&lt;/code&gt;, XML support, encoding options, etc.). Even modern coding agents struggle to handle all these cases reliably and consistently, moreover, OpenAPI specs which aren't well formed needs tolerant parsers. &lt;/p&gt;

&lt;p&gt;That's why it's smarter to use a specialized, battle-tested tool that lets you save tokens for the schema generation part, and then let the AI (or yourself) build the lighter, easier to implement (on a rock solid base), fully customizable, deterministic framework-specific generator.&lt;/p&gt;

&lt;p&gt;The pragmatic split is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use @apical-ts for schemas and route metadata&lt;/li&gt;
&lt;li&gt;use AI or local code for the adapter layer&lt;/li&gt;
&lt;li&gt;keep business logic handwritten&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That gives you flexibility where it is cheap, and determinism where it is hard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example Handler&lt;/strong&gt; (strongly typed):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AddPetHandler&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../../generated/hono/operations/addPet.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addPetHandler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AddPetHandler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;petService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;200&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generated handler type ensure that your business logic remains perfectly aligned with your contract. The handler receives an input object containing already validated &lt;code&gt;body&lt;/code&gt;, &lt;code&gt;query&lt;/code&gt;, &lt;code&gt;headers&lt;/code&gt;, and &lt;code&gt;path parameters&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhqgq6n64qbagudgo8w8g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhqgq6n64qbagudgo8w8g.png" alt="Hono Handler Input" width="800" height="307"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In addition, the return type is strictly enforced at compile-time: you &lt;strong&gt;must&lt;/strong&gt; return a valid combination of &lt;code&gt;status&lt;/code&gt;, &lt;code&gt;contentType&lt;/code&gt;, and &lt;code&gt;data&lt;/code&gt; as defined in your OpenAPI specification.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5itjvxslfv0t5v80zp51.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5itjvxslfv0t5v80zp51.png" alt="Hono Handler Output" width="800" height="255"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  When I would not use this
&lt;/h2&gt;

&lt;p&gt;I would probably stick to code-first when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the team is fully TypeScript-native and Hono-centric&lt;/li&gt;
&lt;li&gt;maintaining a local generator would be overhead rather than leverage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In that case, &lt;code&gt;@hono/zod-openapi&lt;/code&gt; is hard to beat for simplicity and speed.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;@apical-ts/craft&lt;/code&gt; vs &lt;code&gt;@hono/zod-openapi&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;These tools solve related but different problems.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;@hono/zod-openapi&lt;/code&gt; is a great fit when you want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the best immediate Hono DX&lt;/li&gt;
&lt;li&gt;route definitions authored directly in code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;@apical-ts/craft&lt;/code&gt; plus a local Hono generator is a better fit when you want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OpenAPI to remain the source of truth&lt;/li&gt;
&lt;li&gt;portable route metadata and schemas&lt;/li&gt;
&lt;li&gt;a thinner dependency on any single server framework&lt;/li&gt;
&lt;li&gt;the freedom to shape the generated Hono layer yourself&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final take
&lt;/h2&gt;

&lt;p&gt;If you are all-in on Hono and optimizing for raw speed of iteration, code-first is still hard to beat.&lt;/p&gt;

&lt;p&gt;But if OpenAPI needs to stay the source of truth, you do not have to accept weak DX or a rigid framework abstraction.&lt;/p&gt;

&lt;p&gt;Generate the hard, correctness-sensitive pieces with &lt;code&gt;@apical-ts/craft&lt;/code&gt;, keep the Hono layer thin and local, and let handlers stay close to the business domain.&lt;/p&gt;

&lt;p&gt;That is a good middle ground between &lt;strong&gt;strict contract ownership&lt;/strong&gt; and &lt;strong&gt;framework-level flexibility&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ready-to-use Example
&lt;/h2&gt;

&lt;p&gt;Full, ready-to-fork example showing this workflow in action:&lt;br&gt;
&lt;a href="https://github.com/gunzip/apical-ts/tree/main/examples/hono" rel="noopener noreferrer"&gt;https://github.com/gunzip/apical-ts/tree/main/examples/hono&lt;/a&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>openapi</category>
      <category>zod</category>
      <category>architecture</category>
    </item>
    <item>
      <title>How OpenAPI Undermines a Good Developer Experience</title>
      <dc:creator>D.S.</dc:creator>
      <pubDate>Fri, 03 Oct 2025 09:36:41 +0000</pubDate>
      <link>https://dev.to/gunzip_/how-openapi-undermines-a-good-developer-experience-344m</link>
      <guid>https://dev.to/gunzip_/how-openapi-undermines-a-good-developer-experience-344m</guid>
      <description>&lt;p&gt;In the JavaScript/TypeScript ecosystem, tools like &lt;a href="https://trpc.io/" rel="noopener noreferrer"&gt;tRPC&lt;/a&gt; and &lt;a href="https://orpc.unnoq.com/" rel="noopener noreferrer"&gt;oRPC&lt;/a&gt; are gaining traction. This isn’t by chance, it signals a growing need in the JS world: moving beyond the &lt;strong&gt;Developer Experience&lt;/strong&gt; imposed by OpenAPI’s schema-first approach. But why?&lt;/p&gt;

&lt;h2&gt;
  
  
  All OpenAPI Code Generators Are Broken
&lt;/h2&gt;

&lt;p&gt;With the help of Gen-AI, I'm building a &lt;a href="https://gunzip.github.io/apical-ts/" rel="noopener noreferrer"&gt;tool to generate TypeScript code from OpenAPI specs&lt;/a&gt;. Fighting against the standard, I uncovered a systemic issue: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;All existing code generators are unreliable, and it couldn’t be otherwise.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;OpenAPI specs are unnecessarily complex&lt;/strong&gt;, creating a chaotic mess for anyone trying to automate code generation. Here’s why.&lt;/p&gt;

&lt;h3&gt;
  
  
   Error Handling Chaos
&lt;/h3&gt;

&lt;p&gt;Error handling in OpenAPI requires a sisyphean effort. You must manually map different layers: &lt;strong&gt;network errors&lt;/strong&gt; (failed &lt;code&gt;fetch&lt;/code&gt;), &lt;strong&gt;server errors&lt;/strong&gt; (&lt;code&gt;5xx&lt;/code&gt;), &lt;strong&gt;client errors&lt;/strong&gt; (&lt;code&gt;4xx&lt;/code&gt;), and payload serialization/deserialization issues. &lt;/p&gt;

&lt;p&gt;Each status code - (ie. &lt;code&gt;404&lt;/code&gt; returning nothing, &lt;code&gt;429&lt;/code&gt; providing retry parameters, &lt;code&gt;400&lt;/code&gt; delivering unpredictable serialization errors, ...) may (or not) come with a payload in wildly different formats with disconnected meanings.&lt;/p&gt;

&lt;p&gt;Therefore, a client generator cannot merely "implement retries": &lt;strong&gt;users must handle all this logic themselves&lt;/strong&gt; by interpreting the returned data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Serialization Nightmare
&lt;/h3&gt;

&lt;p&gt;Take &lt;a href="https://swagger.io/docs/specification/v3_0/serialization/" rel="noopener noreferrer"&gt;parameter serialization&lt;/a&gt; (ie. &lt;code&gt;style&lt;/code&gt; and &lt;code&gt;explode&lt;/code&gt;). You can send textual data to an endpoint in seven different ways with the same outcome. This redundancy is there for legacy reasons, pointless and breeds ambiguity everywhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  JSON Schema Quirks
&lt;/h3&gt;

&lt;p&gt;JSON Schema is a minefield of edge cases. For example, you can have an allOf intersection with and empty object that specifies a list of required fields without any schema attached.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;SomeSchema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;allOf&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/SomeOtherSchema"&lt;/span&gt;        
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/SomeOtherSchemaAgain"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;object&lt;/span&gt;
      &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;foo&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generator implementation must traverse all fields of all objects in the intersection, only to mark as required - if any - those referenced by this empty object. Guess what? No know tool handles this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wildcard Complexity
&lt;/h3&gt;

&lt;p&gt;Generators must handle vague cases like range status codes (e.g., &lt;code&gt;4XX&lt;/code&gt;, &lt;code&gt;5XX&lt;/code&gt;, ...) alongside the "&lt;code&gt;default&lt;/code&gt;" case, adding yet another layer of complexity that’s nearly impossible to manage keeping a good DX.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Challenge
&lt;/h2&gt;

&lt;p&gt;With so many intersections and details, no tool can handle every scenario. TypeScript developers today are working with tools that are merely "good enough," if that’s even sufficient.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What’s the point of a spec if its core purpose - automatic code generation - is inherently flawed?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;All existing TypeScript generators are approximate at best. You can verify this yourself against a &lt;a href="https://gist.githubusercontent.com/gunzip/6f42c323ba2b7b099a8fd0a0fcfef9ca/raw/beeeb2fdec7a1b3ef5b825ee9f9cbbbd589f93b7/merge.yaml" rel="noopener noreferrer"&gt;valid OpenAPI spec&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Try find a generator that correctly handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Different schemas for multiple &lt;code&gt;2xx&lt;/code&gt; responses&lt;/li&gt;
&lt;li&gt;Different schemas per content type&lt;/li&gt;
&lt;li&gt;Default responses&lt;/li&gt;
&lt;li&gt;Range status codes&lt;/li&gt;
&lt;li&gt;Intersections with empty objects&lt;/li&gt;
&lt;li&gt;Self references&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Time to Rethink the Paradigm
&lt;/h2&gt;

&lt;p&gt;Recently, I came across the &lt;a href="https://blog.cloudflare.com/capnweb-javascript-rpc-library/" rel="noopener noreferrer"&gt;Cap'n Web protocol&lt;/a&gt; from Cloudflare, and it clearly demonstrates a unified approach that delivers a DX unthinkable with OpenAPI.&lt;/p&gt;

&lt;p&gt;OpenAPI solves problems that shouldn’t exist in the first place. The schema-first approach is doomed to deliver a subpar DX. It's so poor that Microsoft decided to implement &lt;a href="https://typespec.io/" rel="noopener noreferrer"&gt;its own alternative DSL&lt;/a&gt; just to produce OpenAPI specs. Entire commercial companies have their core product built around being a decent OpenAPI code generator (!).&lt;/p&gt;

&lt;p&gt;For a new project - especially internal services - I would rather lean towards DX-friendly alternatives like modern RPC protocols or a code-first approach where the API specification is generated directly from the source code.&lt;/p&gt;

&lt;p&gt;Are you using OpenAPI for internal APIs, or have you switched to something more streamlined?&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>typescript</category>
      <category>discuss</category>
      <category>api</category>
    </item>
    <item>
      <title>OpenAPI to TypeScript: a More Reliable and Type-Safe Approach</title>
      <dc:creator>D.S.</dc:creator>
      <pubDate>Sun, 14 Sep 2025 22:42:32 +0000</pubDate>
      <link>https://dev.to/gunzip_/typescript-devs-dont-let-your-openapi-client-generator-lie-to-you-47d6</link>
      <guid>https://dev.to/gunzip_/typescript-devs-dont-let-your-openapi-client-generator-lie-to-you-47d6</guid>
      <description>&lt;p&gt;If you’re building REST API clients (or servers) with TypeScript, you expect type safety to save the day. But most existing OpenAPI-to-Typescript generators give a false sense of security, hiding pitfalls that can bite in production. After benchmarking 20+ tools, here’s what I found, and how I choose to fix it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Poor error handling
&lt;/h2&gt;

&lt;p&gt;Calling&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;should be safe, but the method signature often ignores network issues like DNS failures or timeouts. You’re forced to wrap it in a &lt;code&gt;try/catch&lt;/code&gt;, but without clear documentation on what errors to expect, you’re left guessing. Even when documented, this approach is fragile and prevents efficient error handling as validation errors (versus OpenAPI specs) end up in the same bag as network errors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limited handling of HTTP statuses
&lt;/h2&gt;

&lt;p&gt;Most generators emit types only for 2xx responses, ignoring 4xx/5xx errors with specific payloads. If the API returns a 400 or 500 with some &lt;code&gt;ProblemJson&lt;/code&gt;, generated TypeScript types (and/or runtime schemas) won’t reflect it, leaving you vulnerable to runtime surprises.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multiple success responses, different payloads
&lt;/h2&gt;

&lt;p&gt;Some APIs return different payloads for 200 vs. 201 status codes. Many generated clients only handle the &lt;em&gt;first&lt;/em&gt; successful status code. Other clients either treat non-200 responses as unknown errors, or merge them into some vague Typescript union, forcing further runtime checks to discriminate. What’s the point of using the generated code then?&lt;/p&gt;

&lt;h2&gt;
  
  
  Mishandled default responses
&lt;/h2&gt;

&lt;p&gt;The smartest generators create &lt;strong&gt;discriminated unions&lt;/strong&gt; for all response types (200, 201, 400, 500, etc.), but &lt;code&gt;default&lt;/code&gt; responses (covering unspecified cases in OpenAPI) are treated as generic errors and/or lack payload typing and validation, leading to inaccurate types.&lt;/p&gt;

&lt;h2&gt;
  
  
  Content-type being ignored
&lt;/h2&gt;

&lt;p&gt;APIs returning multiple content types require discrimination by both status code and content-type. Afaik, no existing Typescript generator supports this. Ideally, you should be able to write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Typescript should know that r.data is SomeJsonModel&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/xml&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Typescript should know that r.data is SomeXmlModel&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  OpenAPI Schema mismatch
&lt;/h2&gt;

&lt;p&gt;OpenAPI schemas are more expressive than TypeScript types. For example, a schema defining an email string or regex pattern often becomes a plain string in TypeScript. Even tools with runtime validation struggle to support advanced OpenAPI features like string patterns or &lt;code&gt;oneOf&lt;/code&gt; vs &lt;code&gt;anyOf&lt;/code&gt;, leading to inaccurate types and runtime bugs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;After evaluating the landscape, I faced a dilemma: convince maintainers of top tools to adopt breaking changes for their thousands of users or build a new solution from scratch. I chose the latter and built an open-source TypeScript client generator that prioritizes safe, &lt;strong&gt;accurate types&lt;/strong&gt; and excellent Developer Experience:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gunzip.github.io/apical-ts/" rel="noopener noreferrer"&gt;https://gunzip.github.io/apical-ts/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What's your biggest pain point with generated OpenAPI clients / servers?&lt;/p&gt;

&lt;p&gt;I’d love to hear your thoughts to shape this project, drop a comment, or check out the prototype!&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>openapi</category>
      <category>zod</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
