<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Abdhesh Nayak]]></title><description><![CDATA[Abdhesh Nayak]]></description><link>https://blogs.anayak.com.np</link><generator>RSS for Node</generator><lastBuildDate>Wed, 15 Apr 2026 17:11:43 GMT</lastBuildDate><atom:link href="https://blogs.anayak.com.np/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Inkube: Run Local Services as If They Were Inside the Kubernetes Cluster]]></title><description><![CDATA[TL;DR: I built Inkube, a tool that lets you run local services with the same environment, network access, and runtime conditions as a Kubernetes pod — without actually deploying. Built to ease local development in complex k8s setups.


The Problem: D...]]></description><link>https://blogs.anayak.com.np/inkube-run-local-services-as-if-they-were-inside-the-kubernetes-cluster</link><guid isPermaLink="true">https://blogs.anayak.com.np/inkube-run-local-services-as-if-they-were-inside-the-kubernetes-cluster</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Productivity]]></category><category><![CDATA[networking]]></category><category><![CDATA[tunneling]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Abdhesh Nayak]]></dc:creator><pubDate>Tue, 01 Jul 2025 14:05:54 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p>TL;DR: I built <a target="_blank" href="https://github.com/abdheshnayak/inkube">Inkube</a>, a tool that lets you <strong>run local services with the same environment, network access, and runtime conditions as a Kubernetes pod</strong> — without actually deploying. Built to ease local development in complex k8s setups.</p>
</blockquote>
<hr />
<h2 id="heading-the-problem-dev-inside-kubernetes-is-painful">The Problem: Dev Inside Kubernetes is Painful</h2>
<p>If you've ever worked with a large distributed system deployed in Kubernetes, you know how hard it is to:</p>
<ul>
<li><p>Mirror the cluster environment locally.</p>
</li>
<li><p>Connect to in-cluster services (via DNS or service discovery).</p>
</li>
<li><p>Access environment variables, secrets, and configmaps.</p>
</li>
<li><p>Debug or test things without full deployment cycles.</p>
</li>
</ul>
<p>This setup cost me hours of context switching. I wanted a way to spin up my app <strong>locally</strong> but have it <strong>behave like it's in the cluster</strong>.</p>
<hr />
<h2 id="heading-the-solution-inkube">The Solution: Inkube</h2>
<p>I created <a target="_blank" href="https://github.com/abdheshnayak/inkube">Inkube</a> to address this exact need.</p>
<p><img src="https://github.com/abdheshnayak/inkube/blob/main/logo.png?raw=true" alt="inkube logo" /></p>
<h3 id="heading-what-it-does">✨ What it does:</h3>
<ul>
<li><p>🧠 Mirrors the <strong>runtime environment</strong> of any pod ( <code>env vars</code>, <code>env mounts</code> )</p>
</li>
<li><p>🌐 Provides <strong>network tunneling</strong>, so your local app can access in-cluster services (and vice versa)</p>
</li>
<li><p>🔐 Pulls <strong>secrets/config</strong> directly from the pod spec</p>
</li>
<li><p>📦 Includes a <strong>package manager</strong> for syncing dependencies</p>
</li>
<li><p>🧪 Great for debugging, testing, or iterative development — without a full CI/CD cycle</p>
</li>
</ul>
<hr />
<h2 id="heading-powered-by-devbox-kubehttpsgithubcomjetpack-iodevboxvpn">Powered By Devbox <a target="_blank" href="https://github.com/jetpack-io/devbox">+ Kube</a>VPN</h2>
<p><a target="_blank" href="https://github.com/kubenetworks/kubevpn">Inkube</a> uses:</p>
<ul>
<li><p>🧰 <a target="_blank" href="https://github.com/jetpack-io/devbox"><strong>Devbo</strong></a><a target="_blank" href="https://github.com/jetpack-io/devbox"><strong>x</strong></a> to install &amp; lock matching system packages for application.</p>
</li>
<li><p>🌐 <a target="_blank" href="https://github.com/kubenetworks/kubevpn"><strong>KubeVPN</strong></a> under t<a target="_blank" href="https://github.com/jetpack-io/devbox">he hoo</a>d to <a target="_blank" href="https://github.com/kubenetworks/kubevpn">establ</a>ish bidirectional tunnels and handle DNS/networking.</p>
</li>
</ul>
<p>But Inkube wraps b<a target="_blank" href="https://github.com/jetpack-io/devbox">oth in</a> a d<a target="_blank" href="https://github.com/kubenetworks/kubevpn">ev-frie</a>ndly, zero-config CLI so you don't have to fight with flags or YAML.</p>
<hr />
<h2 id="heading-how-it-works">How It Works</h2>
<ol>
<li><p><strong>Choose a pod</strong>: You tell Inkube which pod in the cluster you want to mirror.</p>
</li>
<li><p><strong>Inkube pulls metadata</strong>: It extracts env vars, mounts, and config.</p>
</li>
<li><p><strong>Tunnels traffic</strong>: It creates a bidirectional tunnel between your machine and the cluster.</p>
</li>
<li><p><strong>Local launch</strong>: Run your app locally — but it behaves as if it's inside the cluster.</p>
</li>
</ol>
<blockquote>
<p>Think: <a target="_blank" href="https://www.telepresence.io/">Telepresence</a>, but simpler, more dev-focused, and built for quick iteration cycles.</p>
</blockquote>
<hr />
<h2 id="heading-why-this-matters">Why This Matters</h2>
<p>Modern teams often have robust Kubernetes setups — but devs don’t always want to (or can’t) replicate full environments locally. Tools like Tilt, Skaffold, and Telepresence exist, but each has a learning curve or infra requirements.</p>
<p><strong>Inkube</strong> is intentionally minimal and developer-first. Ideal for:</p>
<ul>
<li><p>Working on one service in a large microservice setup</p>
</li>
<li><p>Troubleshooting integration issues</p>
</li>
<li><p>Exploring services in unfamiliar clusters</p>
</li>
<li><p>Avoiding long deploy cycles for every small change</p>
</li>
</ul>
<hr />
<h2 id="heading-example-usage">Example Usage</h2>
<pre><code class="lang-bash">inkube init <span class="hljs-comment"># you will get prompt to pick namespace, deployment &amp; container</span>

