<?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[Navayuvan's Blog]]></title><description><![CDATA[I write about building real-world software with AI, focusing on what broke, what scaled, and what finally worked, so you can design workflows that fit your own ]]></description><link>https://blogs.navayuvan.com</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1766638304733/94744ee2-8ee7-44a6-81d0-032ce20bf29c.png</url><title>Navayuvan&apos;s Blog</title><link>https://blogs.navayuvan.com</link></image><generator>RSS for Node</generator><lastBuildDate>Fri, 15 May 2026 17:09:37 GMT</lastBuildDate><atom:link href="https://blogs.navayuvan.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Three Layers of Tool Call Hardening for AI Agents]]></title><description><![CDATA[In current software engineering,We're building a lot of AI Agents on our products right now. And having an AI agent in your product is how you keep your product alive, right? That's how the world is m]]></description><link>https://blogs.navayuvan.com/three-layers-of-tool-call-hardening-for-ai-agents</link><guid isPermaLink="true">https://blogs.navayuvan.com/three-layers-of-tool-call-hardening-for-ai-agents</guid><category><![CDATA[ai agents]]></category><category><![CDATA[AI agent security]]></category><category><![CDATA[llm]]></category><category><![CDATA[promptinjections]]></category><category><![CDATA[agentic AI]]></category><category><![CDATA[Software Engineering]]></category><dc:creator><![CDATA[Navayuvan Subramanian]]></dc:creator><pubDate>Mon, 11 May 2026 16:34:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6544f59782acc66595e68746/b04aba67-3e57-45f4-b048-fd6821c9cbb9.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In current software engineering,We're building a lot of AI Agents on our products right now. And having an AI agent in your product is how you keep your product alive, right? That's how the world is moving.</p>
<p>And while everyone is busy building AI agents — tweaking prompts, giving tool calls, focusing on model choice and parameters — there is one critical area most developers sometimes skip.</p>
<p><strong>Tool harness and security.</strong></p>
<p>Not the prompt. Not the model. The harness around your tools — how you design them, constrain them, and control what the agent can actually do with them.</p>
<p>And skipping this will cost you a lot in terms of both security and reliability.</p>
<hr />
<h2>What Even Is Tool Harness?</h2>
<p>When you give an AI agent a tool, you're not just giving it a function. You're giving it a boundary. A set of rules about what it can touch, what it can't, and how it should behave when it acts.</p>
<p>Most of us don't think about it that way. We write the tool, attach it to the agent, and move on. The harness — the constraints, the access controls, the behavioral guardrails — gets left to the prompt.</p>
<p>That's the mistake.</p>
<p>Prompts can be overridden. Prompts can be manipulated. Prompts can be ignored. The harness needs to live at the code level, the execution level, the architecture level.</p>
<p>And here's how to build it properly. There are three layers.</p>
<hr />
<h2>Layer 1: Strip Identity Params — Inject Them Server-Side</h2>
<p>The first layer is about access control. And it starts with your tool schema.</p>
<p>Let's say you're building a to-do app with an AI agent. You give it a <code>list_tasks</code> tool. Your schema looks like this:</p>
<pre><code class="language-json">{
  "name": "list_tasks",
  "parameters": {
    "user_id": "string",
    "filters": {
      "status": "string",
      "due_before": "string"
    }
  }
}
</code></pre>
<p>Looks fine, right?</p>
<p>It's not.</p>
<p>Because <code>user_id</code> is in the schema, the agent can pass <em>any</em> user ID it wants. A malicious prompt, a confused model, a prompt injection — any of these could have your agent fetching data it has absolutely no business touching. There's no authentication. There's no authorization.</p>
<p>The fix: strip all identity params from the schema. Things like <code>user_id</code>, <code>account_id</code>, <code>workspace_id</code>, <code>knowledge_base_id</code> — these define the scope of who sees what. The agent doesn't get to decide scope. You do.</p>
<pre><code class="language-json">{
  "name": "list_tasks",
  "parameters": {
    "filters": {
      "status": "string",
      "due_before": "string"
    }
  }
}
</code></pre>
<p>And when the tool executes, inject the identity yourself — from the authenticated session:</p>
<pre><code class="language-typescript">async function list_tasks(params: { filters: Filters }, session: Session) {
  const userId = session.userId; // you control this, not the agent
  return db.tasks.findMany({
    where: {
      userId,
      ...params.filters,
    },
  });
}
</code></pre>
<p>The agent says <em>what</em> it needs. You decide <em>whose</em> data gets touched. That's the harness. 💡</p>
<hr />
<h2>Layer 2: Enforce Behavioral Constraints at the Code Level</h2>
<p>The second layer is about how your tools behave — not just what they can access.</p>
<p>If you've used Claude Code, you'd have seen this error:</p>
<blockquote>
<p><em>"A file cannot be written before it has been read."</em></p>
</blockquote>
<p>That's not a prompt instruction. That's a hard constraint baked into the tool itself. The developers at Claude Code took a very human behavior — open the file, read it, understand it, <em>then</em> edit it — and enforced it at the execution level.</p>
<p>That's exactly what we need to do with our own tools.</p>
<p>For example, if you have an <code>update_task</code> tool, don't let the agent call it cold. Enforce a read-first constraint at the code level:</p>
<pre><code class="language-typescript">async function update_task(params: UpdateTaskParams, session: Session) {
  const lastRead = await cache.get(`task_read:\({params.task_id}:\){session.userId}`);

  if (!lastRead || Date.now() - lastRead &gt; 60_000) {
    throw new Error(
      "Task must be read before it can be updated. Call get_task first."
    );
  }

  return db.tasks.update({
    where: { id: params.task_id },
    data: params.updates,
  });
}
</code></pre>
<p>You can mention this in the prompt too — but the check has to live in code. Not just in a system prompt the model might miss or ignore. The execution layer is where the harness lives. 🔒</p>
<hr />
<h2>Layer 3: Pre-flight Validation with a Reasoning Agent</h2>
<p>This one is more advanced. I haven't shipped it in my own product yet — but I know this will work.</p>
<p>The idea: before any tool call executes, require the agent to pass a <code>reason</code> — a short explanation of <em>why</em> it's calling that tool.</p>
<pre><code class="language-json">{
  "name": "list_tasks",
  "parameters": {
    "reason": "string",
    "filters": {
      "status": "string",
      "due_before": "string"
    }
  }
}
</code></pre>
<p>This forces the agent to think before it acts. It might actually realize the reason isn't valid and decide not to call the tool at all.</p>
<p>And you can take it further — spin up a lightweight validation agent running on a small, fast model that takes the tool name, the reason, and the conversation context, and decides whether the call is actually justified:</p>
<pre><code class="language-typescript">async function validateToolCall(toolName: string, reason: string, context: string) {
  const response = await llm.complete({
    model: "fast-small-model",
    prompt: `
      Tool requested: ${toolName}
      Reason given: ${reason}
      Conversation context: ${context}

      Is this tool call justified? Reply YES or NO with a brief explanation.
    `,
  });

  return response.text.startsWith("YES");
}
</code></pre>
<p>If the validation agent says no — the tool doesn't run.</p>
<p>This catches hallucinated tool calls, prompt injection attempts, and cases where the agent is just calling tools out of habit rather than necessity. 🛡️</p>
<hr />
<h2>Wrapping Up</h2>
<p>We are designing so many agents today. And we're doing it fast. But the harness — the security, the constraints, the access controls — is getting left behind.</p>
<p>At the very least, we should be sure we're not giving an agent access to something it shouldn't have. That the tools we build have opinions about how they get used. That there are guardrails that exist at the architecture level, not just in a prompt.</p>
<p>Strip the identity params. Enforce behavioral constraints in code. Add a reasoning checkpoint before execution.</p>
<p>These three layers won't just make your agent more secure. They'll make it more reliable, more predictable, and way easier to debug when something goes wrong.</p>
<p>And trust me — something will go wrong. The question is whether your harness was ready for it.</p>
<p>Hope you liked the read, follow me on my socials for more tech content. See you in the next blog 👋🏻</p>
]]></content:encoded></item><item><title><![CDATA[I Reverse Engineered Claude's UI Widget — And It Changed How I Think About Building LLM Apps]]></title><description><![CDATA[So we've all seen Anthropic ship features at an incredible pace, right? And the easy assumption is — ah, they probably have Mythos, some model more powerful than what's publicly available, and they're]]></description><link>https://blogs.navayuvan.com/i-reverse-engineered-claude-s-ui-wi-and-it-changed-how-i-think-about-building-llm-apps</link><guid isPermaLink="true">https://blogs.navayuvan.com/i-reverse-engineered-claude-s-ui-wi-and-it-changed-how-i-think-about-building-llm-apps</guid><category><![CDATA[claude]]></category><category><![CDATA[claude-code]]></category><category><![CDATA[claude ai]]></category><category><![CDATA[AI]]></category><category><![CDATA[#anthropic]]></category><category><![CDATA[llm]]></category><category><![CDATA[agentic AI]]></category><category><![CDATA[software development]]></category><category><![CDATA[Software Engineering]]></category><dc:creator><![CDATA[Navayuvan Subramanian]]></dc:creator><pubDate>Wed, 22 Apr 2026 13:53:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6544f59782acc66595e68746/4fafc469-c393-4500-928b-53f7febcb6bb.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>So we've all seen Anthropic ship features at an incredible pace, right? And the easy assumption is — ah, they probably have Mythos, some model more powerful than what's publicly available, and they're using that internally to move fast.</p>
<p>But that's not the only reason. And honestly, it's not even the most interesting one.</p>
<p>About three months back, I started using Claude as my primary assistant for pretty much everything. And I noticed something that genuinely caught my attention.</p>
<p>When I ask Claude something simple, it responds in plain text. But when the answer is complex, or when there's a lot of information to show — it renders a UI right inside the Claude app. An interactive widget I can actually play with. Not just text. A real interface.</p>
<p>I started wondering — <em>how are they doing this?</em> 🤔</p>
<hr />
<h2>My First Guess Was Wrong</h2>
<p>My initial assumption was that Anthropic had built a library of React components, given the LLM instructions on when and how to use each one, and when Claude responds, it generates a JSON payload that the frontend maps to those components.</p>
<p>That seemed reasonable to me.</p>
<p>I was completely wrong.</p>
<p>So I opened Claude on the web, pulled up the network tab, and inspected the actual response. I reverse engineered how Claude renders its UI.</p>
<p>What I found was surprising. 👀</p>
<hr />
<h2>What's Actually Happening</h2>
<p>The response wasn't JSON. It wasn't a reference to any predefined component.</p>
<p><strong>It was a plain HTML, CSS, and JavaScript file — with inline styles.</strong></p>
<p>That's when it clicked. They're not using a component library to build the UI. They went a level below that. They provided Claude with a <strong>design system</strong> — the design principles, the basic styling rules, how a button should look and behave — and then asked Claude to generate HTML, CSS, and JavaScript on its own.</p>
<p>They take that single HTML file and <strong>render it in an iframe</strong>.</p>
<blockquote>
<p><em>"They didn't build the UI. They taught Claude how to build it."</em></p>
</blockquote>
<p>Think about what this means. LLMs write good code. Anthropic gave Claude a design system and said — generate the UI. And as the model gets smarter, the UI it generates gets better. Automatically. Without changing a single line of their own code. 🚀</p>
<img src="https://cdn.hashnode.com/uploads/covers/6544f59782acc66595e68746/f158a968-a2e8-4f93-b9f2-83b76c60233e.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h2>Splitting the Hands from the Brain</h2>
<p>I later came across a blog post from Anthropic that described this concept — <em>splitting the hands from the brain</em>.</p>
<p>The idea is this: most developers write prompts and instructions that are tightly coupled to a specific model. If a model doesn't do something well, you go in and patch the prompt. You hardcode workarounds. You over-instruct.</p>
<p>What Anthropic is doing instead is providing <strong>raw tools</strong> to the LLM and letting the model figure out how to use them.</p>
<blockquote>
<p><em>"The instructions stay the same. The model just gets better at using them."</em></p>
</blockquote>
<p>So if you're using Claude Sonnet 4.6, the UI it generates is solid. Move to Opus, it gets significantly better. Move to Mythos — it's on another level entirely. And Anthropic didn't have to touch their instructions. The model just got better at using the same tools.</p>
<p>That's the key insight. 💡</p>
<img src="https://cdn.hashnode.com/uploads/covers/6544f59782acc66595e68746/69b14329-378c-460e-94e5-76e2c8467b66.png" alt="" />

