<?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[Tech Scene w/ Anant Bhasin]]></title><description><![CDATA[Tech Scene w/ Anant Bhasin]]></description><link>https://blog.bhasin.dev</link><generator>RSS for Node</generator><lastBuildDate>Tue, 06 Oct 2026 15:26:24 GMT</lastBuildDate><atom:link href="https://blog.bhasin.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Just-In-Time Image Generation: How I Slashed Curse.You Response Times from 3 Seconds to 300ms]]></title><description><![CDATA[The Challenge: Instant, Shareable Content
In the hyper-fast world of social media, speed is everything. When a link is shared, you have a fraction of a second to capture attention. My tool, Curse.You, generates personalized meme images on the fly, wh...]]></description><link>https://blog.bhasin.dev/just-in-time-image-generation-how-i-slashed-curseyou-response-times-from-3-seconds-to-300ms</link><guid isPermaLink="true">https://blog.bhasin.dev/just-in-time-image-generation-how-i-slashed-curseyou-response-times-from-3-seconds-to-300ms</guid><category><![CDATA[image processing]]></category><category><![CDATA[openGraph]]></category><category><![CDATA[image generation]]></category><category><![CDATA[cloudflare-worker]]></category><category><![CDATA[cloudflare]]></category><dc:creator><![CDATA[Anant Bhasin]]></dc:creator><pubDate>Mon, 13 Oct 2025 14:03:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1760363881551/973d9cc6-e87c-4714-a18a-3a27ca370dfb.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-the-challenge-instant-shareable-content"><strong>The Challenge: Instant, Shareable Content</strong></h2>
<p>In the hyper-fast world of social media, speed is everything. When a link is shared, you have a fraction of a second to capture attention. My tool, <a target="_blank" href="http://Curse.You">Curse.You</a>, generates personalized meme images on the fly, which automatically unfurl into rich previews on platforms like Discord, Twitter, and Telegram.</p>
<p>The core challenge? I needed to generate and serve a unique, custom image in the brief moment a social media crawler scrapes our URL. These crawlers are impatient; they'll time out in as little as two seconds. My initial attempts were far too slow, clocking in at <strong>over 3 seconds</strong>.</p>
<p>This is the story of how I battled cold starts and rendering bottlenecks on our journey to an <strong>average 300ms response time</strong>, all powered by Cloudflare Workers.</p>
<h2 id="heading-the-road-to-300ms-a-three-act-drama"><strong>The Road to 300ms: A Three-Act Drama</strong></h2>
<p>My quest for speed was an iterative process of building, failing, and learning. Each approach taught me a critical lesson about performance in a serverless edge environment.</p>
<h3 id="heading-act-i-the-high-quality-dream-with-rust-wasm-time-3-seconds"><strong>Act I: The High-Quality Dream with Rust + WASM (Time: 3+ Seconds)</strong></h3>
<p>My first attempt aimed for perfection. I chose Rust for its performance and resvg for its superb SVG rendering quality, compiling the entire package to WebAssembly (WASM) to run on Cloudflare Workers.</p>
<p>I have used the Resvg stack before, in WASM, and have it live on NitroQR right now. What could go wrong, right? Well, I actually utilise <strong>tiny-skia</strong>, the rendering engine subset of skia that was built for Resvg. The overheads between the two apparently look wildly different, even with the difference in usage accounted for.</p>
<p><strong>The Theory:</strong> Combine the speed of Rust with the quality of a professional-grade SVG renderer.</p>
<p><strong>The Reality:</strong> The overhead was crushing.</p>
<ul>
<li><p><strong>WASM Initialization:</strong> ~800ms</p>
</li>
<li><p><strong>resvg Rendering:</strong> ~2,000ms</p>
</li>
<li><p><strong>Total Time:</strong> A sluggish <strong>~2,850ms</strong></p>
</li>
</ul>
<p><strong>The Downfall:</strong></p>
<ul>
<li><p><strong>WASM Cold Starts:</strong> The initialization cost on each request was a massive bottleneck.</p>
</li>
<li><p><strong>Desktop-Grade Libraries:</strong> resvg is a powerful tool, but it's optimized for environments with more memory and processing power, not the constrained sandbox of a serverless worker.</p>
</li>
<li><p><strong>Bundle Size:</strong> The complex dependency tree resulted in a large bundle, further slowing things down.</p>
</li>
</ul>
<p><strong>Lesson #1:</strong> The theoretical performance of a language doesn't matter if the environment's overhead negates the benefits.</p>
<h3 id="heading-act-ii-the-javascript-pivot-with-svg-libraries-time-15-seconds"><strong>Act II: The JavaScript Pivot with SVG Libraries (Time: 1.5+ Seconds)</strong></h3>
<p>Reasoning that a native JavaScript solution would avoid WASM's overhead, I switched to popular NPM libraries for SVG-to-PNG conversion.</p>
<p><strong>The Theory:</strong> Stick to the native runtime (JavaScript) and leverage the rich Node.js ecosystem.</p>
<p><strong>The Reality:</strong> I cut our time in half, but it was still far too slow.</p>
<ul>
<li><p><strong>Library Loading:</strong> ~200ms</p>
</li>
<li><p><strong>Canvas Rendering:</strong> ~1,200ms</p>
</li>
<li><p><strong>Total Time:</strong> A middling <strong>~1,530ms</strong></p>
</li>
</ul>
<p><strong>The Downfall:</strong></p>
<ul>
<li><p><strong>Hidden Complexities:</strong> Most libraries use a canvas implementation under the hood, which has its own performance limitations within the Workers environment.</p>
</li>
<li><p><strong>Heavy Dependencies:</strong> Pulling in large libraries designed for browsers or Node.js introduced significant parsing and memory overhead.</p>
</li>
</ul>
<p><strong>Lesson #2:</strong> "Off-the-shelf" doesn't mean "optimized for the edge." Serverless functions require purpose-built or minimalistic dependencies.</p>
<h3 id="heading-act-iii-the-breakthrough-with-direct-raster-generation-time-300ms"><strong>Act III: The Breakthrough with Direct Raster Generation (Time: 300ms)</strong></h3>
<p>Frustrated with high-level abstractions, I went back to first principles. What if I abandoned SVG entirely and just... built the image pixel by pixel?</p>
<p><strong>The Theory:</strong> Eliminate all layers of abstraction—no SVG parsing, no complex layout engines, no external libraries. Manipulate a raw pixel buffer directly.</p>
<p><strong>The Reality:</strong> A resounding success.</p>
<ul>
<li><p><strong>Pixel Buffer Creation:</strong> ~80ms</p>
</li>
<li><p><strong>Text Rendering (Custom):</strong> ~120ms</p>
</li>
<li><p><strong>PNG Encoding (UPNG.js):</strong> ~60ms</p>
</li>
<li><p><strong>R2 Upload:</strong> ~35ms</p>
</li>
<li><p><strong>Total Time:</strong> A blazing-fast <strong>~300ms</strong></p>
</li>
</ul>
<p><strong>The Win:</strong> By controlling every pixel, I eliminated all bottlenecks. The code was simple, had zero dependencies for the core generation logic, and was perfectly suited for the constrained serverless environment. While this means it only supports a-z right now, that is good enough for an MVP.</p>
<p><strong>Lesson #3:</strong> For maximum performance, solve the simplest version of your problem. We didn't need a full graphics engine; we just needed to place colored rectangles on a screen.</p>
<h2 id="heading-how-it-works-building-an-image-from-scratch"><strong>How It Works: Building an Image from Scratch</strong></h2>
<p>My final solution is built around a RasterGenerator class that operates directly on a pixel buffer.</p>
<p><strong>The Core Strategy:</strong></p>
<ol>
<li><p><strong>Allocate Memory:</strong> Create a Uint8ClampedArray, which is a simple array representing all the RGBA (Red, Green, Blue, Alpha) values for every pixel in the image.</p>
</li>
<li><p><strong>Draw Manually:</strong> Implement our own basic functions to draw rectangles and text by directly changing the values in the array.</p>
</li>
<li><p><strong>Render Text with a Bitmap Font:</strong> The secret sauce was creating our own simple bitmap font. Instead of parsing font files and calculating glyphs, we just store characters as arrays of 1s and 0s. This is incredibly fast and has zero overhead.</p>
</li>
<li><p><strong>Encode to PNG:</strong> Use the lightweight and highly efficient UPNG.js library to compress the final pixel buffer into a PNG file.</p>
</li>
</ol>
<p>This approach completely bypasses the need for complex, CPU-intensive graphics libraries.</p>
<h2 id="heading-key-architectural-decisions"><strong>Key Architectural Decisions</strong></h2>
<p>Beyond the generation logic, several other components were critical for achieving speed and reliability.</p>
<h3 id="heading-outsmarting-the-cache-with-a-two-step-redirect"><strong>Outsmarting the Cache with a Two-Step Redirect</strong></h3>
<p>Social media platforms cache aggressively. If a user shares <a target="_blank" href="http://curse.you/michael">curse.you/michael</a>, the crawler might see a cached version. To ensure a fresh image every time, we use a redirect flow:</p>
<ol>
<li><p><strong>Initial Request:</strong> <a target="_blank" href="https://curse.you/michael">https://curse.you/michael</a></p>
</li>
<li><p><strong>Server Response:</strong> A temporary redirect (HTTP 307) to a unique URL: <a target="_blank" href="https://curse.you/michael/a9b4c1d8">https://curse.you/michael/a9b4c1d8</a></p>
</li>
<li><p><strong>Final Page:</strong> The unique URL serves the HTML with OpenGraph tags pointing to the freshly generated image.</p>
</li>
</ol>
<p>This forces the crawler to treat every share as a unique page, defeating its cache while allowing the final generated image asset to be cached permanently on our CDN. The next step is to make this a persistent url, so that it serves the same curse every time. Soon!</p>
<h3 id="heading-building-the-fortress-security-amp-abuse-prevention"><strong>Building the Fortress: Security &amp; Abuse Prevention</strong></h3>
<p>A tool that generates content from user input is a prime target for abuse. The defense is multi-layered:</p>
<ul>
<li><p><strong>Verified Crawler Allowlist:</strong> We only perform on-demand generation for crawlers we trust (e.g., Discord, Twitter).</p>
</li>
<li><p><strong>Aggressive Rate Limiting:</strong> All other traffic faces strict limits.</p>
</li>
<li><p><strong>Content Filtering:</strong> We block known spam patterns and restricted words.</p>
</li>
<li><p><strong>Cloudflare WAF:</strong> We leverage Cloudflare's Web Application Firewall for an extra layer of protection.</p>
</li>
</ul>
<h3 id="heading-the-stack-that-makes-it-possible"><strong>The Stack That Makes It Possible</strong></h3>
<ul>
<li><p><strong>Runtime:</strong> <strong>Cloudflare Workers</strong> provide the global, low-latency compute environment. V8 isolates mean near-zero cold start times.</p>
</li>
<li><p><strong>Storage:</strong> <strong>Cloudflare R2</strong> is perfect for serving images. With zero egress fees and really small file sizes, it's incredibly cost-effective for viral content.</p>
</li>
<li><p><strong>Language:</strong> <strong>Modern JavaScript (ES2022)</strong> with zero-dependency generation logic.</p>
</li>
</ul>
<h2 id="heading-the-results-by-the-numbers"><strong>The Results: By the Numbers</strong></h2>
<p>After deploying the direct raster approach, monitoring speaks for itself:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Metric</strong></td><td><strong>Performance</strong></td></tr>
</thead>
<tbody>
<tr>
<td><strong>Average Response Time</strong></td><td><strong>287ms</strong></td></tr>
<tr>
<td><strong>P95 Response Time</strong></td><td>420ms</td></tr>
<tr>
<td><strong>Error Rate</strong></td><td>0.03%</td></tr>
<tr>
<td><strong>Image Cache Hit Rate</strong></td><td>97.2%</td></tr>
</tbody>
</table>
</div><h2 id="heading-final-lessons-learned"><strong>Final Lessons Learned</strong></h2>
<p>This journey from 3 seconds to 300ms reinforced a fundamental principle of software engineering: <strong>simplicity scales.</strong></p>
<ol>
<li><p><strong>Embrace Constraints:</strong> The limitations of the serverless environment forced me to find a more efficient solution. What started as a problem became my greatest catalyst for innovation.</p>
</li>
<li><p><strong>Question Abstractions:</strong> High-level libraries are great for rapid development, but they often hide performance costs. Don't be afraid to go "down a level" and build a custom solution when speed is critical.</p>
</li>
<li><p><strong>The Right Tool for the Job:</strong> Rust/WASM is a phenomenal technology, but it wasn't the right fit <em>for this specific problem</em>. The lean, dependency-free JavaScript solution proved to be the winner.</p>
</li>
</ol>
<p>By ditching complex graphics engines for direct pixel manipulation, I built a system that is not only 10x faster but also more robust, secure, and cost-effective at global scale.</p>
]]></content:encoded></item><item><title><![CDATA[Distributed Telemetry with Golang, React, and Sentry]]></title><description><![CDATA[First thing's first!
What is Telemetry?
From Stackify's blog post,

Telemetry is the automatic recording and transmission of data from remote or inaccessible sources to an IT system in a different location for monitoring and analysis.

So, deploying ...]]></description><link>https://blog.bhasin.dev/distributed-telemetry-sentry</link><guid isPermaLink="true">https://blog.bhasin.dev/distributed-telemetry-sentry</guid><category><![CDATA[React]]></category><category><![CDATA[Go Language]]></category><category><![CDATA[Microservices]]></category><category><![CDATA[monitoring]]></category><category><![CDATA[sdk]]></category><dc:creator><![CDATA[Anant Bhasin]]></dc:creator><pubDate>Mon, 24 Jan 2022 16:28:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1642850815528/zNkacDYz5.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>First thing's first!</strong></p>
<h3 id="heading-what-is-telemetry">What is Telemetry?</h3>
<p>From <a target="_blank" href="https://stackify.com/telemetry-tutorial/">Stackify's blog post</a>,</p>
<blockquote>
<p>Telemetry is the automatic recording and transmission of data from remote or inaccessible sources to an IT system in a different location for monitoring and analysis.</p>
</blockquote>
<p>So, deploying solutions with telemetry tools built-in allows us to monitor how an app performs, how long certain actions take, and most importantly, when things do go wrong(and eventually, they will), well-implemented telemetry proves essential in finding out exactly what happened <strong>fast</strong>!</p>
<p>It is one of those things you might not want to integrate into everything you create. A portfolio website, for example, is probably not worth the effort of integrating telemetry into, but dealing with large amounts of user data is a much more sensitive task and would definitely warrant some additional security measures and investment.</p>
<h3 id="heading-sentry-and-golang">Sentry and Golang</h3>
<p>Sentry is a distributed telemetry platform. The keyword here is <strong>distributed</strong>. Apart from just helping monitor independent services or applications separately, Sentry builds in a structure that allows the platform to stitch together related user instances from different sources and provide a very convenient bird's eye view of exactly what checkpoints a user session passed through, along with the application state at that point.</p>
<h3 id="heading-lets-implement-it">Let's implement it</h3>
<p>To understand it better, we can now do a quick test project.</p>
<p>At the end of the following instructions, you should have something like the below graphic:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643034986127/CelQ4kCaO.png" alt="Sentry Trace" /></p>
<p>If you look carefully, you'll be able to see the step where the browser calls our API, and sentry stitches together the backend spans as well, to give you a complete performance outlook of all your services in one place. I am referring to the part shown below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643035097375/VawA0EXf2.png" alt="Sentry frontend-backend tracing" /></p>
<p>First, head on to <a target="_blank" href="https://sentry.io">Sentry.io</a> and register a free account.</p>
<p>Once logged in, <strong>create two projects</strong> in your new team. The free tier should suffice.
One of these projects is going to be used for our react application, while the other one will be used for the Go backend server. You can name and choose the configurations appropriately.</p>
<p>Keep a note of the DSN for both of the projects. You can find them in the project setting, by clicking on the "Client Keys" options from the Settings sidebar menu, like below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643035403819/GOuXecEeI.png" alt="DSN keys" />
You want the one on top!</p>
<p>Next, we can set up both our projects. If you wish to directly look at the code(with comments), you can follow this link to the <a target="_blank" href="https://github.com/aKn1ghtOut/gin-sentry-example">Github repository</a>.</p>
<p>We want to create a basic go server and the basic create react app template to test our tooling setup.</p>
<p>So, make sure you have go and Node setup. Yarn is optional.</p>
<p>Create the react app with </p>
<pre><code>npx create<span class="hljs-operator">-</span>react<span class="hljs-operator">-</span>app
</code></pre><p>and create a new go repo with </p>
<pre><code>go mod <span class="hljs-keyword">init</span>
</code></pre><p>For the react app, the setup is very basic. We just want to initialize Sentry, and add a useEffect with our network request. Sentry takes care of the rest.</p>
<p>So, add the below in index.js, above the ReactDOM.render call</p>
<pre><code>Sentry.init({
  dsn: process.env.REACT_APP_SENTRY_DSN, <span class="hljs-comment">// Retreiving the DSN from .env file</span>
  integrations: [<span class="hljs-keyword">new</span> Integrations.BrowserTracing()], <span class="hljs-comment">// Enabling automatic browser tracing</span>
  tracesSampleRate: <span class="hljs-number">1.0</span>, <span class="hljs-comment">// Capturing all events</span>
});
</code></pre><p>Then, in App.js, let's add the network request we wish to monitor. Add a useEffect block in the App functional component, as below:</p>
<pre><code>useEffect(() <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> {
    const url <span class="hljs-operator">=</span> `${process.env.REACT_APP_API_URL}getPosts`; <span class="hljs-comment">// get API host from .env file</span>
    fetch(url)
      .then(res <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> res.json())
      .then(res <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> {
        console.log({
          res
        });
      })
      .catch(reason <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> {
        console.error({
          reason
        })
      })
  }, [])
