<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>OpenAPI · Jared Lynskey</title>
    <link>https://jared.lynskey.co.nz/en/tags/openapi/</link>
    <description>Short stories of value on software engineering and engineering leadership — performance reviews, hiring and scaling teams, CI/CD, DevOps monitoring and agentic AI. Written from Seoul.</description>
    <generator>Hugo</generator>
    <language>en</language>
    <copyright>© 2026 Jared Lynskey</copyright>
    <lastBuildDate>Wed, 28 Oct 2026 00:00:00 +1300</lastBuildDate>
    <atom:link href="https://jared.lynskey.co.nz/en/tags/openapi/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Orval: Generate a Typed API Client and Mocks From Your OpenAPI Spec</title>
      <link>https://jared.lynskey.co.nz/en/posts/2024/2024-04-10-openapitools/</link>
      <guid isPermaLink="true">https://jared.lynskey.co.nz/en/posts/2024/2024-04-10-openapitools/</guid>
      <pubDate>Wed, 10 Apr 2024 00:30:03 +0000</pubDate>
      <dc:creator>Me</dc:creator>
      <category>API Development</category>
      <category>Orval</category>
      <category>OpenAPI</category>
      <category>TypeScript</category>
      <category>React Query</category>
      <category>MSW</category>
      <category>Frontend</category>
      <category>Backend</category>
      <category>Teamwork</category>
      <category>Efficiency</category>
      <description>Orval reads an OpenAPI spec and generates a typed TypeScript client, React Query hooks and MSW mocks, so nobody hand-writes the API layer. How to set it up, how the mocks work, and how it compares with openapi-generator, openapi-typescript and Hey API.</description>
      <content:encoded><![CDATA[<p>Orval is a code generator that reads an OpenAPI (Swagger) spec and writes your frontend&rsquo;s API layer for you: a typed function or React Query hook for every endpoint, TypeScript models that match the contract exactly, and, if you ask for them, MSW mock handlers filled with Faker data. You commit one config file, run <code>orval</code>, and run it again whenever the spec changes.</p>
<p>In the first post of this series I made the case for agreeing on an OpenAPI contract before anyone writes code. This one is about the tool that makes the contract actually pay off.</p>
<p>Hand-writing the frontend&rsquo;s API layer is one of those jobs nobody admits to enjoying. You copy types out of the backend docs, write the same request wrapper for the fortieth time, and three weeks later someone renames a field and half your calls quietly break. Orval deletes that job. Point it at your OpenAPI spec and it generates the client code for you: typed functions for every endpoint, models that match the contract exactly, and mocks you can develop against before the backend exists.</p>

<h2 class="relative group">Why the frontend team likes it
    <div id="why-the-frontend-team-likes-it" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#why-the-frontend-team-likes-it" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>The mock generation is the underrated part. Once the spec is agreed, the frontend can spin up a fake server and build the entire UI against it, instead of waiting for endpoints that are perpetually &ldquo;almost done&rdquo;. When the real backend lands, you point the client at it and things mostly just work, because both sides were built from the same document.</p>
<p>The other win is that the boring code is now correct by construction. Nobody fat-fingers a field name in a hand-rolled request body, because nobody writes a hand-rolled request body anymore.</p>

<h2 class="relative group">Why the backend team likes it too
    <div id="why-the-backend-team-likes-it-too" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#why-the-backend-team-likes-it-too" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>The spec being the single source of truth changes the day-to-day conversations. Instead of chat threads asking &ldquo;what does this endpoint return again?&rdquo;, the frontend just reads the generated types. Backend devs get interrupted less, and they get feedback sooner: if the frontend builds against the mocks and something about the API feels awkward, you find out while it&rsquo;s still cheap to change, not after it&rsquo;s in production.</p>

<h2 class="relative group">How it fits the workflow
    <div id="how-it-fits-the-workflow" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#how-it-fits-the-workflow" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Nothing fancy. The backend proposes the spec, both teams review it, the frontend generates the client and mocks with Orval and starts building, and the backend implements the real endpoints to the same contract. When the spec changes, both sides regenerate, and the compiler tells you exactly what broke. That last part is the whole point: drift between frontend and backend becomes a compile error instead of a production bug.</p>

<h2 class="relative group">Setting it up
    <div id="setting-it-up" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#setting-it-up" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Install it as a dev dependency, along with whatever the generated code will import. For React Query hooks and mocks that&rsquo;s TanStack Query, MSW and Faker:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">npm install --save-dev orval msw @faker-js/faker
</span></span><span class="line"><span class="cl">npm install @tanstack/react-query</span></span></code></pre></div></div>
<p>Then add an <code>orval.config.ts</code> at the root of the project. This one is close to the example in Orval&rsquo;s own docs:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">defineConfig</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;orval&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">defineConfig</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nx">petstore</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">input</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">target</span><span class="o">:</span> <span class="s1">&#39;./openapi.yaml&#39;</span><span class="p">,</span> <span class="c1">// a path or a URL
</span></span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nx">output</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">mode</span><span class="o">:</span> <span class="s1">&#39;single&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">target</span><span class="o">:</span> <span class="s1">&#39;./src/api/petstore.ts&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">schemas</span><span class="o">:</span> <span class="s1">&#39;./src/api/model&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">client</span><span class="o">:</span> <span class="s1">&#39;react-query&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">httpClient</span><span class="o">:</span> <span class="s1">&#39;fetch&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">mock</span>: <span class="kt">true</span><span class="p">,</span> <span class="c1">// MSW handlers plus Faker data
</span></span></span><span class="line"><span class="cl">      <span class="nx">override</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">fetch</span><span class="o">:</span> <span class="p">{</span> <span class="nx">includeHttpResponseReturnType</span>: <span class="kt">false</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div>
<p><code>client</code> decides what you get: plain <code>fetch</code> or <code>axios</code> functions, hooks for React Query, Vue Query, Svelte Query or SWR, Angular services, or Zod schemas. The <code>includeHttpResponseReturnType: false</code> line makes the fetch client return the response body itself rather than <code>{ data, status }</code>, which is usually what a component wants.</p>
<p>Add a script so nobody has to remember the command:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;scripts&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;api&#34;</span><span class="p">:</span> <span class="s2">&#34;orval&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p><code>npm run api</code> writes the hooks and models. A component then uses them like any other React Query hook, with the types already filled in:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">useListPets</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;./api/petstore&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kd">function</span> <span class="nx">PetList() {</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="p">{</span> <span class="nx">data</span>: <span class="kt">pets</span><span class="p">,</span> <span class="nx">isLoading</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">useListPets</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">isLoading</span><span class="p">)</span> <span class="k">return</span> <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span><span class="nx">Loading</span><span class="err">…</span><span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;;</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">ul</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span><span class="nx">pets</span><span class="o">?</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">pet</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">&lt;</span><span class="nt">li</span> <span class="na">key</span><span class="o">=</span><span class="p">{</span><span class="nx">pet</span><span class="p">.</span><span class="nx">id</span><span class="p">}&gt;{</span><span class="nx">pet</span><span class="p">.</span><span class="nx">name</span><span class="p">}&lt;/</span><span class="nt">li</span><span class="p">&gt;)}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">ul</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p>Rename a field in the spec, run <code>npm run api</code> again, and <code>pet.name</code> turns red everywhere it&rsquo;s used. That&rsquo;s the drift check from above, and it costs nothing to run.</p>

<h2 class="relative group">Developing against the mocks
    <div id="developing-against-the-mocks" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#developing-against-the-mocks" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>With <code>mock: true</code>, Orval also writes an MSW request handler for each endpoint, returning Faker data shaped like the response schema, plus a function that returns all of them. Hand that list to MSW&rsquo;s <code>setupWorker</code> in the browser or <code>setupServer</code> in tests and the app runs against a fake backend that already matches the contract. Each handler also takes an override, so a test can return a specific payload, an empty list or an error state without anyone writing a fixture by hand.</p>
<p>That&rsquo;s the part that lets the frontend start on day one. The mocks aren&rsquo;t a separate artefact someone has to maintain; they&rsquo;re regenerated from the same spec as the client, so they can&rsquo;t fall behind it.</p>

<h2 class="relative group">Orval vs openapi-generator, openapi-typescript and Hey API
    <div id="orval-vs-openapi-generator-openapi-typescript-and-hey-api" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#orval-vs-openapi-generator-openapi-typescript-and-hey-api" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>They all start from an OpenAPI spec, but they stop at different places.</p>
<table>
	<thead>
			<tr>
					<th></th>
					<th>What it generates</th>
					<th>Best fit</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><strong>Orval</strong></td>
					<td>Typed client functions or framework hooks (React Query, SWR, Vue, Svelte, Angular), models, MSW mocks, Zod schemas</td>
					<td>A TypeScript frontend that wants hooks and mocks from one config</td>
			</tr>
			<tr>
					<td><strong>openapi-generator</strong></td>
					<td>Clients and server stubs in dozens of languages, TypeScript included</td>
					<td>Teams that need the same spec turned into Java, Kotlin, Swift and TypeScript</td>
			</tr>
			<tr>
					<td><strong>openapi-typescript</strong></td>
					<td>Types only, paired with <code>openapi-fetch</code> for a small typed fetch wrapper</td>
					<td>You want the types and nothing else, with almost no runtime code</td>
			</tr>
			<tr>
					<td><strong>Hey API</strong> (<code>@hey-api/openapi-ts</code>)</td>
					<td>A typed SDK, with plugins for TanStack Query, Zod and others</td>
					<td>Similar ground to Orval, built around a plugin system</td>
			</tr>
	</tbody>
</table>
<p>If you only need types, openapi-typescript is the lightest option and it&rsquo;s hard to argue with. openapi-generator earns its place when the spec has to feed more than one language; its TypeScript output works, but it reads like it was written for Java first. Between Orval and Hey API it mostly comes down to taste and which one&rsquo;s output you&rsquo;d rather read. I keep using Orval because the mocks come out of the same run as the hooks, and that&rsquo;s the feature that changed how the two teams work together.</p>
<p>It&rsquo;s not glamorous tooling. It just quietly removes a whole category of arguments between teams, and I&rsquo;ll take that trade every time.</p>
]]></content:encoded>
      <media:content url="https://jared.lynskey.co.nz/en/posts/2024/2024-04-10-openapitools/featured.jpg" medium="image" type="image/jpeg" />
    </item>
    <item>
      <title>API Design-First Workflow: Planning the OpenAPI Contract Each Sprint</title>
      <link>https://jared.lynskey.co.nz/en/posts/2023/2023-10-08-planning-an-api/</link>
      <guid isPermaLink="true">https://jared.lynskey.co.nz/en/posts/2023/2023-10-08-planning-an-api/</guid>
      <pubDate>Mon, 09 Oct 2023 00:30:03 +0000</pubDate>
      <dc:creator>Jared Lynskey</dc:creator>
      <category>API</category>
      <category>OpenAPI</category>
      <category>API Design</category>
      <category>Planning</category>
      <category>Mock Server</category>
      <description>The API design-first process we run each sprint: a backend and a frontend owner agree the OpenAPI contract, CI stands up a mock server, and both sides build in parallel. Example YAML included.</description>
      <content:encoded><![CDATA[<p>I wrote recently about <a href="https://jared.lynskey.co.nz/en/posts/2023/2023-10-08-open-api/" >API-first development with OpenAPI</a> in general terms. This is the concrete version: the planning workflow we&rsquo;re trialling on my team, and the example YAML that goes with it.</p>
<p>In short, the design-first process runs once per sprint:</p>
<ol>
<li>Pick two owners for the sprint&rsquo;s API work, one from backend and one from frontend.</li>
<li>Together they write or update the OpenAPI schema against the sprint&rsquo;s requirements, and review it until both sides agree.</li>
<li>The schema is committed to the repo. From here on it&rsquo;s the contract.</li>
<li>CI stands up a mock server from the committed schema.</li>
<li>Frontend builds against the mock while backend implements the real endpoints.</li>
<li>Once the real service is deployed, the spec it generates becomes the starting point for the next sprint&rsquo;s schema.</li>
</ol>

<h2 class="relative group">Where this came from
    <div id="where-this-came-from" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#where-this-came-from" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>The push came from our retro. The feedback was blunt about how little visibility anyone had into API planning: backend built endpoints against their own reading of the requirements, frontend discovered the response shapes after the fact, and the gaps surfaced at integration time — the most expensive possible moment. The proposal was to pull both sides into the planning phase and make the OpenAPI schema the thing everything hangs off.</p>

<h2 class="relative group">The process
    <div id="the-process" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-process" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Each week we pick two representatives, one backend and one frontend. Their job is to understand the sprint&rsquo;s requirements and get the planning OpenAPI schema to match. They review the schema together, revise whatever doesn&rsquo;t line up, and by the end both sides know exactly what&rsquo;s being built — before anyone builds it.</p>
<p>Once the revisions settle, the schema gets committed to the repo, and CI/CD generates a mock API from the yaml. When the sprint wraps up, a fresh yaml is cut covering the next sprint&rsquo;s API requirements, and the cycle repeats.</p>

<h2 class="relative group">Tooling notes
    <div id="tooling-notes" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#tooling-notes" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>For visualising the yaml, <a href="https://editor.swagger.io/"  target="_blank" rel="noreferrer">Swagger Editor</a> does the job: upload the file and see it rendered as a proper Swagger document.</p>
<p>The CI/CD pipeline needs to accept a yaml and stand up a mock server from it. <a href="https://github.com/stoplightio/prism"  target="_blank" rel="noreferrer">Prism</a>, from Stoplight, is a widely used option and handles OpenAPI 3.1:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">npm install -g @stoplight/prism-cli
</span></span><span class="line"><span class="cl">prism mock openapi.yaml</span></span></code></pre></div></div>
<p>That serves every path in the schema on <code>http://127.0.0.1:4010</code> and answers with the <code>examples</code> from the schema, so <code>GET /lists/10</code> returns the example list from the appendix. It also enforces the contract: because the example declares an API key, a request without an <code>X-API-Key</code> header gets a 401, which is exactly the behaviour the frontend should be building against. Inside a container, add <code>-h 0.0.0.0</code> so it&rsquo;s reachable from outside. <a href="https://github.com/muonsoft/openapi-mock"  target="_blank" rel="noreferrer">openapi-mock</a> is a lighter alternative that fills responses with random data.</p>
<p>It&rsquo;s also worth linting the schema before the mock goes up, so a broken contract fails the build instead of confusing the frontend. <a href="https://github.com/stoplightio/spectral"  target="_blank" rel="noreferrer">Spectral</a> needs a one-line ruleset to get going:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">npm install -g @stoplight/spectral-cli
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s1">&#39;extends: [&#34;spectral:oas&#34;]&#39;</span> &gt; .spectral.yaml
</span></span><span class="line"><span class="cl">spectral lint openapi.yaml</span></span></code></pre></div></div>
<p>Finally, this schema is a planning artefact only. Once the real API is deployed, the service should generate its own openapi.json, which you can download and feed back into the next round of planning.</p>

<h2 class="relative group">Why bother
    <div id="why-bother" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#why-bother" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Because the arguments move to the cheapest possible place. With a frontend and a backend engineer in the room while the schema is written, misunderstandings get caught before they become code. The API requirements are agreed once instead of renegotiated mid-sprint, the mock server means frontend starts immediately rather than waiting, and far fewer critical details slip through — they&rsquo;d have to get past two pairs of eyes and a written contract.</p>
<p>None of this is revolutionary. It&rsquo;s just planning made concrete enough that a machine can serve it back to you as a mock — which, it turns out, is about the level of concrete a plan needs to be.</p>

<h2 class="relative group">Appendix A - yaml example
    <div id="appendix-a---yaml-example" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#appendix-a---yaml-example" aria-label="Anchor">#</a>
    </span>
    
</h2>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">openapi</span><span class="p">:</span><span class="w"> </span><span class="m">3.1.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">info</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="l">MyApp Sprint 3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="l">Planning schema for sprint 3. Drives the mock server only.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="m">0.3.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">servers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">url</span><span class="p">:</span><span class="w"> </span><span class="l">https://api-mock.example.com/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">tags</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">lists</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="l">Saved lists feature (PROJ-123)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">security</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">api_key</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">/lists</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">get</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">tags</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">lists]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">summary</span><span class="p">:</span><span class="w"> </span><span class="l">Return the current user&#39;s saved lists</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">operationId</span><span class="p">:</span><span class="w"> </span><span class="l">getLists</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">responses</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">&#39;200&#39;</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="l">OK</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">content</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">application/json</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">schema</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">array</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">items</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">$ref</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;#/components/schemas/List&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="l">/lists/{listId}:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">get</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">tags</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">lists]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">summary</span><span class="p">:</span><span class="w"> </span><span class="l">Find a list by ID</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">operationId</span><span class="p">:</span><span class="w"> </span><span class="l">getListById</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">parameters</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">listId</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">in</span><span class="p">:</span><span class="w"> </span><span class="l">path</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">required</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">schema</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">integer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">format</span><span class="p">:</span><span class="w"> </span><span class="l">int64</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">responses</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">&#39;200&#39;</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="l">OK</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">content</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">application/json</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">schema</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">$ref</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;#/components/schemas/List&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">&#39;404&#39;</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="l">List not found</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">components</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">schemas</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">List</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">object</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">required</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">id, title]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">properties</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">id</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">integer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">format</span><span class="p">:</span><span class="w"> </span><span class="l">int64</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">examples</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="m">10</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">title</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">examples</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">Weekend reading]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">itemCount</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">integer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">examples</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="m">7</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">securitySchemes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">api_key</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">apiKey</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">X-API-Key</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">in</span><span class="p">:</span><span class="w"> </span><span class="l">header</span></span></span></code></pre></div></div>
]]></content:encoded>
      <media:content url="https://jared.lynskey.co.nz/en/posts/2023/2023-10-08-planning-an-api/featured.jpeg" medium="image" type="image/jpeg" />
    </item>
    <item>
      <title>API-First Development with OpenAPI: Frontend and Backend in Parallel</title>
      <link>https://jared.lynskey.co.nz/en/posts/2023/2023-10-08-open-api/</link>
      <guid isPermaLink="true">https://jared.lynskey.co.nz/en/posts/2023/2023-10-08-open-api/</guid>
      <pubDate>Sun, 08 Oct 2023 00:30:03 +0000</pubDate>
      <dc:creator>Jared Lynskey</dc:creator>
      <category>OpenAPI</category>
      <category>backend</category>
      <category>frontend</category>
      <category>API design</category>
      <category>software development</category>
      <category>team collaboration</category>
      <description>API-first development in practice: agree on the OpenAPI contract first, stand up a mock server, and the frontend stops waiting on the backend.</description>
      <content:encoded><![CDATA[<p>The most reliable schedule-killer I&rsquo;ve seen isn&rsquo;t hard engineering — it&rsquo;s the frontend sitting around waiting for an endpoint that&rsquo;s &ldquo;almost done&rdquo;. The fix we&rsquo;ve settled on at work is boring and effective: agree on the API contract first, in OpenAPI, before anyone writes code.</p>

<h3 class="relative group">The waiting game
    <div id="the-waiting-game" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-waiting-game" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The traditional flow is sequential. Backend designs and builds the API, and only once the endpoints actually work does frontend start integrating. Every backend delay lands directly on the frontend&rsquo;s calendar, and the frontend&rsquo;s calendar is usually where the release date lives.</p>

<h3 class="relative group">Contract first, code second
    <div id="contract-first-code-second" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#contract-first-code-second" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>With a schema-first flow, both sides start almost at the same time. Before any code exists, the API&rsquo;s structure goes into an OpenAPI schema — every endpoint, request and response model, auth method and error code. That document becomes the contract between frontend and backend.</p>
<p>The payoff is immediate. Everyone understands the feature the same way before it&rsquo;s built. Frontend can build UI while backend is still implementing. Changes move faster because there&rsquo;s one agreed source of truth to update. And you can generate a mock server straight from the schema, so frontend testing starts before a single real endpoint exists.</p>

<h3 class="relative group">How it runs in practice
    <div id="how-it-runs-in-practice" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#how-it-runs-in-practice" aria-label="Anchor">#</a>
    </span>
    
</h3>
<ol>
<li>Gather requirements, same as for anything.</li>
<li>Design the schema: endpoints, request/response models, authentication, error codes.</li>
<li>Have both frontend and backend review it — this is where the contract earns its keep.</li>
<li>Generate a mock server (Swagger and Postman both do this) and let frontend integrate against it.</li>
<li>Backend implements the real endpoints to the same schema.</li>
<li>Swap mocks for real endpoints as they land, testing continuously so the contract holds.</li>
<li>Keep talking. The schema is a living document, not a stone tablet.</li>
</ol>

<h3 class="relative group">The catches
    <div id="the-catches" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-catches" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>It isn&rsquo;t free. Writing a thorough schema upfront takes real time. When requirements change, the schema has to change first, and the ripples hit both teams. And everyone needs at least a working familiarity with OpenAPI and the tooling around it.</p>
<p>Even so, I&rsquo;ve never seen the upfront cost lose to the integration chaos it prevents. Frontend starts on day one, and the arguments happen over a document instead of over broken builds.</p>
]]></content:encoded>
      <media:content url="https://jared.lynskey.co.nz/en/posts/2023/2023-10-08-open-api/featured.jpg" medium="image" type="image/jpeg" />
    </item>
  </channel>
</rss>
