<?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: Marvin Rocha</title>
    <description>The latest articles on DEV Community by Marvin Rocha (@marvin_rocha_24917bf64d0e).</description>
    <link>https://dev.to/marvin_rocha_24917bf64d0e</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2021003%2F454f8696-be60-41be-9e5b-ac9d4dd03995.png</url>
      <title>DEV Community: Marvin Rocha</title>
      <link>https://dev.to/marvin_rocha_24917bf64d0e</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/marvin_rocha_24917bf64d0e"/>
    <language>en</language>
    <item>
      <title>nestjs-docfy: mock servers, contract testing, and a much sharper MCP server</title>
      <dc:creator>Marvin Rocha</dc:creator>
      <pubDate>Sun, 02 Aug 2026 00:15:01 +0000</pubDate>
      <link>https://dev.to/marvin_rocha_24917bf64d0e/nestjs-docfy-mock-servers-contract-testing-and-a-much-sharper-mcp-server-568a</link>
      <guid>https://dev.to/marvin_rocha_24917bf64d0e/nestjs-docfy-mock-servers-contract-testing-and-a-much-sharper-mcp-server-568a</guid>
      <description>&lt;p&gt;A few weeks ago I shared nestjs-docfy here — a library that moves Swagger decorators out of NestJS controllers into companion &lt;code&gt;*.controller.docs.ts&lt;/code&gt; files, docfy-ui as an AI-first reference UI, and docfy-mcp exposing your API catalog to coding agents via &lt;code&gt;list_endpoints&lt;/code&gt;/&lt;code&gt;get_endpoint&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Since then the CLI grew a full local dev workflow around the spec itself, and docfy-mcp went from "read the docs" to "verify the API is telling the truth."&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;docfy mock&lt;/code&gt;: a server without the server
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;\&lt;/code&gt;&lt;code&gt;shell&lt;br&gt;
npx nestjs-docfy mock --spec openapi.json --port 4010&lt;br&gt;
\&lt;/code&gt;&lt;code&gt;\&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Spins up a throwaway HTTP server straight from your OpenAPI document — every path returns a schema-shaped response. Useful for frontend work against an API that isn't built yet, or for pointing an agent at something real instead of a static spec file.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;docfy test&lt;/code&gt;: contract testing off the spec
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;\&lt;/code&gt;&lt;code&gt;shell&lt;br&gt;
npx nestjs-docfy test --spec openapi.json --base-url http://localhost:3000&lt;br&gt;
\&lt;/code&gt;&lt;code&gt;\&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Fires a real request at every documented endpoint and validates the live response against its declared schema. Catches the exact failure mode API docs are famous for: the code moved on, the docs didn't. CI-friendly, non-zero exit on drift.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;docfy init&lt;/code&gt;: zero to configured
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;\&lt;/code&gt;&lt;code&gt;shell&lt;br&gt;
npx nestjs-docfy init&lt;br&gt;
\&lt;/code&gt;&lt;code&gt;\&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;One command, scaffolds the &lt;code&gt;docfy-export.ts&lt;/code&gt; entry file and wires &lt;code&gt;DocfyModule.forRoot()&lt;/code&gt; for you. No more copy-pasting from the README.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;--link-controller&lt;/code&gt;: less boilerplate
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;\&lt;/code&gt;&lt;code&gt;shell&lt;br&gt;
npx nestjs-docfy generate --link-controller&lt;br&gt;
\&lt;/code&gt;&lt;code&gt;\&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Auto-inserts &lt;code&gt;@WithDocs()&lt;/code&gt; into the controller so newly generated &lt;code&gt;.controller.docs.ts&lt;/code&gt; files are actually wired in — one less manual step per endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Breaking changes, surfaced in the PR itself
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;docfy-pr-check-reusable.yml&lt;/code&gt; now runs a spec diff and posts breaking vs. informational field changes as a PR comment. You see the blast radius of an API change before merge, not after a consumer files a bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  docfy-mcp: from lookup to verification
&lt;/h2&gt;