</code></pre><p>The above effect now runs once on pageload of the app.
This is all for the setup in our frontend. Sentry automatically adds its markers in the network requests and records them.</p>
<p>Next, we will be adding sentry to our GIN backend. Next, will be just a lot of code for our GIN application. This includes our router and one controller. Once the GIN app is done, we add the sentry code.</p>
<p>The backend will only be used to get the 10 latest stories from Hackernews, retrieve their info one by one using their ids, and return all of the stories to the frontend.</p>
<p>The go.mod should look something like the below with the following direct dependencies:</p>
<pre><code>module yourginserverproject

go <span class="hljs-number">1.17</span>

<span class="hljs-built_in">require</span> (
    github.com/getsentry<span class="hljs-operator">/</span>sentry<span class="hljs-operator">-</span>go v0<span class="hljs-number">.12</span><span class="hljs-number">.0</span>
    github.com/gin<span class="hljs-operator">-</span>gonic<span class="hljs-operator">/</span>contrib v0<span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-number">-20201101042839</span><span class="hljs-operator">-</span>6a891bf89f19
    github.com/gin<span class="hljs-operator">-</span>gonic<span class="hljs-operator">/</span>gin v1<span class="hljs-number">.7</span><span class="hljs-number">.7</span>
    github.com/joho<span class="hljs-operator">/</span>godotenv v1<span class="hljs-number">.4</span><span class="hljs-number">.0</span>
)
</code></pre><p>Add the dependencies to your go.mod and run the following command:</p>
<pre><code><span class="hljs-keyword">go</span> mod download
</code></pre><p>Next, add main.go in the project root:</p>
<pre><code>package main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"log"</span>
    <span class="hljs-string">"os"</span>

    <span class="hljs-string">"yourginserverproject/controllers"</span>

    <span class="hljs-string">"github.com/gin-gonic/gin"</span>

    <span class="hljs-string">"github.com/gin-contrib/cors"</span>
)

