<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>Trpc on kmcd.dev</title><link>https://kmcd.dev/tags/trpc/</link><description>Recent content in Trpc on kmcd.dev</description><generator>Hugo -- gohugo.io</generator><language>en</language><copyright>All Rights Reserved</copyright><lastBuildDate>Tue, 28 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://kmcd.dev/tags/trpc/index.xml" rel="self" type="application/rss+xml"/><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>
<h3 id="the-power-of-pre-defined-api-contracts">The Power of Pre-defined API Contracts</h3>
<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>
<h3 id="protobuf-the-language-of-apis">Protobuf: The Language of APIs</h3>
<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" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span>syntax <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;proto3&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">User</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">string</span> name <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">int32</span> id <span style="color:#81a1c1">=</span> <span style="color:#b48ead">2</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">string</span> email <span style="color:#81a1c1">=</span> <span style="color:#b48ead">3</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  Address address <span style="color:#81a1c1">=</span> <span style="color:#b48ead">4</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">Address</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">string</span> street <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">string</span> city <span style="color:#81a1c1">=</span> <span style="color:#b48ead">2</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">string</span> state <span style="color:#81a1c1">=</span> <span style="color:#b48ead">3</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">string</span> zip <span style="color:#81a1c1">=</span> <span style="color:#b48ead">4</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</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>
<h3 id="grpc-building-apis-on-a-solid-foundation">gRPC: Building APIs on a Solid Foundation</h3>
<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>
<h4 id="introducing-services-and-requestresponse-types-with-grpc">Introducing Services and Request/Response Types with gRPC</h4>
<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" style="color:#d8dee9;background-color:#2e3440;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span>syntax <span style="color:#81a1c1">=</span> <span style="color:#a3be8c">&#34;proto3&#34;</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">service</span> UserService <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1;font-weight:bold">rpc</span> CreateUser<span style="color:#eceff4">(</span>CreateUserRequest<span style="color:#eceff4">)</span> <span style="color:#81a1c1;font-weight:bold">returns</span> <span style="color:#eceff4">(</span>User<span style="color:#eceff4">)</span> <span style="color:#eceff4">{}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1;font-weight:bold">rpc</span> GetUser<span style="color:#eceff4">(</span>GetUserRequest<span style="color:#eceff4">)</span> <span style="color:#81a1c1;font-weight:bold">returns</span> <span style="color:#eceff4">(</span>User<span style="color:#eceff4">)</span> <span style="color:#eceff4">{}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">CreateUserRequest</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  User user <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#81a1c1;font-weight:bold">message</span> <span style="color:#8fbcbb">GetUserRequest</span> <span style="color:#eceff4">{</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span>  <span style="color:#81a1c1">int32</span> id <span style="color:#81a1c1">=</span> <span style="color:#b48ead">1</span><span style="color:#eceff4">;</span><span style="color:#bf616a">
</span></span></span><span style="display:flex;"><span><span style="color:#bf616a"></span><span style="color:#eceff4">}</span><span style="color:#bf616a">
</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>
<h4 id="server-reflection">Server Reflection</h4>
<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>
<h3 id="distributing-api-contracts">Distributing API Contracts</h3>
<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>
<h3 id="what-about-public-apis">What about public APIs?</h3>
<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></channel></rss>