<hr />
<h2>Why This Should Change How We Build LLM Apps</h2>
<p>We have access to the same models Anthropic is using. But what are most of us doing? We're hardcoding logic into prompts. We're writing harness that's tightly coupled to a specific model's behavior. And the moment a smarter model ships, that harness becomes stale.</p>
<p>We should stop encoding specific instructions into prompts and start thinking about building <strong>better tools with clearer interfaces</strong> — tools that any LLM, today or two years from now, can pick up and use effectively.</p>
<blockquote>
<p><em>"Stop writing instructions for the model. Start building tools for it."</em></p>
</blockquote>
<hr />
<h2>It's Not as Simple as Writing a Good Prompt</h2>
<p>I'll be honest — I used to think building LLM apps was straightforward. Give it a good prompt, tweak it when something breaks, move on.</p>
<p>That's not how it works.</p>
<p>Architecting an agent properly takes real thought. What Anthropic is doing is genuinely different from what most companies are doing right now. We're still treating AI like a rule-following system — developers trying to hardcode intelligence into a prompt instead of letting the model use its own.</p>
<p>Here's a better way to think about it: imagine you're handed a fixed set of components and told to build something. No flexibility, no room to think. You just assemble what's given.</p>
<p>Now imagine instead someone hands you a <strong>design system</strong> — guidelines, principles, a foundation — and says, <em>make it look great, adapt as needed</em>. Suddenly there's room for judgment. For creativity. For the model to actually do what it's good at.</p>
<blockquote>
<p><em>"Give a model components, and it assembles. Give it a design system, and it creates."</em></p>
</blockquote>
<p>That's what Anthropic figured out. And I think it's worth all of us taking a step back and rethinking how we're building.</p>
<p>Hope you like the read, see you on the next blog!</p>
]]></content:encoded></item><item><title><![CDATA[Why Using Multiple Claude Code Accounts Is Broken — And the Setup That Finally Fixed It]]></title><description><![CDATA[I use multiple Claude Code accounts.
One is my office account with the Pro plan. Another is a shared account I use with my colleagues on the Max plan.
At first, I assumed this would be simple.
Log out]]></description><link>https://blogs.navayuvan.com/multiple-claude-code-workspace</link><guid isPermaLink="true">https://blogs.navayuvan.com/multiple-claude-code-workspace</guid><category><![CDATA[claude-code]]></category><category><![CDATA[claude.ai]]></category><category><![CDATA[AI]]></category><category><![CDATA[Developer Workflow]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[AI Development Services]]></category><category><![CDATA[ai agents]]></category><category><![CDATA[coding agents]]></category><category><![CDATA[developer experience]]></category><dc:creator><![CDATA[Navayuvan Subramanian]]></dc:creator><pubDate>Fri, 03 Apr 2026 13:22:03 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6544f59782acc66595e68746/78fcf973-183a-497f-84d5-9cadf36e7d6f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I use multiple Claude Code accounts.</p>
<p>One is my office account with the Pro plan. Another is a shared account I use with my colleagues on the Max plan.</p>
<p>At first, I assumed this would be simple.</p>
<p>Log out. Log in. Maybe switch profiles. Done.</p>
<p>But that is not how Claude Code works.</p>
<p>Every time I switched accounts, I either lost my project history, lost my custom commands, or had to keep setting everything up again.</p>
<p>And the worst part?</p>
<p>The only thing that actually needed to be different between accounts was the authentication.</p>
<p>Everything else — my project history, my commands, my MCP setup, my CLAUDE.md instructions, my settings — should have stayed the same.</p>
<p>Instead, Claude Code treats everything as one giant config folder.</p>
<p>So if you want multiple accounts, you end up duplicating everything.</p>
<p>That quickly becomes painful.</p>
<h2>The Actual Problem</h2>
<p>Claude Code stores almost everything inside a single config directory.</p>
<p>That includes:</p>
<ul>
<li><p>Authentication</p>
</li>
<li><p>Settings</p>
</li>
<li><p>Project history</p>
</li>
<li><p>Custom commands</p>
</li>
<li><p>MCP configuration</p>
</li>
<li><p>CLAUDE.md instructions</p>
</li>
</ul>
<p>The issue is that authentication is account-specific.</p>
<p>But the rest is not.</p>
<p>I do not want separate project histories for every account. I do not want to recreate the same custom commands three times. I definitely do not want to copy-paste the same CLAUDE.md file every time I make a change.</p>
<p>After a few switches, my setup looked like this:</p>
<ul>
<li><p>One account had the latest commands</p>
</li>
<li><p>Another had the right MCP config</p>
</li>
<li><p>A third one had the actual project history I wanted</p>
</li>
</ul>
<p>Everything was fragmented.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6544f59782acc66595e68746/e32c17eb-b4ea-4815-aa25-311925aa7e35.svg" alt="" style="display:block;margin:0 auto" />