&lt;p&gt;The MCP server picked up three tools that turn it from a reference into an actual QA loop for agents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;lint_spec&lt;/code&gt;&lt;/strong&gt; — flags spec-quality issues: missing summary/description, missing tags, undocumented 4xx/5xx, duplicate operation IDs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;diff_specs&lt;/code&gt;&lt;/strong&gt; — compares the loaded catalog against another spec (a file, a URL, a previous git tag) and reports breaking vs. informational changes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;contract_test&lt;/code&gt;&lt;/strong&gt; — fires real requests at a running server and validates responses against the schema, with a &lt;code&gt;filter&lt;/code&gt; and repeatable &lt;code&gt;--header&lt;/code&gt; for auth, plus a timeout and response-size cap so a runaway endpoint can't hang the agent's turn&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Combined with &lt;code&gt;list_endpoints&lt;/code&gt;/&lt;code&gt;get_endpoint&lt;/code&gt;, an agent implementing a client can now lint the spec it's about to consume, diff it against what it saw last session, and contract-test its own integration — all inside the editor, no browser round-trip.&lt;/p&gt;

&lt;p&gt;Security hardening shipped alongside: &lt;code&gt;contract_test&lt;/code&gt; requests are capped and time-boxed, and hitting arbitrary servers is opt-in via an allowlist rather than default-open.&lt;/p&gt;

&lt;h2&gt;
  
  
  Docs, now in 10 languages
&lt;/h2&gt;

&lt;p&gt;nestdocfy.com is fully localized — EN, PT, ES, DE, FR, IT, NL, PL, ZH, JA — same content, same nav, picked up automatically from the browser's locale.&lt;/p&gt;