devbox add &lt;pkg&gt; <span class="hljs-comment"># install package </span>

inkube dev <span class="hljs-comment"># you will access the shell with all env vars mounted and network access</span>

inkube intercept <span class="hljs-comment"># all traffic coming on deployment will start hitting your local machine.</span>
<span class="hljs-comment"># it will also stop all running container of that deployment.</span>

inkube leave <span class="hljs-comment"># stop intercepting traffic.</span>

<span class="hljs-built_in">exit</span> <span class="hljs-comment"># exit from ther inkube shell</span>
</code></pre>
<p>Your local process now:</p>
<ul>
<li><p>Has the same env vars as selected deployment’s container.</p>
</li>
<li><p>Talks to other services via in-cluster DNS</p>
</li>
<li><p>Can receive traffic from the cluster (via tunnel)</p>
</li>
<li><p>Logs and behaves like it's "inside"</p>
</li>
</ul>
<h3 id="heading-initialize">Initialize</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1751378339127/145fde66-544b-41a0-96c7-89d04937e35e.gif" alt class="image--center mx-auto" /></p>
<h3 id="heading-access-development-shell">Access Development Shell</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1751378378377/1a68c540-4aef-4979-908b-f9422af36a33.gif" alt class="image--center mx-auto" /></p>
<h3 id="heading-intercepting-traffic">Intercepting Traffic</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1751378408442/6704cd45-08d4-4c0d-aea2-2d0315e05945.gif" alt class="image--center mx-auto" /></p>
<h3 id="heading-accessing-container-environments">Accessing Container Environments</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1751378425343/972ad0e1-900d-4f10-ade8-872b26f58066.gif" alt class="image--center mx-auto" /></p>
<h3 id="heading-package-manager">Package Manager</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1751377034349/ad15f5e1-b781-486e-948a-1afbc8bb27ee.gif" alt class="image--center mx-auto" /></p>
<hr />
<h2 id="heading-open-source-amp-contributions-welcome">Open Source &amp; Contributions Welcome</h2>
<p>Inkube is open source (MIT). You can check it out here:</p>
<p>🔗 <a target="_blank" href="https://github.com/abdheshnayak/inkube">https://github.com/abdheshnayak/inkube</a></p>
<p>If this scratches an itch you’ve had, or if you have feedback — I’d love to hear it.</p>
<hr />
<p><strong>Thanks for reading!</strong><br />If you found Inkube helpful, consider sharing it with a friend or teammate. Your feedback means a lot!</p>
]]></content:encoded></item><item><title><![CDATA[How to Achieve Type-Safe Integration Between Frontend and Backend]]></title><description><![CDATA[One of the major challenges in frontend-backend integration is maintaining type safety. When backend API structures change, frontend developers often struggle to detect breaking changes until runtime. To solve this, we need an automated approach that...]]></description><link>https://blogs.anayak.com.np/how-to-achieve-type-safe-integration-between-frontend-and-backend</link><guid isPermaLink="true">https://blogs.anayak.com.np/how-to-achieve-type-safe-integration-between-frontend-and-backend</guid><category><![CDATA[TypeScript]]></category><category><![CDATA[golang]]></category><category><![CDATA[Integration Testing]]></category><category><![CDATA[GraphQL]]></category><dc:creator><![CDATA[Abdhesh Nayak]]></dc:creator><pubDate>Fri, 14 Mar 2025 20:50:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1741984335753/2d796c08-110e-4a42-9110-558d99138c75.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>One of the major challenges in frontend-backend integration is maintaining type safety. When backend API structures change, frontend developers often struggle to detect breaking changes until runtime. To solve this, we need an automated approach that catches these issues at compile time.</p>
<p>A robust solution is to <strong>generate types directly from the backend API</strong> and use them in the frontend. This way, whenever there are breaking changes on the backend, the frontend can easily detect them by regenerating the types. This significantly improves developer experience and reduces integration errors.</p>
<h3 id="heading-approach">Approach</h3>
<p>In this article, we'll explore an approach to <strong>automatically generate TypeScript types from a backend API response</strong> and use them in a frontend application. We'll use:</p>
<ul>
<li><p><strong>Golang</strong> for the backend</p>
</li>
<li><p><strong>Next.js with TypeScript</strong> for the frontend</p>
</li>
<li><p><strong>GraphQL</strong> for API communication</p>
</li>
</ul>
<p>Although this method can be implemented with REST APIs using <strong>OpenAPI Specification</strong>, GraphQL provides a more automated solution since entity types can be directly inferred from real backend entities.</p>
<h3 id="heading-why-this-matters">Why This Matters</h3>
<p>While working at <strong>Kloudlite</strong>, we faced this challenge with a six-member team. Manually writing types for every GraphQL query and API response was time-consuming. Automating this process saved significant development hours, allowing us to focus on building features rather than worrying about integration. With this setup, frontend developers always receive expected data, reducing the chances of runtime errors.</p>
<h3 id="heading-steps-to-implement">Steps to Implement</h3>
<p>We'll go step by step to set up this integration:</p>
<ol>
<li><p>Define backend entities in Golang</p>
</li>
<li><p>Generate GraphQL types from these entities</p>
</li>
<li><p>Expose GraphQL queries from the backend</p>
</li>
<li><p>At frontend accumulate all queries in one place and generate TypeScript types from gql SDL.</p>
</li>
</ol>
<h2 id="heading-defining-backend-entities-in-golang">Defining Backend Entities in Golang</h2>
<p>To demonstrate the type generation approach, let's build a simple <strong>Todo application</strong> where we fetch and manage tasks from a backend. We'll use <strong>GORM</strong> for database operations.</p>
<h3 id="heading-defining-the-todo-entity">Defining the Todo Entity</h3>
<p>We'll start by defining the <code>Todo</code> entity using GORM:</p>
<pre><code class="lang-go"><span class="hljs-keyword">type</span> Todo <span class="hljs-keyword">struct</span> {
    gorm.Model <span class="hljs-string">`graphql:"noinput"`</span>
    Title      <span class="hljs-keyword">string</span> <span class="hljs-string">`json:"title"`</span>
    Done       <span class="hljs-keyword">bool</span>   <span class="hljs-string">`json:"done"`</span>
}
</code></pre>
<h3 id="heading-implementing-the-domain-layer">Implementing the Domain Layer</h3>
<p>The <strong>domain layer</strong> abstracts database operations and provides a clean interface for managing todos.</p>
<h4 id="heading-define-the-domain-interface">Define the Domain Interface</h4>
<pre><code class="lang-go"><span class="hljs-keyword">type</span> Domain <span class="hljs-keyword">interface</span> {
    AddTodo(ctx context.Context, todo *entities.Todo) (*entities.Todo, error)
    ListTodos(ctx context.Context) ([]*entities.Todo, error)
    GetTodo(ctx context.Context, id <span class="hljs-keyword">int</span>) (*entities.Todo, error)
    UpdateTodo(ctx context.Context, id <span class="hljs-keyword">int</span>, todo *entities.Todo) (*entities.Todo, error)
    DeleteTodo(ctx context.Context, id <span class="hljs-keyword">int</span>) error
}
</code></pre>
<h4 id="heading-implement-the-domain-logic">Implement the Domain Logic</h4>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">New</span><span class="hljs-params">(db *gorm.DB, ev *env.Env)</span> <span class="hljs-title">Domain</span></span> {
    <span class="hljs-keyword">return</span> &amp;domain{
        db:  db,
        env: ev,
    }
}