<p>And every time I switched accounts, something would silently stop working.</p>
<h2>The Setup That Actually Works</h2>
<p>The fix was separating authentication from everything else.</p>
<p>Instead of giving every account its own full Claude config, I created:</p>
<ul>
<li><p>One shared folder for everything common</p>
</li>
<li><p>One small folder per account containing only auth</p>
</li>
</ul>
<p>My setup now looks like this:</p>
<p>The fix was separating authentication from everything else.</p>
<p>Instead of giving every account its own full Claude config, I created:</p>
<ul>
<li><p>One shared folder for everything common</p>
</li>
<li><p>One small folder per account containing only auth</p>
</li>
</ul>
<p>My setup now looks like this:</p>
<pre><code class="language-bash">~/.claude-shared/
  settings.json
  CLAUDE.md
  projects/
  commands/
  mcp.json

~/.claude-office-pro/
  auth.json
  settings.json -&gt; ~/.claude-shared/settings.json
  CLAUDE.md -&gt; ~/.claude-shared/CLAUDE.md
  projects -&gt; ~/.claude-shared/projects
  commands -&gt; ~/.claude-shared/commands
  mcp.json -&gt; ~/.claude-shared/mcp.json

~/.claude-shared-max/
  auth.json
  settings.json -&gt; ~/.claude-shared/settings.json
  CLAUDE.md -&gt; ~/.claude-shared/CLAUDE.md
  projects -&gt; ~/.claude-shared/projects
  commands -&gt; ~/.claude-shared/commands
  mcp.json -&gt; ~/.claude-shared/mcp.json
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/6544f59782acc66595e68746/5d5f77d7-b5ce-4681-91c1-7994060a2c3d.svg" alt="" style="display:block;margin:0 auto" />

<p>The only real file that is different between accounts is <code>auth.json</code>.</p>
<p>Everything else is shared using symlinks.</p>
<p>Which means:</p>
<ul>
<li><p>I log into each account once</p>
</li>
<li><p>All accounts use the same project history</p>
</li>
<li><p>All accounts use the same commands</p>
</li>
<li><p>All accounts use the same CLAUDE.md instructions</p>
</li>
<li><p>Changing something in one place updates it everywhere</p>
</li>
</ul>
<p>This was the first setup that actually felt usable.</p>
<h2>Setting It Up</h2>
<p>First, create the shared folder and separate account folders.</p>
<pre><code class="language-bash">mkdir -p ~/.claude-shared
mkdir -p ~/.claude-office-pro
mkdir -p ~/.claude-shared-max
</code></pre>
<p>Then move all the common files into the shared folder.</p>
<pre><code class="language-bash">mv ~/.claude/settings.json ~/.claude-shared/
mv ~/.claude/CLAUDE.md ~/.claude-shared/
mv ~/.claude/projects ~/.claude-shared/
mv ~/.claude/commands ~/.claude-shared/
mv ~/.claude/mcp.json ~/.claude-shared/
</code></pre>
<p>Now create symlinks inside each account folder.</p>
<pre><code class="language-bash">ln -s ~/.claude-shared/settings.json ~/.claude-office-pro/settings.json
ln -s ~/.claude-shared/CLAUDE.md ~/.claude-office-pro/CLAUDE.md
ln -s ~/.claude-shared/projects ~/.claude-office-pro/projects
ln -s ~/.claude-shared/commands ~/.claude-office-pro/commands
ln -s ~/.claude-shared/mcp.json ~/.claude-office-pro/mcp.json

ln -s ~/.claude-shared/settings.json ~/.claude-shared-max/settings.json
ln -s ~/.claude-shared/CLAUDE.md ~/.claude-shared-max/CLAUDE.md
ln -s ~/.claude-shared/projects ~/.claude-shared-max/projects
ln -s ~/.claude-shared/commands ~/.claude-shared-max/commands
ln -s ~/.claude-shared/mcp.json ~/.claude-shared-max/mcp.json
</code></pre>
<p>Then create aliases so switching accounts becomes one command.</p>
<pre><code class="language-bash">alias claude-office='CLAUDE_CONFIG_DIR=~/.claude-office-pro claude'
alias claude-shared='CLAUDE_CONFIG_DIR=~/.claude-shared-max claude'
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/6544f59782acc66595e68746/fd44232c-6cd4-4709-b36e-4dd6f863190c.svg" alt="" style="display:block;margin:0 auto" />