&lt;p&gt;Nothing about the existing setup changes — same &lt;code&gt;DocfyModule.forRoot()&lt;/code&gt;, same &lt;code&gt;*.controller.docs.ts&lt;/code&gt; convention. Everything above is additive.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;\&lt;/code&gt;&lt;code&gt;shell&lt;br&gt;
npm install nestjs-docfy&lt;br&gt;
npx docfy-mcp --url &amp;lt;your-openapi-json&amp;gt;&lt;br&gt;
\&lt;/code&gt;&lt;code&gt;\&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/MarvinRF/nest-docfy" rel="noopener noreferrer"&gt;🔗 nestjs-docfy on GitHub&lt;/a&gt; · &lt;a href="https://www.npmjs.com/package/nestjs-docfy" rel="noopener noreferrer"&gt;📦 nestjs-docfy on npm&lt;/a&gt; · &lt;a href="https://www.npmjs.com/package/docfy-mcp" rel="noopener noreferrer"&gt;📦 docfy-mcp on npm&lt;/a&gt; · &lt;a href="https://www.nestdocfy.com" rel="noopener noreferrer"&gt;📖 docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If your API docs drift from reality, or your agent is still guessing your API's shape from stale comments, this release closes both gaps.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>typescript</category>
      <category>nestjs</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Nestjs Docfy v0.7: export without a running server, and an MCP server for your API docs</title>
      <dc:creator>Marvin Rocha</dc:creator>
      <pubDate>Wed, 29 Jul 2026 02:38:45 +0000</pubDate>
      <link>https://dev.to/marvin_rocha_24917bf64d0e/nestjs-docfy-v07-export-without-a-running-server-and-an-mcp-server-for-your-api-docs-5ba</link>
      <guid>https://dev.to/marvin_rocha_24917bf64d0e/nestjs-docfy-v07-export-without-a-running-server-and-an-mcp-server-for-your-api-docs-5ba</guid>
      <description>&lt;p&gt;A few weeks ago I shared nestjs-docfy here — a library that moves Swagger decorators out of NestJS controllers into companion &lt;code&gt;*.controller.docs.ts&lt;/code&gt; files, plus docfy-ui, an AI-first reference UI with a "Copy for AI" button on every endpoint. Two things kept coming up since: &lt;strong&gt;you shouldn't need Postgres running just to read your own API's docs, and there was no way for an AI agent to query the API directly without a human pasting Swagger UI into the chat.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;v0.7 closes both, and ships a new companion project to go with it: docfy-mcp.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgt9u2j3pei714wsz33zg.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgt9u2j3pei714wsz33zg.png" alt="nest docfy documentation home" width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;export&lt;/code&gt;: your OpenAPI document without &lt;code&gt;.listen()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;SwaggerModule.createDocument()&lt;/code&gt; needs a fully-booted Nest app to introspect routes and DTOs — but "fully booted" doesn't mean "listening on a port," and in most setups it doesn't mean "infrastructure running" either. Most &lt;code&gt;TypeOrmModule&lt;/code&gt;/&lt;code&gt;ioredis&lt;/code&gt;/&lt;code&gt;kafkajs&lt;/code&gt; clients connect lazily, so the DI container resolves fine without a live database or broker.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;nestjs-docfy export&lt;/code&gt; takes advantage of that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx nestjs-docfy &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nt"&gt;--entry&lt;/span&gt; docfy-export.ts &lt;span class="nt"&gt;--out&lt;/span&gt; openapi.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The entry file is the same handful of lines your &lt;code&gt;main.ts&lt;/code&gt; already has, minus &lt;code&gt;.listen()&lt;/code&gt;:&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="c1"&gt;// docfy-export.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NestFactory&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="s1"&gt;@nestjs/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DocumentBuilder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SwaggerModule&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="s1"&gt;@nestjs/swagger&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AppModule&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="s1"&gt;./app.module&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="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;function &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;app&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;NestFactory&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;AppModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;logger&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DocumentBuilder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;setTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;My API&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;setVersion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1.0.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SwaggerModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createDocument&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="nx"&gt;config&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="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt; &lt;span class="c1"&gt;// `app` gets closed for you afterward&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I tested this against a real multi-service NestJS monorepo — Postgres, Redis, Kafka, Zookeeper, all stopped — and &lt;code&gt;export&lt;/code&gt; still produced the full document, cold, on the first try.&lt;/p&gt;

&lt;p&gt;One thing worth knowing: this doesn't fix a provider with a genuinely eager, hard-failing connection in its constructor or &lt;code&gt;onModuleInit&lt;/code&gt;. &lt;code&gt;export&lt;/code&gt; only avoids the one thing NestJS itself doesn't actually need — an open port. If your app blocks bootstrap on a live DB connection today, it still will under &lt;code&gt;export&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Also: informational output goes to stderr, not stdout, specifically so &lt;code&gt;export --entry x.ts &amp;gt; openapi.json&lt;/code&gt; stays safe to pipe.&lt;/p&gt;

&lt;h2&gt;
  
  
  docfy-mcp: your API as MCP tools
&lt;/h2&gt;

&lt;p&gt;The AI-first framing on docfy-ui was always about a human copying text into a chat. docfy-mcp removes the copying:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx docfy-mcp &lt;span class="nt"&gt;--url&lt;/span&gt; http://localhost:3000/api-json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;list_endpoints&lt;/code&gt;&lt;/strong&gt; — method, path, and summary for everything in the catalog, with an optional &lt;code&gt;filter&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;get_endpoint&lt;/code&gt;&lt;/strong&gt; — full Purpose/Request/Parameters/Validation/Success Response/Error Responses text for one endpoint, given &lt;code&gt;method&lt;/code&gt; + &lt;code&gt;path&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Register it once in &lt;code&gt;.mcp.json&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;"mcpServers"&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;"docfy"&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;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&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="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"docfy-mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"--url"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:3000/api-json"&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="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;and an agent like Claude Code or Cursor can call &lt;code&gt;get_endpoint&lt;/code&gt; directly instead of guessing from a stale comment.&lt;/p&gt;

