<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title>Graphql - Tag - Tracy Atteberry</title><link>https://tracyatteberry.com/tags/graphql/</link><description>Graphql - Tag - Tracy Atteberry</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><managingEditor>tracy@magicbydesign.com (Tracy Atteberry)</managingEditor><webMaster>tracy@magicbydesign.com (Tracy Atteberry)</webMaster><lastBuildDate>Tue, 07 Jan 2025 00:00:00 +0000</lastBuildDate><image><url>https://tracyatteberry.com/images/feed-icon.jpg</url><title>Graphql - Tag - Tracy Atteberry</title><link>https://tracyatteberry.com/tags/graphql/</link></image><atom:link href="https://tracyatteberry.com/tags/graphql/" rel="self" type="application/rss+xml"/><item><title>You don't need GraphQL</title><link>https://tracyatteberry.com/posts/no_graphql/</link><pubDate>Tue, 07 Jan 2025 00:00:00 +0000</pubDate><author>Tracy Atteberry</author><guid>https://tracyatteberry.com/posts/no_graphql/</guid><description><![CDATA[<div class="featured-image">
                <img src="https://tracyatteberry.com/posts/no_graphql/graphql.png" referrerpolicy="no-referrer">
            </div><h2 id="introduction">Introduction</h2>
<p>If you&rsquo;ve built Rails applications in the last few years, you&rsquo;ve probably felt
the pressure to adopt GraphQL. The sales pitch is compelling: a more flexible
API, happier frontend developers, and an end to the dreaded over-fetching of
data. Companies like GitHub and Shopify have embraced it, and the ecosystem of
tools keeps growing. It&rsquo;s easy to feel like you&rsquo;re falling behind if you
haven&rsquo;t jumped on the GraphQL bandwagon.</p>
<p>But here&rsquo;s the thing: while GraphQL can be a powerful tool for certain
applications, it&rsquo;s become a default choice without enough critical examination
of its trade-offs. After spending several years building and maintaining Rails
applications both with and without GraphQL, I&rsquo;ve found that its carrying costs
often outweigh its benefits for typical Rails applications.</p>
<p>This isn&rsquo;t just about the initial learning curve or setup time. It&rsquo;s about the
ongoing maintenance burden, the hidden complexity costs, and the
often-overlooked strengths of Rails&rsquo; conventional approach to API design. In
many cases, the problems that GraphQL promises to solve can be addressed more
simply using Rails&rsquo; built-in tools and well-established patterns.</p>
<p>Let&rsquo;s explore why GraphQL might be more complexity than your Rails application
needs, and how to make this evaluation for your own projects.</p>
<h2 id="why-graphql-seems-attractive">Why GraphQL seems attractive</h2>
<p>Let&rsquo;s be honest: GraphQL&rsquo;s appeal is far from superficial. When you first see a
well-designed GraphQL API in action, it feels like magic. Imagine you&rsquo;re
building a dashboard that displays user profiles with their recent orders and
reviews. With a traditional REST API, you might need three or four separate
endpoints, carefully orchestrated on the frontend. With GraphQL, you simply
write a query that exactly matches your data needs:</p>
<div class="code-block code-line-numbers" style="counter-reset: code-block 0">
    <div class="code-header language-graphql">
        <span class="code-title"><i class="arrow fas fa-chevron-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-graphql" data-lang="graphql"><span class="line"><span class="cl"><span class="kd">query</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="py">user</span><span class="p">(</span><span class="py">id</span><span class="p">:</span><span class="w"> </span><span class="nc">123</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="py">name</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="py">recentOrders</span><span class="p">(</span><span class="py">last</span><span class="p">:</span><span class="w"> </span><span class="nc">5</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="py">amount</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="py">status</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="py">reviews</span><span class="p">(</span><span class="py">last</span><span class="p">:</span><span class="w"> </span><span class="nc">3</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="py">rating</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="py">content</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="p">}</span></span></span></code></pre></div></div>
<p>This kind of flexibility makes frontend developers extraordinarily happy. They
get precisely the data they need, no more and no less. No more wrangling
multiple API endpoints or dealing with over-fetched data. No more adding
backend endpoints every time the frontend needs a slightly different data
shape.</p>
<p>The benefits seem to stack up quickly:</p>
<p>First, there&rsquo;s the elegant solution to the chronic problem of over-fetching.
Instead of receiving every field from your <code>User</code> model when you only need the
name, you get exactly what you asked for. In a world where mobile data usage
matters, this can seem like a compelling advantage.</p>
<p>Then there&rsquo;s the development workflow. Frontend teams can work more
independently, experimenting with different data requirements without needing
constant backend changes. They can iterate faster, and the self-documenting
nature of GraphQL schemas means they always know exactly what data is
available.</p>
<p>The single endpoint approach also feels cleaner than maintaining dozens of REST
endpoints. Rather than debating whether something should be its own endpoint or
included in an existing one, you can just add it to your schema and let clients
decide when to request it.</p>
<p>For teams juggling multiple frontend applications – perhaps a web app, a mobile
app, and a partner API – GraphQL&rsquo;s flexibility seems particularly appealing.
Each client can request its own specific data shape without requiring custom
endpoints.</p>
<p>These benefits are real, and they&rsquo;ve driven GraphQL&rsquo;s adoption at companies
dealing with complex data requirements and multiple client applications. But as
we&rsquo;ll explore in the next section, implementing and maintaining these
capabilities comes with significant costs that aren&rsquo;t immediately obvious from
the demos and documentation.</p>
<h2 id="the-real-costs-of-graphql-implementation">The real costs of GraphQL implementation</h2>
<p>While GraphQL&rsquo;s benefits shine in demos, its costs become apparent once you
start implementing it in a real Rails application. Let&rsquo;s look at what actually
happens when you add GraphQL to your typical Rails app.</p>
<p>First, there&rsquo;s the initial setup. Even with helpful gems like <code>graphql-ruby</code>,
you&rsquo;re looking at significant work to get started. Every model that needs to be
exposed requires a new type definition:</p>
<div class="code-block code-line-numbers" style="counter-reset: code-block 0">
    <div class="code-header language-ruby">
        <span class="code-title"><i class="arrow fas fa-chevron-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">module</span> <span class="nn">Types</span>
</span></span><span class="line"><span class="cl">  <span class="k">class</span> <span class="nc">UserType</span> <span class="o">&lt;</span> <span class="no">Types</span><span class="o">::</span><span class="no">BaseObject</span>
</span></span><span class="line"><span class="cl">    <span class="n">field</span> <span class="ss">:id</span><span class="p">,</span> <span class="no">ID</span><span class="p">,</span> <span class="ss">null</span><span class="p">:</span> <span class="kp">false</span>
</span></span><span class="line"><span class="cl">    <span class="n">field</span> <span class="ss">:name</span><span class="p">,</span> <span class="nb">String</span><span class="p">,</span> <span class="ss">null</span><span class="p">:</span> <span class="kp">false</span>
</span></span><span class="line"><span class="cl">    <span class="n">field</span> <span class="ss">:email</span><span class="p">,</span> <span class="nb">String</span><span class="p">,</span> <span class="ss">null</span><span class="p">:</span> <span class="kp">false</span>
</span></span><span class="line"><span class="cl">    <span class="n">field</span> <span class="ss">:orders</span><span class="p">,</span> <span class="o">[</span><span class="no">Types</span><span class="o">::</span><span class="no">OrderType</span><span class="o">]</span><span class="p">,</span> <span class="ss">null</span><span class="p">:</span> <span class="kp">false</span>
</span></span><span class="line"><span class="cl">    <span class="n">field</span> <span class="ss">:reviews</span><span class="p">,</span> <span class="o">[</span><span class="no">Types</span><span class="o">::</span><span class="no">ReviewType</span><span class="o">]</span><span class="p">,</span> <span class="ss">null</span><span class="p">:</span> <span class="kp">false</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">orders</span>
</span></span><span class="line"><span class="cl">      <span class="no">BatchLoader</span><span class="o">::</span><span class="no">GraphQL</span><span class="o">.</span><span class="n">for</span><span class="p">(</span><span class="n">object</span><span class="o">.</span><span class="n">id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="o">.</span><span class="n">batch</span> <span class="k">do</span> <span class="o">|</span><span class="n">user_ids</span><span class="p">,</span> <span class="n">loader</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="no">Order</span><span class="o">.</span><span class="n">where</span><span class="p">(</span><span class="ss">user_id</span><span class="p">:</span> <span class="n">user_ids</span><span class="p">)</span><span class="o">.</span><span class="n">group_by</span><span class="p">(</span><span class="o">&amp;</span><span class="ss">:user_id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="o">.</span><span class="n">each</span> <span class="p">{</span> <span class="o">|</span><span class="n">user_id</span><span class="p">,</span> <span class="n">orders</span><span class="o">|</span> <span class="n">loader</span><span class="o">.</span><span class="n">call</span><span class="p">(</span><span class="n">user_id</span><span class="p">,</span> <span class="n">orders</span><span class="p">)</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="k">end</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div>
<p>See that <code>orders</code> method? That&rsquo;s handling N+1 queries, one of the first
problems you&rsquo;ll encounter. While Active Record makes it easy to
<code>includes(:orders)</code> in a regular Rails controller, with GraphQL you&rsquo;ll need to
implement batch loading for every relationship. Tools like <code>batch-loader</code> help,
but they add another layer of complexity to your codebase.</p>
<p>Authorization becomes more complex too. Instead of handling permissions in your
controllers, you now need to think about field-level authorization:</p>
<div class="code-block code-line-numbers" style="counter-reset: code-block 0">
    <div class="code-header language-ruby">
        <span class="code-title"><i class="arrow fas fa-chevron-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">module</span> <span class="nn">Types</span>
</span></span><span class="line"><span class="cl">  <span class="k">class</span> <span class="nc">UserType</span> <span class="o">&lt;</span> <span class="no">Types</span><span class="o">::</span><span class="no">BaseObject</span>
</span></span><span class="line"><span class="cl">    <span class="n">field</span> <span class="ss">:email</span><span class="p">,</span> <span class="nb">String</span><span class="p">,</span> <span class="ss">null</span><span class="p">:</span> <span class="kp">false</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="n">authorize!</span> <span class="ss">:admin</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">authorize!</span><span class="p">(</span><span class="n">role</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="k">raise</span> <span class="no">GraphQL</span><span class="o">::</span><span class="no">UnauthorizedError</span> <span class="k">unless</span> <span class="n">context</span><span class="o">[</span><span class="ss">:current_user</span><span class="o">]&amp;.</span><span class="n">has_role?</span><span class="p">(</span><span class="n">role</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div>
<p>But the real maintenance burden comes from keeping your schema in sync with
your models. Add a field to your <code>User</code> model? You&rsquo;ll need to update the
<code>UserType</code>. Add a new relationship? That&rsquo;s a new type definition and possibly new
batch loading code. What used to be handled automatically by Active Record now
requires explicit schema updates.</p>
<p>Testing becomes more involved as well. Instead of testing REST endpoints that
map cleanly to controller actions, you&rsquo;re now testing queries and mutations
that can touch multiple parts of your schema. A single query might need to
verify authorization, resolve relationships, and handle errors across several
types:</p>
<div class="code-block code-line-numbers" style="counter-reset: code-block 0">
    <div class="code-header language-ruby">
        <span class="code-title"><i class="arrow fas fa-chevron-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">RSpec</span><span class="o">.</span><span class="n">describe</span> <span class="s2">&#34;Users Query&#34;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">let</span><span class="p">(</span><span class="ss">:query</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="o">&lt;&lt;~</span><span class="no">GRAPHQL</span>
</span></span><span class="line"><span class="cl">      <span class="n">query</span><span class="p">(</span><span class="vg">$id</span><span class="p">:</span> <span class="no">ID</span><span class="o">!</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">user</span><span class="p">(</span><span class="nb">id</span><span class="p">:</span> <span class="vg">$id</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nb">name</span>
</span></span><span class="line"><span class="cl">          <span class="n">email</span>
</span></span><span class="line"><span class="cl">          <span class="n">orders</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">amount</span>
</span></span><span class="line"><span class="cl">          <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="no">GRAPHQL</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">it</span> <span class="s2">&#34;returns user data with orders&#34;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">user</span> <span class="o">=</span> <span class="n">create</span><span class="p">(</span><span class="ss">:user</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">create_list</span><span class="p">(</span><span class="ss">:order</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="ss">user</span><span class="p">:</span> <span class="n">user</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="no">MyAppSchema</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">query</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="ss">variables</span><span class="p">:</span> <span class="p">{</span> <span class="nb">id</span><span class="p">:</span> <span class="n">user</span><span class="o">.</span><span class="n">id</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="ss">context</span><span class="p">:</span> <span class="p">{</span> <span class="ss">current_user</span><span class="p">:</span> <span class="n">create</span><span class="p">(</span><span class="ss">:admin_user</span><span class="p">)</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Now check the deeply nested response structure</span>
</span></span><span class="line"><span class="cl">    <span class="n">expect</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">dig</span><span class="p">(</span><span class="s2">&#34;data&#34;</span><span class="p">,</span> <span class="s2">&#34;user&#34;</span><span class="p">,</span> <span class="s2">&#34;orders&#34;</span><span class="p">))</span><span class="o">.</span><span class="n">to</span> <span class="n">be_present</span>
</span></span><span class="line"><span class="cl">    <span class="n">expect</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">dig</span><span class="p">(</span><span class="s2">&#34;data&#34;</span><span class="p">,</span> <span class="s2">&#34;user&#34;</span><span class="p">,</span> <span class="s2">&#34;orders&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">length</span><span class="p">)</span><span class="o">.</span><span class="n">to</span> <span class="n">eq</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div>
<p>Performance monitoring becomes trickier too. Instead of clear controller
actions that map to specific business operations, you&rsquo;re dealing with arbitrary
queries that can access your data in countless ways. Tools like Scout or New
Relic need extra configuration to make sense of GraphQL operations, and
debugging production issues often requires more context to understand exactly
what data was being requested.</p>
<p>The schema documentation, while automatic, needs constant attention. As your
schema grows, keeping the documentation clear and useful becomes its own
maintenance task. Unlike Rails&rsquo; REST conventions, which are widely understood,
GraphQL schemas need to be carefully documented to be useful to client
developers.</p>
<p>These costs compound over time. Each new feature adds complexity to your
schema, each new relationship needs careful performance consideration, and each
new team member needs to understand not just Rails conventions, but your
specific GraphQL implementation choices.</p>
<h2 id="rails-built-in-solutions-are-often-sufficient">Rails&rsquo; built-in solutions are often sufficient</h2>
<p>One of Rails&rsquo; greatest strengths has always been its sensible defaults and
conventional solutions. While these might seem boring compared to GraphQL&rsquo;s
flexibility, they often solve the same problems with significantly less
complexity.</p>
<p>Let&rsquo;s tackle the common problems that drive teams to GraphQL, and look at how
Rails already solves them:</p>
<p><strong>&ldquo;But we need to avoid N+1 queries!&rdquo;</strong></p>
<p>Rails has had a solution for this since the beginning. Active Record&rsquo;s eager
loading is both powerful and easy to use:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-ruby">
        <span class="code-title"><i class="arrow fas fa-chevron-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">index</span>
</span></span><span class="line"><span class="cl">  <span class="n">users</span> <span class="o">=</span> <span class="no">User</span><span class="o">.</span><span class="n">includes</span><span class="p">(</span><span class="ss">:orders</span><span class="p">,</span> <span class="ss">:reviews</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">where</span><span class="p">(</span><span class="ss">active</span><span class="p">:</span> <span class="kp">true</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">limit</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl">  <span class="n">render</span> <span class="ss">json</span><span class="p">:</span> <span class="n">users</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div>
<p><strong>&ldquo;But we need flexible response shapes!&rdquo;</strong></p>
<p>Rails serializers have you covered. Using a gem like <code>active_model_serializers</code>
or even just plain Ruby objects, you can create multiple representations of
your data:</p>
<div class="code-block code-line-numbers" style="counter-reset: code-block 0">
    <div class="code-header language-ruby">
        <span class="code-title"><i class="arrow fas fa-chevron-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">UserSerializer</span> <span class="o">&lt;</span> <span class="no">ActiveModel</span><span class="o">::</span><span class="no">Serializer</span>
</span></span><span class="line"><span class="cl">  <span class="n">attributes</span> <span class="ss">:id</span><span class="p">,</span> <span class="ss">:name</span>
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl">  <span class="n">attribute</span> <span class="ss">:full_data</span><span class="p">,</span> <span class="k">if</span><span class="p">:</span> <span class="ss">:admin?</span>
</span></span><span class="line"><span class="cl">  <span class="n">has_many</span> <span class="ss">:recent_orders</span>
</span></span><span class="line"><span class="cl">  <span class="n">has_many</span> <span class="ss">:recent_reviews</span>
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">recent_orders</span>
</span></span><span class="line"><span class="cl">    <span class="n">object</span><span class="o">.</span><span class="n">orders</span><span class="o">.</span><span class="n">last</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">admin?</span>
</span></span><span class="line"><span class="cl">    <span class="n">current_user</span><span class="o">.</span><span class="n">admin?</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div>
<p><strong>&ldquo;But we need to combine multiple resources!&rdquo;</strong></p>
<p>Rails controllers can handle this elegantly:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-ruby">
        <span class="code-title"><i class="arrow fas fa-chevron-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">dashboard</span>
</span></span><span class="line"><span class="cl">  <span class="n">user</span> <span class="o">=</span> <span class="no">User</span><span class="o">.</span><span class="n">includes</span><span class="p">(</span><span class="ss">:orders</span><span class="p">,</span> <span class="ss">:reviews</span><span class="p">)</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="n">params</span><span class="o">[</span><span class="ss">:id</span><span class="o">]</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl">  <span class="n">render</span> <span class="ss">json</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="ss">user</span><span class="p">:</span> <span class="no">UserSerializer</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">user</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="ss">recent_activity</span><span class="p">:</span> <span class="no">ActivitySerializer</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">user</span><span class="o">.</span><span class="n">recent_activity</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="ss">account_summary</span><span class="p">:</span> <span class="no">AccountSerializer</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">user</span><span class="o">.</span><span class="n">account</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div>
<p><strong>&ldquo;But what about caching?&rdquo;</strong></p>
<p>Rails&rsquo; caching is battle-tested and works great with JSON endpoints:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-ruby">
        <span class="code-title"><i class="arrow fas fa-chevron-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">OrdersController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">index</span>
</span></span><span class="line"><span class="cl">    <span class="n">orders</span> <span class="o">=</span> <span class="no">Rails</span><span class="o">.</span><span class="n">cache</span><span class="o">.</span><span class="n">fetch</span><span class="p">(</span><span class="o">[</span><span class="s2">&#34;orders&#34;</span><span class="p">,</span> <span class="n">params</span><span class="o">[</span><span class="ss">:page</span><span class="o">]]</span><span class="p">,</span> <span class="ss">expires_in</span><span class="p">:</span> <span class="mi">1</span><span class="o">.</span><span class="n">hour</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="no">Order</span><span class="o">.</span><span class="n">page</span><span class="p">(</span><span class="n">params</span><span class="o">[</span><span class="ss">:page</span><span class="o">]</span><span class="p">)</span><span class="o">.</span><span class="n">to_json</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="n">render</span> <span class="ss">json</span><span class="p">:</span> <span class="n">orders</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div>
<p><strong>&ldquo;But we need versioning!&rdquo;</strong></p>
<p>Rails has conventional patterns for API versioning that are well understood:</p>
<div class="code-block code-line-numbers" style="counter-reset: code-block 0">
    <div class="code-header language-ruby">
        <span class="code-title"><i class="arrow fas fa-chevron-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">module</span> <span class="nn">Api</span>
</span></span><span class="line"><span class="cl">  <span class="k">module</span> <span class="nn">V1</span>
</span></span><span class="line"><span class="cl">    <span class="k">class</span> <span class="nc">UsersController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
</span></span><span class="line"><span class="cl">      <span class="c1"># V1 implementation</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl">  <span class="k">module</span> <span class="nn">V2</span>
</span></span><span class="line"><span class="cl">    <span class="k">class</span> <span class="nc">UsersController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
</span></span><span class="line"><span class="cl">      <span class="c1"># V2 implementation with new fields</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div>
<p><strong>&ldquo;But our frontend needs different data shapes for different screens!&rdquo;</strong></p>
<p>Instead of one massive GraphQL schema, consider scope-specific endpoints that
map to your UI needs:</p>
<div class="code-block code-line-numbers" style="counter-reset: code-block 0">
    <div class="code-header language-ruby">
        <span class="code-title"><i class="arrow fas fa-chevron-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">module</span> <span class="nn">Api</span>
</span></span><span class="line"><span class="cl">  <span class="k">class</span> <span class="nc">UserProfileController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">show</span>
</span></span><span class="line"><span class="cl">      <span class="n">user</span> <span class="o">=</span> <span class="no">User</span><span class="o">.</span><span class="n">includes</span><span class="p">(</span><span class="ss">:profile_data</span><span class="p">)</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="n">params</span><span class="o">[</span><span class="ss">:id</span><span class="o">]</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">render</span> <span class="ss">json</span><span class="p">:</span> <span class="no">UserProfileSerializer</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">user</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl">  <span class="k">class</span> <span class="nc">UserDashboardController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">show</span>
</span></span><span class="line"><span class="cl">      <span class="n">user</span> <span class="o">=</span> <span class="no">User</span><span class="o">.</span><span class="n">includes</span><span class="p">(</span><span class="ss">:dashboard_data</span><span class="p">)</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="n">params</span><span class="o">[</span><span class="ss">:id</span><span class="o">]</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">render</span> <span class="ss">json</span><span class="p">:</span> <span class="no">UserDashboardSerializer</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">user</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div>
<p>The beauty of these Rails solutions isn&rsquo;t just their simplicity – it&rsquo;s that
they compose well together and follow patterns that any Rails developer can
understand. They leverage Rails&rsquo; performance optimizations, work seamlessly
with the asset pipeline and caching, and integrate naturally with the rest of
your Rails tooling.</p>
<p>Most importantly, these solutions scale with your application&rsquo;s actual needs.
Need more complex data fetching? Add a query object. Need more sophisticated
caching? Rails&rsquo; caching framework can handle it. Need to optimize payload size?
Use <code>jbuilder</code> or custom serializers to send exactly what you need.</p>
<h2 id="hidden-operational-costs">Hidden operational costs</h2>
<p>While we&rsquo;ve discussed the technical implementation costs of GraphQL, there&rsquo;s a
whole category of operational costs that often go unconsidered until they hit
your team in production. Let&rsquo;s explore these hidden costs that can
significantly impact your team&rsquo;s velocity and operational efficiency.</p>
<h3 id="the-learning-curve-tax">The learning curve tax</h3>
<p>Every new Rails developer knows what <code>users_controller#show</code> does. But drop a
new developer into a GraphQL codebase, and they&rsquo;re facing questions like:</p>
<div class="code-block code-line-numbers" style="counter-reset: code-block 0">
    <div class="code-header language-ruby">
        <span class="code-title"><i class="arrow fas fa-chevron-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># What&#39;s the difference between these again?</span>
</span></span><span class="line"><span class="cl"><span class="n">field</span> <span class="ss">:total</span><span class="p">,</span> <span class="nb">Integer</span><span class="p">,</span> <span class="ss">null</span><span class="p">:</span> <span class="kp">true</span>
</span></span><span class="line"><span class="cl"><span class="n">field</span> <span class="ss">:total</span><span class="p">,</span> <span class="nb">Integer</span><span class="p">,</span> <span class="ss">null</span><span class="p">:</span> <span class="kp">false</span>
</span></span><span class="line"><span class="cl"><span class="n">field</span> <span class="ss">:total</span><span class="p">,</span> <span class="nb">Integer</span>  <span class="c1"># Wait, what&#39;s the default?</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Why isn&#39;t this working?</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">orders</span>
</span></span><span class="line"><span class="cl">  <span class="n">object</span><span class="o">.</span><span class="n">orders</span>  <span class="c1"># Oops, N+1 query!</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># How do I handle authorization here?</span>
</span></span><span class="line"><span class="cl"><span class="n">field</span> <span class="ss">:secret_data</span><span class="p">,</span> <span class="nb">String</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># Is this the right place for auth?</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div>
<p>Even experienced Rails developers need time to become productive with GraphQL.
This learning curve isn&rsquo;t just about syntax – it&rsquo;s about understanding resolver
patterns, type systems, schema design, and performance implications. While your
senior developers might pick it up quickly, you&rsquo;re adding complexity for every
new hire and junior developer.</p>
<h3 id="the-debugging-tax">The debugging tax</h3>
<p>When something goes wrong in a REST endpoint, the debugging process is
straightforward: check the logs, find the controller action, and follow the
stack trace. With GraphQL, debugging becomes more complex:</p>
<div class="code-block code-line-numbers" style="counter-reset: code-block 0">
    <div class="code-header language-ruby">
        <span class="code-title"><i class="arrow fas fa-chevron-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># Your logs might show this query:</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">user</span><span class="p">(</span><span class="nb">id</span><span class="p">:</span> <span class="s2">&#34;123&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">orders</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">items</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">product</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">price</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># But where&#39;s the N+1 query coming from?</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Is it in the orders resolver?</span>
</span></span><span class="line"><span class="cl"><span class="c1"># The items resolver?</span>
</span></span><span class="line"><span class="cl"><span class="c1"># The product resolver?</span></span></span></code></pre></div></div>
<p>Your APM tools like New Relic or Scout need additional configuration to make
sense of GraphQL operations. Stack traces become less helpful because the entry
point is always your GraphQL engine. And good luck trying to quickly reproduce
an issue when you need to reconstruct the exact query that caused it.</p>
<h3 id="the-documentation-tax">The documentation tax</h3>
<p>With REST, your API documentation often follows naturally from your controller
actions and serializers. With GraphQL, maintaining clear documentation becomes
a constant task:</p>
<div class="code-block code-line-numbers" style="counter-reset: code-block 0">
    <div class="code-header language-ruby">
        <span class="code-title"><i class="arrow fas fa-chevron-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">module</span> <span class="nn">Types</span>
</span></span><span class="line"><span class="cl">  <span class="k">class</span> <span class="nc">OrderType</span> <span class="o">&lt;</span> <span class="no">Types</span><span class="o">::</span><span class="no">BaseObject</span>
</span></span><span class="line"><span class="cl">    <span class="n">field</span> <span class="ss">:status</span><span class="p">,</span> <span class="nb">String</span><span class="p">,</span> 
</span></span><span class="line"><span class="cl">      <span class="ss">null</span><span class="p">:</span> <span class="kp">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="ss">description</span><span class="p">:</span> <span class="s2">&#34;Order status (pending, processing, shipped, delivered)&#34;</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="n">field</span> <span class="ss">:total</span><span class="p">,</span> <span class="nb">Integer</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="ss">null</span><span class="p">:</span> <span class="kp">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="ss">description</span><span class="p">:</span> <span class="s2">&#34;Order total in cents&#34;</span>
</span></span><span class="line"><span class="cl">      
</span></span><span class="line"><span class="cl">    <span class="c1"># Multiply this by every field in your schema...</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div>
<p>While GraphQL is self-documenting in theory, in practice you need to maintain
descriptions, deprecation notices, and usage examples. This documentation needs
to be kept in sync with your implementation, and it needs to be detailed enough
for client developers to use effectively.</p>
<h3 id="the-testing-tax">The testing tax</h3>
<p>Testing complexity increases exponentially with GraphQL. Instead of testing
discrete controller actions, you&rsquo;re testing combinations of fields and
resolvers:</p>
<div class="code-block code-line-numbers" style="counter-reset: code-block 0">
    <div class="code-header language-ruby">
        <span class="code-title"><i class="arrow fas fa-chevron-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># A simple REST controller test</span>
</span></span><span class="line"><span class="cl"><span class="no">RSpec</span><span class="o">.</span><span class="n">describe</span> <span class="no">UsersController</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">it</span> <span class="s2">&#34;returns user data&#34;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">get</span> <span class="ss">:show</span><span class="p">,</span> <span class="ss">params</span><span class="p">:</span> <span class="p">{</span> <span class="nb">id</span><span class="p">:</span> <span class="n">user</span><span class="o">.</span><span class="n">id</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">expect</span><span class="p">(</span><span class="n">response</span><span class="p">)</span><span class="o">.</span><span class="n">to</span> <span class="n">be_successful</span>
</span></span><span class="line"><span class="cl">    <span class="n">expect</span><span class="p">(</span><span class="n">json_response</span><span class="o">[</span><span class="ss">:name</span><span class="o">]</span><span class="p">)</span><span class="o">.</span><span class="n">to</span> <span class="n">eq</span> <span class="n">user</span><span class="o">.</span><span class="n">name</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># The equivalent GraphQL test</span>
</span></span><span class="line"><span class="cl"><span class="no">RSpec</span><span class="o">.</span><span class="n">describe</span> <span class="no">Types</span><span class="o">::</span><span class="no">QueryType</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">let</span><span class="p">(</span><span class="ss">:query</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="o">&lt;&lt;~</span><span class="no">GQL</span>
</span></span><span class="line"><span class="cl">      <span class="n">query</span><span class="p">(</span><span class="vg">$id</span><span class="p">:</span> <span class="no">ID</span><span class="o">!</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">user</span><span class="p">(</span><span class="nb">id</span><span class="p">:</span> <span class="vg">$id</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nb">name</span>
</span></span><span class="line"><span class="cl">          <span class="n">orders</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">status</span>
</span></span><span class="line"><span class="cl">            <span class="n">items</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">              <span class="n">product</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nb">name</span>
</span></span><span class="line"><span class="cl">                <span class="n">price</span>
</span></span><span class="line"><span class="cl">              <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">          <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="no">GQL</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl">  <span class="n">it</span> <span class="s2">&#34;returns nested user data&#34;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">execute_query</span><span class="p">(</span><span class="n">query</span><span class="p">,</span> <span class="ss">variables</span><span class="p">:</span> <span class="p">{</span> <span class="nb">id</span><span class="p">:</span> <span class="n">user</span><span class="o">.</span><span class="n">id</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Now check the deeply nested response structure</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Don&#39;t forget to test authorization at each level</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># And error handling</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># And N+1 query protection</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div>
<h3 id="the-production-support-tax">The production support tax</h3>
<p>When production issues occur, the complexity of GraphQL makes them harder to
diagnose and fix:</p>
<ul>
<li>Performance problems are harder to isolate because a single query might touch
dozens of resolvers</li>
<li>Error reporting becomes more complex because errors can occur at multiple
levels of the query</li>
<li>Capacity planning is trickier because client queries can be arbitrarily
complex</li>
<li>Query complexity limits and timeouts need careful tuning to prevent DOS
vulnerabilities</li>
</ul>
<p>Each of these costs might seem manageable in isolation, but they compound over
time and across team size. A five-person team might handle these overhead
costs, but as your team grows, these costs scale with each new developer, each
new feature, and each new client application.</p>
<h2 id="when-graphql-makes-sense-and-when-it-doesnt">When GraphQL makes sense (and when it doesn&rsquo;t)</h2>
<p>After all this criticism of GraphQL, you might be wondering if there&rsquo;s ever a
right time to use it. The answer is yes – GraphQL can be the right choice, but
the conditions need to justify its complexity.</p>
<h3 id="when-graphql-might-be-worth-the-cost">When GraphQL might be worth the cost</h3>
<p>You should consider GraphQL when your application has:</p>
<ul>
<li>
<p><strong>Multiple, significantly different clients</strong>: If you&rsquo;re building separate web,
mobile, and partner API experiences that each need very different data shapes,
GraphQL&rsquo;s flexibility becomes valuable. GitHub is a perfect example – they
serve their web UI, mobile apps, and third-party integrations all from the same
GraphQL API.</p>
</li>
<li>
<p><strong>Complex, nested data requirements</strong>: If your frontend frequently needs to fetch
deeply nested, interconnected data that would require multiple REST roundtrips,
GraphQL can simplify this orchestration. Think social networks where you need
user profiles, their posts, comments on those posts, and profiles of users who
commented – all in one view.</p>
</li>
<li>
<p><strong>A large, dedicated API team</strong>: When you have the resources to properly
maintain, monitor, and optimize a GraphQL implementation, its benefits become
more achievable. Companies like Shopify can justify GraphQL because they have
teams dedicated to their API infrastructure.</p>
</li>
</ul>
<h3 id="when-to-skip-graphql">When to skip GraphQL</h3>
<p>Stick with Rails&rsquo; conventional REST API patterns when:</p>
<ul>
<li>Your application is primarily CRUD: If most of your endpoints map cleanly to
resource actions (create, read, update, delete), REST already handles this
perfectly. Adding GraphQL would be overengineering.</li>
</ul>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-ruby">
        <span class="code-title"><i class="arrow fas fa-chevron-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># This is all you need for most cases</span>
</span></span><span class="line"><span class="cl"><span class="n">resources</span> <span class="ss">:orders</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">resources</span> <span class="ss">:line_items</span>
</span></span><span class="line"><span class="cl">  <span class="n">member</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">post</span> <span class="ss">:refund</span>
</span></span><span class="line"><span class="cl">    <span class="n">post</span> <span class="ss">:ship</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div>
<ul>
<li>
<p>Your data shape is relatively stable: If your frontend needs aren&rsquo;t
constantly changing and you don&rsquo;t have radically different client requirements,
REST endpoints with well-designed serializers will serve you well.</p>
</li>
<li>
<p>You have a small to medium team: If you can&rsquo;t dedicate significant
engineering resources to GraphQL infrastructure, the maintenance burden will
likely outweigh the benefits.</p>
</li>
</ul>
<h3 id="making-the-decision">Making the decision</h3>
<p>Before adopting GraphQL, ask yourself:</p>
<ol>
<li>Can Rails&rsquo; built-in tools solve our current problems?</li>
<li>Do we have the engineering resources to properly implement and maintain GraphQL?</li>
<li>Are our API requirements complex enough to justify the overhead?</li>
<li>Have we measured the actual performance impact of our current REST implementation?</li>
</ol>
<p>Remember, you can always start with a REST API and add GraphQL later if needed.
Many teams have found success with a hybrid approach – using REST for simple
CRUD operations and GraphQL for more complex data requirements.</p>
<h3 id="final-thoughts">Final thoughts</h3>
<p>GraphQL isn&rsquo;t bad technology – it&rsquo;s just often misapplied. For many Rails
applications, the conventional REST approach, combined with thoughtful use of
Rails&rsquo; built-in tools, provides a simpler, more maintainable solution. Before
jumping on the GraphQL bandwagon, make sure its benefits truly outweigh its
considerable carrying costs for your specific use case.</p>
<blockquote>
<p>🔍 In software development, simpler solutions tend to lead to happier teams
and more maintainable codebases in the long run. Sometimes the &ldquo;boring&rdquo;
solution is exactly what your application needs.</p>
</blockquote>
]]></description></item></channel></rss>