<p>Now when I want to use my office Pro account, I run:</p>
<pre><code class="language-bash">claude-office
</code></pre>
<p>And when I want to switch to the shared Max account that I use with my colleagues:</p>
<pre><code class="language-bash">claude-shared
</code></pre>
<p>That is it.</p>
<p>No logging in again. No copying files. No broken commands.</p>
<h2>The Bigger Lesson</h2>
<p>This looked like a Claude Code problem.</p>
<p>But it is actually a common problem with developer tools.</p>
<p>Most tools bundle:</p>
<ul>
<li><p>Identity</p>
</li>
<li><p>Configuration</p>
</li>
<li><p>User data</p>
</li>
</ul>
<p>Into one folder.</p>
<p>That works fine until you need multiple accounts.</p>
<p>Then suddenly the thing you actually want to isolate — authentication — is tied to a bunch of things that should have been shared.</p>
<p>The best setups separate those concerns.</p>
<p>Authentication should be isolated. Everything else should be reusable.</p>
<p>Once I changed my Claude setup to follow that idea, switching accounts stopped feeling like context switching.</p>
<p>It finally felt like the same workspace, just with a different login.</p>
<p>And honestly, that is probably how it should have worked in the first place.</p>
]]></content:encoded></item><item><title><![CDATA[Why Most AI Coding Workflows Fail — and How Mine Cut 2 Weeks of Work to 2 Days]]></title><description><![CDATA[Strong suggestion: don’t copy my workflow — read through the failures, mistakes, and learnings, and use them to craft a workflow that works for you.
If you want to jump straight to my current workflow, go here →👉 Iteration 7: My Current Workflow (Wh...]]></description><link>https://blogs.navayuvan.com/why-most-ai-coding-workflows-fail-and-how-mine-cut-2-weeks-of-work-to-2-days</link><guid isPermaLink="true">https://blogs.navayuvan.com/why-most-ai-coding-workflows-fail-and-how-mine-cut-2-weeks-of-work-to-2-days</guid><category><![CDATA[Developer]]></category><category><![CDATA[AI]]></category><category><![CDATA[coding]]></category><category><![CDATA[cursor]]></category><category><![CDATA[cursor ai]]></category><category><![CDATA[software development]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[software architecture]]></category><dc:creator><![CDATA[Navayuvan Subramanian]]></dc:creator><pubDate>Tue, 23 Dec 2025 04:36:22 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1766463176811/a59c3e08-b6be-4d37-9506-980ca0bdb04f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p><strong>Strong suggestion: don’t copy my workflow — read through the failures, mistakes, and learnings, and use them to craft a workflow that works for you.</strong></p>
<p>If you want to jump straight to my <strong>current workflow</strong>, go here →<br />👉 <a target="_blank" href="https://blogs.navayuvan.dev/why-most-ai-coding-workflows-fail-and-how-mine-cut-2-weeks-of-work-to-2-days#heading-iteration-7-my-current-workflow-what-finally-worked">Iteration 7: My Current Workflow (What Finally Worked)</a></p>
</blockquote>
<p>I’ve been thinking about writing this for a while.</p>
<p>I’m still learning, but over the past year, I’ve been coding with AI almost every day — both in personal projects and in production systems at work. I’m not claiming this is <em>the</em> best way to use AI, but this is the workflow that helped me compress what usually takes <strong>three weeks into two days</strong>.</p>
<p>This blog isn’t about a single “aha” moment.<br />It’s about <strong>iterations</strong> — where things worked, where they broke, and what each failure taught me.</p>
<hr />
<h2 id="heading-iteration-1-ai-felt-like-magic">Iteration 1: AI Felt Like Magic</h2>
<p>When I first started coding with AI, I was honestly impressed.</p>
<p>Things that used to take real mental effort were getting done effortlessly:</p>
<ul>
<li><p>Solving a LeetCode problem</p>
</li>
<li><p>Creating Simple UI with React and Stylings</p>
</li>
<li><p>Writing a simple API to CRUD a model</p>
</li>
</ul>
<p>AI handled all of this extremely well.</p>
<p>At this stage, I was mostly using Cursor for <strong>personal projects</strong>. These projects were small, the codebase was tiny, and the AI had very little context to reason about.</p>
<p>Naturally, I assumed:</p>
<blockquote>
<p>“If this works so well for personal projects, it should work even better for office projects.”</p>
</blockquote>
<p>That assumption didn’t last long.</p>
<hr />
<h2 id="heading-iteration-2-production-codebases-change-everything">Iteration 2: Production Codebases Change Everything</h2>
<p>Around 6–8 months ago, my company gave me a paid Cursor license.<br />I started using it on our <strong>actual product codebase</strong>.</p>
<p>The difference was immediate.</p>
<p>Same prompts.<br />Same workflow.<br />Completely different results.</p>
<p>In personal projects, the AI was dealing with hundreds or a few thousand lines of code.<br />In production, it had to reason about <strong>tens of thousands of lines</strong>, multiple modules, and years of architectural decisions.</p>
<p>That’s when things started breaking.</p>
<hr />
<h2 id="heading-iteration-3-ask-plan-build-worked-until-it-didnt">Iteration 3: Ask → Plan → Build (Worked… Until It Didn’t)</h2>
<p>To regain control, I introduced structure.</p>
<p>Before implementing any feature, I started doing this:</p>
<ol>
<li><p>Switch Cursor to <strong>Ask mode</strong></p>
</li>
<li><p>Ask it to understand specific folders or modules</p>
</li>
<li><p>Explain the feature I wanted to build</p>
</li>
<li><p>Ask it to give me a <strong>plan</strong></p>
</li>
<li><p>Switch to <strong>Agent mode</strong> and ask it to implement the plan</p>
</li>
</ol>
<p>This was before Cursor even had a dedicated Plan mode.</p>
<p>And honestly — it worked really well.</p>
<p>I completed multiple backend-heavy features using this approach, and I was genuinely surprised by how well the AI understood the code and executed changes.</p>
<p>Until I hit a full-stack feature.</p>
<hr />
<h2 id="heading-iteration-4-full-stack-complexity-broke-the-workflow">Iteration 4: Full-Stack Complexity Broke the Workflow</h2>
<p>The next feature involved everything:</p>
<ul>
<li><p>Frontend</p>
</li>
<li><p>Backend</p>
</li>
<li><p>Queues and background jobs</p>
</li>
<li><p>Socket events</p>
</li>
<li><p>Real-time UI updates</p>
</li>
<li><p>State management</p>
</li>
</ul>
<p>The number of moving parts exploded.</p>
<p>I followed the same workflow.</p>
<p>The output was bad.</p>
<ul>
<li><p>TypeScript types replaced with <code>any</code> and <code>unknown</code></p>
</li>
<li><p>Method signature mismatches</p>
</li>
<li><p>Code written just to “make it work”</p>
</li>
<li><p>Hidden bugs everywhere</p>
</li>
</ul>
<p>That’s when I realized something important:</p>
<blockquote>
<p>The workflow didn’t change.<br />The <strong>task complexity did</strong>.</p>
</blockquote>
<hr />
<h2 id="heading-iteration-5-treating-ai-like-a-fresh-developer">Iteration 5: Treating AI Like a Fresh Developer</h2>
<p>I asked myself a simple question:</p>
<blockquote>
<p>“If I were giving this task to a fresh developer, how would I do it?”</p>
</blockquote>
<p>I wouldn’t explain everything at once.</p>
<p>So I split the feature into <strong>independent chunks</strong>:</p>
<ol>
<li><p>Backend</p>
</li>
<li><p>Frontend</p>
</li>
</ol>
<p>The backend agent didn’t know about the frontend.<br />The frontend agent only knew the API contract.</p>
<p>For the backend, I gave very clear instructions:</p>
<ul>
<li><p>Queue behaviour</p>
</li>
<li><p>API design</p>
</li>
<li><p>Event flow</p>
</li>
</ul>
<p>Then I handed the API contract to the frontend.</p>
<p>This worked <strong>beautifully</strong>.</p>
<p>Until I tried spinning up a new microservice.</p>
<hr />
<h2 id="heading-iteration-6-plan-mode-enters-and-still-fails-at-scale">Iteration 6: Plan Mode Enters (And Still Fails at Scale)</h2>
<p>Around this time, <strong>Cursor launched Plan mode</strong>, and naturally, I started using it heavily.</p>
<p>On paper, this felt like the missing piece.</p>
<p>The idea was simple:</p>
<ul>
<li><p>Create a detailed plan in markdown</p>
</li>
<li><p>Let the agent refer to it instead of holding everything in memory</p>
</li>
<li><p>Execute step by step</p>
</li>
</ul>
<p>For medium-sized features, this worked <em>really</em> well.</p>
<p>But then I hit a much bigger task.</p>
<p>This feature involved:</p>
<ul>
<li><p>Creating a new microservice</p>
</li>
<li><p>Linking it with existing services</p>
</li>
<li><p>Implementing cutting edge tools</p>
</li>
<li><p>Adopting modern Node.js and TypeScript patterns</p>
</li>
<li><p>Handling cross-service communication</p>
</li>
</ul>
<p>I brainstormed deeply with the AI and created a <em>pixel-perfect plan</em>.</p>
<p>The plan itself was solid.</p>
<p>The execution wasn’t.</p>
<ul>
<li><p><code>any</code> everywhere</p>
</li>
<li><p>Interface mismatches</p>
</li>
<li><p>Build failures</p>
</li>
<li><p>Runtime issues</p>
</li>
</ul>
<p>That’s when I finally understood the real problem:</p>
<blockquote>
<p><strong>Even with Plan mode, a single massive plan is still too much context.</strong></p>
</blockquote>
<p>Plan mode helped — but it didn’t remove the need to <strong>break the problem down further</strong>.</p>
<hr />
<h2 id="heading-why-this-fails-even-for-humans">Why This Fails (Even for Humans)</h2>
<p>Imagine your CTO hands you:</p>
<ul>
<li><p>An 800–900 page tech requirement document, includes</p>
</li>
<li><p>Multiple microservices</p>
</li>
<li><p>Multiple protocols like Kafka or gRPC</p>
</li>
<li><p>Multiple external dependencies</p>
</li>
</ul>
<p>…and asks you to <strong>implement everything in a single go</strong>.</p>
<p>You wouldn’t be productive.</p>
<p>That’s why we have:</p>
<ul>
<li><p>Epics</p>
</li>
<li><p>User stories</p>
</li>
<li><p>Tasks</p>
</li>
</ul>
<p>But when it comes to AI, we forget this and treat it like a <strong>supernatural entity</strong>.</p>
<p>It’s not.</p>
<p>It has limits.</p>
<hr />
<h2 id="heading-iteration-7-my-current-workflow-what-finally-worked">Iteration 7: My Current Workflow (What Finally Worked)</h2>
<p>This is the workflow that actually stuck.</p>
<h3 id="heading-step-1-start-with-high-level-architecture">Step 1: Start With High-Level Architecture</h3>
<p>No function signatures.<br />No deep implementation details.</p>
<p>Just:</p>
<ul>
<li><p>Models</p>
</li>
<li><p>Responsibilities</p>
</li>
<li><p>Clear system boundaries</p>
</li>
</ul>
<hr />
<h3 id="heading-step-2-let-ai-propose-mid-level-phases">Step 2: Let AI Propose Mid-Level Phases</h3>
<p>I ask the AI to break the feature into phases.</p>
<p>Usually, I get something like:</p>
<ul>
<li><p>8 phases</p>
</li>
<li><p>Each phase with 4–5 bullet points</p>
</li>
</ul>
<p>Now the big feature is split into <strong>manageable chunks</strong>.</p>
<hr />
<h3 id="heading-step-3-create-a-separate-plan-for-each-phase">Step 3: Create a Separate Plan for Each Phase</h3>
<p>Each phase gets its own plan:</p>
<ul>
<li><p>Separate document</p>
</li>
<li><p>Less than ~500 lines</p>
</li>
<li><p>Focused and crisp</p>
</li>
</ul>
<p>A 5,000-line plan is no better than no plan.</p>
<hr />
<h3 id="heading-step-4-manual-review-is-mandatory">Step 4: Manual Review Is Mandatory</h3>
<p>Before building anything, I review each plan:</p>
<ul>
<li><p>Fix naming</p>
</li>
<li><p>Correct modeling flaws</p>
</li>
<li><p>Adjust structure</p>
</li>
</ul>
<p>You <strong>cannot blindly trust AI</strong>.<br />You still own the architecture.</p>
<hr />
<h3 id="heading-step-5-build-phase-by-phase">Step 5: Build Phase by Phase</h3>
<p>Slow.<br />Deliberate.<br />Controlled.</p>
<p>Phase 1 → Build<br />Phase 2 → Build<br />…<br />Phase 8 → Build</p>
<p>This is where everything finally clicked.</p>
<hr />
<h2 id="heading-the-result">The Result</h2>
<p>The entire feature was completed in <strong>2–3 days</strong>.</p>
<p>Without this workflow, it would have easily taken <strong>weeks</strong>.</p>
<p>The code was:</p>
<ul>
<li><p>Clean</p>
</li>
<li><p>Strongly typed</p>
</li>
<li><p>No unnecessary <code>any</code></p>
</li>
<li><p>Easy to reason about and maintain</p>
</li>
</ul>
<hr />
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>AI productivity gains are real — but only if you respect its limits.</p>
<p>Think of AI as:</p>
<blockquote>
<p>A very fast fresh developer who needs <strong>clear, scoped, precise instructions</strong></p>
</blockquote>
<p>This is my <strong>7th iteration</strong> of this workflow, and it’ll iterate as the complexity increases.</p>
<p>If you think I missed something, tell me.<br />If you’ve found a better approach, share it.</p>
<p>Let’s learn from each other and write <strong>better code with AI</strong>, not just more code.</p>
<p>Cheers!</p>
]]></content:encoded></item><item><title><![CDATA[TypeScript Won’t Save Your Product: The Case for Clean Code Practices]]></title><description><![CDATA[You think using TypeScript is gonna keep your product alive, definitely NOT 😂 Here's why!
People are obsessed with TypeScript, and I don't say it's wrong.
But when it's used incorrectly (without proper clean code principles), even it'll cause a disa...]]></description><link>https://blogs.navayuvan.com/typescript-wont-save-your-product-the-case-for-clean-code-practices</link><guid isPermaLink="true">https://blogs.navayuvan.com/typescript-wont-save-your-product-the-case-for-clean-code-practices</guid><category><![CDATA[TypeScript]]></category><category><![CDATA[clean code]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[developer productivity]]></category><dc:creator><![CDATA[Navayuvan Subramanian]]></dc:creator><pubDate>Sat, 14 Dec 2024 15:39:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1734190679040/f00c64e3-e032-43ae-b9f1-968160be8816.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You think using TypeScript is gonna keep your product alive, definitely NOT 😂 Here's why!</p>
<p>People are obsessed with TypeScript, and I don't say it's wrong.</p>
<p>But when it's used incorrectly (without proper clean code principles), even it'll cause a disaster instead of saving us.</p>
<p>Let me give you an example:</p>
<h3 id="heading-the-problem">The Problem</h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> getLogs = <span class="hljs-function">(<span class="hljs-params">
  userId: <span class="hljs-built_in">string</span>,
  <span class="hljs-keyword">from</span>?: <span class="hljs-built_in">Date</span>,
  to?: <span class="hljs-built_in">Date</span>,
  sort?: <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">string</span>, <span class="hljs-built_in">number</span>&gt;,
  limit?: <span class="hljs-built_in">number</span>,
  lastLogId?: <span class="hljs-built_in">string</span>
</span>) =&gt;</span> {};
</code></pre>
<p>This function might look fine at first glance, but it's not.</p>
<p>Now think about the consumer:</p>
<p>How will they know the order of all those optional parameters?</p>
<p>Having this many parameters in a function not only makes it harder to maintain but also makes it prone to errors.</p>
<h3 id="heading-the-solution">The Solution</h3>
<p>Here's a cleaner approach:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">interface</span> LogsOptions {
  interval?: {
    <span class="hljs-keyword">from</span>?: <span class="hljs-built_in">Date</span>;
    to?: <span class="hljs-built_in">Date</span>;
  };
  pagination?: {
    limit?: <span class="hljs-built_in">number</span>;
    lastLogId?: <span class="hljs-built_in">string</span>;
  };
  sort?: <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">string</span>, <span class="hljs-built_in">number</span>&gt;;
}