<span class="hljs-title">func</span> <span class="hljs-title">main</span>() {
    <span class="hljs-title">r</span> :<span class="hljs-operator">=</span> <span class="hljs-title">GetRouter</span>()
    <span class="hljs-title">r</span>.<span class="hljs-title">Use</span>(<span class="hljs-title">gin</span>.<span class="hljs-title">Logger</span>())
    <span class="hljs-title"><span class="hljs-keyword">if</span></span> <span class="hljs-title">err</span> :<span class="hljs-operator">=</span> <span class="hljs-title">r</span>.<span class="hljs-title">Run</span>(<span class="hljs-string">":8090"</span>); err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        log.Fatal(<span class="hljs-string">"Unable to start:"</span>, err)
    }
}

func GetRouter() <span class="hljs-operator">*</span>gin.Engine {
    r :<span class="hljs-operator">=</span> gin.Default()

    corsConfig :<span class="hljs-operator">=</span> cors.DefaultConfig()
    corsConfig.AllowAllOrigins <span class="hljs-operator">=</span> <span class="hljs-literal">true</span>
    corsConfig.AllowHeaders <span class="hljs-operator">=</span> append(corsConfig.AllowHeaders, <span class="hljs-string">"*"</span>) <span class="hljs-comment">// For test project</span>

    r.Use(cors.New(corsConfig))

    r.GET(<span class="hljs-string">"/getPosts"</span>, controllers.GetPostsHandler)

    <span class="hljs-keyword">return</span> r
}
</code></pre><p>The above starts a very basic GIN server with no routes/controllers. Now, we add one controller in controllers/getPosts.go that services out fetch request from the react application:</p>
<pre><code>package controllers

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"context"</span>
    <span class="hljs-string">"encoding/json"</span>
    <span class="hljs-string">"fmt"</span>
    <span class="hljs-string">"io/ioutil"</span>
    <span class="hljs-string">"net/http"</span>
)