<span class="hljs-keyword">type</span> domain <span class="hljs-keyword">struct</span> {
    db  *gorm.DB
    env *env.Env
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(d *domain)</span> <span class="hljs-title">AddTodo</span><span class="hljs-params">(ctx context.Context, todo *entities.Todo)</span> <span class="hljs-params">(*entities.Todo, error)</span></span> {
    result := d.db.Create(todo)
    <span class="hljs-keyword">if</span> result.Error != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, result.Error
    }

    <span class="hljs-keyword">return</span> todo, <span class="hljs-literal">nil</span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(d *domain)</span> <span class="hljs-title">DeleteTodo</span><span class="hljs-params">(ctx context.Context, id <span class="hljs-keyword">int</span>)</span> <span class="hljs-title">error</span></span> {
    result := d.db.Delete(&amp;entities.Todo{}, id)
    <span class="hljs-keyword">if</span> result.Error != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> result.Error
    }

    <span class="hljs-keyword">if</span> result.RowsAffected == <span class="hljs-number">0</span> {
        <span class="hljs-keyword">return</span> fmt.Errorf(<span class="hljs-string">"no todo found with id %d"</span>, id)
    }

    <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(d *domain)</span> <span class="hljs-title">GetTodo</span><span class="hljs-params">(ctx context.Context, id <span class="hljs-keyword">int</span>)</span> <span class="hljs-params">(*entities.Todo, error)</span></span> {

    <span class="hljs-keyword">var</span> todo entities.Todo
    result := d.db.First(&amp;todo, id)
    <span class="hljs-keyword">if</span> result.Error != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, result.Error
    }

    <span class="hljs-keyword">return</span> &amp;todo, <span class="hljs-literal">nil</span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(d *domain)</span> <span class="hljs-title">ListTodos</span><span class="hljs-params">(ctx context.Context)</span> <span class="hljs-params">([]*entities.Todo, error)</span></span> {
    <span class="hljs-keyword">var</span> todos []*entities.Todo
    result := d.db.Find(&amp;todos)
    <span class="hljs-keyword">if</span> result.Error != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, result.Error
    }

    <span class="hljs-keyword">return</span> todos, <span class="hljs-literal">nil</span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(d *domain)</span> <span class="hljs-title">UpdateTodo</span><span class="hljs-params">(ctx context.Context, id <span class="hljs-keyword">int</span>, todo *entities.Todo)</span> <span class="hljs-params">(*entities.Todo, error)</span></span> {
    result := d.db.Save(todo)
    <span class="hljs-keyword">if</span> result.Error != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, result.Error
    }

    <span class="hljs-keyword">if</span> result.RowsAffected == <span class="hljs-number">0</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, fmt.Errorf(<span class="hljs-string">"no todo found with id %d"</span>, id)
    }

    <span class="hljs-keyword">return</span> todo, <span class="hljs-literal">nil</span>
}
</code></pre>
<h2 id="heading-setting-up-graphql-server-amp-generating-graphql-types">Setting Up Graphql Server &amp; Generating GraphQL Types</h2>
<p>Here’s how you can set up your GraphQL server and integrate the process of generating GraphQL types from your Golang structs, step by step:</p>
<h3 id="heading-step-1-set-up-the-graphql-server">Step 1: Set Up the GraphQL Server</h3>
<p>We will begin by following the <a target="_blank" href="https://www.apollographql.com/blog/using-graphql-with-golang">Apollo GraphQL Golang tutorial</a>, which provides a simple boilerplate for setting up a GraphQL server in Golang. After implementing the boilerplate, you’ll have a server like this:</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Graphql</span><span class="hljs-params">(d domain.Domain, ev *env.Env)</span> <span class="hljs-title">error</span></span> {
    srv := handler.New(generated.NewExecutableSchema(generated.Config{
        Resolvers: &amp;graph.Resolver{
            Domain: d,
        },
    }))

    srv.AddTransport(transport.Options{})
    srv.AddTransport(transport.GET{})
    srv.AddTransport(transport.POST{})

    srv.SetQueryCache(lru.NewIntrospection{})
    srv.Use(extension.AutomaticPersistedQuery{
        Cache: lru.New ,
    })

    http.Handlound.Handler(<span class="hljs-string">"GraphQL playground"</span>, <span class="hljs-string">"/query"</span>))
    http.Handle(<span class="hljs-string">"/query"</span>, srv)

    logging.Get().Info(fmt.Sprintf(<span class="hljs-string">"connect to http://localhost:%d/ for GraphQL playground"</span>, ev.PORT))
    <span class="hljs-keyword">return</span> http.ListenAndServe(fmt.Sprintf(<span class="hljs-string">":%d"</span>, ev.PORT), <span class="hljs-literal">nil</span>)
}
</code></pre>
<p>This code does the following:</p>
<ul>
<li><p>Initializes a GraphQL server using <code>handler.New</code> with the schema generated from your <code>Resolvers</code>.</p>
</li>
<li><p>Sets up different transports (GET, POST, and Options) for handling requests.</p>
</li>
<li><p>Uses query caching and introspection for GraphQL metadata.</p>
</li>
<li><p>Sets up the <strong>GraphQL Playground</strong> at the <code>/</code> endpoint and the query handler at <code>/query</code>.</p>
</li>
<li><p>Finally, it listens on the specified port and logs the URL to access the playground.</p>
</li>
</ul>
<h3 id="heading-step-2-generate-graphql-types-from-golang-struct">Step 2: Generate GraphQL Types From Golang Struct</h3>
<p>Next, we’ll use the <a target="_blank" href="https://github.com/abdheshnayak/struct-to-gql">struct-to-gql</a> tool to automatically generate GraphQL types from your Golang structs.</p>
<h4 id="heading-steps">Steps:</h4>
<ol>
<li><p>Add <a target="_blank" href="https://github.com/abdheshnayak/struct-to-gql/">github.com/abdheshnayak/struct-to-gql</a> and <a target="_blank" href="http://github.com/abdheshnayak/struct-to-gql/pkg/parser">github.com/abdheshnayak/struct-to-gql/pkg/parser</a> to your tools.go so it will be available for use.</p>
<pre><code class="lang-go"> <span class="hljs-comment">//go:build tools</span>
 <span class="hljs-comment">// +build tools</span>

 <span class="hljs-keyword">package</span> tools

 <span class="hljs-keyword">import</span> (
     _ <span class="hljs-string">"github.com/99designs/gqlgen"</span>
     _ <span class="hljs-string">"github.com/abdheshnayak/struct-to-gql"</span>
     _ <span class="hljs-string">"github.com/abdheshnayak/struct-to-gql/pkg/parser"</span>
 )