<span class="hljs-keyword">const</span> getLogs = <span class="hljs-function">(<span class="hljs-params">userId: <span class="hljs-built_in">string</span>, options?: LogsOptions</span>) =&gt;</span> {};
</code></pre>
<p>This approach is:<br />✅ Cleaner<br />✅ Easier to maintain<br />✅ Less prone to errors</p>
<p>Consumers can immediately understand what parameters are available without guessing.</p>
<h3 id="heading-moral-of-the-story">Moral of the story</h3>
<p>TypeScript alone won't save your product. Regardless of the language or framework you use, clean code principles play a vital role in your product's longevity.</p>
<p>Frameworks and languages come and go, but the fundamentals stay the same.</p>
<p>💬 What practices do you follow to keep your product alive? Share your thoughts in the comments!</p>
<p>Cheers,<br /><a target="_blank" href="https://navayuvan.dev">Navayuvan Subramanian</a></p>
]]></content:encoded></item><item><title><![CDATA[Why Small PRs Aren’t Always the Best Solution: Lessons from Startup Development]]></title><description><![CDATA[We often hear experts say, "Compose small PRs! The smaller the PRs, the easier they are to understand." But that's not always right! 🤔
I used to believe that small PRs were the cleanest and best way to develop.
But in recent times, I've realized thi...]]></description><link>https://blogs.navayuvan.com/why-small-prs-arent-always-the-best-solution-lessons-from-startup-development</link><guid isPermaLink="true">https://blogs.navayuvan.com/why-small-prs-arent-always-the-best-solution-lessons-from-startup-development</guid><category><![CDATA[software development]]></category><category><![CDATA[code review]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[Developer]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[Git]]></category><dc:creator><![CDATA[Navayuvan Subramanian]]></dc:creator><pubDate>Sat, 14 Dec 2024 15:33:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1734190294915/82627702-c9c0-4e8d-a433-f64cc9d526da.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>We often hear experts say, "<strong><em>Compose small PRs! The smaller the PRs, the easier they are to understand.</em></strong>" But that's not always right! 🤔</p>
<p>I used to believe that small PRs were the cleanest and best way to develop.</p>
<p>But in recent times, I've realized this approach doesn't always work.</p>
<p>This might be effective for companies moving at a slower pace.</p>
<p>However, for startups pushing out at least one release a week, this can slow things down significantly. 🐢</p>
<p>In most startups, developers own large chunks of features.</p>
<p>Splitting these into smaller tasks and raising separate PRs not only wastes time but also makes reviewers lose context about the feature as a whole. 😵‍💫</p>
<p>But then, reviewing a PR with 100+ file changes is no walk in the park either. So, what's the solution?</p>
<p>Here's what works for me:</p>
<h3 id="heading-provide-a-clear-description-of-the-changes">Provide a clear description of the changes.</h3>
<p>You don't need to be overly detailed, but a high-level summary of the changes goes a long way.</p>
<h3 id="heading-avoid-unnecessary-changes">Avoid unnecessary changes</h3>
<p>Skip alignment changes or edits unrelated to the feature you're working on.</p>
<h3 id="heading-annotate-unrelated-changes">Annotate unrelated changes</h3>
<p>If some unrelated changes are unavoidable, add comments explaining why they're included. This helps reviewers stay on track.</p>
<h3 id="heading-streamline-priority-reviews">Streamline priority reviews.</h3>
<p>Let's be real---no one jumps to review a massive PR immediately. If it's a priority, set up a quick call with the reviewers to explain the changes. It makes the review process faster and more efficient.</p>
<h3 id="heading-respect-the-reviewers-time">Respect the reviewer's time</h3>
<p>Ask your reviewers when they can reasonably review the PR and follow up after the deadline. Respecting their time is key.</p>
<h3 id="heading-follow-up-without-annoying">Follow up without annoying</h3>
<p>Reviewers have their own priorities, and forgetting your PR is natural. Gently remind them without being pushy to get it reviewed.</p>
<hr />
<p>This is how I approach or request PR reviews at my workplace.</p>
<p>How do you handle your PR reviews? Let me know in the comments! 💬</p>
<p>And don't forget to follow <a target="_blank" href="https://navayuvan.dev">Navayuvan Subramanian</a> for more tech insights! 🚀</p>
]]></content:encoded></item><item><title><![CDATA[Know what is Developer's Bias and stay away from it!]]></title><description><![CDATA[We developers have a problem: we think we can build anything and use it ourselves instead of paying for it. But that's not correct! 🚫
Let me make it clear with an example: Todoist.
It's a simple to-do app with a monthly subscription of $5.
Now, we m...]]></description><link>https://blogs.navayuvan.com/know-what-is-developers-bias-and-stay-away-from-it</link><guid isPermaLink="true">https://blogs.navayuvan.com/know-what-is-developers-bias-and-stay-away-from-it</guid><category><![CDATA[Productivity]]></category><category><![CDATA[development]]></category><dc:creator><![CDATA[Navayuvan Subramanian]]></dc:creator><pubDate>Sat, 14 Dec 2024 15:23:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1734189683831/8f2c7bdd-b325-4f37-822a-b9292fadac1e.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>We developers have a problem: we think we can build anything and use it ourselves instead of paying for it. But that's not correct! 🚫</p>
<p>Let me make it clear with an example: <a target="_blank" href="https://todoist.com/">Todoist</a>.</p>
<p>It's a simple to-do app with a monthly subscription of $5.</p>
<p>Now, we might think, "I can develop that app myself." But, hear me out!</p>
<p>The payment we provide them includes:<br />👉 The research they've done to make a feature usable.<br />👉 The efforts they've put in to integrate with other applications. (Slack, G-Cal, etc.)<br />👉 Client app support across multiple platforms (browser extensions, desktop, web apps, etc.).<br />👉 Constant improvement of existing features or the development of new ones.<br />👉 And many more.</p>
<p>Now, can we do all of this consistently just to save $5? Absolutely not! 🙅‍♂️</p>
<p>The efforts involved in doing these are enormous, and there's an entire company dedicated to it. So, instead of building it ourselves, paying for the subscription seems like a smarter choice. 💡</p>
<p>Does that mean we should never build an app for ourselves? Not at all. 🙌</p>
<p>Here's when it makes sense to build the app ourselves:<br />- When there are no apps on the market suitable for our needs.<br />- When the amount we'd pay for an app is less than or equal to the value of the time we save by not developing it ourselves.<br />- When the existing apps in the market have major pain points, solving them could even lead to a new startup or business idea.</p>
<p>If any of these criteria are met, then building it ourselves might be the way to go! 🚀</p>
<p>In the end, it's all about balancing time and effort. ⚖️</p>
<p>So, the next time you think about building an app yourself, take a moment to reflect on whether it's really worth the time and effort you're about to invest. Then, decide wisely! ✅</p>
<p>And, don't forget to follow me. I'll be dropping some interesting stuffs soon 😉</p>
<p>Your fellow developer,<br /><a target="_blank" href="https://navayuvan.dev">Navayuvan Subramanian</a></p>
]]></content:encoded></item><item><title><![CDATA[Efficient API Consumption in React TypeScript]]></title><description><![CDATA[APIs are playing an important role in software development today. In the world of cloud computing, no application can function without APIs. The way you architect and consume these APIs can significantly impact your app's performance.
Let’s explore b...]]></description><link>https://blogs.navayuvan.com/efficient-api-consumption-in-react-typescript</link><guid isPermaLink="true">https://blogs.navayuvan.com/efficient-api-consumption-in-react-typescript</guid><category><![CDATA[React]]></category><category><![CDATA[React Native]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[react-query]]></category><category><![CDATA[axios]]></category><dc:creator><![CDATA[Navayuvan Subramanian]]></dc:creator><pubDate>Sat, 24 Aug 2024 10:04:04 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1724504050330/0823aac9-f399-459a-ad36-02bee841b3aa.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>APIs are playing an important role in software development today. In the world of cloud computing, no application can function without APIs. The way you architect and consume these APIs can significantly impact your app's performance.</p>
<p>Let’s explore building a robust HTTP Client in React TypeScript, integrating caching with Tanstack Query (formerly React Query), and creating custom hooks that streamline your API calls.</p>
<h2 id="heading-understanding-http-apis">🌐 Understanding HTTP APIs</h2>
<p>HTTP (HyperText Transfer Protocol) is the universal language systems use to communicate. Whether it’s fetching data, updating a resource, or deleting something from the server, HTTP has got us covered. Here’s a quick refresher on the HTTP methods:</p>
<ul>
<li><p><strong>GET</strong>: Grabs the data you need from the server.</p>
</li>
<li><p><strong>POST</strong>: Sends data to create something new on the server.</p>
</li>
<li><p><strong>PUT</strong>: Updates an existing resource entirely.</p>
</li>
<li><p><strong>PATCH</strong>: Updates a part of an existing resource.</p>
</li>
<li><p><strong>DELETE</strong>: Removes something from the server.</p>
</li>
</ul>
<p>Each method is like a tool in your toolbox—pick the right one for the job, and your APIs will be a joy to work with.</p>
<h2 id="heading-why-axios">💡 Why Axios?</h2>
<p>So, why are we using <strong>Axios</strong> for our HTTP Client? Imagine having a personal assistant who handles all the boring tasks like adding headers, dealing with JSON, and managing timeouts—Axios does just that! It’s a library that simplifies HTTP requests, making our code cleaner and easier to maintain.</p>
<h2 id="heading-creating-the-http-client">🛠️ Creating the HTTP Client</h2>
<h3 id="heading-step-1-installing-axios">Step 1: Installing Axios</h3>
<p>Before we dive into the fun stuff, let’s install Axios:</p>
<pre><code class="lang-bash">npm install axios
</code></pre>
<h3 id="heading-step-2-setting-up-the-http-client">Step 2: Setting Up the HTTP Client</h3>
<p>Now, let’s set up our HTTP Client to make API calls a breeze.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">"axios"</span>;