<span class="hljs-title"><span class="hljs-keyword">type</span></span> <span class="hljs-title">GetPostsResponseItem</span> <span class="hljs-title"><span class="hljs-keyword">struct</span></span> {
    <span class="hljs-title">Title</span>       <span class="hljs-title"><span class="hljs-keyword">string</span></span> `<span class="hljs-title">json</span>:<span class="hljs-string">"title"</span>`
    <span class="hljs-title">Description</span> <span class="hljs-title"><span class="hljs-keyword">string</span></span> `<span class="hljs-title">json</span>:<span class="hljs-string">"text"</span>`
    <span class="hljs-title">Type</span>        <span class="hljs-title"><span class="hljs-keyword">string</span></span> `<span class="hljs-title">json</span>:<span class="hljs-string">"type"</span>`
}

<span class="hljs-title"><span class="hljs-keyword">type</span></span> <span class="hljs-title">GetPostsResponse</span> []<span class="hljs-title">GetPostsResponseItem</span>

<span class="hljs-title">func</span> <span class="hljs-title">getPostsCall</span>() <span class="hljs-title">GetPostsResponse</span> {
    <span class="hljs-title"><span class="hljs-keyword">var</span></span> <span class="hljs-title">res</span> <span class="hljs-title">GetPostsResponse</span>

    <span class="hljs-comment">// Start first HTTP request</span>
    <span class="hljs-title">httpClient</span> :<span class="hljs-operator">=</span> <span class="hljs-operator">&amp;</span><span class="hljs-title">http</span>.<span class="hljs-title">Client</span>{}
    <span class="hljs-title">req</span>, <span class="hljs-title">err</span> :<span class="hljs-operator">=</span> <span class="hljs-title">http</span>.<span class="hljs-title">NewRequest</span>(<span class="hljs-string">"GET"</span>, <span class="hljs-string">"https://hacker-news.firebaseio.com/v0/newstories.json?orderBy=\"$key\"&amp;limitToFirst=10"</span>, <span class="hljs-title">nil</span>)
    <span class="hljs-title"><span class="hljs-keyword">if</span></span> <span class="hljs-title">err</span> <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-title">nil</span> {
        <span class="hljs-title">fmt</span>.<span class="hljs-title">Print</span>(<span class="hljs-string">"Error 1:"</span>, <span class="hljs-title">err</span>.<span class="hljs-title"><span class="hljs-built_in">Error</span></span>())
    }
    <span class="hljs-title">req</span>.<span class="hljs-title">Close</span> <span class="hljs-operator">=</span> <span class="hljs-title"><span class="hljs-literal">true</span></span>
    <span class="hljs-title">resp</span>, <span class="hljs-title">err2</span> :<span class="hljs-operator">=</span> <span class="hljs-title">httpClient</span>.<span class="hljs-title">Do</span>(<span class="hljs-title">req</span>)
    <span class="hljs-title"><span class="hljs-keyword">if</span></span> <span class="hljs-title">err2</span> <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-title">nil</span> {
        <span class="hljs-title">fmt</span>.<span class="hljs-title">Print</span>(<span class="hljs-string">"Error 2:"</span>, <span class="hljs-title">err2</span>.<span class="hljs-title"><span class="hljs-built_in">Error</span></span>())
    }
    <span class="hljs-title">body</span>, <span class="hljs-title">err3</span> :<span class="hljs-operator">=</span> <span class="hljs-title">ioutil</span>.<span class="hljs-title">ReadAll</span>(<span class="hljs-title">resp</span>.<span class="hljs-title">Body</span>) <span class="hljs-comment">// JSON content in byte-array</span>
    <span class="hljs-title">resp</span>.<span class="hljs-title">Body</span>.<span class="hljs-title">Close</span>()
    <span class="hljs-title"><span class="hljs-keyword">if</span></span> <span class="hljs-title">err3</span> <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-title">nil</span> {
        <span class="hljs-title">fmt</span>.<span class="hljs-title">Print</span>(<span class="hljs-string">"Error 2: "</span>, <span class="hljs-title">err3</span>.<span class="hljs-title"><span class="hljs-built_in">Error</span></span>())
    }
    <span class="hljs-comment">// End HTTP request</span>

    <span class="hljs-title"><span class="hljs-keyword">var</span></span> <span class="hljs-title">ids</span> []<span class="hljs-title"><span class="hljs-keyword">int</span></span>
    <span class="hljs-title">json</span>.<span class="hljs-title">Unmarshal</span>(<span class="hljs-title">body</span>, <span class="hljs-operator">&amp;</span><span class="hljs-title">ids</span>) <span class="hljs-comment">// Get IDs for top stories</span>

    <span class="hljs-title">fmt</span>.<span class="hljs-title">Print</span>(<span class="hljs-title">ids</span>[0:10])

    <span class="hljs-title"><span class="hljs-keyword">for</span></span> <span class="hljs-title">i</span> :<span class="hljs-operator">=</span> <span class="hljs-title">range</span> <span class="hljs-title">ids</span> {
        <span class="hljs-title">id</span> :<span class="hljs-operator">=</span> <span class="hljs-title">ids</span>[<span class="hljs-title">i</span>]

        <span class="hljs-comment">// Get meta data for every story</span>
        <span class="hljs-title">urlLink</span> :<span class="hljs-operator">=</span> <span class="hljs-title">fmt</span>.<span class="hljs-title">Sprintf</span>(<span class="hljs-string">"https://hacker-news.firebaseio.com/v0/item/%v.json"</span>, <span class="hljs-title">id</span>) 
        <span class="hljs-title">iResp</span>, <span class="hljs-title">err</span> :<span class="hljs-operator">=</span> <span class="hljs-title">http</span>.<span class="hljs-title">Get</span>(<span class="hljs-title">urlLink</span>)
        <span class="hljs-title"><span class="hljs-keyword">if</span></span> <span class="hljs-title">err</span> <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-title">nil</span> {
            <span class="hljs-title"><span class="hljs-keyword">continue</span></span>
        }

        <span class="hljs-title">resB</span>, <span class="hljs-title"><span class="hljs-keyword">_</span></span> :<span class="hljs-operator">=</span> <span class="hljs-title">ioutil</span>.<span class="hljs-title">ReadAll</span>(<span class="hljs-title">iResp</span>.<span class="hljs-title">Body</span>)
        <span class="hljs-title"><span class="hljs-keyword">var</span></span> <span class="hljs-title">temp</span> <span class="hljs-title">GetPostsResponseItem</span>
        <span class="hljs-title">json</span>.<span class="hljs-title">Unmarshal</span>(<span class="hljs-title">resB</span>, <span class="hljs-operator">&amp;</span><span class="hljs-title">temp</span>)
        <span class="hljs-title">res</span> <span class="hljs-operator">=</span> <span class="hljs-title">append</span>(<span class="hljs-title">res</span>, <span class="hljs-title">temp</span>)
    }

    <span class="hljs-title"><span class="hljs-keyword">return</span></span> <span class="hljs-title">res</span>
}