&lt;p&gt;One gotcha, and the reason &lt;code&gt;--url&lt;/code&gt; doesn't just delegate to swagger-parser's own HTTP resolver: swagger-parser's SSRF guard blocks &lt;code&gt;localhost&lt;/code&gt;/private addresses by default. That's exactly the case here — pointing at a local NestJS dev server — so &lt;code&gt;docfy-mcp&lt;/code&gt; fetches the spec itself instead. If &lt;code&gt;--url&lt;/code&gt; 404s (the OpenAPI JSON path isn't a fixed convention — &lt;code&gt;/api-json&lt;/code&gt;, &lt;code&gt;/docs-json&lt;/code&gt;, &lt;code&gt;/swagger-json&lt;/code&gt;, whatever your project chose), it probes a handful of common sibling paths on the same origin and suggests one, instead of leaving you to guess blind. For specs behind auth, &lt;code&gt;--header "Authorization: Bearer xyz"&lt;/code&gt; is repeatable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Also new: "Copy MCP Reference" in docfy-ui
&lt;/h2&gt;

&lt;p&gt;Alongside "Copy for AI" and "Copy OpenAPI," every endpoint now has a &lt;strong&gt;Copy MCP Reference&lt;/strong&gt; button — copies &lt;code&gt;METHOD /path&lt;/code&gt; plus the page's canonical URL. Paste it into a chat with an MCP-connected agent and it has exactly what &lt;code&gt;get_endpoint&lt;/code&gt; needs; open the URL and you're looking at that endpoint's docs directly. Same idea as Figma's "copy link to selection."&lt;/p&gt;

&lt;h2&gt;
  
  
  One bug fix worth mentioning
&lt;/h2&gt;

&lt;p&gt;While testing all this against a real project, I found that a plain &lt;code&gt;boolean&lt;/code&gt; field on a response DTO was rendering as &lt;code&gt;oneOf: [{type:"boolean"}, {type:"boolean"}]&lt;/code&gt; instead of &lt;code&gt;{type:"boolean"}&lt;/code&gt;. TypeScript represents the &lt;code&gt;boolean&lt;/code&gt; keyword type as a union of the literal types &lt;code&gt;true | false&lt;/code&gt; internally — confirmed via &lt;code&gt;ts-morph&lt;/code&gt;'s &lt;code&gt;Type.isUnion()&lt;/code&gt; returning &lt;code&gt;true&lt;/code&gt; for a plain &lt;code&gt;success: boolean&lt;/code&gt; field — and the schema-inference code wasn't collapsing the two identical branches. Fixed in 0.6.3.&lt;/p&gt;




&lt;p&gt;Nothing else changes: same install, same &lt;code&gt;DocfyModule.forRoot()&lt;/code&gt;, same &lt;code&gt;*.controller.docs.ts&lt;/code&gt; convention. docfy-mcp and the &lt;code&gt;export&lt;/code&gt; command are additive — if you never touch them, nothing about your existing setup moves.&lt;br&gt;
&lt;/p&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;nestjs-docfy
npx docfy-mcp &lt;span class="nt"&gt;--url&lt;/span&gt; &amp;lt;your-openapi-json&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/MarvinRF/nest-docfy" rel="noopener noreferrer"&gt;🔗 nestjs-docfy on GitHub&lt;/a&gt; · &lt;a href="https://www.npmjs.com/package/nestjs-docfy" rel="noopener noreferrer"&gt;📦 nestjs-docfy on npm&lt;/a&gt; · &lt;a href="https://www.npmjs.com/package/docfy-mcp" rel="noopener noreferrer"&gt;📦 docfy-mcp on npm&lt;/a&gt; · &lt;a href="https://www.nestdocfy.com" rel="noopener noreferrer"&gt;📖 docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If reading your own API's docs required infra you didn't want to boot, or your agent kept guessing your API's shape, this release is for you.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>nestjs</category>
      <category>webdev</category>
      <category>api</category>
    </item>
    <item>
      <title>nestjs-docfy just got Fastify support (and a docs site to go with it)</title>
      <dc:creator>Marvin Rocha</dc:creator>
      <pubDate>Fri, 24 Jul 2026 03:44:29 +0000</pubDate>
      <link>https://dev.to/marvin_rocha_24917bf64d0e/nestjs-docfy-just-got-fastify-support-and-a-docs-site-to-go-with-it-121b</link>
      <guid>https://dev.to/marvin_rocha_24917bf64d0e/nestjs-docfy-just-got-fastify-support-and-a-docs-site-to-go-with-it-121b</guid>
      <description>&lt;p&gt;A couple of weeks ago I shared nestjs-docfy here, a library that moves Swagger decorators out of NestJS controllers into companion &lt;code&gt;*.controller.docs.ts&lt;/code&gt; files, plus docfy-ui, an AI-first reference UI with a "Copy for AI" button on every endpoint. The response was great, and two specific limitations came up repeatedly: &lt;strong&gt;Fastify wasn't supported, and reloading a deep-linked endpoint page in docfy-ui broke.&lt;/strong&gt; I said I'd rather flag those than have people find out mid-deploy.&lt;/p&gt;

&lt;p&gt;0.4.0 closes both.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F45x5donkk2dy17wci4uj.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F45x5donkk2dy17wci4uj.png" alt="nest docfy documentation home"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Fastify support for docfy-ui
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;DocfyUiModule.setup()&lt;/code&gt; was Express-only. It now works on Fastify apps too, through a scoped &lt;code&gt;@fastify/static&lt;/code&gt; registration — the same package &lt;code&gt;@nestjs/swagger&lt;/code&gt; itself already relies on for Fastify's Swagger UI support:&lt;br&gt;
&lt;/p&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; @fastify/static
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NestFactory&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="s1"&gt;@nestjs/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FastifyAdapter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NestFastifyApplication&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="s1"&gt;@nestjs/platform-fastify&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DocfyUiModule&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="s1"&gt;nestjs-docfy&lt;/span&gt;&lt;span class="dl"&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;app&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;NestFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;NestFastifyApplication&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AppModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FastifyAdapter&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="nx"&gt;DocfyUiModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/docs&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="k"&gt;await&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;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same call, same "Copy for AI" button, same zero-config &lt;code&gt;/api-json&lt;/code&gt; discovery — just works on either adapter now.&lt;/p&gt;

&lt;p&gt;One Fastify-specific thing worth knowing: &lt;code&gt;SwaggerModule.setup()&lt;/code&gt; registers its own &lt;code&gt;/api-json&lt;/code&gt; route too. On Express, whichever route wins is whoever registered first, silently. On Fastify, registering the same route twice throws &lt;code&gt;FST_ERR_DUPLICATED_ROUTE&lt;/code&gt; at startup instead — order alone won't save you. If you're combining Fastify with a static, pre-patched spec (&lt;code&gt;staticSpecPath&lt;/code&gt;, for &lt;code&gt;webpack: true&lt;/code&gt; builds), point &lt;code&gt;SwaggerModule.setup()&lt;/code&gt; at a different &lt;code&gt;jsonDocumentUrl&lt;/code&gt; so it doesn't also claim &lt;code&gt;/api-json&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deep-link reload, fixed
&lt;/h2&gt;

&lt;p&gt;If you mounted the UI outside the root — &lt;code&gt;DocfyUiModule.setup('/docs', app)&lt;/code&gt; — and reloaded a specific endpoint's page directly by URL instead of navigating there client-side, you'd get a 404. Routing inside DocfyUiModule was reworked to handle the SPA fallback properly for both adapters, so a hard reload on a deep route now resolves the same as client-side navigation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Also new: a proper docs site
&lt;/h2&gt;

&lt;p&gt;The README carried the full API reference on its own for a while, which worked but wasn't great for actually browsing. There's now a dedicated docs site — same content, organized as an actual reference with search, instead of one long scroll.&lt;/p&gt;

&lt;p&gt;🔗 &lt;a href="https://www.nestdocfy.com" rel="noopener noreferrer"&gt;nestdocfy.com&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Nothing else changes: same install, same &lt;code&gt;DocfyUiModule.setup('/docs', app)&lt;/code&gt;, same zero backend coupling — it still just consumes an OpenAPI 3.0/3.1 document, so it works with any server that exposes one, not just NestJS.&lt;br&gt;
&lt;/p&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;nestjs-docfy
npm &lt;span class="nb"&gt;install &lt;/span&gt;docfy-ui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/MarvinRF/nest-docfy" rel="noopener noreferrer"&gt;🔗 nestjs-docfy on GitHub&lt;/a&gt; · &lt;a href="https://www.npmjs.com/package/nestjs-docfy" rel="noopener noreferrer"&gt;📦 npm package&lt;/a&gt; · &lt;a href="https://www.nestdocfy.com" rel="noopener noreferrer"&gt;📖 docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If Fastify or the deep-link bug was the thing holding you back, this release should clear it.&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>backend</category>
      <category>typescript</category>
      <category>documentation</category>
    </item>
    <item>
      <title>Nestjs — Stop burning AI credits to write Swagger docs, let the CLI do it!</title>
      <dc:creator>Marvin Rocha</dc:creator>
      <pubDate>Thu, 11 Jun 2026 12:46:15 +0000</pubDate>
      <link>https://dev.to/marvin_rocha_24917bf64d0e/nestjs-stop-burning-ai-credits-to-write-swagger-docs-let-the-cli-do-it-2i63</link>
      <guid>https://dev.to/marvin_rocha_24917bf64d0e/nestjs-stop-burning-ai-credits-to-write-swagger-docs-let-the-cli-do-it-2i63</guid>
      <description>&lt;p&gt;Last Sunday I shared nestjs-docfy, a small library to move Swagger decorators out of NestJS controllers into companion &lt;code&gt;*.controller.docs.ts&lt;/code&gt; files. The reception was better than I expected, and a lot of the feedback pointed in the same direction: &lt;strong&gt;the separation is nice, but writing those docs files by hand is still tedious.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So I spent some time on that, and there's quite a bit new in this release.&lt;/p&gt;

&lt;h2&gt;
  
  
  A CLI that writes the boilerplate for you
&lt;/h2&gt;

&lt;p&gt;The biggest addition is a generate command that reads your project with static analysis (no code execution, no ts-node overhead) and produces a pre-filled docs file for every controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx nestjs-docfy generate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generated file comes with inferred summaries, response types, and common error responses already in place. You edit from there instead of starting from scratch.&lt;/p&gt;

&lt;p&gt;It's idempotent by default, running it again won't touch files that already exist. When you add a new endpoint and want to merge only the new method block without losing your existing edits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx nestjs-docfy generate &lt;span class="nt"&gt;--force&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CLI auto-detects your project layout, so monorepos (Nx, Nest CLI, generic &lt;code&gt;packages/&lt;/code&gt; or &lt;code&gt;apps/&lt;/code&gt; structures) work without any configuration.&lt;/p&gt;

&lt;p&gt;There's also a &lt;code&gt;--dry-run&lt;/code&gt; flag if you want to preview output before writing anything to disk.&lt;/p&gt;

&lt;h2&gt;
  
  
  A check command for CI
&lt;/h2&gt;

&lt;p&gt;The other side of the workflow is keeping docs in sync as the codebase evolves. The &lt;code&gt;check&lt;/code&gt; command exits with code 1 if any controller has undocumented methods or no companion file at all:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx nestjs-docfy check
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output looks like this when something is out of sync:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✖ UsersController, undocumented methods: updateProfile, deleteAccount
  → run nestjs-docfy generate --force to merge new methods

✖ 2 controller(s) out of sync.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Drop it into your pipeline and docs drift gets caught before it reaches main.&lt;/p&gt;

&lt;h2&gt;
  
  
  Type-safe method keys
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;docs()&lt;/code&gt; function now enforces that every key in &lt;code&gt;config.methods&lt;/code&gt; actually exists on the controller class. Typos are a compile error, not a silent runtime warning:&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="nf"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;UsersController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;findAll&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...],&lt;/span&gt;    &lt;span class="c1"&gt;// ✔ exists on UsersController&lt;/span&gt;
    &lt;span class="na"&gt;typoMethod&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 error&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;
  
  
  Interface-typed DTOs just work