<span class="hljs-keyword">class</span> HttpClient {
  <span class="hljs-keyword">private</span> instance: AxiosInstance;

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">baseURL: <span class="hljs-built_in">string</span></span>) {
    <span class="hljs-built_in">this</span>.instance = axios.create({
      baseURL,
      withCredentials: <span class="hljs-literal">true</span>,
    });

    <span class="hljs-built_in">this</span>.instance.interceptors.request.use(<span class="hljs-built_in">this</span>.handleRequest);
    <span class="hljs-built_in">this</span>.instance.interceptors.response.use(<span class="hljs-built_in">this</span>.handleResponse);
  }

  <span class="hljs-keyword">private</span> handleRequest = (config: AxiosRequestConfig): <span class="hljs-function"><span class="hljs-params">AxiosRequestConfig</span> =&gt;</span> {
    <span class="hljs-keyword">const</span> token = <span class="hljs-built_in">localStorage</span>.getItem(<span class="hljs-string">"authToken"</span>); <span class="hljs-comment">// Replace with your token logic</span>
    <span class="hljs-keyword">if</span> (token) {
      <span class="hljs-keyword">if</span> (!config.headers) {
        config.headers = {
          Authorization: <span class="hljs-string">`Bearer <span class="hljs-subst">${token}</span>`</span>,
        };
        <span class="hljs-keyword">return</span> config;
      }

      config.headers[<span class="hljs-string">"Authorization"</span>] = <span class="hljs-string">`Bearer <span class="hljs-subst">${token}</span>`</span>;
    }
    <span class="hljs-keyword">return</span> config;
  };

  <span class="hljs-keyword">private</span> handleResponse = (response: AxiosResponse): <span class="hljs-function"><span class="hljs-params">AxiosResponse</span> =&gt;</span> {
    <span class="hljs-keyword">return</span> response;
  };

  <span class="hljs-keyword">public</span> get&lt;T&gt;(
    url: <span class="hljs-built_in">string</span>,
    config?: AxiosRequestConfig
  ): <span class="hljs-built_in">Promise</span>&lt;AxiosResponse&lt;T&gt;&gt; {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.instance.get&lt;T&gt;(url, config);
  }

  <span class="hljs-keyword">public</span> post&lt;T&gt;(
    url: <span class="hljs-built_in">string</span>,
    data: <span class="hljs-built_in">any</span>,
    config?: AxiosRequestConfig
  ): <span class="hljs-built_in">Promise</span>&lt;AxiosResponse&lt;T&gt;&gt; {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.instance.post&lt;T&gt;(url, data, config);
  }

  <span class="hljs-keyword">public</span> put&lt;T&gt;(
    url: <span class="hljs-built_in">string</span>,
    data: <span class="hljs-built_in">any</span>,
    config?: AxiosRequestConfig
  ): <span class="hljs-built_in">Promise</span>&lt;AxiosResponse&lt;T&gt;&gt; {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.instance.put&lt;T&gt;(url, data, config);
  }

  <span class="hljs-keyword">public</span> patch&lt;T&gt;(
    url: <span class="hljs-built_in">string</span>,
    data: <span class="hljs-built_in">any</span>,
    config?: AxiosRequestConfig
  ): <span class="hljs-built_in">Promise</span>&lt;AxiosResponse&lt;T&gt;&gt; {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.instance.patch&lt;T&gt;(url, data, config);
  }

  <span class="hljs-keyword">public</span> <span class="hljs-keyword">delete</span>&lt;T&gt;(
    url: <span class="hljs-built_in">string</span>,
    config?: AxiosRequestConfig
  ): <span class="hljs-built_in">Promise</span>&lt;AxiosResponse&lt;T&gt;&gt; {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.instance.delete&lt;T&gt;(url, config);
  }
}

