<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/"><channel><title>Openapi on kmcd.dev</title><link>https://kmcd.dev/tags/openapi/</link><description>Recent content in Openapi on kmcd.dev</description><generator>Hugo -- gohugo.io</generator><language>en</language><copyright>All Rights Reserved</copyright><lastBuildDate>Tue, 05 May 2026 10:00:00 +0000</lastBuildDate><atom:link href="https://kmcd.dev/tags/openapi/index.xml" rel="self" type="application/rss+xml"/><item><title>ConnectRPC: Where is it now?</title><link>https://kmcd.dev/posts/connectrpc-where-is-it-now/</link><pubDate>Tue, 05 May 2026 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/connectrpc-where-is-it-now/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/connectrpc-where-is-it-now/cover.svg" /> &lt;/p>
                
                Reflecting on two years of ConnectRPC: How it evolved from a gRPC alternative to a complete API ecosystem.
                </description><content:encoded><![CDATA[<p>Two years ago, I wrote <a href="https://kmcd.dev/posts/connectrpc/">Making gRPC more approachable with ConnectRPC</a>. At the time, ConnectRPC was the &ldquo;new kid on the block&rdquo;, a library promising to fix the &ldquo;gRPC tax&rdquo; by supporting HTTP/1.1 and JSON without an extra proxy.</p>
<p>Today, ConnectRPC isn&rsquo;t just a library. It is the core of a toolchain that makes traditional <code>protoc</code> workflows look completely dated. Companies like Anthropic are using it in production to power their SDKs, even maintaining <a href="https://github.com/anthropics/connect-rust" rel="external">their own ConnectRPC library in Rust</a>.</p>
<p>Let&rsquo;s look at how far things have come and how tools like Buf Remote Plugins, Protobuf SDKs, FauxRPC, and native HTTP/3 are changing API development.</p>
<h2 id="code-generation">Code Generation</h2>
<p>One of my biggest complaints in <a href="https://kmcd.dev/posts/working-with-protobuf-in-2024/">Working with Protobuf in 2024</a> was the compatibility matrix from hell. Managing local installations of <code>protoc</code>, <code>protoc-gen-go</code>, and half a dozen other plugins was a miserable onboarding experience. If one person had a slightly different version of a plugin, the generated code drifted, and the CI build would fail for reasons that took twenty minutes to track down.</p>
<p>We can finally stop doing that. Buf Remote Plugins effectively killed the &ldquo;it works on my machine&rdquo; version of <code>protoc</code>. By pointing <code>buf.gen.yaml</code> to remote plugins on the <a href="https://buf.build" rel="external">Buf Schema Registry (BSR)</a>, we get deterministic, zero-install code generation.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># buf.gen.yaml in 2026</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="l">v2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">plugins</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">remote</span><span class="p">:</span><span class="w"> </span><span class="l">buf.build/connectrpc/go:v1.19.1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">out</span><span class="p">:</span><span class="w"> </span><span class="l">gen/go</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">opt</span><span class="p">:</span><span class="w"> </span><span class="l">paths=source_relative</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">remote</span><span class="p">:</span><span class="w"> </span><span class="l">buf.build/protocolbuffers/go:v1.34.1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">out</span><span class="p">:</span><span class="w"> </span><span class="l">gen/go</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">opt</span><span class="p">:</span><span class="w"> </span><span class="l">paths=source_relative</span><span class="w">
</span></span></span></code></pre></div><p>Your CI pipeline doesn&rsquo;t need a bloated custom Docker image packed with binaries anymore. You just need the <code>buf</code> CLI. New hires clone the repo, run one command, and they’re done. It’s the level of &ldquo;it just works&rdquo; that we should have had a decade ago.</p>
<h2 id="first-class-ide-support">First-Class IDE Support</h2>
<p>Writing Protobuf used to feel like coding in a glorified Notepad. We lacked the basic editor intelligence that almost every other major language enjoys.</p>
<p>That changed in early 2026 when Buf released a production-grade Language Server Protocol (LSP) server for Protobuf. It’s bundled directly into the <code>buf</code> CLI, which means whether you use VSCode or Neovim, you finally get go-to-definition and reference finding that actually works.</p>
<p>The LSP is workspace-aware, too. You can cmd-click an imported message from a third-party library and jump straight to the definition on the BSR without manually syncing files. It also catches syntax errors and duplicate modifiers before you even try to compile, which saves you from that annoying &ldquo;context switch to terminal, run build, see error, switch back&rdquo; loop.</p>
<h2 id="format-lint-and-breaking-changes">Format, Lint, and Breaking Changes</h2>
<p>The Buf CLI also provides the kind of guardrails that keep a team from moving into &ldquo;legacy debt&rdquo; territory too quickly.</p>
<p>If you’ve ever sat through a PR review where someone spent ten comments arguing about whether a field should be <code>camelCase</code> or <code>snake_case</code>, <code>buf fmt</code> and <code>buf lint</code> are for you. They end the debate. You run the command, the code is formatted, and the team moves on to actually solving problems.</p>
<p>The real winner is <code>buf breaking</code>. In a microservices setup, accidentally deleting a field or changing a data type in your schema is a great way to wake up the on-call engineer. By running <code>buf breaking</code> in CI, you verify the current schema against previous commits. It catches destructive changes before they hit the main branch, ensuring your contracts stay stable without requiring a human to manually audit every <code>.proto</code> change.</p>
<h2 id="data-validation">Data Validation</h2>
<p>Validation has historically been a tedious chore. Writing endless <code>if req.Age &lt; 0</code> or <code>if req.Email == &quot;&quot;</code> checks in every single handler is a waste of time and a magnet for bugs.</p>
<p><a href="https://protovalidate.com/" rel="external">protovalidate</a> (which recently hit v1.0) moves those rules directly into the Protobuf schema. Since it’s built on Google&rsquo;s Common Expression Language (CEL), you can do more than just check for nulls; you can write complex cross-field logic, like ensuring a &ldquo;start date&rdquo; is always before an &ldquo;end date.&rdquo;</p>
<p>By dropping the <code>protovalidate</code> interceptor into your server, requests are automatically validated before they touch your business logic. But the real &ldquo;aha!&rdquo; moment is the frontend. Your TypeScript client can run these same rules in the browser before the request even leaves. No more maintaining a separate Zod or Yup schema that inevitably gets out of sync with the backend. One source of truth, enforced everywhere.</p>
<h2 id="docs-and-mocks">Docs and Mocks</h2>
<p>Sharing a gRPC endpoint used to be a pain; you couldn&rsquo;t just hand someone a cURL command and expect it to work. ConnectRPC solved that fundamental issue by supporting standard HTTP/1.1 and JSON. But to truly treat these services like REST APIs, we needed the documentation tooling to match. That is why I spent part of 2024 working on <a href="https://kmcd.dev/posts/protoc-gen-connect-openapi/"><strong>protoc-gen-connect-openapi</strong></a>.</p>
<p>Now, <a href="https://kmcd.dev/posts/self-documenting-connect-services/">Self-Documenting Connect Services</a> are essentially the default for me. Because ConnectRPC skips binary framing for unary calls and uses standard HTTP status codes, we can generate an OpenAPI spec directly from the Protobuf definitions. You can spin up a Swagger UI directly from your server, let external users test with JSON, and keep your strict internal contracts intact.</p>
<p>We’ve also mostly solved the &ldquo;waiting for the backend&rdquo; bottleneck. <a href="https://kmcd.dev/posts/fauxrpc/"><strong>FauxRPC</strong></a> uses your Protobuf descriptors to spin up a mock server in seconds. When you pair it with <a href="https://kmcd.dev/posts/fauxrpc-protovalidate/"><strong>protovalidate</strong></a>, the fake data is actually realistic enough to build a frontend against. Some teams are even running <a href="https://kmcd.dev/posts/fauxrpc-testcontainers/">FauxRPC in Testcontainers</a> for integration tests, which is much cleaner than trying to manage a &ldquo;staging&rdquo; backend for every test run.</p>
<h2 id="why-grpc-web-failed">Why gRPC-Web Failed</h2>
<p>To understand why ConnectRPC won the frontend, you have to look at the history of the protocol. Native gRPC relies on HTTP/2 trailers for status codes, but browsers do not expose those trailers to JavaScript. This originally made gRPC effectively unusable on the web.</p>
<p>The official solution to this problem was gRPC-Web. Its intended goal was straightforward: allow developers to use gRPC directly from web applications. As far as that specific goal goes, it was a success. You could finally make gRPC calls from a browser.</p>
<p>But there is a big difference between a technical success and a widely adopted standard. gRPC-Web never truly took off for a number of reasons. First, it was fundamentally unfriendly to modern infrastructure. It required a separate proxy (usually Envoy) just to translate the frontend requests into something the gRPC backend could understand. This added immediate operational overhead to every project.</p>
<p>Worse, it preserved the most frustrating parts of gRPC. Every single request returned a <strong>200 OK</strong>, regardless of whether the server crashed or the resource was missing. It was a baffling design choice that broke the internet&rsquo;s existing contract for observability. You could not rely on standard load balancer metrics, standard browser dev tools, or your generic APM to see if your site was actually healthy. You were forced to use specialized, protocol-aware tooling just to perform basic debugging. If I have to open a dedicated &ldquo;gRPC-aware&rdquo; network tab just to see why a login failed, I feel like it hasn&rsquo;t actually earned the &ldquo;web&rdquo; part of gRPC-Web name.</p>
<p>ConnectRPC stepped in and completely erased the proxy requirement. It also fixed the integration issues with the traditional web. A unary JSON request in ConnectRPC acts exactly like a standard REST call. If a resource is missing, you get a real <strong>404 Not Found</strong>, and your existing monitoring stack just works. It gave frontend developers the familiar, straightforward debugging experience they actually wanted while keeping the strict schema safety that backend teams need.</p>
<h2 id="why-its-my-default-choice">Why it&rsquo;s my default choice</h2>
<p>In 2024, ConnectRPC was about making gRPC more approachable. Now, the underlying protocol is almost an implementation detail. We get the benefits of typed schemas and code generation, but the friction of the &ldquo;gRPC tax&rdquo; is gone.</p>
<p>If you’re still hand-rolling JSON/REST APIs or wrestling with legacy gRPC-go stubs and Envoy proxies, it’s time to move on. The tools are ready, the workflow is better, and your on-call engineer will thank you.</p>
]]></content:encoded></item><item><title>Building APIs with Contracts</title><link>https://kmcd.dev/posts/api-contracts/</link><pubDate>Tue, 28 Apr 2026 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/api-contracts/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/api-contracts/cover.svg" /> &lt;/p>
                
                Building for Scale: Why contract-based APIs are the future.
                </description><content:encoded><![CDATA[<div class="disclaimer">
    This article was originally published in April 2024. It was republished in April 2026 after some significant editing and modernization.
</div>

<p>In today&rsquo;s interconnected world, APIs (Application Programming Interfaces) are the glue that connects computers. They allow different applications to talk to each other, share data, and perform actions. However, traditional methods of creating APIs often lead to frustrating challenges: breaking changes in JSON APIs, silent failures due to missing fields, frontend and backend drift, or schema mismatches that result in the classic &ldquo;works on my machine&rdquo; excuse.</p>
<p>Imagine a real-world scenario where the backend team renames a <code>userId</code> field to <code>user_id</code> and deploys their changes. Instantly, the frontend checkout process breaks in production because the API had no strict enforcement to catch the mismatch.</p>
<p>This is where <strong>contract-based APIs</strong> come in. A contract-based API is one where the schema is defined first in a formal specification, and both client and server are generated or validated against that contract. They reduce ambiguity and enforce consistency across services.</p>
<h2 id="the-power-of-pre-defined-api-contracts">The Power of Pre-defined API Contracts</h2>
<p>A contract-based API defines exactly what data can be exchanged, in what format, and what actions can be performed. This strict, pre-defined agreement unlocks several immediate advantages:</p>
<ul>
<li><strong>Improved Developer Experience:</strong> Developers on both sides (client and server) have a clear understanding of what is expected, making integration smoother.</li>
<li><strong>Automated Documentation:</strong> Contracts serve as self-documenting artifacts. This reduces the need for manual documentation maintenance and ensures the docs stay in sync with the actual API implementation.</li>
<li><strong>Reduced Errors:</strong> Mismatched data formats or API changes become less likely, leading to fewer bugs. Contracts act as a validation layer that catches potential issues early.</li>
<li><strong>Easier Integration:</strong> Contracts act as a single source of truth. Developers can quickly understand how to interact with the API without extensive back and forth communication.</li>
<li><strong>Streamlined Development:</strong> These APIs often enable tools to automatically generate code for both client and server implementations. This eliminates manual boilerplate so you can focus on core logic.</li>
</ul>
<h2 id="protobuf-the-language-of-apis">Protobuf: The Language of APIs</h2>
<p>In modern distributed systems, the foundation of many contract-based APIs lies in <a href="https://protobuf.dev/" rel="external"><strong>Protocol Buffers (protobuf)</strong></a>. It is a language-neutral data format specifically designed for structured messages.</p>
<p>Unlike JSON, which is a text-based format designed to be human-readable, Protobuf is a <strong>binary format</strong>. This means you trade the ability to natively read the raw data in transit for significant performance gains:</p>
<ul>
<li><strong>Smaller Message Sizes:</strong> Protobuf messages are compact and efficient, which leads to faster transmission and reduced bandwidth usage.</li>
<li><strong>Faster Parsing:</strong> Parsing binary protobuf messages is significantly faster compared to traditional formats like JSON or XML.</li>
<li><strong>Built-in Versioning:</strong> Protobuf uses field numbers (the <code>= 1</code>, <code>= 2</code> in the code below) to identify data. This allows for excellent backward and forward compatibility. You can add new fields without breaking older clients that do not know about them yet.</li>
<li><strong>Cross-language Compatibility:</strong> Protobuf definitions are language-agnostic. Code for interacting with the API can be generated for almost any modern programming language.</li>
</ul>
<p>Because the data is binary, you cannot simply open your browser&rsquo;s network tab and read the payloads by default. You will usually need to rely on modern browser extensions (like the gRPC-Web or Connect dev tools) to decode the traffic. It also requires setting up specialized tooling and build steps to compile the generated code.</p>
<p>Here is a basic example of a <code>.proto</code> file defining messages for a user and an address:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-protobuf" data-lang="protobuf"><span class="line"><span class="cl"><span class="n">syntax</span> <span class="o">=</span> <span class="s">&#34;proto3&#34;</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">message</span> <span class="nc">User</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">string</span> <span class="n">name</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">int32</span> <span class="n">id</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">string</span> <span class="n">email</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="n">Address</span> <span class="n">address</span> <span class="o">=</span> <span class="mi">4</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">message</span> <span class="nc">Address</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">string</span> <span class="n">street</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">string</span> <span class="n">city</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">string</span> <span class="n">state</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">string</span> <span class="n">zip</span> <span class="o">=</span> <span class="mi">4</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</span></span></span></code></pre></div><p>In this example, the <code>User</code> message has fields for name, ID, email, and an <code>Address</code> message. These defined structures ensure consistent data exchange between applications.</p>
<blockquote>
<p><strong>Key idea:</strong> Protobuf relies on immutable field numbers instead of field names. This golden rule guarantees backward and forward compatibility.</p>
</blockquote>
<h2 id="grpc-building-apis-on-a-solid-foundation">gRPC: Building APIs on a Solid Foundation</h2>
<p><strong>gRPC (gRPC Remote Procedure Call)</strong> is a high-performance framework that builds upon protobuf&rsquo;s strengths. It provides a powerful way to implement remote procedure calls, allowing applications to interact using clients generated for each language.</p>
<h3 id="introducing-services-and-requestresponse-types-with-grpc">Introducing Services and Request/Response Types with gRPC</h3>
<p>We can expand the <code>.proto</code> file to define a service called <code>UserService</code> with methods for user management:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-protobuf" data-lang="protobuf"><span class="line"><span class="cl"><span class="n">syntax</span> <span class="o">=</span> <span class="s">&#34;proto3&#34;</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">service</span> <span class="n">UserService</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="k">rpc</span> <span class="n">CreateUser</span><span class="p">(</span><span class="n">CreateUserRequest</span><span class="p">)</span> <span class="k">returns</span> <span class="p">(</span><span class="n">User</span><span class="p">)</span> <span class="p">{}</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="k">rpc</span> <span class="n">GetUser</span><span class="p">(</span><span class="n">GetUserRequest</span><span class="p">)</span> <span class="k">returns</span> <span class="p">(</span><span class="n">User</span><span class="p">)</span> <span class="p">{}</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">message</span> <span class="nc">CreateUserRequest</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="n">User</span> <span class="n">user</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">message</span> <span class="nc">GetUserRequest</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">int32</span> <span class="n">id</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</span></span></span></code></pre></div><p>This example defines a <code>UserService</code> with two methods: <code>CreateUser</code> and <code>GetUser</code>. Each method takes a specific request message and returns a response.</p>
<p>Notice how clear the intention is. A helpful mental model to contrast modern APIs is:</p>
<ul>
<li><strong>REST</strong> is resource-oriented (relying on URLs and HTTP verbs).</li>
<li><strong>gRPC</strong> is action-oriented (relying on explicit methods).</li>
</ul>
<p>A reader of this spec does not have to map vague HTTP verbs like &ldquo;POST&rdquo; to actions like &ldquo;create.&rdquo; Also, these method names are <a href="https://en.wiktionary.org/wiki/greppable" rel="external">greppable</a>. It is trivial to locate every use of <code>CreateUser</code> across several repositories, making refactoring and impact analysis much easier.</p>
<h3 id="server-reflection">Server Reflection</h3>
<p>Another powerful feature of the gRPC ecosystem is <strong>Server Reflection</strong>. This allows clients or debugging tools (like Postman or grpcurl) to query the server at runtime to discover the available services and methods. This eliminates the need to distribute <code>.proto</code> files to developers just so they can explore the API structure.</p>
<h2 id="distributing-api-contracts">Distributing API Contracts</h2>
<p>Defining a contract is only half the battle. How do the frontend and backend teams actually share that <code>.proto</code> file? If the schema is not easily accessible, the contract is useless.</p>
<p>In practice, teams usually solve this distribution problem in one of three ways:</p>
<ol>
<li><strong>Monorepos:</strong> Storing the backend, frontend, and API definitions in a single repository so all code shares the same source of truth.</li>
<li><strong>Package Managers:</strong> Generating the client SDKs in a CI/CD pipeline and publishing them as internal NPM, Maven, or Go packages.</li>
<li><strong>Schema Registries:</strong> Using dedicated tools like the <a href="https://buf.build/" rel="external">Buf Schema Registry</a> to manage, version, and distribute Protobuf files securely across an organization.</li>
</ol>
<h2 id="what-about-public-apis">What about public APIs?</h2>
<p>Historically, strict RPC contracts were tough for external, public-facing APIs. If your primary consumers were third-party developers, handing them a raw Protobuf file or expecting them to set up gRPC clients caused massive friction. They just wanted to use standard REST with JSON.</p>
<p>This is where tools like <a href="https://connectrpc.com/" rel="external"><strong>ConnectRPC</strong></a> shine. ConnectRPC allows you to define your API using Protobuf, but it automatically exposes endpoints that support standard HTTP/1.1 and JSON serialization as a fallback format.</p>
<p>This hybrid approach also solves the local debugging problem. You can configure ConnectRPC to use JSON during local development specifically so you can read the network tab in plain text, and then flip it to highly efficient binary for production. In practice, you write Protobuf once, and get both gRPC and REST/JSON APIs for free.</p>
<p>Even better, because the source of truth is still Protobuf, you can use ecosystem plugins to automatically generate an OpenAPI specification directly from your <code>.proto</code> files. You get a highly maintainable, contract-driven architecture on the backend, while your external users can still <code>curl</code> standard REST endpoints, read plain JSON, and explore your API via a generated Swagger UI. It offers the best of both worlds without compromising the developer experience on either side.</p>
<blockquote>
<p><strong>Key idea:</strong> Tools like ConnectRPC allow you to maintain strict internal Protobuf contracts while exposing standard REST/JSON APIs to external consumers.</p>
</blockquote>
<h2 id="alternatives">Alternatives</h2>
<p>While Protobuf and gRPC are a powerful duo, there are other contract-based API solutions to consider depending on your architecture:</p>
<ul>
<li><a href="https://www.openapis.org/" rel="external"><strong>OpenAPI (Swagger)</strong></a>: Contracts are not exclusive to RPC. You can use OpenAPI to define strict contracts for RESTful services. However, a harsh reality of the industry is that OpenAPI specs often drift from the actual code because they are bolted on after the fact. To make OpenAPI truly safe, teams must rely on strict framework integration (like FastAPI in Python or tsoa in Node) where the code generates the spec, or vice versa.</li>
<li><a href="https://graphql.org/" rel="external"><strong>GraphQL</strong></a>: Arguably the most mainstream contract-driven API paradigm for frontend developers. Its strictly typed schema defines the exact shape of the available data. Unlike gRPC, which has fixed responses, GraphQL allows the client to dictate the exact payload it wants to receive.</li>
<li><a href="https://twitchtv.github.io/twirp/" rel="external"><strong>Twirp</strong></a>: Developed by Twitch, Twirp is a lightweight RPC framework built on top of Protobuf and HTTP/1.1. It shares similarities with ConnectRPC but focuses on absolute simplicity. It avoids the complexity of HTTP/2 and gRPC streams while still providing generated clients, making it an excellent alternative if full gRPC is overkill for your needs.</li>
<li><a href="https://thrift.apache.org/" rel="external"><strong>Thrift</strong></a>: Originally developed at Facebook, Thrift is a language-neutral protocol for defining service contracts similar to Protobuf. It is often found in large-scale data environments and supports various RPC protocols.</li>
<li><a href="https://trpc.io/" rel="external"><strong>tRPC</strong></a>: This tool defines the API schema directly in TypeScript code to be reused on both the client and the server. While it often pairs with libraries like Zod for runtime validation, it lacks true language-agnostic safety across the network boundary since it relies entirely on a TypeScript ecosystem.</li>
<li><a href="https://avro.apache.org/" rel="external"><strong>Avro</strong></a>: This format uses JSON-like schemas but stores data in a compact binary format. It is a staple in the Apache Kafka ecosystem for streaming data pipelines. It handles schema evolution differently than Protobuf (often sending the schema alongside the data), making it highly flexible for dynamic systems.</li>
</ul>
<h2 id="when-not-to-use-api-contracts">When NOT to Use API Contracts</h2>
<p>While these tools are powerful, they are not a silver bullet. You should reconsider using strict API contracts if:</p>
<ul>
<li><strong>You are building small projects or MVPs:</strong> The initial setup, code generation, and boilerplate overhead might slow down your speed of delivery when rapid iteration is the top priority.</li>
<li><strong>Simplicity for external consumers outweighs strict contracts:</strong> If you are building a straightforward public API and are not using a hybrid tool like ConnectRPC, raw JSON over REST remains the path of least resistance for third-party developers.</li>
<li><strong>Your team lacks tooling maturity:</strong> Implementing gRPC or Protobuf requires solid CI/CD pipelines and a team that is comfortable managing build steps, code generation, and backward-compatible schema evolutions.</li>
</ul>
<blockquote>
<p><strong>Key idea:</strong> Strict API contracts add overhead and may not be suitable for small MVPs, simple public APIs, or teams lacking tooling maturity.</p>
</blockquote>
<h2 id="conclusion">Conclusion</h2>
<p>Contract-based APIs offer a significant advantage in building robust and scalable communication between applications. Protobuf and gRPC provide a powerful combination for defining clear contracts and generating highly efficient code.</p>
<p>As a general rule of thumb: if you are building an early-stage prototype, stick to what is fast and familiar. But if you are scaling a complex system across multiple teams and services, contract-based APIs transition from a nice-to-have to an absolute necessity. Once multiple teams depend on your API, contracts stop being optional. They are how you avoid chaos.</p>
]]></content:encoded></item><item><title>Self-Documenting Connect Services</title><link>https://kmcd.dev/posts/self-documenting-connect-services/</link><pubDate>Wed, 25 Sep 2024 10:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/self-documenting-connect-services/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/self-documenting-connect-services/cover_hu_6d1636dac10f3e09.webp" /> &lt;/p>
                
                gRPC can be pretty, too.
                </description><content:encoded><![CDATA[<p>As some of you may know, I&rsquo;ve created a plugin for protoc called <a href="https://github.com/sudorandom/protoc-gen-connect-openapi" rel="external">protoc-gen-connect-openapi</a>. This plugin converts protobuf files into <a href="https://swagger.io/specification/" rel="external">OpenAPI specifications</a> for <a href="https://connectrpc.com/docs/protocol/" rel="external">the Connect protocol</a>. This protocol is very similar to gRPC but for unary RPCs it follows many more traditions that you&rsquo;d expect from an HTTP-based API, like using HTTP status codes appropriately, using the normal <code>Content-Encoding</code> header to specify compression and avoiding putting extra framing inside of the body. Because of this, we can document it more readily with other specifications like OpenAPI.</p>
<p>For more on the plugin itself, refer to <a href="https://kmcd.dev/posts/protoc-gen-connect-openapi/">my older post</a> that introduces protoc-gen-connect-openapi or <a href="https://github.com/sudorandom/protoc-gen-connect-openapi" rel="external">the github repo</a> which has some more updates.</p>
<p>This post is about a new way to use this functionality, from <a href="https://pkg.go.dev/github.com/sudorandom/protoc-gen-connect-openapi/converter" rel="external">a Go library</a>. This Go API provides a simpler interface to generate OpenAPI from protobuf descriptors. You see, protoc plugins accept a <code>*pluginpb.CodeGeneratorRequest</code> and return a <code>*pluginpb.CodeGeneratorResponse</code>. The request type, in particular, is hard to use from Go. You have to encode every option you want to use into a single string. This is fine for a CLI but it&rsquo;s not very friendly for a Go library. But now that I made this library it is much easier to generate OpenAPI specs anywhere you run Go. Let&rsquo;s look at some examples.</p>
<h2 id="generating-openapi">Generating OpenAPI</h2>
<p>First, let&rsquo;s see how we can use this plugin to generate OpenAPI YAML from your protobuf definitions:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">openapiBody</span><span class="p">,</span><span class="w"> </span><span class="nx">_</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">converter</span><span class="p">.</span><span class="nf">GenerateSingle</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">converter</span><span class="p">.</span><span class="nf">WithGlobal</span><span class="p">(),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">converter</span><span class="p">.</span><span class="nf">WithBaseOpenAPI</span><span class="p">([]</span><span class="nb">byte</span><span class="p">(</span><span class="s">`
</span></span></span><span class="line"><span class="cl"><span class="s">openapi: 3.1.0
</span></span></span><span class="line"><span class="cl"><span class="s">info:
</span></span></span><span class="line"><span class="cl"><span class="s">  title: OpenAPI Documentation of gRPC Services
</span></span></span><span class="line"><span class="cl"><span class="s">  description: This is documentation that was generated from [protoc-gen-connect-openapi](https://github.com/sudorandom/protoc-gen-connect-openapi).
</span></span></span><span class="line"><span class="cl"><span class="s">  version: 0.1.2
</span></span></span><span class="line"><span class="cl"><span class="s">`</span><span class="p">)))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nb">string</span><span class="p">(</span><span class="nx">openapiBody</span><span class="p">))</span><span class="w">
</span></span></span></code></pre></div><p>With a few short lines, you now have OpenAPI YAML representation of your ConnectRPC services.</p>
<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="w"></span><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">OpenAPI Documentation of gRPC Services</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">This is documentation that was generated from [protoc-gen-connect-openapi](https://github.com/sudorandom/protoc-gen-connect-openapi).</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><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">/connectrpc.eliza.v1.ElizaService/Say</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">post</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></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">connectrpc.eliza.v1.ElizaService</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">Say</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">connectrpc.eliza.v1.ElizaService.Say</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">Connect-Protocol-Version</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 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">$ref</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;#/components/schemas/connect-protocol-version&#39;</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">Connect-Timeout-Ms</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 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/connect-timeout-header&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">requestBody</span><span class="p">:</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/connectrpc.eliza.v1.SayRequest&#39;</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">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">default</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">Error</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/connect.error&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s2">&#34;200&#34;</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">Success</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/connectrpc.eliza.v1.SayResponse&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">/connectrpc.eliza.v1.ElizaService/Converse</span><span class="p">:</span><span class="w"> </span>{}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">/connectrpc.eliza.v1.ElizaService/Introduce</span><span class="p">:</span><span class="w"> </span>{}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><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">connectrpc.eliza.v1.SayRequest</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">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">sentence</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">title</span><span class="p">:</span><span class="w"> </span><span class="l">sentence</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">SayRequest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">additionalProperties</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">connectrpc.eliza.v1.SayResponse</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">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">sentence</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">title</span><span class="p">:</span><span class="w"> </span><span class="l">sentence</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">SayResponse</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">additionalProperties</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">connect-protocol-version</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">number</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">Connect-Protocol-Version</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">enum</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="m">1</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">Define the version of the Connect protocol</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">const</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">connect-timeout-header</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">number</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">Connect-Timeout-Ms</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">Define the timeout, in ms</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">connect.error</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">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">code</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></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="l">CodeNotFound</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">enum</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="l">CodeCanceled</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="l">CodeUnknown</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="l">CodeInvalidArgument</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="l">CodeDeadlineExceeded</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="l">CodeNotFound</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="l">CodeAlreadyExists</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="l">CodePermissionDenied</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="l">CodeResourceExhausted</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="l">CodeFailedPrecondition</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="l">CodeAborted</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="l">CodeOutOfRange</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="l">CodeInternal</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="l">CodeUnavailable</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="l">CodeDataLoss</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="l">CodeUnauthenticated</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">The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code].</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">message</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">description</span><span class="p">:</span><span class="w"> </span><span class="l">A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">detail</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/google.protobuf.Any&#39;</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">Connect Error</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">additionalProperties</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">description: &#39;Error type returned by Connect</span><span class="p">:</span><span class="w"> </span><span class="l">https://connectrpc.com/docs/go/errors/#http-representation&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">google.protobuf.Any</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">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">type</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">value</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">format</span><span class="p">:</span><span class="w"> </span><span class="l">binary</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">debug</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">additionalProperties</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">additionalProperties</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">description</span><span class="p">:</span><span class="w"> </span><span class="l">Contains an arbitrary serialized message along with a @type that describes the type of the serialized message.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">security</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="w"></span><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">connectrpc.eliza.v1.ElizaService</span><span class="w">
</span></span></span></code></pre></div><p>This file is truncated to only show a single endpoint and related types. To see the full file, <a href="https://kmcd.dev/posts/self-documenting-connect-services/openapi.yaml">click here</a>. The <code>converter.WithGlobal()</code> option uses the global protobuf registry as the source. If you want to use specific (or protobuf file descriptors not in that registry), you can pass in any <code>protoregistry.GlobalFiles</code> value to the <code>converter.WithFiles()</code> option. With <code>converter.WithBaseOpenAPI()</code>, you can specify a base OpenAPI spec that will be used as the basis for the generated one. Here you can add a description, version, security schemes, link to other documentation, etc.</p>
<p>But what&rsquo;s the practical use of this YAML?</p>
<h2 id="show-the-world-what-you-can-do">Show the world what you can do</h2>
<p>With a few additional lines of code, we can leverage one of the numerous OpenAPI documentation visualization tools to transform this YAML into a visually appealing and interactive web page. Here&rsquo;s an example using Elements from Spotlight:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">var</span><span class="w"> </span><span class="nx">tmplElements</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">Must</span><span class="p">(</span><span class="nx">template</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;name&#34;</span><span class="p">).</span><span class="nf">Parse</span><span class="p">(</span><span class="s">`&lt;!doctype html&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;html lang=&#34;en&#34;&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">	&lt;head&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">	&lt;meta charset=&#34;utf-8&#34;&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">	&lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1, shrink-to-fit=no&#34;&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">	&lt;title&gt;OpenAPI Documentation&lt;/title&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">	&lt;script src=&#34;https://unpkg.com/@stoplight/elements@8.3.4/web-components.min.js&#34;&gt;&lt;/script&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">	&lt;link rel=&#34;stylesheet&#34; href=&#34;https://unpkg.com/@stoplight/elements@8.3.4/styles.min.css&#34;&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">	&lt;/head&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">	&lt;body&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">	&lt;elements-api
</span></span></span><span class="line"><span class="cl"><span class="s">		id=&#34;docs&#34;
</span></span></span><span class="line"><span class="cl"><span class="s">		router=&#34;hash&#34;
</span></span></span><span class="line"><span class="cl"><span class="s">		layout=&#34;sidebar&#34;
</span></span></span><span class="line"><span class="cl"><span class="s">	/&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">	&lt;script&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">	(async () =&gt; {
</span></span></span><span class="line"><span class="cl"><span class="s">		const docs = document.getElementById(&#39;docs&#39;);
</span></span></span><span class="line"><span class="cl"><span class="s">		docs.apiDescriptionDocument = atob(&#34;</span><span class="cp">{{</span><span class="w"> </span><span class="na">.DocumentBase64</span><span class="w"> </span><span class="cp">}}</span><span class="s">&#34;);
</span></span></span><span class="line"><span class="cl"><span class="s">	})();
</span></span></span><span class="line"><span class="cl"><span class="s">	&lt;/script&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">	&lt;/body&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;/html&gt;`</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="nf">main</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="w">	</span><span class="nx">mux</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nf">NewServeMux</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">mux</span><span class="p">.</span><span class="nf">Handle</span><span class="p">(</span><span class="nx">elizav1connect</span><span class="p">.</span><span class="nf">NewElizaServiceHandler</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">elizav1connect</span><span class="p">.</span><span class="nx">UnimplementedElizaServiceHandler</span><span class="p">{}))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">openapiBody</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">converter</span><span class="p">.</span><span class="nf">GenerateSingle</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">converter</span><span class="p">.</span><span class="nf">WithGlobal</span><span class="p">(),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">converter</span><span class="p">.</span><span class="nf">WithContentTypes</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="s">&#34;json&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="s">&#34;proto&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">converter</span><span class="p">.</span><span class="nf">WithStreaming</span><span class="p">(</span><span class="kc">true</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">converter</span><span class="p">.</span><span class="nf">WithAllowGET</span><span class="p">(</span><span class="kc">true</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">converter</span><span class="p">.</span><span class="nf">WithBaseOpenAPI</span><span class="p">([]</span><span class="nb">byte</span><span class="p">(</span><span class="s">`
</span></span></span><span class="line"><span class="cl"><span class="s">openapi: 3.1.0
</span></span></span><span class="line"><span class="cl"><span class="s">info:
</span></span></span><span class="line"><span class="cl"><span class="s">  title: OpenAPI Documentation of gRPC Services
</span></span></span><span class="line"><span class="cl"><span class="s">  description: This is documentation that was generated from [protoc-gen-connect-openapi](https://github.com/sudorandom/protoc-gen-connect-openapi).
</span></span></span><span class="line"><span class="cl"><span class="s">`</span><span class="p">)))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatalf</span><span class="p">(</span><span class="s">&#34;err: %s&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">generationTime</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nf">Now</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">mux</span><span class="p">.</span><span class="nf">Handle</span><span class="p">(</span><span class="s">&#34;GET /openapi.html&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nf">HandlerFunc</span><span class="p">(</span><span class="kd">func</span><span class="p">(</span><span class="nx">w</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</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="w">		</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">tmplElements</span><span class="p">.</span><span class="nf">Execute</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span><span class="w"> </span><span class="kd">struct</span><span class="p">{</span><span class="w"> </span><span class="nx">DocumentBase64</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="p">}{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">DocumentBase64</span><span class="p">:</span><span class="w"> </span><span class="nx">base64</span><span class="p">.</span><span class="nx">StdEncoding</span><span class="p">.</span><span class="nf">EncodeToString</span><span class="p">(</span><span class="nx">openapiBody</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">});</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">slog</span><span class="p">.</span><span class="nf">Error</span><span class="p">(</span><span class="s">&#34;rendering_template&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;error&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">mux</span><span class="p">.</span><span class="nf">Handle</span><span class="p">(</span><span class="s">&#34;GET /openapi.yaml&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nf">HandlerFunc</span><span class="p">(</span><span class="kd">func</span><span class="p">(</span><span class="nx">w</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</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="w">		</span><span class="nx">http</span><span class="p">.</span><span class="nf">ServeContent</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;openapi.yaml&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">generationTime</span><span class="p">,</span><span class="w"> </span><span class="nx">bytes</span><span class="p">.</span><span class="nf">NewReader</span><span class="p">(</span><span class="nx">openapiBody</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">addr</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="s">&#34;127.0.0.1:6660&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">log</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">&#34;Starting connectrpc on http://%s&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">addr</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">log</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">&#34;OpenAPI Doc Page http://%s/openapi.html&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">addr</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">log</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">&#34;OpenAPI Spec http://%s/openapi.yaml&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">addr</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">srv</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">Server</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">Addr</span><span class="p">:</span><span class="w">    </span><span class="nx">addr</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">Handler</span><span class="p">:</span><span class="w"> </span><span class="nx">mux</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">srv</span><span class="p">.</span><span class="nf">ListenAndServe</span><span class="p">();</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatalf</span><span class="p">(</span><span class="s">&#34;error: %s&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>And here&rsquo;s what it looks like whenever you hit <code>http://127.0.0.1.:6660/openapi.html</code> in a web browser:</p>

    
    
        
        
            
        
        <img src="https://kmcd.dev/posts/self-documenting-connect-services/openapi-sshot_hu_8a1f37f0a5e89e2e.webp"
             alt="" class="center" width="800px"/>
    


<p>To see the demo for yourself, <a href="https://kmcd.dev/posts/self-documenting-connect-services/openapi.html">click here!</a></p>
<p>In this example, we&rsquo;re using the <a href="https://www.npmjs.com/package/@stoplight/elements" rel="external">@stoplight/elements</a> library to render the OpenAPI documentation. The <code>tmplElements</code> template embeds the generated OpenAPI YAML (Base64 encoded) into an HTML page, providing a user-friendly interface to explore your API&rsquo;s endpoints, request/response structures, and more. Note that we&rsquo;re using base64 so that none of the YAML characters accidentally escape the javascript string and mess everything up for us. You can also have the script load the OpenAPI spec from a URL, which is what <a href="https://github.com/stoplightio/elements?tab=readme-ov-file#web-component" rel="external">many of the examples show</a>.</p>
<p>You may also notice some additional options in this example, like <code>converter.WithContentTypes()</code>, <code>converter.WithStreaming(true)</code> and <code>converter.WithAllowGET(true)</code>. These give you more control over content types, whether you want OpenAPI for streaming calls (which may be complicated to support for OpenAPI) and whether you want to generate documentation for GET requests, that Connect supports if you can set the <code>idempotency_level</code> option to <code>NO_SIDE_EFFECTS</code>. For more information on GET requests and a comprehensive list of available options, refer to the <a href="https://connectrpc.com/docs/go/get-requests-and-caching/" rel="external">the Connect documentation</a> and the <a href="https://pkg.go.dev/github.com/sudorandom/protoc-gen-connect-openapi/converter" rel="external">protoc-gen-connect-openapi Go documentation</a>, respectively.</p>
<h2 id="benefits-of-self-documenting-services">Benefits of Self-Documenting Services</h2>
<p>Clear and interactive documentation makes it easier for developers to understand and integrate with your APIs. This reduces the friction between teams and gives you something to point to in case there are questions about the API. Not everyone is fluent in reading protobuf, but HTTP documentation is much friendlier.</p>
<p>By generating documentation from protobufs, it is much easier for documentation to stay in sync with your codebase. Note that adding comments to your protobuf types, fields, services and methods also get carried over to this OpenAPI specification (and the generated protobuf/gRPC/gRPC-Web/Connect source code) so protobuf files can act as a single place to document everything about that service.</p>
<h2 id="conclusion">Conclusion</h2>
<p>By combining the power of protoc-gen-connect-openapi with OpenAPI visualization tools, you can effortlessly generate self-documenting Connect services. This approach streamlines development, fosters collaboration, and empowers developers to consume your APIs effectively.</p>
]]></content:encoded></item><item><title>Introducing protoc-gen-connect-openapi</title><link>https://kmcd.dev/posts/protoc-gen-connect-openapi/</link><pubDate>Tue, 20 Feb 2024 00:00:00 +0000</pubDate><guid>https://kmcd.dev/posts/protoc-gen-connect-openapi/</guid><description> 
                &lt;p> &lt;img hspace="5" src="https://kmcd.dev/posts/protoc-gen-connect-openapi/cover_hu_666c893d7cc82563.webp" /> &lt;/p>
                
                
                </description><content:encoded><![CDATA[<p><a href="https://connectrpc.com" rel="external">ConnectRPC</a> is a fantastic set of libraries that bridge gRPC into the web. gRPC is no longer relegated to the microservice box. Now it can spread its legs into the browser with gRPC-Web or the Connect protocol. The connect protocol, unlike <a href="https://github.com/grpc/grpc-web" rel="external">gRPC-Web</a>, allows for many standard web tools to work for non-streaming APIs. For unary RPCs Connect exposes an API that is simply JSON over HTTP like you&rsquo;ve seen a million times before so tools like curl, postman, the Javascript Fetch API, etc. all work nicely with Connect. ConnectRPC also provides all three protocols (gRPC, gRPC-Web and Connect) <a href="https://connectrpc.com/docs/multi-protocol/" rel="external">using a single port on a single server</a>. That means that you no longer need proxies to enable gRPC-Web and all standard gRPC tools are also at your disposal as well.</p>
<p>Here&rsquo;s what an HTTP request looks like for a unary RPC with connect:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-http" data-lang="http"><span class="line"><span class="cl"><span class="err">&gt; </span><span class="nf">POST</span> <span class="nn">/connectrpc.greet.v1.GreetService/Greet</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
</span></span><span class="line"><span class="cl"><span class="err">&gt;</span> <span class="l">Host: demo.connectrpc.com</span>
</span></span><span class="line"><span class="cl"><span class="err">&gt;</span> <span class="l">Content-Type: application/json</span>
</span></span><span class="line"><span class="cl"><span class="err">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="g">&gt; {&#34;name&#34;: &#34;Buf&#34;}
</span></span></span><span class="line"><span class="cl"><span class="g">
</span></span></span><span class="line"><span class="cl"><span class="g">&lt; HTTP/1.1 200 OK
</span></span></span><span class="line"><span class="cl"><span class="g">&lt; Content-Type: application/json
</span></span></span><span class="line"><span class="cl"><span class="g">&lt;
</span></span></span><span class="line"><span class="cl"><span class="g">&lt; {&#34;greeting&#34;: &#34;Hello, Buf!&#34;}
</span></span></span></code></pre></div><p>If you mark the endpoint as <code>idempotency_level=NO_SIDE_EFFECTS</code> then you can also call <code>GET</code> on the endpoint so the request body has to go into the query parameters. Here&rsquo;s what that looks like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-http" data-lang="http"><span class="line"><span class="cl"><span class="err">&gt; </span><span class="nf">GET</span> <span class="nn">/connectrpc.greet.v1.GreetService/Greet?encoding=json&amp;message=%7B%22name%22%3A%22Buf%22%7D</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span>
</span></span><span class="line"><span class="cl"><span class="err">&gt;</span> <span class="l">Host: demo.connectrpc.com</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="g">&lt; HTTP/1.1 200 OK
</span></span></span><span class="line"><span class="cl"><span class="g">&lt; Content-Type: application/json
</span></span></span><span class="line"><span class="cl"><span class="g">&lt;
</span></span></span><span class="line"><span class="cl"><span class="g">&lt; {&#34;greeting&#34;: &#34;Hello, Buf!&#34;}
</span></span></span></code></pre></div><p>It&rsquo;s that&hellip; simple? Elegant? No? If you&rsquo;re not sold on ConnectRPC now then this may not be the post for you because the rest will talk about my new tool created for ConnectRPC; <strong><a href="https://github.com/sudorandom/protoc-gen-connect-openapi" rel="external">protoc-gen-connect-openapi</a></strong>.</p>
<h2 id="introducing-protoc-gen-connect-openapi">Introducing protoc-gen-connect-openapi</h2>
<p><a href="https://github.com/sudorandom/protoc-gen-connect-openapi" rel="external">protoc-gen-connect-openapi</a> generates OpenAPI v3.1 files from protobuf files that match the API that the <a href="https://connectrpc.com/docs/protocol" rel="external">Connect protocol</a> exposes.</p>
<p>We can document the Connect API as if it&rsquo;s a real JSON/HTTP API&hellip; because it is, and the gRPC &ldquo;flavor&rdquo; isn&rsquo;t so noticable due to Connect. With <a href="https://github.com/sudorandom/protoc-gen-connect-openapi" rel="external">protoc-gen-connect-openapi</a> you can declare your API using protobuf, serve it using gRPC/gRPC-WEb/Connect and fully document it without the API consumers ever knowing what protobuf is or how to read it. To me, this is the best of all worlds.</p>
<p>So, specifically, what good are OpenAPI files generated for Connect? What does that do? Well, first of all, it allows you to generate documentation with tools like <a href="https://github.com/Redocly/redocly-cli" rel="external">redocly</a> or <a href="https://github.com/stoplightio/elements" rel="external">elements</a>.</p>

<img src="https://kmcd.dev/posts/protoc-gen-connect-openapi/screenshot_hu_9cb348a5b1695378.webp"
        alt="Screenshot of protoc-gen-connect-openapi with redocly"/>
<p>Additionally, with tools like <a href="https://openapi-generator.tech" rel="external">OpenAPI-Generator</a> you can generate API clients for languages that Connect doesn&rsquo;t support yet (for non-streaming methods).</p>
<div class="container">
  <pre class="mermaid">flowchart LR

protobuf(Protobuf) -->|protoc-gen-connect-openapi| openapi(OpenAPI)
openapi -->|elements| docs(Gorgeous\nAPI Documentation)
openapi -->|redocly| docs
openapi -->|openapi-generator| other-languages(Languages that\nConnect doesn't\n support yet)
openapi -->|other tool| ???(Something equally amazing)
click elements "https://github.com/stoplightio/elements" _blank
click openapi-generator "https://github.com/OpenAPITools/openapi-generator" _blank
  </pre>
</div>
<p>In summary, I hope this tool will extend the contract-based usage of protobuf along with ConnectRPC&rsquo;s RPC-based API into further places with more documentation, more code generation, and additional validation.</p>
<p>For more information, check out the <a href="https://github.com/sudorandom/protoc-gen-connect-openapi" rel="external">protoc-gen-connect-openapi repo on Github</a>.</p>
]]></content:encoded></item></channel></rss>