&lt;/h2&gt;

&lt;p&gt;This one came up a lot. If your response or body type is a TypeScript interface, Swagger can't use it as a &lt;code&gt;type:&lt;/code&gt; value because interfaces are erased at runtime. Previously you had to convert them to classes or write the schema by hand.&lt;/p&gt;

&lt;p&gt;Now the CLI detects this automatically and generates an inline &lt;code&gt;schema: object&lt;/code&gt; instead:&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="c1"&gt;// Your existing interface — no changes needed&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;RegisterResponseDto&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&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;Generated output:&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="nc"&gt;ApiResponse&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="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Created&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;boolean&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;nullable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;required&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;success&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Supports primitives, nullable unions, arrays, nested interfaces, and optional properties.&lt;/p&gt;

&lt;h2&gt;
  
  
  class-validator inference
&lt;/h2&gt;

&lt;p&gt;If a DTO uses class-validator decorators and doesn't already have &lt;code&gt;@ApiProperty&lt;/code&gt; on its properties, the CLI infers the full JSON Schema from the validators — no manual annotation needed:&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;class&lt;/span&gt; &lt;span class="nc"&gt;CreateUserDto&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;IsString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;MinLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;IsEmail&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;IsOptional&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;IsString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;bio&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&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;Generated output:&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="nc"&gt;ApiBody&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;properties&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;minLength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;bio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&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="na"&gt;required&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;name&lt;/span&gt;&lt;span class="dl"&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;email&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If any property already has &lt;code&gt;@ApiProperty&lt;/code&gt;, inference is skipped entirely and &lt;code&gt;type: ClassName&lt;/code&gt; is used instead, existing annotations are never overwritten.&lt;/p&gt;