<span class="hljs-title">func</span> <span class="hljs-title">GetPostsHandler</span>(<span class="hljs-title">c</span> <span class="hljs-operator">*</span><span class="hljs-title">gin</span>.<span class="hljs-title">Context</span>) {
    <span class="hljs-title">response</span> :<span class="hljs-operator">=</span> <span class="hljs-title">getPostsCall</span>()

    <span class="hljs-title">c</span>.<span class="hljs-title">JSON</span>(200, <span class="hljs-title">response</span>)
}
</code></pre><p>Now, all that is left for our application to start working(without sentry; for now) is to add a .env configuration file to the react projects to connect it to the backend.</p>
<p>Add a .env file to the root of the react project with the content below:</p>
<pre><code><span class="hljs-attr">REACT_APP_API_URL</span>=http://localhost:<span class="hljs-number">8090</span>/
</code></pre><p>Replace http://localhost:8090/ if you're running the GIN app on a different port/host.
You should now be able to run the app and the server and check the browser console for the output.</p>
<p>Time to add the sentry essentials to our GIN application.</p>
<p>Modify the main.go file as below</p>
<p>Add the following imports:</p>
<pre><code><span class="hljs-keyword">import</span> (
    sentry <span class="hljs-string">"github.com/getsentry/sentry-go"</span>
    sentrygin <span class="hljs-string">"github.com/getsentry/sentry-go/gin"</span>
    <span class="hljs-string">"github.com/joho/godotenv"</span>
)
</code></pre><p>In the GetRouter function, add the sentry middleware right after initialization or the variable r:</p>
<pre><code>r.Use(sentrygin.New(sentrygin.Options{}))
</code></pre><p>and finally, for main.go, add the sentry initialization code at the top of the main function:</p>
<pre><code>    godotenv.Load()
    sentryDsn :<span class="hljs-operator">=</span> os.Getenv(<span class="hljs-string">"SENTRY_DSN"</span>)
    err :<span class="hljs-operator">=</span> sentry.Init(sentry.ClientOptions{
        Dsn:              sentryDsn,
        TracesSampleRate: <span class="hljs-number">1</span>,
        Debug:            <span class="hljs-literal">true</span>,
    })
    <span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        log.Fatalf(<span class="hljs-string">"sentry.Init: %s"</span>, err)
    }
