<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Aditya Chauhan</title>
    <description>The latest articles on DEV Community by Aditya Chauhan (@aditya_chauhan_).</description>
    <link>https://dev.to/aditya_chauhan_</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1639220%2F6695ca24-dd05-4f96-b0c9-5f01540a12b4.jpg</url>
      <title>DEV Community: Aditya Chauhan</title>
      <link>https://dev.to/aditya_chauhan_</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aditya_chauhan_"/>
    <language>en</language>
    <item>
      <title>Protobuf vs JSON</title>
      <dc:creator>Aditya Chauhan</dc:creator>
      <pubDate>Sat, 27 Jun 2026 12:08:36 +0000</pubDate>
      <link>https://dev.to/aditya_chauhan_/protobuf-vs-json-2cak</link>
      <guid>https://dev.to/aditya_chauhan_/protobuf-vs-json-2cak</guid>
      <description>&lt;p&gt;Modern applications thrive on fast and efficient communication. While JSON has long been the go-to format for data exchange, many large-scale tech companies are now rethinking their choice. Companies like Atlassian, Netflix, and Google have shifted to Protocol Buffers (Protobuf) to optimize performance and reduce payload sizes across services.&lt;/p&gt;

&lt;p&gt;Their motivation? Faster API responses, smaller data transfers, better compatibility for growing systems, and built-in schema enforcement.&lt;/p&gt;

&lt;p&gt;In this guide, we'll break down what JSON and Protobuf are, compare their strengths and weaknesses, and walk you through practical examples in Ruby. Whether you're building microservices or optimizing existing APIs, this guide will help you decide.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. What is JSON?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;JSON (JavaScript Object Notation)&lt;/strong&gt; is a lightweight, human-readable data format commonly used in web APIs and configs. It's based on JavaScript syntax and supported by virtually all programming languages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Alice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"age"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"isActive"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  2. Why is JSON Preferred?
&lt;/h2&gt;