<span class="hljs-keyword">const</span> httpClient = <span class="hljs-keyword">new</span> HttpClient(<span class="hljs-string">"https://jsonplaceholder.typicode.com"</span>);
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> httpClient;
</code></pre>
<h3 id="heading-create-a-user-model">Create a User Model</h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> Address {
  street: <span class="hljs-built_in">string</span>;
  suite: <span class="hljs-built_in">string</span>;
  city: <span class="hljs-built_in">string</span>;
  zipcode: <span class="hljs-built_in">string</span>;
  geo: {
    lat: <span class="hljs-built_in">string</span>;
    lng: <span class="hljs-built_in">string</span>;
  };
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> Company {
  name: <span class="hljs-built_in">string</span>;
  catchPhrase: <span class="hljs-built_in">string</span>;
  bs: <span class="hljs-built_in">string</span>;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> User {
  id: <span class="hljs-built_in">number</span>;
  name: <span class="hljs-built_in">string</span>;
  username: <span class="hljs-built_in">string</span>;
  email: <span class="hljs-built_in">string</span>;
  address: Address;
  phone: <span class="hljs-built_in">string</span>;
  website: <span class="hljs-built_in">string</span>;
  company: Company;
}
</code></pre>
<h2 id="heading-handling-api-requests">🔄 Handling API Requests</h2>
<h3 id="heading-creating-a-user">Creating a User</h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> createUser = <span class="hljs-keyword">async</span> (userData: UserData) =&gt; {
  <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> httpClient.post&lt;User&gt;(<span class="hljs-string">'/users'</span>, userData);
  <span class="hljs-keyword">return</span> response.data;
};
</code></pre>
<h3 id="heading-fetching-users">Fetching Users</h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> fetchUsers = <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> httpClient.get&lt;User[]&gt;(<span class="hljs-string">'/users'</span>);
  <span class="hljs-keyword">return</span> response.data;
};
</code></pre>
<h3 id="heading-updating-a-user">Updating a User</h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> updateUser = <span class="hljs-keyword">async</span> (userId: <span class="hljs-built_in">string</span>, userData: UserData) =&gt; {
  <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> httpClient.put&lt;User&gt;(<span class="hljs-string">`/users/<span class="hljs-subst">${userId}</span>`</span>, userData);
  <span class="hljs-keyword">return</span> response.data;
};
</code></pre>
<h3 id="heading-deleting-a-user">Deleting a User</h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> deleteUser = <span class="hljs-keyword">async</span> (userId: <span class="hljs-built_in">string</span>) =&gt; {
  <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> httpClient.delete(<span class="hljs-string">`/users/<span class="hljs-subst">${userId}</span>`</span>);
  <span class="hljs-keyword">return</span> response.data;
};
</code></pre>
<h2 id="heading-the-power-of-caching-with-tanstack-query">🗄️ The Power of Caching with Tanstack Query</h2>
<p>When consuming APIs, one of the biggest challenges is managing the state of the data—especially when it comes to caching. Caching allows us to store responses so that repeated requests for the same data can be served faster, improving the overall performance of our application. This is where <strong>Tanstack Query</strong> comes in. Tanstack Query is a powerful data-fetching library that makes it simple to manage server-state in your React apps, with built-in caching, synchronization, and more.</p>
<h3 id="heading-installation">Installation</h3>
<p>To get started, install Tanstack Query:</p>
<pre><code class="lang-bash">npm install @tanstack/react-query
</code></pre>
<h2 id="heading-creating-custom-hooks-for-api-requests">⚙️ Creating Custom Hooks for API Requests</h2>
<p>Creating custom hooks makes your API calls reusable and easier to manage. For each type of request (fetch, create, update, delete), we'll create a custom hook using Tanstack Query and the HttpClient we built earlier.</p>
<h3 id="heading-setting-up-query-keys">Setting Up Query Keys</h3>
<p>First, let's define some constants for our query keys:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// queryKeys.ts</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> QUERY_KEYS = {
  USERS: <span class="hljs-string">'users'</span>,
  USER: <span class="hljs-function">(<span class="hljs-params">userId: <span class="hljs-built_in">string</span></span>) =&gt;</span> [<span class="hljs-string">'user'</span>, userId],
};
</code></pre>
<h3 id="heading-custom-hooks-examples">Custom Hooks Examples</h3>
<h4 id="heading-fetching-users-1">Fetching Users</h4>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useQuery } <span class="hljs-keyword">from</span> <span class="hljs-string">'@tanstack/react-query'</span>;
<span class="hljs-keyword">import</span> httpClient <span class="hljs-keyword">from</span> <span class="hljs-string">'../api/HttpClient'</span>;
<span class="hljs-keyword">import</span> { QUERY_KEYS } <span class="hljs-keyword">from</span> <span class="hljs-string">'../api/queryKeys'</span>;
<span class="hljs-keyword">import</span> { User } <span class="hljs-keyword">from</span> <span class="hljs-string">'../api/interfaces'</span>;

<span class="hljs-keyword">const</span> useFetchUsers = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">return</span> useQuery&lt;User[]&gt;({
    queryKey: [QUERY_KEYS.USERS],
    queryFn: <span class="hljs-function">() =&gt;</span> httpClient.get&lt;User[]&gt;(<span class="hljs-string">"/users"</span>).then(<span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> res.data),
  });
};
</code></pre>
<h4 id="heading-creating-a-user-1">Creating a User</h4>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useMutation, useQueryClient } <span class="hljs-keyword">from</span> <span class="hljs-string">'@tanstack/react-query'</span>;
<span class="hljs-keyword">import</span> httpClient <span class="hljs-keyword">from</span> <span class="hljs-string">'../api/HttpClient'</span>;
<span class="hljs-keyword">import</span> { QUERY_KEYS } <span class="hljs-keyword">from</span> <span class="hljs-string">'../api/queryKeys'</span>;
<span class="hljs-keyword">import</span> { User } <span class="hljs-keyword">from</span> <span class="hljs-string">'../api/interfaces'</span>;

<span class="hljs-keyword">const</span> useCreateUser = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> queryClient = useQueryClient();

  <span class="hljs-keyword">return</span> useMutation({
    mutationFn: <span class="hljs-function">(<span class="hljs-params">newUser: Omit&lt;User, <span class="hljs-string">"id"</span>&gt;</span>) =&gt;</span>
      httpClient.post&lt;User&gt;(<span class="hljs-string">"/users"</span>, newUser),
    onSuccess: <span class="hljs-function">() =&gt;</span> {
      queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.USERS] });
    },
  });
};
</code></pre>
<h4 id="heading-updating-a-user-1">Updating a User</h4>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useMutation, useQueryClient } <span class="hljs-keyword">from</span> <span class="hljs-string">'@tanstack/react-query'</span>;
<span class="hljs-keyword">import</span> httpClient <span class="hljs-keyword">from</span> <span class="hljs-string">'../api/HttpClient'</span>;
<span class="hljs-keyword">import</span> { QUERY_KEYS } <span class="hljs-keyword">from</span> <span class="hljs-string">'../api/queryKeys'</span>;
<span class="hljs-keyword">import</span> { User } <span class="hljs-keyword">from</span> <span class="hljs-string">'../api/interfaces'</span>;