</code></pre><p>The first line will load the variable from the <strong>project .env file</strong> to the environment, where we can pull the Sentry DSN variable from. The next few lines initialize the SDK itself.</p>
<p>Now, adding the sentry goodies to our controller itself. Sentry with Golang is more hands-on, of course. We change it as follows:</p>
<ol>
<li>Add <code>sentry.captureException(err)</code> in all of the error blocks.</li>
<li>Add a context.Context type parameter in all of the nested functions to pass the parent's Sentry context.</li>
<li>Add Sentry span initialization to all the functions to monitor full time taken by the functions themselves.</li>
<li>Manage spans for all the external communication. In this case, that refers to the HTTP requests. They might be Database calls in your application.</li>
</ol>
<p>Once that is done, the file will look like below(comments added):</p>
<pre><code><span class="hljs-keyword">package</span> controllers

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"context"</span>
    <span class="hljs-string">"encoding/json"</span>
    <span class="hljs-string">"fmt"</span>
    <span class="hljs-string">"io/ioutil"</span>
    <span class="hljs-string">"net/http"</span>

    <span class="hljs-string">"github.com/getsentry/sentry-go"</span>
    <span class="hljs-string">"github.com/gin-gonic/gin"</span>
)

<span class="hljs-keyword">type</span> GetPostsResponseItem <span class="hljs-keyword">struct</span> {
    Title       <span class="hljs-keyword">string</span> <span class="hljs-string">`json:"title"`</span>
    Description <span class="hljs-keyword">string</span> <span class="hljs-string">`json:"text"`</span>
    Type        <span class="hljs-keyword">string</span> <span class="hljs-string">`json:"type"`</span>
}