</code></pre>
</li>
<li><p><strong>generate the schemas form your struct by using script like below</strong>:</p>
<pre><code class="lang-bash"> working_dir=$(mktemp -d)

 mkdir -p <span class="hljs-string">"<span class="hljs-variable">$working_dir</span>"</span>
 - go run github.com/abdheshnayak/struct-to-gql
     --struct package.Entity <span class="hljs-comment"># example: github.com/abdheshnayak/gorm-practice/app/entities.Todo</span>
 &gt; <span class="hljs-variable">$working_dir</span>/main.go
 - |+
 <span class="hljs-built_in">pushd</span> <span class="hljs-string">"<span class="hljs-variable">$working_dir</span>"</span>
 go run main.go --dev --out-dir ../graph/struct-to-graphql 
 <span class="hljs-built_in">popd</span>
 - rm -rf <span class="hljs-variable">$working_dir</span>
</code></pre>
<p> In above script we created a work directory where a go file generated by the tool by executing which we can get our real GraphQL schema for the type we defined in above with struct flag.</p>
</li>
</ol>
<h3 id="heading-step-3-use-generated-graphql-types">Step 3: Use Generated GraphQL Types</h3>
<p>Once the GraphQL types are generated, you can <strong>import the generated schema</strong> into your existing GraphQL schema. For example, in your <strong>GraphQL YAML configuration file</strong>, you can reference both the existing schema and the generated types:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">schema:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">graph/*.graphqls</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">graph/struct-to-graphql/*.graphqls</span>
</code></pre>
<p>This configuration allows the server to merge the generated schema files from the <code>struct-to-graphql</code> tool with your existing GraphQL schema. As a result, your backend GraphQL server will have access to the newly generated types and will be able to serve data using the updated schema.</p>
<p>Once integrated, you can directly use the generated GraphQL types in your queries and mutations. For instance:</p>
<pre><code class="lang-graphql"><span class="hljs-keyword">type</span> Query {
  <span class="hljs-symbol">todos:</span> [Todo!]!
  todo(<span class="hljs-symbol">id:</span> ID!): Todo
}

<span class="hljs-keyword">type</span> Mutation {
  createTodo(<span class="hljs-symbol">input:</span> TodoIn!): Todo!
  updateTodo(<span class="hljs-symbol">id:</span> ID!, <span class="hljs-symbol">input:</span> TodoIn!): Todo!
}
</code></pre>
<p>And also we can implement the resolvers accordingly:</p>
<pre><code class="lang-go"><span class="hljs-comment">// CreateTodo is the resolver for the createTodo field.</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(r *mutationResolver)</span> <span class="hljs-title">CreateTodo</span><span class="hljs-params">(ctx context.Context, input entities.Todo)</span> <span class="hljs-params">(*entities.Todo, error)</span></span> {
    <span class="hljs-keyword">return</span> r.Resolver.Domain.AddTodo(ctx, &amp;input)
}

<span class="hljs-comment">// UpdateTodo is the resolver for the updateTodo field.</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(r *mutationResolver)</span> <span class="hljs-title">UpdateTodo</span><span class="hljs-params">(ctx context.Context, id <span class="hljs-keyword">int</span>, input entities.Todo)</span> <span class="hljs-params">(*entities.Todo, error)</span></span> {
    <span class="hljs-keyword">return</span> r.Resolver.Domain.UpdateTodo(ctx, id, &amp;input)
}

<span class="hljs-comment">// Todos is the resolver for the todos field.</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(r *queryResolver)</span> <span class="hljs-title">Todos</span><span class="hljs-params">(ctx context.Context)</span> <span class="hljs-params">([]*entities.Todo, error)</span></span> {
    <span class="hljs-keyword">return</span> r.Resolver.Domain.ListTodos(ctx)
}

<span class="hljs-comment">// Todo is the resolver for the todo field.</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(r *queryResolver)</span> <span class="hljs-title">Todo</span><span class="hljs-params">(ctx context.Context, id <span class="hljs-keyword">int</span>)</span> <span class="hljs-params">(*entities.Todo, error)</span></span> {
    <span class="hljs-keyword">return</span> r.Resolver.Domain.GetTodo(ctx, id)
}
</code></pre>
<p>So, we will get the GraphQL server and access our Todo application and will be able to query from our GraphQL server, as shown in the screenshot.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741980923454/626ade1c-9e81-4181-89eb-e3acd5bef962.png" alt class="image--center mx-auto" /></p>
<p>So we are done with our backend part, where we used a single Todo struct and gql schema from the same type. Next, we are going to proceed to generating TypeScript types from the gql SDL (Schema Definition Language) and implementing a better design pattern to consume the gql API efficiently.</p>
<h2 id="heading-organizing-graphql-queries-in-a-maintainable-way">Organizing GraphQL Queries in a Maintainable Way</h2>
<p>When building a frontend application with GraphQL, it's crucial to structure queries and mutations in a way that ensures maintainability and scalability. In this article, we'll explore a centralized approach to managing GraphQL queries and mutations while using an <code>executor</code> function to streamline request handling.</p>
<h3 id="heading-structuring-graphql-queries-and-mutations">Structuring GraphQL Queries and Mutations</h3>
<p>To keep our GraphQL operations well-organized, we define all queries and mutations in a single module. This approach enhances maintainability and provides a single source of truth for API interactions.</p>
<h3 id="heading-key-features">Key Features:</h3>
<ul>
<li><p><strong>Centralized Queries and Mutations</strong>: All GraphQL operations are stored in one place for better organization and easy management.</p>
</li>
<li><p><strong>Transform Function</strong>: Processes and refines API responses to return structured data.</p>
</li>
<li><p><strong>Vars Function</strong>: Defines expected query variables, ensuring type safety and predictability.</p>
</li>
<li><p><strong>Automated Query Extraction</strong>: Queries are extracted and stored in a <code>.graphql</code> file for use with Codegen.</p>
</li>
</ul>
<h3 id="heading-example-of-centralized-queries-and-mutations">Example of Centralized Queries and Mutations</h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> gql <span class="hljs-keyword">from</span> <span class="hljs-string">"graphql-tag"</span>;
<span class="hljs-keyword">import</span> { executor } <span class="hljs-keyword">from</span> <span class="hljs-string">"./executor"</span>;
<span class="hljs-keyword">import</span> { 
  MainGetTodoQuery,
  MainGetTodoQueryVariables,
  MainListTodosQuery,
  MainListTodosQueryVariables,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"@/generated/types/server"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> queries = <span class="hljs-function">() =&gt;</span> ({
  listTodos: executor(
    gql<span class="hljs-string">`query ListTodos { todos { title done Model { ID } } }`</span>,
    {
      transform: <span class="hljs-function">(<span class="hljs-params">data: MainListTodosQuery</span>) =&gt;</span> data.todos,
      vars(_: MainListTodosQueryVariables) {},
    }
  ),
  getTodo: executor(
    gql<span class="hljs-string">`query GetTodo($input: ID!) { todo(id: $input) { title } }`</span>,
    {
      transform: <span class="hljs-function">(<span class="hljs-params">data: MainGetTodoQuery</span>) =&gt;</span> data.todo,
      vars(_: MainGetTodoQueryVariables) {},
    }
  )
});
</code></pre>
<p>So in above code we can see the the imported types for the queries. these types are auto generated from the defined queries that we are going to discuss in detail.</p>
<h2 id="heading-implementing-the-executor-function">Implementing the Executor Function</h2>
<p>The <code>executor</code> function simplifies GraphQL request execution using Axios while handling responses efficiently. It accepts a query or mutation along with transformation logic and variable definitions, ensuring a streamlined API interaction process.</p>
<h3 id="heading-key-features-1">Key Features:</h3>
<ul>
<li><p><strong>Dynamic Query Execution</strong>: The function accommodates any GraphQL query or mutation and executes it with the provided variables.</p>
</li>
<li><p><strong>Automatic Response Handling</strong>: Extracts relevant data from API responses, making them easier to work with.</p>
</li>
<li><p><strong>Error Management</strong>: Captures and processes errors for better debugging and resilience.</p>
</li>
<li><p><strong>GraphQL Query Printing</strong>: Converts <code>DocumentNode</code> into a string using the <code>print</code> function from <code>graphql</code>.</p>
</li>
</ul>
<h3 id="heading-example-of-the-executor-function">Example of the Executor Function</h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { DocumentNode, print } <span class="hljs-keyword">from</span> <span class="hljs-string">"graphql"</span>;
<span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">"axios"</span>;
<span class="hljs-keyword">import</span> { urls } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/config/constants"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> executor = &lt;T, T2, T3&gt;(
  query: DocumentNode,
  options: { transform: <span class="hljs-function">(<span class="hljs-params">data: T</span>) =&gt;</span> T2; vars: <span class="hljs-function">(<span class="hljs-params">data: T3</span>) =&gt;</span> <span class="hljs-built_in">void</span> }
) =&gt; {
  <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">async</span> (data: T3) =&gt; {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> axios.post(urls.api, {
        query: print(query),
        variables: data
      }, { headers: { <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span> } });

      <span class="hljs-keyword">if</span> (result.data.errors) {
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-built_in">JSON</span>.stringify(result.data.errors));
      }

      <span class="hljs-keyword">return</span> options.transform(result.data.data);
    } <span class="hljs-keyword">catch</span> (err: <span class="hljs-built_in">any</span>) {
      <span class="hljs-keyword">throw</span> err.response?.data?.errors || err;
    }
  };

  res.Query = <span class="hljs-function">() =&gt;</span> query;
  <span class="hljs-keyword">return</span> res;
};
</code></pre>
<h2 id="heading-automating-query-extraction-for-code-generation">Automating Query Extraction for Code Generation</h2>
<p>To ensure type safety and maintainability, we use Codegen to generate TypeScript types for our GraphQL queries. We first extract queries and save them to a <code>.graphql</code> file, which Codegen then uses to generate types.</p>
<h3 id="heading-extracting-queries">Extracting Queries</h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> fs <span class="hljs-keyword">from</span> <span class="hljs-string">"fs"</span>;
<span class="hljs-keyword">import</span> { DocumentNode, print } <span class="hljs-keyword">from</span> <span class="hljs-string">"graphql"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> loader = (handler: <span class="hljs-function">() =&gt;</span> { [key: <span class="hljs-built_in">string</span>]: { Query(): DocumentNode } }, prefix: <span class="hljs-built_in">string</span>) =&gt; {
  <span class="hljs-keyword">const</span> gqlQueries = handler();
  <span class="hljs-keyword">const</span> updatedQueries: { [name: <span class="hljs-built_in">string</span>]: DocumentNode } = {};

  <span class="hljs-built_in">Object</span>.entries(gqlQueries).forEach(<span class="hljs-function">(<span class="hljs-params">[key, query]</span>) =&gt;</span> {
    <span class="hljs-comment">// @ts-ignore</span>
    query.Query().definitions[<span class="hljs-number">0</span>].name.value = <span class="hljs-string">`<span class="hljs-subst">${prefix}</span><span class="hljs-subst">${key.charAt(<span class="hljs-number">0</span>).toUpperCase() + key.slice(<span class="hljs-number">1</span>)}</span>`</span>;
    updatedQueries[key] = query.Query();
  });

  <span class="hljs-keyword">return</span> <span class="hljs-built_in">Object</span>.values(updatedQueries).map(print).join(<span class="hljs-string">"\n\n"</span>);
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> ensureDirectoryExistence = <span class="hljs-function">(<span class="hljs-params">dirPath: <span class="hljs-built_in">string</span></span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (!fs.existsSync(dirPath)) fs.mkdirSync(dirPath, { recursive: <span class="hljs-literal">true</span> });
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> docPath = <span class="hljs-string">"generated/gql"</span>;
</code></pre>
<h3 id="heading-writing-extracted-queries-to-a-file">Writing Extracted Queries to a File</h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> fs <span class="hljs-keyword">from</span> <span class="hljs-string">"fs"</span>;
<span class="hljs-keyword">import</span> { docPath, ensureDirectoryExistence, loader } <span class="hljs-keyword">from</span> <span class="hljs-string">"./loader"</span>;
<span class="hljs-keyword">import</span> { queries } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/lib/queries"</span>;

<span class="hljs-keyword">const</span> init = <span class="hljs-function">() =&gt;</span> {
  ensureDirectoryExistence(docPath);
  <span class="hljs-keyword">const</span> results = [loader(queries, <span class="hljs-string">"main"</span>)];
  <span class="hljs-keyword">const</span> info = <span class="hljs-string">`# This file is auto-generated. Do not modify it manually.\n# Generated by pnpm gql:parse`</span>;

  fs.writeFileSync(<span class="hljs-string">`<span class="hljs-subst">${docPath}</span>/queries.graphql`</span>, <span class="hljs-string">`<span class="hljs-subst">${info}</span>\n\n<span class="hljs-subst">${results.join(<span class="hljs-string">"\n\n"</span>)}</span>`</span>);
};

init();
</code></pre>
<h2 id="heading-codegen-configuration">Codegen Configuration</h2>
<p>Now we can feed our generated queries file to Codegen to generate TypeScript types that can be used within our application.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">overwrite:</span> <span class="hljs-literal">true</span>
<span class="hljs-attr">schema:</span> <span class="hljs-string">"http://localhost:8080/query"</span>
<span class="hljs-attr">generates:</span>
  <span class="hljs-attr">generated/types/server.ts:</span>
    <span class="hljs-attr">documents:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">generated/gql/*.graphql</span>
    <span class="hljs-attr">plugins:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"typescript"</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">typescript-operations</span>
    <span class="hljs-attr">config:</span>
      <span class="hljs-attr">onlyOperationTypes:</span> <span class="hljs-literal">true</span>
      <span class="hljs-attr">skipTypename:</span> <span class="hljs-literal">true</span>
      <span class="hljs-attr">ignoreEnumValuesFromSchema:</span> <span class="hljs-literal">true</span>
      <span class="hljs-attr">enumsAsTypes:</span> <span class="hljs-literal">true</span>
      <span class="hljs-attr">maybeValue:</span> <span class="hljs-string">'T'</span>
    <span class="hljs-attr">hooks:</span>
      <span class="hljs-attr">afterOneFileWrite:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">'eslint --fix'</span>
</code></pre>
<blockquote>
<p>So with above steps we can generate the types for our our graphql queries automatically when any changes is pushed to the backend entity.</p>
</blockquote>
<h3 id="heading-using-the-query-in-our-react-component">Using the query in our react component</h3>
<p>Here is how we can use the query in our react component, i am considering the client side react component but you can use the same with other fronend frameworks as well.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [todos, setTodos] = useState&lt;MainListTodosQuery[<span class="hljs-string">"todos"</span>]&gt;([]);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    (<span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">const</span> todos = <span class="hljs-keyword">await</span> queries().listTodos({});

      setTodos(todos);
    })();
  }, []);

  <span class="hljs-keyword">return</span> (
    &lt;Table aria-label=<span class="hljs-string">"Example table with dynamic content"</span>&gt;
      &lt;TableHeader columns={columns}&gt;
        {<span class="hljs-function">(<span class="hljs-params">column</span>) =&gt;</span> &lt;TableColumn key={column.key}&gt;{column.label}&lt;/TableColumn&gt;}
      &lt;/TableHeader&gt;
      &lt;TableBody
        items={todos.map(<span class="hljs-function">(<span class="hljs-params">todo</span>) =&gt;</span> ({
          ...todo,
          done: todo.done ? <span class="hljs-string">"done"</span> : <span class="hljs-string">"pending"</span>,
        }))}
      &gt;
        {<span class="hljs-function">(<span class="hljs-params">item</span>) =&gt;</span> (
          &lt;TableRow key={item.Model.ID}&gt;
            {<span class="hljs-function">(<span class="hljs-params">columnKey</span>) =&gt;</span> (
              &lt;TableCell&gt;{getKeyValue(item, columnKey)}&lt;/TableCell&gt;
            )}
          &lt;/TableRow&gt;
        )}
      &lt;/TableBody&gt;
    &lt;/Table&gt;
  );
}
</code></pre>
<p>So the response of the listTodos call we will get the typed response like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742039325988/bb61f494-b798-4fe4-b643-249e089f4a8b.png" alt class="image--center mx-auto" /></p>
<p>And finally we listed our todos items on frontend:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742039384936/0c6a57a7-f863-42c3-ba0f-1ec22dd09d90.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-benefits-of-this-approach">Benefits of This Approach</h2>
<p>By structuring GraphQL queries in a centralized manner and leveraging an executor function, we achieve:</p>
<ul>
<li><p><strong>Improved Code Organization</strong>: All API interactions are neatly structured in a single place.</p>
</li>
<li><p><strong>Enhanced Maintainability</strong>: Queries and mutations are easy to update and modify.</p>
</li>
<li><p><strong>Type Safety</strong>: Using TypeScript ensures queries return the expected data types, reducing runtime errors.</p>
</li>
<li><p><strong>Efficient API Calls</strong>: The executor function streamlines request handling and error management, ensuring a smooth development experience.</p>
</li>
</ul>
<blockquote>
<p>All of the above implementation available in the given repository.</p>
</blockquote>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/abdheshnayak/articles/tree/main/type-safe-back-front">https://github.com/abdheshnayak/articles/tree/main/type-safe-back-front</a></div>
]]></content:encoded></item></channel></rss>