&lt;h2&gt;
  
  
  @HttpCode() awareness
&lt;/h2&gt;

&lt;p&gt;The CLI reads &lt;code&gt;@HttpCode()&lt;/code&gt; on route handlers and uses the correct status code in the generated &lt;code&gt;ApiResponse&lt;/code&gt;. A &lt;code&gt;@Post&lt;/code&gt; with &lt;code&gt;@HttpCode(204)&lt;/code&gt; gets &lt;code&gt;status: 204&lt;/code&gt;, not the default 201.&lt;/p&gt;




&lt;p&gt;The core idea is still the same — controllers express behavior, docs files express documentation, but now the friction of setting that up and keeping it current is mostly gone.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/MarvinRF/nest-docfy" rel="noopener noreferrer"&gt;🔗 nestjs-docfy on GitHub&lt;/a&gt; · &lt;a href="https://www.npmjs.com/package/nestjs-docfy" rel="noopener noreferrer"&gt;📦 npm package&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you were holding off because of the manual setup, this release should make it a lot more practical.&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>webdev</category>
      <category>typescript</category>
      <category>node</category>
    </item>
    <item>
      <title>NestJS controllers drowning in Swagger decorators? I built a small fix!</title>
      <dc:creator>Marvin Rocha</dc:creator>
      <pubDate>Mon, 08 Jun 2026 16:56:57 +0000</pubDate>
      <link>https://dev.to/marvin_rocha_24917bf64d0e/my-nestjs-controllers-were-drowning-in-swagger-decorators-so-i-built-a-small-fix-17b4</link>
      <guid>https://dev.to/marvin_rocha_24917bf64d0e/my-nestjs-controllers-were-drowning-in-swagger-decorators-so-i-built-a-small-fix-17b4</guid>
      <description>&lt;p&gt;I want to share something I've been thinking about for a while while working with NestJS, and a small library I put together to deal with it. Not a revolutionary idea, just something that bothered me enough to actually do something about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The thing that kept bothering me