<span class="hljs-keyword">const</span> useUpdateUser = <span class="hljs-function">(<span class="hljs-params">userId: <span class="hljs-built_in">number</span></span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> queryClient = useQueryClient();

  <span class="hljs-keyword">return</span> useMutation({
    mutationFn: <span class="hljs-function">(<span class="hljs-params">updatedUser: Partial&lt;Omit&lt;User, <span class="hljs-string">"id"</span>&gt;&gt;</span>) =&gt;</span>
      httpClient.put&lt;User&gt;(<span class="hljs-string">`/users/<span class="hljs-subst">${userId}</span>`</span>, updatedUser),
    onSuccess: <span class="hljs-function">() =&gt;</span> {
      queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.USERS] });
      queryClient.invalidateQueries({
        queryKey: [QUERY_KEYS.USER(userId.toString())],
      });
    },
  });
};
</code></pre>
<h4 id="heading-deleting-a-user-1">Deleting a User</h4>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useMutation, useQueryClient } <span class="hljs-keyword">from</span> <span class="hljs-string">'@tanstack/react-query'</span>;
<span class="hljs-keyword">import</span> httpClient <span class="hljs-keyword">from</span> <span class="hljs-string">'../api/HttpClient'</span>;
<span class="hljs-keyword">import</span> { QUERY_KEYS } <span class="hljs-keyword">from</span> <span class="hljs-string">'../api/queryKeys'</span>;

<span class="hljs-keyword">const</span> useDeleteUser = <span class="hljs-function">(<span class="hljs-params">userId: <span class="hljs-built_in">number</span></span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> queryClient = useQueryClient();

  <span class="hljs-keyword">return</span> useMutation({
    mutationFn: <span class="hljs-function">() =&gt;</span> httpClient.delete(<span class="hljs-string">`/users/<span class="hljs-subst">${userId}</span>`</span>),
    onSuccess: <span class="hljs-function">() =&gt;</span> {
      queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.USERS] });
    },
  });
};
</code></pre>
<h2 id="heading-consumption-of-hooks">Consumption of Hooks!</h2>
<p>Here's a sample of how you can consume those hooks. We're using <a target="_blank" href="https://jsonplaceholder.typicode.com/">jsonplaceholder</a> for mock APIs. You can see it in HttpClient file.</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">const</span> App: React.FC = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> { data: users } = useFetchUsers();
  <span class="hljs-keyword">const</span> createUser = useCreateUser();
  <span class="hljs-keyword">const</span> updateUser = useUpdateUser(<span class="hljs-number">1</span>); <span class="hljs-comment">// Example user ID</span>
  <span class="hljs-keyword">const</span> deleteUser = useDeleteUser(<span class="hljs-number">1</span>); <span class="hljs-comment">// Example user ID</span>

  <span class="hljs-keyword">return</span> (
      &lt;div&gt;
        &lt;h1&gt;Users&lt;/h1&gt;
        &lt;button onClick={<span class="hljs-function">() =&gt;</span> createUser.mutate(newUser)}&gt;Create User&lt;/button&gt;
        &lt;button
          onClick={<span class="hljs-function">() =&gt;</span>
            updateUser.mutate({
              name: <span class="hljs-string">"Updated User"</span>,
            })
          }
        &gt;
          Update User
        &lt;/button&gt;
        &lt;button onClick={<span class="hljs-function">() =&gt;</span> deleteUser.mutate()}&gt;Delete User&lt;/button&gt;

        &lt;ul&gt;
          {users?.map(<span class="hljs-function">(<span class="hljs-params">user: User</span>) =&gt;</span> (
            &lt;li key={user.id}&gt;
              {user.id} - {user.name}
            &lt;/li&gt;
          ))}
        &lt;/ul&gt;
      &lt;/div&gt;
  );
};
</code></pre>
<h2 id="heading-wrapping-it-up">📦 Wrapping It Up</h2>
<p>Efficiently consuming APIs is crucial for building performant and scalable applications. By setting up a robust HTTP Client with Axios, handling state with Tanstack Query, and creating custom hooks for each API request, you’ll be well on your way to mastering API consumption in React TypeScript.</p>
<p>Remember, the key is to keep your code modular and reusable. As your application grows, these practices will help you maintain a clean and efficient codebase.</p>
]]></content:encoded></item><item><title><![CDATA[Introducing you to Never Forget!]]></title><description><![CDATA[Motivation
Have you ever missed an important task because your reminder got buried in the avalanche of notifications? I used to struggle with this too, until I developed something that completely transformed how I manage tasks.
Like many of you, I re...]]></description><link>https://blogs.navayuvan.com/introducing-you-to-never-forget</link><guid isPermaLink="true">https://blogs.navayuvan.com/introducing-you-to-never-forget</guid><category><![CDATA[Productivity]]></category><category><![CDATA[Mobile Development]]></category><category><![CDATA[Flutter]]></category><category><![CDATA[TODOLIST]]></category><category><![CDATA[Todoist]]></category><category><![CDATA[Node.js]]></category><dc:creator><![CDATA[Navayuvan Subramanian]]></dc:creator><pubDate>Thu, 22 Aug 2024 02:00:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1724291742158/72a1b5d8-383d-412b-9479-43dc67f1233f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-motivation">Motivation</h1>
<p>Have you ever missed an important task because your reminder got buried in the avalanche of notifications? I used to struggle with this too, until I developed something that completely transformed how I manage tasks.</p>
<p>Like many of you, I relied on standard reminder apps, but they just weren’t cutting it. Sure, they send notifications with a sound, but with the constant barrage of alerts, spotting the right one can feel like finding a needle in a haystack.</p>
<p>I needed something more—a reminder that not only grabs my attention but refuses to be ignored until I act on it. After searching for an app that could do this and coming up empty-handed, I decided to create my own solution.</p>
<p>While building a full-fledged app like Todoist was beyond my immediate scope, I developed a helper application to complement it: NeverForget.</p>
<p>The idea is simple yet powerful: when you add a task to Todoist with a date and time, NeverForget will notify you at the perfect moment.</p>
<p>But here’s the game-changer—the notification is persistent and can’t be cleared until you tap “Done.”</p>
<p>For the past four months, NeverForget has helped me remember and complete tasks I would have otherwise overlooked. 📝</p>
<p>Let me show you how it works.</p>
<h1 id="heading-how-to-add-reminders-in-neverforget">How to Add Reminders in NeverForget</h1>
<p>First things first: You can’t directly add and manage tasks in NeverForget. It’s designed to be a helper application.</p>
<p>If you’re a Todoist user, adding tasks with specific properties will trigger notifications in NeverForget. Here’s how you can set it up.</p>
<h2 id="heading-connect-todoist-with-neverforget">Connect Todoist with NeverForget</h2>
<ol>
<li><p>Download NeverForget from the Play Store and create an account or log in.</p>
</li>
<li><p>If you’re a new user, you’ll be prompted to "Connect to Todoist."</p>
</li>
<li><p>Click on that option and proceed to log into your Todoist account.</p>
</li>
<li><p>Once your accounts are connected, you’ll land on the Empty Home page.</p>
</li>
</ol>
<h2 id="heading-add-a-reminder-from-todoist-to-neverforget">Add a Reminder from Todoist to NeverForget</h2>
<ol>
<li><p>Open Todoist and create a label named "Reminder" or "Reminders."</p>
</li>
<li><p>To add a reminder, ensure your task meets two criteria:</p>
<ol>
<li><p>It includes the “Reminder” label we just created.</p>
</li>
<li><p>It has a due date and time to trigger the notification.</p>
</li>
</ol>
</li>
</ol>
<p>And that’s it! Once you’ve set this up, NeverForget will ensure you never miss an important task again.</p>
<h1 id="heading-whats-next">What’s Next?</h1>
<p>We’re excited to announce that more integrations, like Google Calendar, are on the way! Stay tuned as we continue to expand NeverForget’s capabilities, making it even easier to stay on top of your tasks across multiple platforms.</p>
]]></content:encoded></item></channel></rss>