&lt;p&gt;JSON is often the default choice because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Readable&lt;/strong&gt;: Easily understood and edited by humans&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple&lt;/strong&gt;: No extra libraries needed in JavaScript/Node.js&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interoperable&lt;/strong&gt;: Works across all platforms and languages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debug-friendly&lt;/strong&gt;: Can be logged and inspected directly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Universal&lt;/strong&gt;: Every language has built-in or one-line library support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But JSON has limitations in &lt;strong&gt;performance&lt;/strong&gt;, &lt;strong&gt;file size&lt;/strong&gt;, and &lt;strong&gt;type safety&lt;/strong&gt;, especially in large systems.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. What are Protocol Buffers?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Protocol Buffers (Protobuf)&lt;/strong&gt; is a binary serialization format developed by Google. It encodes data into compact, fast, structured bytes using &lt;code&gt;.proto&lt;/code&gt; schema definitions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compact binary format (3-10x smaller than JSON)&lt;/li&gt;
&lt;li&gt;Faster parsing and serialization&lt;/li&gt;
&lt;li&gt;Schema enforcement via &lt;code&gt;.proto&lt;/code&gt; files&lt;/li&gt;
&lt;li&gt;Built-in backward and forward compatibility&lt;/li&gt;
&lt;li&gt;Code generation for multiple languages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt; &lt;code&gt;.proto&lt;/code&gt; &lt;strong&gt;file:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight protobuf"&gt;&lt;code&gt;&lt;span class="na"&gt;syntax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"proto3"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;int32&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="na"&gt;is_active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Protobuf vs JSON: Key Differences
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;JSON&lt;/th&gt;
&lt;th&gt;Protobuf&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Format&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Text (UTF-8)&lt;/td&gt;
&lt;td&gt;Binary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Human Readable&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No (bytes)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Size&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Larger&lt;/td&gt;
&lt;td&gt;Smaller (3-10x)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Speed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Slower parsing&lt;/td&gt;
&lt;td&gt;Faster parsing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Schema&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Optional/loose&lt;/td&gt;
&lt;td&gt;Required (&lt;code&gt;.proto&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Type Safety&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Weak (runtime errors)&lt;/td&gt;
&lt;td&gt;Strong (compile-time checks)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Versioning&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manual (URL versioning)&lt;/td&gt;
&lt;td&gt;Built-in (field numbers)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Browser-friendly&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Native&lt;/td&gt;
&lt;td&gt;Needs library&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Concrete example:&lt;/strong&gt; A list of 1000 products&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JSON: ~150KB payload, ~300ms download on 3G&lt;/li&gt;
&lt;li&gt;Protobuf: ~45KB payload, ~90ms download on 3G&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  5. How to Use Protobuf in Ruby
&lt;/h2&gt;

&lt;p&gt;In Ruby, Protobuf is typically used with pre-generated classes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Define the &lt;code&gt;.proto&lt;/code&gt; File
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight protobuf"&gt;&lt;code&gt;&lt;span class="na"&gt;syntax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"proto3"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;Customer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;int32&lt;/span&gt; &lt;span class="na"&gt;seats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;CreateCustomerRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;CreateCustomerResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;Customer&lt;/span&gt; &lt;span class="na"&gt;customer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Generate Ruby Code
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install protoc compiler (platform-specific)&lt;/span&gt;
&lt;span class="c"&gt;# macOS: brew install protobuf&lt;/span&gt;
&lt;span class="c"&gt;# Linux: apt-get install protobuf-compiler&lt;/span&gt;

&lt;span class="c"&gt;# Generate Ruby classes&lt;/span&gt;
protoc &lt;span class="nt"&gt;--ruby_out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;app/contracts customer.proto
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generates &lt;code&gt;customer_pb.rb&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Generated by the protocol buffer compiler. DO NOT EDIT!&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'google/protobuf'&lt;/span&gt;

&lt;span class="n"&gt;descriptor_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"..."&lt;/span&gt; &lt;span class="c1"&gt;# Binary schema data&lt;/span&gt;

&lt;span class="no"&gt;Google&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Protobuf&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DescriptorPool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generated_pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_serialized_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;descriptor_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;MyApp&lt;/span&gt;
  &lt;span class="no"&gt;Customer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Google&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Protobuf&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DescriptorPool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generated_pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Customer"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;msgclass&lt;/span&gt;
  &lt;span class="no"&gt;CreateCustomerRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Google&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Protobuf&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DescriptorPool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generated_pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"CreateCustomerRequest"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;msgclass&lt;/span&gt;
  &lt;span class="no"&gt;CreateCustomerResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Google&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Protobuf&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DescriptorPool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generated_pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"CreateCustomerResponse"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;msgclass&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Use in Your Application
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Creating a message&lt;/span&gt;
&lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;MyApp&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CreateCustomerRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"Acme Corp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="s2"&gt;"billing@acme.com"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Accessing fields&lt;/span&gt;
&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;   &lt;span class="c1"&gt;# =&amp;gt; "Acme Corp"&lt;/span&gt;
&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;  &lt;span class="c1"&gt;# =&amp;gt; "billing@acme.com"&lt;/span&gt;

&lt;span class="c1"&gt;# Serialize to JSON (for HTTP APIs)&lt;/span&gt;
&lt;span class="n"&gt;json_payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; '{"name":"Acme Corp","email":"billing@acme.com"}'&lt;/span&gt;

&lt;span class="c1"&gt;# Parse incoming JSON&lt;/span&gt;
&lt;span class="n"&gt;raw_json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'{"name":"Acme Corp","email":"billing@acme.com"}'&lt;/span&gt;
&lt;span class="n"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;MyApp&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CreateCustomerRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;  &lt;span class="c1"&gt;# =&amp;gt; "Acme Corp"&lt;/span&gt;

&lt;span class="c1"&gt;# Serialize to binary (for internal services)&lt;/span&gt;
&lt;span class="n"&gt;binary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_proto&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; &amp;lt;Binary data&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;# Parse from binary&lt;/span&gt;
&lt;span class="n"&gt;decoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;MyApp&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CreateCustomerRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  6. Real-World API Patterns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pattern 1: Twirp (JSON over HTTP with Protobuf Schema)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/twitchtv/twirp" rel="noopener noreferrer"&gt;Twirp&lt;/a&gt; is a simple RPC framework from Twitch that uses Protobuf for API contracts but communicates over HTTP with JSON.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rules:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All endpoints use POST&lt;/li&gt;
&lt;li&gt;Content-Type: &lt;code&gt;application/json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;URL pattern: &lt;code&gt;/api/{service}/{method}&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Controller&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UsersController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
    &lt;span class="c1"&gt;# Decode JSON body to Protobuf object&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;UsersPb&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CreateUserRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raw_post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Business logic here...&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Encode response to JSON&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;UsersPb&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CreateUserResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="ss"&gt;user: &lt;/span&gt;&lt;span class="no"&gt;UsersPb&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Type safety at the API boundary&lt;/li&gt;
&lt;li&gt;Human-readable JSON for debugging&lt;/li&gt;
&lt;li&gt;Automatic schema documentation via &lt;code&gt;.proto&lt;/code&gt; files&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Pattern 2: gRPC (Binary over HTTP/2)
&lt;/h3&gt;

&lt;p&gt;For internal microservices, &lt;a href="https://grpc.io/" rel="noopener noreferrer"&gt;gRPC&lt;/a&gt; uses Protobuf's binary format over HTTP/2 for maximum performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Characteristics:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Binary payloads (smaller, faster)&lt;/li&gt;
&lt;li&gt;HTTP/2 (multiplexing, streaming)&lt;/li&gt;
&lt;li&gt;Strongly typed stubs in all languages&lt;/li&gt;
&lt;li&gt;Bi-directional streaming support&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  7. When to Use Each
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Use Protobuf when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Building microservices that communicate frequently&lt;/li&gt;
&lt;li&gt;Performance and bandwidth are critical (mobile, IoT)&lt;/li&gt;
&lt;li&gt;You need strict schemas and versioning&lt;/li&gt;
&lt;li&gt;Data payloads are large or frequent&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Use JSON when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Building browser-facing APIs&lt;/li&gt;
&lt;li&gt;You want human-readable data for debugging&lt;/li&gt;
&lt;li&gt;Quick prototyping and development&lt;/li&gt;
&lt;li&gt;Working with third-party/public APIs&lt;/li&gt;
&lt;li&gt;Team is unfamiliar with Protobuf toolchain&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Hybrid Approach (Best of Both)
&lt;/h3&gt;

&lt;p&gt;Many teams use &lt;strong&gt;both&lt;/strong&gt; strategically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Protobuf binary&lt;/strong&gt; for internal service-to-service communication (speed)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSON&lt;/strong&gt; for browser/client-facing APIs (readability)&lt;/li&gt;
&lt;li&gt;The same &lt;code&gt;.proto&lt;/code&gt; file generates both binary and JSON serializers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser ──HTTP/JSON──▶ API Gateway ──gRPC/Protobuf──▶ User Service
                                      │
                                      └─gRPC/Protobuf──▶ Payment Service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  8. Common Gotchas
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Field Numbers Matter
&lt;/h3&gt;

&lt;p&gt;In Protobuf, each field has a number (e.g., &lt;code&gt;string name = 1;&lt;/code&gt;). These numbers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Must never change for existing fields&lt;/li&gt;
&lt;li&gt;Can be reused only after a field is deleted for 1+ years&lt;/li&gt;
&lt;li&gt;Enable backward compatibility (old code ignores new fields)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Default Values
&lt;/h3&gt;

&lt;p&gt;Protobuf has implicit defaults:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;string&lt;/code&gt; → &lt;code&gt;""&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;int32&lt;/code&gt; → &lt;code&gt;0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;bool&lt;/code&gt; → &lt;code&gt;false&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can't distinguish between "field not set" and "field set to default" without using &lt;code&gt;optional&lt;/code&gt; keyword (proto3).&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Required vs Optional
&lt;/h3&gt;

&lt;p&gt;In proto3, all fields are optional by default. If you need explicit presence tracking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight protobuf"&gt;&lt;code&gt;&lt;span class="k"&gt;optional&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="n"&gt;Now&lt;/span&gt; &lt;span class="n"&gt;you&lt;/span&gt; &lt;span class="n"&gt;can&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt; &lt;span class="n"&gt;if&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="n"&gt;was&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  9. Conclusion
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;th&gt;Recommendation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Human readability&lt;/td&gt;
&lt;td&gt;JSON&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance (internal services)&lt;/td&gt;
&lt;td&gt;Protobuf&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser/frontend APIs&lt;/td&gt;
&lt;td&gt;JSON&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Microservices (service mesh)&lt;/td&gt;
&lt;td&gt;Protobuf + gRPC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RPC with schema enforcement&lt;/td&gt;
&lt;td&gt;Protobuf + Twirp&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debugging&lt;/td&gt;
&lt;td&gt;JSON&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Schema evolution&lt;/td&gt;
&lt;td&gt;Protobuf&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both formats have their place. JSON is perfect for human-facing interfaces and simple APIs, while Protobuf excels in high-performance, structured systems.&lt;/p&gt;




</description>
      <category>api</category>
      <category>microservices</category>
      <category>performance</category>
      <category>ruby</category>
    </item>
    <item>
      <title>Mental Model for Federated GraphQL</title>
      <dc:creator>Aditya Chauhan</dc:creator>
      <pubDate>Thu, 25 Jun 2026 06:43:42 +0000</pubDate>
      <link>https://dev.to/aditya_chauhan_/mental-model-for-federated-graphql-kf2</link>
      <guid>https://dev.to/aditya_chauhan_/mental-model-for-federated-graphql-kf2</guid>
      <description>&lt;p&gt;Most GraphQL material explains schemas, queries, mutations, resolvers, and pagination. That is necessary, but it is not enough once the graph is split across multiple backend services. At that point, the important question becomes architectural:&lt;/p&gt;

&lt;p&gt;How does one client request become work across multiple backend services without exposing that backend topology to the client?&lt;/p&gt;

&lt;p&gt;.&lt;br&gt;
.&lt;br&gt;
.&lt;/p&gt;

&lt;h2&gt;
  
  
  GraphQL federation
&lt;/h2&gt;

&lt;p&gt;Apollo describes federation as a way to combine multiple APIs into a single federated GraphQL API. The client sends one GraphQL request to the router, and the router coordinates the API calls needed to produce one response.&lt;/p&gt;

&lt;p&gt;Without federation, teams usually end up with one of two uncomfortable options. Either clients learn too much about backend service boundaries, or a central API layer becomes responsible for manually stitching together everything. The first option couples frontend code to backend topology. The second often turns into a slow-moving aggregation layer owned by nobody cleanly.&lt;/p&gt;

&lt;p&gt;Federation takes a different route. Backend teams can own separate parts of the graph as subgraphs, while clients continue to query a single API shape.&lt;/p&gt;

&lt;p&gt;The client does not need to know which service owns users, billing, schedules, permissions, or analytics. It should only know the graph contract.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8olzs5kg94o7s68gm7vk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8olzs5kg94o7s68gm7vk.png" alt="request_flow" width="799" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The supergraph
&lt;/h2&gt;

&lt;p&gt;Subgraphs are what backend teams own. The supergraph is what clients query. The gateway uses the supergraph to decide where each part of the operation should run.&lt;/p&gt;

&lt;p&gt;A subgraph is a GraphQL schema owned by one backend service or domain. It contributes types, fields, and relationships to the overall graph.&lt;/p&gt;

&lt;p&gt;The supergraph is the composed result of those subgraphs. It contains the information the gateway needs to understand field ownership and build a plan for executing an operation across one or more services.&lt;/p&gt;

&lt;p&gt;.&lt;br&gt;
.&lt;br&gt;
.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gateway
&lt;/h2&gt;

&lt;p&gt;It is tempting to think of a GraphQL gateway as a simple proxy in front of services. That model is too weak.&lt;/p&gt;

&lt;p&gt;A proxy forwards requests. A federated GraphQL gateway understands the operation, validates it against the composed graph, builds a query plan, sends sub-requests to the relevant subgraphs, and assembles the response back into the shape the client requested.&lt;/p&gt;

&lt;p&gt;Apollo Gateway's documentation explains that the gateway processes the supergraph SDL on startup. That SDL includes routing information for subgraphs, and the gateway uses it to create query plans that can execute across one or more subgraphs.&lt;/p&gt;

&lt;p&gt;When a client asks for fields owned by a single subgraph, the gateway can send one sub-request. When the operation crosses ownership boundaries, the gateway has to coordinate multiple subgraph calls. Some of those calls may be independent. Some may depend on data returned from an earlier step.&lt;/p&gt;

&lt;p&gt;The client still sees one GraphQL response.&lt;/p&gt;

&lt;p&gt;.&lt;br&gt;
.&lt;br&gt;
.&lt;/p&gt;

&lt;h2&gt;
  
  
  Schema publication
&lt;/h2&gt;

&lt;p&gt;In Apollo terminology, the gateway can be initialized with a supergraph SDL. A static supergraph means the gateway needs a restart to pick up schema changes. Apollo's gateway describe dynamic supergraph updates, where the gateway receives an initial supergraph and can later update it through a callback. Polling, webhooks, and file watchers are examples of update mechanisms.&lt;/p&gt;

&lt;p&gt;It creates two separate architectural paths:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The control plane publishes and validates schema changes.&lt;/li&gt;
&lt;li&gt;The data plane serves live GraphQL requests using the currently active supergraph.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The control plane is where subgraph schemas are registered, composed, checked, and promoted. The data plane is where client operations are planned and executed.&lt;/p&gt;

&lt;p&gt;A field can exist in a subgraph implementation but still fail through the gateway if the composed supergraph has not been updated. A schema can compose locally but not be active in the runtime gateway. A backend service can be healthy while the graph artifact consumed by the gateway is stale.&lt;/p&gt;

&lt;p&gt;.&lt;br&gt;
.&lt;br&gt;
.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.apollographql.com/docs/graphos/schema-design/federated-schemas/federation" rel="noopener noreferrer"&gt;Apollo Federation: federated schemas&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.apollographql.com/docs/federation/v1/gateway#updating-the-supergraph-schema" rel="noopener noreferrer"&gt;Apollo Gateway: updating the supergraph schema&lt;/a&gt;&lt;/p&gt;

</description>
      <category>graphql</category>
      <category>apollo</category>
      <category>architecture</category>
    </item>
    <item>
      <title>GraphQL Clicked When I Stopped Thinking About Endpoints</title>
      <dc:creator>Aditya Chauhan</dc:creator>
      <pubDate>Mon, 22 Jun 2026 13:03:58 +0000</pubDate>
      <link>https://dev.to/aditya_chauhan_/graphql-clicked-when-i-stopped-thinking-about-endpoints-163g</link>
      <guid>https://dev.to/aditya_chauhan_/graphql-clicked-when-i-stopped-thinking-about-endpoints-163g</guid>
      <description>&lt;p&gt;If you have built more than a couple of APIs, you have probably felt the friction of REST at scale. You ship an endpoint, the frontend team asks for one more field, you version the route, the mobile team needs a different shape of the same data, and six months later you are maintaining /v3/users/:id/full next to /v2/users/:id/summary and nobody remembers which one the Android app actually calls.&lt;/p&gt;

&lt;p&gt;GraphQL was built to kill that exact pain. It is a query language and runtime that lets clients ask for precisely the data they need — no more, no less — from a single endpoint, against a strongly typed schema that doubles as living documentation.&lt;/p&gt;

&lt;p&gt;GraphQL started making sense to me when I stopped treating it as "one endpoint instead of many" and started treating it as a backend-owned contract.&lt;/p&gt;

&lt;p&gt;The client still asks for data. The response still comes back as JSON. But the important design work lives behind that request: the schema, resolvers, authorization rules, nullability choices, pagination model, and the cost of each field the backend exposes.&lt;/p&gt;

&lt;p&gt;I recently spent time understanding GraphQL from this backend angle in a ruby production codebase. This is the mental model I wish I had before reading real GraphQL APIs.&lt;/p&gt;

&lt;p&gt;.&lt;br&gt;
.&lt;/p&gt;

&lt;h2&gt;
  
  
  GraphQL solves a data-shape problem
&lt;/h2&gt;

&lt;p&gt;Most product screens do not naturally think in endpoints. They think in workflows.&lt;/p&gt;

&lt;p&gt;A product screen may need viewer context, one primary resource, access state, display metadata, and the first page of related records. With fixed REST-style endpoints, teams often end up choosing between a few imperfect options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Return too much data from a broad endpoint.&lt;/li&gt;
&lt;li&gt;Make the frontend call several endpoints for one screen.&lt;/li&gt;
&lt;li&gt;Add screen-specific endpoints that become hard to reuse.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GraphQL approaches this differently. The backend publishes a typed schema, and the client selects a permitted shape from that schema.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fha3mqqaeop1xa4mlefb0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fha3mqqaeop1xa4mlefb0.png" alt="request_execution" width="800" height="347"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;.&lt;br&gt;
.&lt;br&gt;
.&lt;/p&gt;

&lt;h2&gt;
  
  
  The schema is the public API surface
&lt;/h2&gt;

&lt;p&gt;The schema answers questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which root fields exist?&lt;/li&gt;
&lt;li&gt;Which types can clients query?&lt;/li&gt;
&lt;li&gt;Which fields are available on each type?&lt;/li&gt;
&lt;li&gt;Which arguments does a field accept?&lt;/li&gt;
&lt;li&gt;Which fields require authorization?&lt;/li&gt;
&lt;li&gt;Which fields need custom resolver logic?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once a field is exposed, clients can start depending on it. That makes every schema field a product decision, not just an implementation detail.&lt;/p&gt;

&lt;p&gt;.&lt;br&gt;
.&lt;br&gt;
.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resolvers: Where the Work Happens
&lt;/h2&gt;

&lt;p&gt;Resolvers are the part of the backend that turn fields into real values.&lt;/p&gt;

&lt;p&gt;For a root field, a resolver may load a record using an external identifier, read request context, check access, and return an object that GraphQL can continue resolving.&lt;/p&gt;

&lt;p&gt;For nested fields, the work can vary a lot. Some fields are simple attributes. Some are calculated methods. Some call another resolver. Some need stricter authorization than the parent object. Some load data from a join table or another service.&lt;/p&gt;

&lt;p&gt;That is why GraphQL does not remove backend complexity. It organizes that complexity around fields.&lt;/p&gt;

&lt;p&gt;This also explains a common production risk: a small-looking nested field can become expensive. If a resolver runs once for every item in a list and performs its own database query each time, the API can hit an N+1 problem quickly.&lt;/p&gt;

&lt;p&gt;.&lt;br&gt;
.&lt;br&gt;
.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mutations are write boundaries
&lt;/h2&gt;

&lt;p&gt;Queries read data. Mutations change data.&lt;/p&gt;

&lt;p&gt;The backend design question is not just "which method updates the record?" A mutation should define the full write contract:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What input is required?&lt;/li&gt;
&lt;li&gt;Which object is being changed?&lt;/li&gt;
&lt;li&gt;Who can perform the change?&lt;/li&gt;
&lt;li&gt;What does success return?&lt;/li&gt;
&lt;li&gt;Which validation errors can the user fix?&lt;/li&gt;
&lt;li&gt;Which failures should be treated as system or authorization errors?&lt;/li&gt;
&lt;li&gt;Which side effects run after success?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;.&lt;br&gt;
.&lt;br&gt;
.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connections make pagination explicit
&lt;/h2&gt;

&lt;p&gt;Pagination was the part that initially felt the most noisy to me.&lt;/p&gt;

&lt;p&gt;Instead of returning a plain list, GraphQL APIs often return a connection. A connection is a wrapper around one page of results.&lt;/p&gt;

&lt;p&gt;The basic vocabulary is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connection: the paginated list wrapper.&lt;/li&gt;
&lt;li&gt;Edge: one result in that list, plus metadata about the relationship.&lt;/li&gt;
&lt;li&gt;Node: the actual object.&lt;/li&gt;
&lt;li&gt;Cursor: the bookmark for this result.&lt;/li&gt;
&lt;li&gt;PageInfo: metadata that tells the client whether another page exists and where to continue.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb2qj5xsu1b75f4j4y4ip.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb2qj5xsu1b75f4j4y4ip.png" alt="connection_model" width="799" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The part that made edges click for me was this:&lt;/p&gt;

&lt;p&gt;Node is the thing. Edge is the relationship to that thing.&lt;/p&gt;

&lt;p&gt;Imagine the same object appearing through two different relationships. The object itself is unchanged, but the metadata about each relationship may be different.&lt;/p&gt;

&lt;p&gt;That metadata belongs to the relationship. It should not necessarily live on the object itself.&lt;/p&gt;

&lt;p&gt;That is why edges exist. They give the API a place to put relationship-specific data, while nodes stay focused on the actual object.&lt;/p&gt;

&lt;p&gt;.&lt;br&gt;
.&lt;br&gt;
.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generated types make contract drift visible
&lt;/h2&gt;

&lt;p&gt;In many modern GraphQL frontends, operation files are not just raw strings. Tooling reads the backend schema and the client operations, then generates typed variables, typed response shapes, and query or mutation hooks.&lt;/p&gt;

&lt;p&gt;This creates useful feedback.&lt;/p&gt;

&lt;p&gt;If the backend removes a field that an operation uses, code generation can fail. If a backend field changes from always-present to maybe-null, the generated frontend type changes. If an enum value changes, the compiler may catch places that need to be updated.&lt;/p&gt;

&lt;p&gt;This is one of GraphQL's strongest collaboration benefits. The contract can be checked before the bug reaches a browser.&lt;/p&gt;

&lt;p&gt;But it only works if the schema, operations, and generated files stay in sync.&lt;/p&gt;

&lt;p&gt;For backend engineers, that means a schema change is not done just because the server boots. You also need to think about generated client impact:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which operations select this field?&lt;/li&gt;
&lt;li&gt;Are enum values stable?&lt;/li&gt;
&lt;li&gt;Is this a removal or should it be a deprecation?&lt;/li&gt;
&lt;li&gt;Will generated types change in a meaningful way?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;.&lt;br&gt;
.&lt;br&gt;
.&lt;/p&gt;

&lt;h2&gt;
  
  
  GraphQL is a tradeoff, not an automatic upgrade
&lt;/h2&gt;

&lt;p&gt;After this learning cycle, I do not think of GraphQL as better REST.&lt;/p&gt;

&lt;p&gt;It is a different API design tradeoff.&lt;/p&gt;

&lt;p&gt;GraphQL is a strong fit when product screens need precise nested data, different clients need different slices of the same domain object, and typed contracts improve frontend/backend collaboration.&lt;/p&gt;

&lt;p&gt;It may be the wrong tool when the workflow is a simple file upload, a command with no meaningful selected response shape, a heavy async process, or an API where caching and strict cost control matter more than selection flexibility.&lt;/p&gt;

&lt;p&gt;The main tradeoff is visibility.&lt;/p&gt;

&lt;p&gt;With a REST endpoint, request cost is often easier to reason about from the route. With GraphQL, request cost depends on the operation shape. One endpoint can represent many different execution paths.&lt;/p&gt;

</description>
      <category>graphql</category>
      <category>learning</category>
    </item>
  </channel>
</rss>