&lt;/h2&gt;

&lt;p&gt;Every time I documented an endpoint, my controller went from something readable to something like this:&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="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;ApiTags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users&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="nd"&gt;Controller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users&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;class&lt;/span&gt; &lt;span class="nc"&gt;UsersController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;ApiOperation&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Create a user&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="nd"&gt;ApiBody&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CreateUserDto&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;ApiResponse&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="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserEntity&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;ApiResponse&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="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid input&lt;/span&gt;&lt;span class="dl"&gt;'&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="nd"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;dto&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CreateUserDto&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;UserEntity&lt;/span&gt;&lt;span class="o"&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;usersService&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;dto&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// ... imagine 4 more endpoints like this&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual routing logi, the part I care about, gets buried under documentation metadata. They're two different concerns sharing the same file, and it starts to feel noisy.&lt;/p&gt;

&lt;p&gt;We already separate tests into *.spec.ts files. It felt weird that documentation was treated differently.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I tried
&lt;/h2&gt;

&lt;p&gt;I built &lt;a href="https://github.com/MarvinRF/nest-docfy" rel="noopener noreferrer"&gt;nestjs-docfy&lt;/a&gt;, a small module that moves Swagger decorators to a companion file using a &lt;code&gt;*.controller.docs.ts&lt;/code&gt; naming convention. The controller stays focused on routing, and the docs file handles everything OpenAPI-related.&lt;/p&gt;

