<?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: Chaitanya Patil</title>
    <description>The latest articles on DEV Community by Chaitanya Patil (@chaitanyahoon).</description>
    <link>https://dev.to/chaitanyahoon</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%2F3928959%2Faedb9f6d-06aa-4641-bf3f-1b4d09fcb06d.jpeg</url>
      <title>DEV Community: Chaitanya Patil</title>
      <link>https://dev.to/chaitanyahoon</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chaitanyahoon"/>
    <language>en</language>
    <item>
      <title>I built a Node.js package that auto-generates API contracts from real traffic</title>
      <dc:creator>Chaitanya Patil</dc:creator>
      <pubDate>Wed, 13 May 2026 10:16:09 +0000</pubDate>
      <link>https://dev.to/chaitanyahoon/i-built-a-nodejs-package-that-auto-generates-api-contracts-from-real-traffic-2ak</link>
      <guid>https://dev.to/chaitanyahoon/i-built-a-nodejs-package-that-auto-generates-api-contracts-from-real-traffic-2ak</guid>
      <description>&lt;p&gt;Last month, a teammate renamed a field from &lt;code&gt;userName&lt;/code&gt; to &lt;code&gt;username&lt;/code&gt; in a single API endpoint. No tests broke. No TypeScript errors. The PR got merged on a Friday afternoon.&lt;/p&gt;

&lt;p&gt;Monday morning, our React app was rendering blank user profiles for every customer. The frontend was reading &lt;code&gt;userName&lt;/code&gt; — a field that no longer existed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: schema drift
&lt;/h2&gt;

&lt;p&gt;API responses change shape over time. It happens in three ways, and none of them are loud about it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A teammate refactors.&lt;/strong&gt; They rename a field, change an integer ID to a UUID string, or make a previously-required field optional. The backend tests pass because they test backend logic, not response shape.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A database migration shifts output.&lt;/strong&gt; You add a column, drop a column, change a default from &lt;code&gt;0&lt;/code&gt; to &lt;code&gt;null&lt;/code&gt;. The ORM happily returns the new shape. Nobody downstream knows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A third-party API updates quietly.&lt;/strong&gt; The weather API you depend on starts returning &lt;code&gt;temperature&lt;/code&gt; as a string instead of a number. Their changelog? Three weeks late.&lt;/p&gt;

&lt;p&gt;The common thread: the &lt;em&gt;shape&lt;/em&gt; of the data changed, but nothing in your stack was watching for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Existing solutions (and why they fall short)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Writing Zod schemas by hand.&lt;/strong&gt; You can manually define schemas for every endpoint, but keeping them in sync with actual responses is a full-time job. They get stale within weeks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenAPI specs.&lt;/strong&gt; Great in theory. In practice, most teams under 20 people don't maintain them, and when they do, the spec drifts from reality anyway.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;End-to-end TypeScript (tRPC, ts-rest).&lt;/strong&gt; Only works if you own both the API and the client. Doesn't help with third-party APIs or microservices in different languages.&lt;/p&gt;

&lt;p&gt;I wanted something that learns from real traffic. No manual schemas. No spec files to maintain.&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution: respekt
&lt;/h2&gt;

&lt;p&gt;I built &lt;a href="https://www.npmjs.com/package/respekt" rel="noopener noreferrer"&gt;respekt&lt;/a&gt; — a Node.js middleware that observes your API responses, locks the shapes as contracts, and enforces them automatically.&lt;/p&gt;

&lt;p&gt;Three steps:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Observe traffic in dev/staging
&lt;/h3&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="nx"&gt;respekt&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;respekt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;respekt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;routes&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="s1"&gt;/api/*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;sampleSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;outputDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./contracts&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The middleware silently intercepts &lt;code&gt;res.json()&lt;/code&gt; calls. It watches 50 responses per route and records every field, type, and edge case — nullable fields, optional keys, arrays, nested objects, ISO date strings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Lock the contracts
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx respekt lock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This reads the traffic log, infers Zod schemas + JSON Schemas, and writes a &lt;code&gt;*.schema.json&lt;/code&gt; contract file for each route. You commit these to git.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Enforce in production
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;respekt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enforce&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;contractsDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./contracts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;onViolation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;throw&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// or 'warn' or 'log'&lt;/span&gt;
  &lt;span class="na"&gt;strict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&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;Every response is now validated against the locked contract. If a field changes type, disappears, or a new unexpected field appears (in strict mode), you get a &lt;code&gt;RespektViolation&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"route"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GET /api/users"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"violatedAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-05-13T10:22:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"violations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user.age"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"expected"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"number"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"received"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user.role"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"expected"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"present"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"received"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"missing"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CLI also has &lt;code&gt;npx respekt diff&lt;/code&gt; (exits with code 1 on drift — perfect for CI) and &lt;code&gt;npx respekt report&lt;/code&gt; (prints a summary table of all monitored routes).&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works under the hood
&lt;/h2&gt;

&lt;p&gt;The inference engine samples N responses per route and builds a merged shape tree. A field that's a string in 45 samples and null in 5? &lt;code&gt;z.string().nullable()&lt;/code&gt;. A key that's missing in 3 out of 50 responses? &lt;code&gt;.optional()&lt;/code&gt;. ISO 8601 strings get detected automatically as &lt;code&gt;z.string().datetime()&lt;/code&gt;. Empty arrays are flagged as &lt;code&gt;z.unknown()&lt;/code&gt; with a warning comment until real elements are observed.&lt;/p&gt;

&lt;p&gt;The output is both a Zod schema (as a TypeScript string you can copy into your codebase) and a standard JSON Schema used for runtime validation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it out
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;respekt zod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;npm:&lt;/strong&gt; &lt;a href="https://www.npmjs.com/package/respekt" rel="noopener noreferrer"&gt;npmjs.com/package/respekt&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/Chaitanyahoon/respekt" rel="noopener noreferrer"&gt;github.com/Chaitanyahoon/respekt&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's v0.1.0 — early stage but fully functional with 59 tests passing. Works with Express 4+, Express 5, and Fastify 4+. Zero runtime dependencies besides &lt;code&gt;zod&lt;/code&gt; and &lt;code&gt;micromatch&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Coming next: a native Fastify plugin (no middleware adapter needed) and a &lt;code&gt;respekt watch&lt;/code&gt; mode for live drift monitoring.&lt;/p&gt;

&lt;p&gt;Contributions and feedback are very welcome. If you've been bitten by silent API drift, I'd love to hear your story.&lt;/p&gt;

</description>
      <category>node</category>
      <category>typescript</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