<span class="hljs-keyword">type</span> GetPostsResponse []GetPostsResponseItem

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">getPostsCall</span><span class="hljs-params">(sentryCtx context.Context)</span> <span class="hljs-title">GetPostsResponse</span></span> {
    <span class="hljs-comment">// sentry span init for function, set parent to context from calling function</span>
    <span class="hljs-keyword">defer</span> sentry.Recover()
    span := sentry.StartSpan(sentryCtx, <span class="hljs-string">"[GetPostsCall]"</span>)
    <span class="hljs-keyword">defer</span> span.Finish()

    <span class="hljs-keyword">var</span> res GetPostsResponse

    httpClient := &amp;http.Client{}
    span2 := sentry.StartSpan(span.Context(), <span class="hljs-string">"[HTTP] Get latests stories"</span>) <span class="hljs-comment">// Start nested span to monitor first http request</span>
    req, err := http.NewRequest(<span class="hljs-string">"GET"</span>, <span class="hljs-string">"https://hacker-news.firebaseio.com/v0/newstories.json?orderBy=\"$key\"&amp;limitToFirst=10"</span>, <span class="hljs-literal">nil</span>)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        fmt.Print(<span class="hljs-string">"Error 1:"</span>, err.Error())
        sentry.CaptureException(err)
    }
    req.Close = <span class="hljs-literal">true</span>
    resp, err2 := httpClient.Do(req)
    <span class="hljs-keyword">if</span> err2 != <span class="hljs-literal">nil</span> {
        fmt.Print(<span class="hljs-string">"Error 2:"</span>, err2.Error())
        sentry.CaptureException(err2)
    }
    body, err3 := ioutil.ReadAll(resp.Body)
    resp.Body.Close()
    span2.Finish() <span class="hljs-comment">// Request workflow finished; end span</span>
    <span class="hljs-keyword">if</span> err3 != <span class="hljs-literal">nil</span> {
        fmt.Print(<span class="hljs-string">"Error 2: "</span>, err3.Error())
        sentry.CaptureException(err3)
    }

    fmt.Printf(<span class="hljs-string">"Body : %s"</span>, <span class="hljs-keyword">string</span>(body))

    <span class="hljs-keyword">var</span> ids []<span class="hljs-keyword">int</span>
    json.Unmarshal(body, &amp;ids)

    fmt.Print(ids[<span class="hljs-number">0</span>:<span class="hljs-number">10</span>])

    span3 := sentry.StartSpan(span.Context(), <span class="hljs-string">"[HTTP] Get data for stories"</span>) <span class="hljs-comment">// Start span to monitor for loop execution time</span>
    <span class="hljs-keyword">for</span> i := <span class="hljs-keyword">range</span> ids {
        id := ids[i]

        urlLink := fmt.Sprintf(<span class="hljs-string">"https://hacker-news.firebaseio.com/v0/item/%v.json"</span>, id)
        iResp, err := http.Get(urlLink)
        <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
            sentry.CaptureException(err)
            <span class="hljs-keyword">continue</span>
        }

        resB, _ := ioutil.ReadAll(iResp.Body)
        <span class="hljs-keyword">var</span> temp GetPostsResponseItem
        json.Unmarshal(resB, &amp;temp)
        res = <span class="hljs-built_in">append</span>(res, temp)
    }
    span3.Finish()

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

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">GetPostsHandler</span><span class="hljs-params">(c *gin.Context)</span></span> {
    <span class="hljs-comment">// Sentry transaction initialization</span>
    <span class="hljs-keyword">defer</span> sentry.Recover()
    span := sentry.StartSpan(c, <span class="hljs-string">"[Getposts]"</span>, sentry.TransactionName(<span class="hljs-string">"GETPOSTS"</span>), sentry.ContinueFromRequest(c.Request)) <span class="hljs-comment">// Take trace id from request header</span>
    <span class="hljs-keyword">defer</span> span.Finish()

    response := getPostsCall(span.Context()) <span class="hljs-comment">// Call nested function; Could be DAO methods</span>

    c.JSON(<span class="hljs-number">200</span>, response)
}
</code></pre><p>Now, all that is left is to add/update the .env files</p>
<p>Update the .env file for the React project with your Sentry React project's DSN like below:</p>
<pre><code><span class="hljs-attr">REACT_APP_API_URL</span>=http://localhost:<span class="hljs-number">8090</span>/
<span class="hljs-attr">REACT_APP_SENTRY_DSN</span>=https://dsn.sentry.io/<span class="hljs-number">123456</span>
</code></pre><p>And add a .env file to the GIN project with your Sentry Go project DSN like:</p>
<pre><code><span class="hljs-attr">SENTRY_DSN</span>=https://dsn.sentry.io/<span class="hljs-number">123456</span>
</code></pre><p>We are finally ready. Now, run both the projects and open the web app.</p>
<p>Keep the page open and wait a few minutes for Sentry to stitch together the frontend and backend transactions. Then, just open the Backend project in sentry and click on <code>View Transactions</code>. Click on the <strong>GETPOSTS</strong> transaction. You should see your new transaction here:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643040470559/482w4bYeQ.png" alt="Transcations" /></p>
<p>Click on one and you'll see the backend transaction. Now, click on <strong>Parent</strong> from the top right box:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643040574228/I9WSv48w1.png" alt="Click on Parent" /></p>
<p>Now, you should see something resembling the first screenshot from this post. And that is how you add distributed tracing to your React WebApp with a GIN backend!</p>
<p><em>Started writing this with the intention of publishing this within Trell's blog during my internship. Got a little late for that, but better late than never I guess! :)</em></p>
]]></content:encoded></item></channel></rss>