&lt;p&gt;users.controller.ts  after&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="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;WithDocs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Controller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users&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;class&lt;/span&gt; &lt;span class="nc"&gt;UsersController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Post&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="nd"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;dto&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CreateUserDto&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;UserEntity&lt;/span&gt;&lt;span class="o"&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;usersService&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;dto&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;users.controller.docs.ts&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;docs&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="s1"&gt;nestjs-docfy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;UsersController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;classDecorators&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;ApiTags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
  &lt;span class="na"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="nc"&gt;ApiOperation&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Create a user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="nc"&gt;ApiBody&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CreateUserDto&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="nc"&gt;ApiResponse&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="nx"&gt;HttpStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CREATED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserEntity&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="nc"&gt;ApiResponse&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="nx"&gt;HttpStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BAD_REQUEST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid input&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;You add &lt;code&gt;DocfyModule.forRoot()&lt;/code&gt; to your &lt;code&gt;AppModule&lt;/code&gt;, that's the only required change. During &lt;code&gt;NestFactory.create()&lt;/code&gt;, the module resolves companion files for controllers marked with &lt;code&gt;@WithDocs()&lt;/code&gt; and applies the metadata via &lt;code&gt;Reflect&lt;/code&gt;. Since this happens before &lt;code&gt;SwaggerModule.createDocument()&lt;/code&gt; runs, the Swagger generator sees everything exactly as if the decorators were inline.&lt;/p&gt;

&lt;p&gt;No changes to your bootstrap flow, no magic at runtime.&lt;/p&gt;

&lt;p&gt;It's a small thing, but it made my controllers a lot easier to read. If your team deals with the same noise, it might be worth a look.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/MarvinRF/nest-docfy" rel="noopener noreferrer"&gt;🔗 nestjs-docfy on GitHub&lt;/a&gt;  ·  &lt;a href="https://www.npmjs.com/package/nestjs-docfy" rel="noopener noreferrer"&gt;📦 npm package&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Curious if this separation makes sense in your codebase, or if there's a better way you've found to handle it.&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>typescript</category>
      <category>architecture</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
