<?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[Sachin Chaurasiya Blog]]></title><description><![CDATA[Passionate about knowledge sharing, I regularly share insights and expertise through my blogs to empower and educate others.]]></description><link>https://blog.sachinchaurasiya.dev</link><generator>RSS for Node</generator><lastBuildDate>Sun, 17 May 2026 08:59:20 GMT</lastBuildDate><atom:link href="https://blog.sachinchaurasiya.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Open Source is Not Just for Hacktoberfest]]></title><description><![CDATA[Every October, the developer community buzzes with Hacktoberfest energy. PRs fly, t-shirts are earned, and GitHub turns green. But here's what nobody talks about: what happens in November?
For most contributors, the answer is simple: nothing. The rep...]]></description><link>https://blog.sachinchaurasiya.dev/open-source-is-not-just-for-hacktoberfest</link><guid isPermaLink="true">https://blog.sachinchaurasiya.dev/open-source-is-not-just-for-hacktoberfest</guid><category><![CDATA[Open Source]]></category><category><![CDATA[open source]]></category><category><![CDATA[AI]]></category><category><![CDATA[agentic AI]]></category><dc:creator><![CDATA[Sachin Chaurasiya]]></dc:creator><pubDate>Tue, 30 Dec 2025 11:31:43 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1766918270549/e56e2f46-cb23-4e5a-b42e-ade6a8b67ec6.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Every October, the developer community buzzes with Hacktoberfest energy. PRs fly, t-shirts are earned, and GitHub turns green. But here's what nobody talks about: <strong>what happens in November?</strong></p>
<p>For most contributors, the answer is simple: nothing. The repositories they contributed to become distant memories, the architecture they briefly touched remains unexplored, and the relationships they could have built with maintainers never formed.</p>
<p>I decided to do things differently this year. Let me tell you how one Hacktoberfest discovery turned into a masterclass in production AI systems.</p>
<h2 id="heading-the-problem-october-only-open-source">The Problem: October-Only Open Source</h2>
<p>Let's be honest about the Hacktoberfest paradox:</p>
<p><strong>The Surface-Level Contribution Trap</strong>: Quick documentation fixes and typo corrections are valuable, don't get me wrong. But if that's ALL you do, you're missing the forest for the trees. The real learning happens when you understand why the code is structured a certain way, not just that a semicolon was missing.</p>
<p><strong>Chasing Swag Over Growth</strong>: T-shirts and badges are nice, but they don't teach you how to build production-grade systems. The developers who grow fastest are the ones who stick around after October.</p>
<p><strong>Missed Opportunities</strong>: The best open source contributions come from contributors who understand the codebase deeply. That takes time, more than one month.</p>
<h2 id="heading-the-discovery-finding-skyfloai">The Discovery: Finding Skyflo.ai</h2>
<p>While searching for interesting projects to contribute to, I stumbled upon <a target="_blank" href="https://github.com/skyflo-ai/skyflo">Skyflo.ai</a>, an open-source AI agent for DevOps and cloud-native operations.</p>
<p>As someone actively learning AI engineering and building agent architectures, this wasn't just another contribution opportunity. It was exactly what I was looking to learn:</p>
<ul>
<li><p><strong>LangGraph</strong> for stateful agent orchestration</p>
</li>
<li><p><strong>MCP (Model Context Protocol)</strong> for standardized tool execution</p>
</li>
<li><p><strong>Human-in-the-loop</strong> safety patterns</p>
</li>
<li><p><strong>Kubernetes-native</strong> deployment</p>
</li>
</ul>
<p>Instead of submitting a quick PR and moving on, I decided to dive deep.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766918381406/4360b2f8-e4be-465f-bf54-fc7fd10fd0c1.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-what-is-skyfloai">What is Skyflo.ai?</h3>
<p><a target="_blank" href="https://skyflo.ai/">Skyflo.ai</a> is an AI copilot that unifies Kubernetes operations and CI/CD systems behind a natural-language interface. Instead of memorizing CLI commands or clicking through UIs, you just tell <a target="_blank" href="https://skyflo.ai/">Skyflo.ai</a> what you want:</p>
<pre><code class="lang-plaintext">Show me the last 200 lines of logs for checkout in production. 
If there are errors, summarize them.
</code></pre>
<p>Or:</p>
<pre><code class="lang-plaintext">Progressively canary rollout auth-backend in dev through 10/25/50/100 steps
</code></pre>
<p>The magic is in how it does this safely, with human approval required for any mutating operation.</p>
<h2 id="heading-understanding-the-architecture">Understanding the Architecture</h2>
<p>This is where the real learning happened. Skyflo's architecture is a textbook example of how to build production AI agent systems.</p>
<h3 id="heading-the-three-layer-design">The Three-Layer Design</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766918525676/4adcb0f6-321b-45a3-b126-c6c0579315b4.png" alt class="image--center mx-auto" /></p>
<p><strong>1. Frontend Layer: Command Center</strong></p>
<ul>
<li><p>Built with Next.js, TypeScript, and Tailwind</p>
</li>
<li><p>Real-time streaming: SSE to frontend, Streamable HTTP for MCP</p>
</li>
<li><p>Shows every action the agent takes in real-time</p>
</li>
</ul>
<p><strong>2. Intelligence Layer: The Engine</strong></p>
<ul>
<li><p>FastAPI backend with LangGraph workflows</p>
</li>
<li><p>Manages the <strong>Plan → Execute → Verify loop</strong></p>
</li>
<li><p>Handles approvals and checkpoints</p>
</li>
<li><p>Real-time SSE updates to UI</p>
</li>
</ul>
<p><strong>3. Tool Layer: MCP Server</strong></p>
<ul>
<li><p>FastMCP implementation for tool execution</p>
</li>
<li><p>Standardized tools for kubectl, Helm, Jenkins, Argo Rollouts</p>
</li>
<li><p>Safe, consistent actions across all integrations</p>
</li>
</ul>
<h3 id="heading-why-this-architecture-works">Why This Architecture Works</h3>
<p>The separation of concerns is beautiful:</p>
<ul>
<li><p><strong>UI changes</strong> don't affect the agent logic</p>
</li>
<li><p><strong>New tools</strong> can be added without touching the intelligence layer</p>
</li>
<li><p><strong>Each component</strong> is independently deployable and testable</p>
</li>
</ul>
<h2 id="heading-key-learnings-from-production-ai-systems">Key Learnings from Production AI Systems</h2>
<h3 id="heading-1-langgraph-for-stateful-agents">1. LangGraph for Stateful Agents</h3>
<p>Traditional LLM chains are stateless—you send a prompt, get a response, done. But real-world AI agents need to:</p>
<ul>
<li><p>Remember context across multiple steps</p>
</li>
<li><p>Handle failures gracefully with checkpoints</p>
</li>
<li><p>Support human intervention at any point</p>
</li>
</ul>
<p>LangGraph provides graph-based orchestration that enables all of this. The agent's workflow is defined as nodes and edges, with state persisted at each step.</p>
<p>Here's how Skyflo.ai implements this workflow in <code>engine/src/api/agent/graph.py</code>:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> langgraph.graph <span class="hljs-keyword">import</span> StateGraph, START, END

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_build_graph</span>(<span class="hljs-params">self</span>) -&gt; StateGraph:</span>
    workflow = StateGraph(AgentState)

    <span class="hljs-comment"># Define the workflow nodes</span>
    workflow.add_node(<span class="hljs-string">"entry"</span>, self._entry_node)
    workflow.add_node(<span class="hljs-string">"model"</span>, self._model_node)
    workflow.add_node(<span class="hljs-string">"gate"</span>, self._gate_node)
    workflow.add_node(<span class="hljs-string">"final"</span>, self._final_node)

    <span class="hljs-comment"># Define the flow</span>
    workflow.add_edge(START, <span class="hljs-string">"entry"</span>)
    workflow.add_conditional_edges(
        <span class="hljs-string">"entry"</span>, route_from_entry,
        {<span class="hljs-string">"gate"</span>: <span class="hljs-string">"gate"</span>, <span class="hljs-string">"model"</span>: <span class="hljs-string">"model"</span>}
    )
    workflow.add_conditional_edges(
        <span class="hljs-string">"model"</span>, route_after_model,
        {<span class="hljs-string">"gate"</span>: <span class="hljs-string">"gate"</span>, <span class="hljs-string">"model"</span>: <span class="hljs-string">"model"</span>, <span class="hljs-string">"final"</span>: <span class="hljs-string">"final"</span>}
    )
    workflow.add_conditional_edges(
        <span class="hljs-string">"gate"</span>, route_after_gate,
        {<span class="hljs-string">"model"</span>: <span class="hljs-string">"model"</span>, <span class="hljs-string">"final"</span>: <span class="hljs-string">"final"</span>}
    )
    workflow.add_edge(<span class="hljs-string">"final"</span>, END)

    <span class="hljs-keyword">return</span> workflow
</code></pre>
<p>This creates a stateful workflow where the agent can loop between planning (model), execution (gate), and verification phases until the task is complete.</p>
<h3 id="heading-2-mcp-the-usb-c-for-ai">2. MCP: The USB-C for AI</h3>
<p>Model Context Protocol is becoming the standard for how AI agents interact with tools. Instead of building custom integrations for each tool (the "M x N nightmare"), MCP provides:</p>
<ul>
<li><p>A universal interface for tool discovery</p>
</li>
<li><p>Standardized invocation patterns</p>
</li>
<li><p>Clean separation between agent logic and tool implementation</p>
</li>
</ul>
<p>Think of it as "OpenAPI for AI agents."</p>
<h3 id="heading-3-human-in-the-loop-is-non-negotiable">3. Human-in-the-Loop is Non-Negotiable</h3>
<p>For DevOps operations, automatic execution without approval is dangerous. Skyflo's pattern:</p>
<ol>
<li><p>Agent creates a plan</p>
</li>
<li><p>User reviews and approves</p>
</li>
<li><p>Agent executes</p>
</li>
<li><p>Agent verifies the outcome</p>
</li>
<li><p>Repeat until complete</p>
</li>
</ol>
<p>This <strong>Plan → Execute → Verify</strong> loop with human approval gates is a pattern every production AI system should adopt.</p>
<h3 id="heading-4-real-time-streaming-builds-trust">4. Real-Time Streaming Builds Trust</h3>
<p>Users need to see what the agent is doing. Skyflo.ai streams every action in real-time:</p>
<ul>
<li><p>Tool invocations</p>
</li>
<li><p>Intermediate reasoning</p>
</li>
<li><p>Execution results</p>
</li>
<li><p>Verification steps</p>
</li>
</ul>
<p>This transparency is critical when your agent is touching production infrastructure. Skyflo.ai streams events via SSE from the Engine to the UI, while the Engine communicates with the MCP server over Streamable HTTP transport for efficient tool execution.</p>
<h3 id="heading-5-safety-first-design">5. Safety-First Design</h3>
<p>Key safety patterns I observed:</p>
<ul>
<li><p><strong>Dry-run by default</strong> for Helm operations</p>
</li>
<li><p><strong>Diff-first</strong> before any apply</p>
</li>
<li><p><strong>Approval gates</strong> for all mutations</p>
</li>
<li><p><strong>Audit logging</strong> of every action</p>
</li>
</ul>
<p><strong>Architecture Note</strong>: The communication between components uses different protocols optimized for their use case:</p>
<ul>
<li><p><strong>Engine → UI</strong>: Server-Sent Events (SSE) for real-time user feedback</p>
</li>
<li><p><strong>Engine → MCP Server</strong>: Streamable HTTP transport for tool execution</p>
</li>
</ul>
<h2 id="heading-my-contributions-to-skyfloai">My Contributions to Skyflo.ai</h2>
<p>Over the past few months, I've contributed multiple features and fixes to Skyflo.ai:</p>
<h3 id="heading-features-shipped">Features Shipped</h3>
<ul>
<li><p><a target="_blank" href="https://github.com/skyflo-ai/skyflo/pull/59"><strong>Jenkins Build Control</strong></a>: Added tools to stop/cancel running builds, enabling full CI/CD lifecycle management</p>
</li>
<li><p><a target="_blank" href="https://github.com/skyflo-ai/skyflo/pull/50"><strong>Kubernetes Rollout Management</strong></a>: Implemented rollout history and rollback tools for safer deployments</p>
</li>
<li><p><a target="_blank" href="https://github.com/skyflo-ai/skyflo/pull/39"><strong>Helm Template Rendering</strong></a>: Added <code>helm_template</code> tool to preview manifests before deployment</p>
</li>
<li><p><a target="_blank" href="https://github.com/skyflo-ai/skyflo/pull/38"><strong>Label Selector for K8s Resources</strong></a>: Enhanced <code>k8s_get</code> tool with label selectors for more precise resource queries</p>
</li>
<li><p><a target="_blank" href="https://github.com/skyflo-ai/skyflo/pull/37"><strong>Chat History Search</strong></a>: Implemented debounced server-side search for better conversation management</p>
</li>
</ul>
<h3 id="heading-bug-fixes-amp-ux-improvements">Bug Fixes &amp; UX Improvements</h3>
<ul>
<li><p><a target="_blank" href="https://github.com/skyflo-ai/skyflo/pull/63"><strong>Message Continuity Fix</strong></a>: Resolved critical issue where chat messages disappeared after tool approval/denial</p>
</li>
<li><p><a target="_blank" href="https://github.com/skyflo-ai/skyflo/pull/63"><strong>Approval Flow Refinement</strong></a>: Streamlined approval action handling and message finalization in the UI</p>
</li>
<li><p><a target="_blank" href="https://github.com/skyflo-ai/skyflo/pull/43"><strong>Navigation Enhancements</strong></a>: Made logo clickable and added GitHub project link to navbar</p>
</li>
<li><p><a target="_blank" href="https://github.com/skyflo-ai/skyflo/pull/52"><strong>Profile Management</strong></a>: Fixed button state management for profile updates</p>
</li>
<li><p><a target="_blank" href="https://github.com/skyflo-ai/skyflo/pull/49"><strong>SSE Timeout Fix</strong></a>: Increased Nginx proxy timeouts to prevent 60-second SSE connection cutoffs</p>
</li>
</ul>
<h3 id="heading-documentation">Documentation</h3>
<ul>
<li><a target="_blank" href="https://github.com/skyflo-ai/skyflo/pull/36">Fixed architecture guide link</a> in <code>CONTRIBUTING.md</code> to help new contributors</li>
</ul>
<p>Each contribution taught me something new about production AI systems, from SSE streaming patterns to Kubernetes operations safety.</p>
<h2 id="heading-my-journey-challenges-and-breakthroughs">My Journey: Challenges and Breakthroughs</h2>
<p>Contributing to Skyflo.ai wasn't always smooth sailing. Here are the challenges I faced and what I learned from them:</p>
<h3 id="heading-understanding-langgraph-state-management">Understanding LangGraph State Management</h3>
<p><strong>The Challenge</strong>: At first, I didn't understand how state flows through the workflow nodes. The conditional edges and state updates seemed complex.</p>
<p><strong>The Breakthrough</strong>: After reading through <code>engine/src/api/agent/graph.py</code> and experimenting locally, I realized that LangGraph's state is additive, each node returns updates that merge with the existing state. This pattern makes it easy to maintain conversation context while allowing nodes to be independent.</p>
<h3 id="heading-decoding-the-mcp-abstraction">Decoding the MCP Abstraction</h3>
<p><strong>The Challenge</strong>: The abstraction between the Engine, MCP Client, and MCP Server initially confused me. I couldn't understand why we needed three separate components.</p>
<p><strong>The Breakthrough</strong>: Once I traced a tool call through the entire flow, it clicked:</p>
<ol>
<li><p>Engine receives user intent via LLM</p>
</li>
<li><p>MCP Client (<code>engine/src/api/services/mcp_client.py</code>) acts as a bridge</p>
</li>
<li><p>MCP Server (<code>mcp/tools/</code>) executes actual kubectl/helm commands</p>
</li>
</ol>
<p>This separation means you can swap out tools without touching the agent logic—brilliant architecture.</p>
<h3 id="heading-grasping-the-approval-flow">Grasping the Approval Flow</h3>
<p><strong>The Challenge</strong>: Understanding when and how approval gates trigger took time. The interaction between <code>approval_decisions</code> state and <code>ApprovalPending</code> exceptions was not immediately obvious.</p>
<p><strong>The Breakthrough</strong>: I discovered that the gate node checks if a tool requires approval, then raises <code>ApprovalPending</code> which halts execution. The state is checkpointed, and when the user approves/denies, the workflow resumes from exactly where it stopped. This is production-grade error handling.</p>
<h2 id="heading-beyond-hacktoberfest-the-year-round-journey">Beyond Hacktoberfest: The Year-Round Journey</h2>
<h3 id="heading-october-the-starting-point">October: The Starting Point</h3>
<p>Hacktoberfest is a fantastic catalyst. It lowers the barrier to entry and introduces you to projects you'd never discover otherwise. Use it as your launchpad, not your destination.</p>
<h3 id="heading-november-december-go-deeper">November-December: Go Deeper</h3>
<p>This is where the real learning happens:</p>
<ul>
<li><p><strong>Read the entire codebase</strong>, not just the file you're changing</p>
</li>
<li><p><strong>Join Discord/Slack discussions</strong> to understand the roadmap</p>
</li>
<li><p><strong>Pick up complex issues</strong> that intimidate you</p>
</li>
<li><p><strong>Ask questions</strong> about architectural decisions</p>
</li>
</ul>
<h3 id="heading-year-round-become-a-maintainer">Year-Round: Become a Maintainer</h3>
<p>Consistent contributions build trust. Over time:</p>
<ul>
<li><p>You start reviewing other contributors' PRs</p>
</li>
<li><p>Maintainers ask for your input on design decisions</p>
</li>
<li><p>You shape the project's future direction</p>
</li>
<li><p>You build lasting professional relationships</p>
</li>
</ul>
<h2 id="heading-the-contributor-mindset">The Contributor Mindset</h2>
<p>Here's what separates occasional contributors from impactful ones:</p>
<p><strong>1. Choose projects you want to learn from</strong></p>
<p>Don't just pick easy projects to farm PRs. Pick projects that use technologies you want to master. My contribution to Skyflo.ai taught me more about production AI systems than any tutorial could.</p>
<p><strong>2. Contribute in multiple ways</strong></p>
<ul>
<li><p>Code features and bug fixes</p>
</li>
<li><p>Documentation improvements</p>
</li>
<li><p>Test coverage</p>
</li>
<li><p>Code reviews</p>
</li>
<li><p>Community support</p>
</li>
</ul>
<p>All contributions are valuable. Mix them up.</p>
<p><strong>3. Build relationships</strong></p>
<p>Open source is as much about people as it is about code. The maintainers and contributors you meet today become your professional network tomorrow.</p>
<p><strong>4. Track your growth, not just your PRs</strong></p>
<p>The real metric isn't merged PRs, it's skills gained, patterns learned, and confidence built.</p>
<p><img src="https://ik.imagekit.io/customer2026/Social%20Media/skyflow-oss-contribution-example.gif" alt class="image--center mx-auto" /></p>
<h2 id="heading-your-turn-start-now">Your Turn: Start Now</h2>
<p>Whether it's Skyflo.ai or another project that excites you, the best time to start contributing is now. Not next October, now.</p>
<p>Here's your action plan:</p>
<ol>
<li><p><strong>Find a project</strong> that aligns with what you want to learn</p>
</li>
<li><p><strong>Read the contributing guidelines</strong> and code of conduct</p>
</li>
<li><p><strong>Set up the development environment</strong> locally</p>
</li>
<li><p><strong>Start with the codebase</strong>, not the issues, understand before you contribute</p>
</li>
<li><p><strong>Join the community</strong> (Discord, Slack, Discussions)</p>
</li>
<li><p><strong>Pick your first issue</strong> and dive in</p>
</li>
<li><p><strong>Stay around</strong> after your first PR merges</p>
</li>
</ol>
<h2 id="heading-resources">Resources</h2>
<ul>
<li><p><strong>Skyflo.ai GitHub</strong>: <a target="_blank" href="https://github.com/skyflo-ai/skyflo">github.com/skyflo-ai/skyflo</a></p>
</li>
<li><p><strong>Skyflo.ai Discord</strong>: <a target="_blank" href="https://discord.gg/kCFNavMund">discord.gg/kCFNavMund</a></p>
</li>
<li><p><strong>LangGraph Docs</strong>: <a target="_blank" href="https://docs.langchain.com/langgraph">docs.langchain.com/langgraph</a></p>
</li>
<li><p><strong>Model Context Protocol</strong>: <a target="_blank" href="https://modelcontextprotocol.io">modelcontextprotocol.io</a></p>
</li>
</ul>
<h2 id="heading-connect-with-me">Connect With Me</h2>
<p>I'm sharing my AI engineering journey, open source contributions, and developer productivity tips:</p>
<ul>
<li><p><strong>YouTube</strong>: <a target="_blank" href="https://www.youtube.com/@sachin-chaurasiya">@sachin-chaurasiya</a></p>
</li>
<li><p><strong>Blog</strong>: <a target="_blank" href="https://blog.sachinchaurasiya.dev/">blog.sachinchaurasiya.dev</a></p>
</li>
<li><p><strong>LinkedIn</strong>: <a target="_blank" href="https://www.linkedin.com/in/sachin-chaurasiya/">in/sachin-chaurasiya</a></p>
</li>
<li><p><strong>X</strong>: <a target="_blank" href="https://x.com/sachindotcom">@sachindotcom</a></p>
</li>
<li><p><strong>Dev.to</strong>: <a target="_blank" href="https://dev.to/sachinchaurasiya">sachinchaurasiya</a></p>
</li>
</ul>
<hr />
<p><em>What's a project that taught you something unexpected? Drop a comment, I'd love to hear your open source stories.</em></p>
]]></content:encoded></item><item><title><![CDATA[The #1 Skill Most Developers Miss When Using AI Coding Agents]]></title><description><![CDATA[The debate over AI coding agents is missing the most important factor. It’s not about prompt engineering, it’s about understanding the context window.
Developers are divided. One side claims coding agents suck. The other insists you’re just using the...]]></description><link>https://blog.sachinchaurasiya.dev/the-1-skill-most-developers-miss-when-using-ai-coding-agents</link><guid isPermaLink="true">https://blog.sachinchaurasiya.dev/the-1-skill-most-developers-miss-when-using-ai-coding-agents</guid><category><![CDATA[AI]]></category><category><![CDATA[agents]]></category><category><![CDATA[llm]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Sachin Chaurasiya]]></dc:creator><pubDate>Sat, 27 Dec 2025 18:01:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1766839263118/36844264-d28a-4869-b17e-9d16aab8ba4b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The debate over AI coding agents is missing the most important factor. It’s not about prompt engineering, it’s about understanding the context window.</p>
<p>Developers are divided. One side claims <strong>coding agents suck</strong>. The other insists <strong>you’re just using them wrong; it’s a skill issue.</strong> While there is truth to both perspectives, the most common skill issue isn’t about prompt engineering ,  it’s about a fundamental misunderstanding of the tool’s primary constraint.</p>
<blockquote>
<p><em>If there is a skill issue that I see most often with devs, it is not thinking enough about the context window.</em></p>
</blockquote>
<h2 id="heading-what-is-the-context-window">What is the Context Window?</h2>
<p>The <strong>context window</strong> is the complete set of input and output tokens an LLM processes in a single session. Think of it as the model’s <strong>working memory,</strong> everything it can see and consider when generating a response.</p>
<p>This includes:</p>
<ul>
<li><p><strong>Input Tokens</strong>: Your system prompt, instructions, and user messages</p>
</li>
<li><p><strong>Output Tokens</strong>: The assistant’s generated responses</p>
</li>
</ul>
<p><img src="https://cdn-images-1.medium.com/max/1600/1*uO5A0EFUnLCIt8QnmAkdZQ.png" alt /></p>
<p>As your conversation grows, so does the token count. Eventually, you’ll hit a limit set by the model’s provider. This can happen if a conversation becomes too long, or even from a single, very large input like uploading extensive documentation. Exceeding this limit results in an error, and generation will stop cold.</p>
<h2 id="heading-context-windows-in-2025">Context Windows in 2025</h2>
<p>Model providers set different limits based on architecture and cost. These can range from a few thousand to several million tokens , but as we’ll see, <strong>bigger isn’t always better</strong>.</p>
<p><img src="https://cdn-images-1.medium.com/max/1600/1*hck8hucNMQvncbXw2bYA1Q.png" alt /></p>
<blockquote>
<p><em>Gemini has really large context windows, but as we’ll see… bigger is not always better.</em></p>
</blockquote>
<h2 id="heading-the-paradox-more-context-worse-performance">The Paradox: More Context, Worse Performance</h2>
<p>Here’s the counterintuitive truth: <strong>the more information you give a model, the worse it performs at retrieving specific details.</strong> This is true for all models, from the smallest to the largest.</p>
<p>Why doesn’t infinite context exist? Two reasons:</p>
<h3 id="heading-1-cost-amp-memory">1. Cost &amp; Memory</h3>
<p>LLM processing is expensive. Larger contexts consume significantly more memory per process, driving up both computational costs and latency.</p>
<h3 id="heading-2-performance-degradation">2. Performance Degradation</h3>
<p>This is the critical one. An LLM’s attention is <strong>not distributed evenly</strong> across the context. Information at the very beginning and very end of a conversation has the most impact on the output. Information in the middle? It’s often de-prioritized or ignored entirely.</p>
<p>This is called the <strong>Lost in the Middle</strong> problem.</p>
<p><img src="https://cdn-images-1.medium.com/max/1600/1*A5d3IOFwjoKSmp9XcRGdcA.png" alt /></p>
<p>This isn’t a bug, it’s an emergent property of transformer architecture. Interestingly, it mirrors human cognitive biases:</p>
<ul>
<li><p><strong>Primacy Bias</strong>: Better recall for items at the beginning</p>
</li>
<li><p><strong>Recency Bias</strong>: Better recall for items at the end</p>
</li>
</ul>
<blockquote>
<p><em>Just like humans do, models do better with less, more focused information.</em></p>
</blockquote>
<h2 id="heading-case-study-a-10-million-token-window-is-useless-if-the-model-cant-use-it">Case Study: A 10 Million Token Window is Useless If the Model Can’t Use It</h2>
<p>When Meta announced a model with a 10-million-token context window, it seemed like a breakthrough. However, real-world testing quickly revealed it suffered from severe <strong>lost-in-the-middle</strong> problems. You could feed it vast amounts of information, but the model would fail to retrieve or act on it effectively.</p>
<blockquote>
<p><em>When you’re assessing an LLM, you shouldn’t just look at how big the context window is. You should look at how well it retrieves information from its context window.</em></p>
</blockquote>
<h2 id="heading-the-solution-keep-your-context-lean-and-focused">The Solution: Keep Your Context Lean and Focused</h2>
<p>Since shorter context windows suffer less from the <strong>lost-in-the-middle</strong> problem, the key to better performance is <strong>proactive management</strong>. Regularly clearing your coding agent’s chat history refreshes its <strong>memory</strong> and ensures your instructions are given high priority.</p>
<p>This is the single most effective way to improve results.</p>
<h3 id="heading-step-1-get-full-transparency-into-your-context-usage">Step 1: Get Full Transparency Into Your Context Usage</h3>
<p>You cannot manage what you cannot measure. A good coding agent provides tools to inspect the current state of your context window. For example, in Cursor:</p>
<p><img src="https://cdn-images-1.medium.com/max/1600/1*R6F9I0pGmVKNDM6QW5XDtQ.png" alt /></p>
<h3 id="heading-step-2-your-default-action-should-be-to-clear-the-conversation">Step 2: Your Default Action Should Be to <code>clear</code> the Conversation</h3>
<p>When you start a new, unrelated task, or when context usage gets high (e.g., less than 50k tokens free), the best practice is to clear the conversation history entirely. This frees up the entire context window, giving you a blank slate and ensuring maximum performance for the new task.</p>
<p><img src="https://cdn-images-1.medium.com/max/1600/1*0AeM_TjUg_slDX5ijfoiJQ.png" alt /></p>
<h3 id="heading-step-3-use-compact-when-you-need-to-preserve-the-conversations-intent">Step 3: Use <code>compact</code> When You Need to Preserve the Conversation's Intent</h3>
<p><code>compact</code> is an alternative that clears the detailed history but generates an LLM-powered summary. This preserves the <strong>vibes</strong> or core goals of the conversation in a much smaller package.</p>
<p><img src="https://cdn-images-1.medium.com/max/1600/1*Qhxf_tE3PUIkM0ih54vA6g.png" alt /></p>
<blockquote>
<p><em>This preserves some of the intention… like a mini rules file just for this conversation.</em></p>
</blockquote>
<p>While useful, be aware that generating the summary itself takes time and consumes tokens.</p>
<h2 id="heading-a-word-of-warning-hidden-context-can-sabotage-performance">A Word of Warning: Hidden Context Can Sabotage Performance</h2>
<p>Be extremely cautious about tools and configurations that add large amounts of hidden context. This bloats your window from the start, pushing your actual conversation into the dreaded <strong>middle</strong>.</p>
<p><strong>Common Culprits:</strong></p>
<ul>
<li><p><strong>LSP/MCP Servers</strong>: These can inject enormous toolsets into your system prompt</p>
</li>
<li><p><strong>Large Rule Files</strong>: Overly complex or numerous custom rules in tools like Cursor or Claude Code</p>
</li>
</ul>
<p><img src="https://cdn-images-1.medium.com/max/1600/1*e85yOAaywgcSsZEqP1TvUw.png" alt /></p>
<p>When hidden tools consume most of your context, your actual messages end up in the <strong>lost in the middle</strong> zone , exactly where the model pays the least attention.</p>
<h2 id="heading-key-takeaways">Key Takeaways</h2>
<ol>
<li><p><strong>The context window is the model’s entire memory (input + output).</strong> As conversations grow, this fills up quickly and performance suffers.</p>
</li>
<li><p><strong>All models have a hardcoded limit and suffer from lost-in-the-middle attention decay.</strong> Even million-token windows aren’t immune to this problem.</p>
</li>
<li><p><strong>A leaner, more focused context consistently yields better performance.</strong> Clear early, clear often.</p>
</li>
</ol>
<h3 id="heading-the-new-mindset">The New Mindset</h3>
<p>Develop a healthy paranoia about what’s in your context. Actively manage it with tools like <code>clear</code> and <code>compact</code>. This skill is what separates frustrating interactions from productive partnerships with AI.</p>
<p>Mastering the context window is the key to great results.</p>
]]></content:encoded></item><item><title><![CDATA[How to Build Production-Grade Agentic AI]]></title><description><![CDATA[Agentic AI is everywhere right now.
Everyone is building agents, demos, and workflows, but very few of them are production-ready.
I recently read a research paper on designing, developing, and deploying production-grade agentic AI workflows, and it s...]]></description><link>https://blog.sachinchaurasiya.dev/how-to-build-production-grade-agentic-ai</link><guid isPermaLink="true">https://blog.sachinchaurasiya.dev/how-to-build-production-grade-agentic-ai</guid><category><![CDATA[agentic AI]]></category><category><![CDATA[AI]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[System Design]]></category><dc:creator><![CDATA[Sachin Chaurasiya]]></dc:creator><pubDate>Sun, 21 Dec 2025 18:05:27 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1766323471211/3e6abf07-ffd4-43e7-aa75-5c991b7ccaf7.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Agentic AI is everywhere right now.</p>
<p>Everyone is building agents, demos, and workflows, but very few of them are production-ready.</p>
<p>I recently read a research paper on <strong>designing, developing, and deploying production-grade agentic AI workflows</strong>, and it stood out because it focuses less on hype and more on engineering discipline.</p>
<p>This post is a practical breakdown of what it actually takes to build <strong>reliable, scalable, and maintainable agentic AI systems,</strong> not prototypes, not experiments, but systems that can survive in production.</p>
<p>These are my key learnings, translated from research language into real-world engineering insights.</p>
<h2 id="heading-agentic-ai-is-a-shift-in-system-design"><strong>Agentic AI Is a Shift in System Design</strong></h2>
<p>Traditional AI systems were simple:</p>
<ul>
<li><p>Prompt goes in</p>
</li>
<li><p>Response comes out</p>
</li>
</ul>
<p>Agentic AI systems are very different.</p>
<p>They involve agents that can:</p>
<ul>
<li><p>Plan steps</p>
</li>
<li><p>Call tools</p>
</li>
<li><p>Validate results</p>
</li>
<li><p>Retry on failure</p>
</li>
<li><p>Coordinate with other agents</p>
</li>
<li><p>Operate with minimal human intervention</p>
</li>
</ul>
<p>This is not about writing better prompts.</p>
<p>It’s about designing <strong>AI systems</strong>, not AI demos.</p>
<h2 id="heading-from-single-models-to-agentic-workflows"><strong>From Single Models to Agentic Workflows</strong></h2>
<p>Earlier AI models were built for specific tasks:</p>
<ul>
<li><p>Sentiment analysis</p>
</li>
<li><p>Image classification</p>
</li>
<li><p>Entity extraction</p>
</li>
</ul>
<p>Now, with large language models, we have general-purpose reasoning engines. But the real power comes when we combine them into <strong>agentic workflows</strong>.</p>
<p>In an agentic workflow:</p>
<ul>
<li><p>Each agent has a specific role</p>
</li>
<li><p>Multiple agents collaborate</p>
</li>
<li><p>Reasoning, validation, and execution are separated</p>
</li>
</ul>
<p>This modularity is what makes systems reliable and scalable.</p>
<h2 id="heading-one-agent-one-responsibility"><strong>One Agent, One Responsibility</strong></h2>
<p>One of the strongest principles from the paper is simple:</p>
<p><strong>Do not overload agents.</strong></p>
<p>Each agent should:</p>
<ul>
<li><p>Have a single responsibility</p>
</li>
<li><p>Ideally use a single tool</p>
</li>
<li><p>Produce a predictable output</p>
</li>
</ul>
<p>When agents try to do too much:</p>
<ul>
<li><p>Prompts become complex</p>
</li>
<li><p>Behavior becomes non-deterministic</p>
</li>
<li><p>Debugging becomes painful</p>
</li>
</ul>
<p>This is just classic software engineering, applied to AI.</p>
<h2 id="heading-tools-matter-more-than-intelligence"><strong>Tools Matter More Than Intelligence</strong></h2>
<p>A key insight I strongly agree with:</p>
<blockquote>
<p>Agents don’t need to be smarter. They need better tools.</p>
</blockquote>
<p>The reliability of an agent depends on:</p>
<ul>
<li><p>Deterministic tools</p>
</li>
<li><p>Clear input/output contracts</p>
</li>
<li><p>Reduced ambiguity</p>
</li>
</ul>
<p>Your agent is only as good as the tools and boundaries you give it.</p>
<h2 id="heading-dont-use-ai-where-you-dont-need-it"><strong>Don’t Use AI Where You Don’t Need It</strong></h2>
<p>Not everything needs AI.</p>
<p>If a task is deterministic, like:</p>
<ul>
<li><p>Writing files</p>
</li>
<li><p>Calling APIs</p>
</li>
<li><p>Creating database records</p>
</li>
<li><p>Generating timestamps</p>
</li>
</ul>
<p>Don’t ask an LLM to reason about it.</p>
<p>The paper recommends:</p>
<ul>
<li><p>Moving such tasks into <strong>pure functions</strong></p>
</li>
<li><p>Keeping AI only where reasoning is actually required</p>
</li>
</ul>
<p>This reduces:</p>
<ul>
<li><p>Cost</p>
</li>
<li><p>Latency</p>
</li>
<li><p>Failure points</p>
</li>
<li><p>Unpredictable behavior</p>
</li>
</ul>
<h2 id="heading-responsible-ai-through-multi-model-reasoning"><strong>Responsible AI Through Multi-Model Reasoning</strong></h2>
<p>Single-model outputs can hallucinate, drift, or bias results.</p>
<p>A powerful pattern discussed in the paper:</p>
<ul>
<li><p>Use multiple models to generate outputs</p>
</li>
<li><p>Use a reasoning agent to consolidate and validate them</p>
</li>
</ul>
<p>This approach:</p>
<ul>
<li><p>Improves accuracy</p>
</li>
<li><p>Reduces bias</p>
</li>
<li><p>Aligns better with responsible AI practices</p>
</li>
</ul>
<p>Responsible AI is a <strong>system design problem</strong>, not just a model choice.</p>
<h2 id="heading-separate-workflow-logic-from-interfaces"><strong>Separate Workflow Logic from Interfaces</strong></h2>
<p>Another important architectural idea:</p>
<ul>
<li><p>Keep agentic workflow logic separate from MCP servers or external interfaces</p>
</li>
<li><p>MCP servers should act as thin adapters</p>
</li>
<li><p>Core logic should live in a clean backend workflow engine</p>
</li>
</ul>
<p>This separation:</p>
<ul>
<li><p>Improves maintainability</p>
</li>
<li><p>Allows independent scaling</p>
</li>
<li><p>Keeps systems flexible as tools and models evolve</p>
</li>
</ul>
<h2 id="heading-containerization-and-production-readiness"><strong>Containerization and Production Readiness</strong></h2>
<p>Agentic AI systems are production systems.</p>
<p>That means:</p>
<ul>
<li><p>Containerized deployments</p>
</li>
<li><p>Kubernetes orchestration</p>
</li>
<li><p>Logging, monitoring, retries</p>
</li>
<li><p>Secure tool access</p>
</li>
<li><p>Versioned prompts and workflows</p>
</li>
</ul>
<p>Without this, agentic systems remain fragile prototypes.</p>
<h2 id="heading-keep-it-simple-kiss"><strong>Keep It Simple (KISS)</strong></h2>
<p>One of the most important reminders from the paper:</p>
<p><strong>Complexity kills agentic systems.</strong></p>
<p>Over-engineering leads to:</p>
<ul>
<li><p>Hidden behaviors</p>
</li>
<li><p>Hard-to-trace failures</p>
</li>
<li><p>Unmaintainable workflows</p>
</li>
</ul>
<p>Simple, flat, function-driven designs work best, especially when LLMs are involved.</p>
<h2 id="heading-final-thoughts"><strong>Final Thoughts</strong></h2>
<p>Agentic AI is not magic. It’s a system design problem.</p>
<p>What this research paper made very clear is that moving from demos to <strong>production-grade agentic AI</strong> requires strong engineering discipline, clear responsibilities, deterministic tooling, thoughtful orchestration, and simplicity in design.</p>
<p>Models will keep improving, but without good system design, agentic workflows will remain fragile and hard to maintain. The real leverage comes from how we compose agents, tools, and workflows, not from chasing the latest model.</p>
<p>If you’re serious about building agentic AI systems that actually work in production, this paper is worth reading end to end: <a target="_blank" href="https://arxiv.org/abs/2512.08769"><strong>A Practical Guide for Designing, Developing, and Deploying Production-Grade Agentic AI Workflows</strong></a></p>
<p>I’ll continue sharing learnings as I apply these ideas while building real systems.</p>
]]></content:encoded></item><item><title><![CDATA[How to use canvas in Web Workers with OffscreenCanvas]]></title><description><![CDATA[Lately, I have been working on a feature to capture images from media streams and scale them down to reduce their size. This helps save storage and reduce costs before uploading the images to a storage service. For this, I used Canvas to render the i...]]></description><link>https://blog.sachinchaurasiya.dev/how-to-use-canvas-in-web-workers-with-offscreencanvas</link><guid isPermaLink="true">https://blog.sachinchaurasiya.dev/how-to-use-canvas-in-web-workers-with-offscreencanvas</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Frontend Development]]></category><dc:creator><![CDATA[Sachin Chaurasiya]]></dc:creator><pubDate>Sun, 25 May 2025 12:09:03 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1746296838460/c5b3ae72-1abf-4783-9933-3a42eb0deb07.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Lately, I have been working on a feature to capture images from media streams and scale them down to reduce their size. This helps save storage and reduce costs before uploading the images to a storage service. For this, I used Canvas to render the image data from the stream and then convert it into a blob of the required MIME type.</p>
<p>Canvas rendering, animation, and user interaction occur on the main thread. If the rendering and animation are intensive, they can affect performance. After a few weeks, we received feedback from users that the application felt laggy and slow. So, I decided to move the whole pipeline to a web worker. However, Canvas requires DOM access for rendering and animations, which is not available in web workers. This limitation makes using Canvas in web workers challenging.</p>
<p>This is a big challenge if you want to improve the performance of a web application that frequently uses canvas rendering. So, how can we solve this problem? Fortunately, OffscreenCanvas is the answer. OffscreenCanvas provides a canvas that can be rendered offscreen, removing the dependency on the DOM.</p>
<p>In this article, I will discuss the OffscreenCanvas API and how to use it, using image capturing as an example. Let’s get started.</p>
<h2 id="heading-what-is-offscreencanvas">What is OffscreenCanvas?</h2>
<p>OffscreenCanvas is a Web API that provides a canvas that can be rendered offscreen. It decouples the DOM from the Canvas API, so the <code>&lt;canvas&gt;</code> element is no longer entirely dependent on the DOM. Rendering operations can also run inside a worker context, allowing certain tasks to be performed in a separate thread and reducing the load on the main thread.</p>
<p>OffscreenCanvas accepts two parameters: the height and width of the canvas. You can then get the context using the <code>getContext</code> method:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> offScreenCanvas = <span class="hljs-keyword">new</span> OffscreenCanvas(width, height)
<span class="hljs-keyword">const</span> context = offScreenCanvas.getContext(<span class="hljs-string">'2d'</span>)
</code></pre>
<p>Now that you understand the limitations of canvas and the benefits of OffscreenCanvas, let’s look at its usage through an image capturing example.</p>
<h2 id="heading-using-canvas-in-the-main-thread">Using Canvas in the main thread</h2>
<p>Before jumping into OffscreenCanvas, let’s first discuss using canvas on the main thread.</p>
<pre><code class="lang-javascript">&lt;!doctype html&gt;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">http-equiv</span>=<span class="hljs-string">"X-UA-Compatible"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"IE=edge"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Camera Capture Every 5 s<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
      <span class="hljs-selector-tag">body</span> {
        <span class="hljs-attribute">font-family</span>: Arial, Helvetica, sans-serif;
        <span class="hljs-attribute">display</span>: flex;
        <span class="hljs-attribute">flex-direction</span>: column;
        <span class="hljs-attribute">align-items</span>: center;
        <span class="hljs-attribute">gap</span>: <span class="hljs-number">1rem</span>;
        <span class="hljs-attribute">margin</span>: <span class="hljs-number">2rem</span>;
      }
      <span class="hljs-selector-id">#video</span> {
        <span class="hljs-attribute">border</span>: <span class="hljs-number">2px</span> solid <span class="hljs-number">#444</span>;
        <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">8px</span>;
      }
      <span class="hljs-selector-id">#captures</span> {
        <span class="hljs-attribute">display</span>: flex;
        <span class="hljs-attribute">flex-wrap</span>: wrap;
        <span class="hljs-attribute">gap</span>: <span class="hljs-number">0.5rem</span>;
        <span class="hljs-attribute">max-width</span>: <span class="hljs-number">100%</span>;
      }
      <span class="hljs-selector-id">#captures</span> <span class="hljs-selector-tag">img</span> {
        <span class="hljs-attribute">width</span>: <span class="hljs-number">160px</span>; <span class="hljs-comment">/* thumbnail size */</span>
        <span class="hljs-attribute">height</span>: auto;
        <span class="hljs-attribute">border</span>: <span class="hljs-number">1px</span> solid <span class="hljs-number">#888</span>;
        <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">4px</span>;
      }
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"startBtn"</span>&gt;</span>Start Camera<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">video</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"video"</span> <span class="hljs-attr">width</span>=<span class="hljs-string">"640"</span> <span class="hljs-attr">height</span>=<span class="hljs-string">"480"</span> <span class="hljs-attr">autoplay</span> <span class="hljs-attr">playsinline</span> <span class="hljs-attr">muted</span> <span class="hljs-attr">hidden</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">video</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Captured Images (every 5 s)<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"captures"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Type in the input below to experience lag during image capture:<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
        <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span>
        <span class="hljs-attr">id</span>=<span class="hljs-string">"lagInput"</span>
        <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"Type here to experience lag..."</span>
        <span class="hljs-attr">style</span>=<span class="hljs-string">"width: 300px; padding: 8px; margin-bottom: 10px"</span>
      /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"typingStats"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Characters typed: <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"charCount"</span>&gt;</span>0<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Typing lag: <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"lagTime"</span>&gt;</span>0<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span> ms<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
      <span class="hljs-keyword">const</span> startBtn = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'startBtn'</span>);
      <span class="hljs-keyword">const</span> video = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'video'</span>);
      <span class="hljs-keyword">const</span> captures = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'captures'</span>);

      <span class="hljs-keyword">let</span> stream = <span class="hljs-literal">null</span>;
      <span class="hljs-keyword">let</span> captureIntervalId = <span class="hljs-literal">null</span>;

      <span class="hljs-keyword">const</span> canvas = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'canvas'</span>);
      <span class="hljs-keyword">const</span> ctx = canvas.getContext(<span class="hljs-string">'2d'</span>);
      ctx.canvas.width = <span class="hljs-number">640</span>;
      ctx.canvas.height = <span class="hljs-number">480</span>;

      <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">captureFrame</span>(<span class="hljs-params"></span>) </span>{
        <span class="hljs-keyword">if</span> (!stream) <span class="hljs-keyword">return</span>;
        ctx.drawImage(video, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, canvas.width, canvas.height);

        canvas.toBlob(
          <span class="hljs-function">(<span class="hljs-params">blob</span>) =&gt;</span> {
            <span class="hljs-keyword">if</span> (!blob) <span class="hljs-keyword">return</span>;
            <span class="hljs-keyword">const</span> url = URL.createObjectURL(blob);
            <span class="hljs-keyword">const</span> img = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'img'</span>);
            img.src = url;

            img.onload = <span class="hljs-function">() =&gt;</span> URL.revokeObjectURL(url);

            captures.prepend(img);
          },
          <span class="hljs-string">'image/webp'</span>,
          <span class="hljs-number">0.9</span>,
        );
      }

      <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">startCapturing</span>(<span class="hljs-params"></span>) </span>{
        <span class="hljs-keyword">if</span> (captureIntervalId) <span class="hljs-keyword">return</span>;
        captureIntervalId = <span class="hljs-built_in">setInterval</span>(captureFrame, <span class="hljs-number">5000</span>);
      }

      <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">initCamera</span>(<span class="hljs-params"></span>) </span>{
        <span class="hljs-keyword">try</span> {
          stream = <span class="hljs-keyword">await</span> navigator.mediaDevices.getUserMedia({ <span class="hljs-attr">video</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">audio</span>: <span class="hljs-literal">false</span> });
          video.srcObject = stream;
          video.hidden = <span class="hljs-literal">false</span>;
          startBtn.disabled = <span class="hljs-literal">true</span>;
          startCapturing();
        } <span class="hljs-keyword">catch</span> (err) {
          <span class="hljs-built_in">console</span>.error(err);
          alert(<span class="hljs-string">'Unable to access camera: '</span> + err.message);
        }
      }

      startBtn.addEventListener(<span class="hljs-string">'click'</span>, <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">if</span> (!stream) {
          initCamera();
        }
      });

      <span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">'beforeunload'</span>, <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">if</span> (stream) {
          stream.getTracks().forEach(<span class="hljs-function">(<span class="hljs-params">track</span>) =&gt;</span> track.stop());
        }
        <span class="hljs-keyword">if</span> (captureIntervalId) {
          <span class="hljs-built_in">clearInterval</span>(captureIntervalId);
        }
      });

      <span class="hljs-keyword">const</span> lagInput = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'lagInput'</span>);
      <span class="hljs-keyword">const</span> charCount = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'charCount'</span>);
      <span class="hljs-keyword">const</span> lagTime = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'lagTime'</span>);

      lagInput.addEventListener(<span class="hljs-string">'input'</span>, <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">const</span> startTime = performance.now();
        <span class="hljs-keyword">const</span> currentCount = lagInput.value.length;
        charCount.textContent = currentCount;
        <span class="hljs-keyword">const</span> endTime = performance.now();
        <span class="hljs-keyword">const</span> typingLag = endTime - startTime;
        lagTime.textContent = typingLag.toFixed(<span class="hljs-number">2</span>);
      });
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span>
</code></pre>
<p>In this example, we capture an image by creating a canvas element, drawing the image on it, converting it to a blob, and then rendering it in the UI.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> canvas = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'canvas'</span>);
      <span class="hljs-keyword">const</span> ctx = canvas.getContext(<span class="hljs-string">'2d'</span>);
      ctx.canvas.width = <span class="hljs-number">640</span>;
      ctx.canvas.height = <span class="hljs-number">480</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">captureFrame</span>(<span class="hljs-params"></span>) </span>{
        <span class="hljs-keyword">if</span> (!stream) <span class="hljs-keyword">return</span>;
        ctx.drawImage(video, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, canvas.width, canvas.height);

        canvas.toBlob(
          <span class="hljs-function">(<span class="hljs-params">blob</span>) =&gt;</span> {
            <span class="hljs-keyword">if</span> (!blob) <span class="hljs-keyword">return</span>;
            <span class="hljs-keyword">const</span> url = URL.createObjectURL(blob);
            <span class="hljs-keyword">const</span> img = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'img'</span>);
            img.src = url;

            img.onload = <span class="hljs-function">() =&gt;</span> URL.revokeObjectURL(url);

            captures.prepend(img);
          },
          <span class="hljs-string">'image/webp'</span>,
          <span class="hljs-number">0.9</span>,
        );
  }
</code></pre>
<p>Try the example. While capturing an image, type into the input box and observe any lag.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1746294427830/6c11e649-b1c6-4034-86f6-2acd48e6b111.png" alt class="image--center mx-auto" /></p>
<p>Now that we know there is lag, let’s move canvas operations to a worker to improve performance.</p>
<h2 id="heading-using-canvas-in-a-web-worker">Using Canvas in a Web Worker</h2>
<p>We will change the example above by adding a worker script and then using the worker to capture the image.</p>
<p>The <code>initWorker</code> function will create the worker by loading the worker script URL. It will then create the offscreen canvas and use <code>postMessage</code> to send the event data to the worker, passing the offscreen canvas as a transferable object in the second parameter.</p>
<blockquote>
<p><strong>Transferable objects</strong> are objects that can have their resources moved from one place to another, ensuring the resources are used in only one place at a time.</p>
<p>Now, the offscreen canvas will be available in the worker and can be used for canvas-related tasks.</p>
</blockquote>
<p>Offscreen canvas has a different method for converting image data to a blob, called <code>convertToBlob</code>. By default, it provides a high-quality image, so be sure to include the quality parameter as well.</p>
<pre><code class="lang-javascript">...

&lt;body&gt;
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
      ...
      let worker = <span class="hljs-literal">null</span>;
      <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">initWorker</span>(<span class="hljs-params"></span>) </span>{
        <span class="hljs-keyword">const</span> blob = <span class="hljs-keyword">new</span> Blob(
          [
            <span class="hljs-string">`
        self.onmessage = async (e) =&gt; {
          if (e.data.type === 'init') {
            self.canvas = e.data.canvas;
            self.ctx = self.canvas.getContext('2d');
          } else if (e.data.type === 'frame') {
            const { bitmap } = e.data;
            self.ctx.drawImage(bitmap, 0, 0, self.canvas.width, self.canvas.height);
            bitmap.close();
            const blob = await self.canvas.convertToBlob({ type: 'image/webp', quality: 0.9 });
            self.postMessage({ type: 'image', blob });
          }
        };
        `</span>,
          ],
          { <span class="hljs-attr">type</span>: <span class="hljs-string">'application/javascript'</span> },
        );
        <span class="hljs-keyword">const</span> workerUrl = URL.createObjectURL(blob);
        worker = <span class="hljs-keyword">new</span> Worker(workerUrl);

        <span class="hljs-keyword">const</span> offscreen = <span class="hljs-keyword">new</span> OffscreenCanvas(<span class="hljs-number">640</span>, <span class="hljs-number">480</span>);
        worker.postMessage({ <span class="hljs-attr">type</span>: <span class="hljs-string">'init'</span>, <span class="hljs-attr">canvas</span>: offscreen }, [offscreen]);

        worker.onmessage = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
          <span class="hljs-keyword">if</span> (e.data.type === <span class="hljs-string">'image'</span>) {
            <span class="hljs-keyword">const</span> url = URL.createObjectURL(e.data.blob);
            <span class="hljs-keyword">const</span> img = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'img'</span>);
            img.src = url;
            img.onload = <span class="hljs-function">() =&gt;</span> URL.revokeObjectURL(url);
            captures.prepend(img);
          }
        };
      }

    </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span>
&lt;/body&gt;
...
</code></pre>
<p>Then update <code>captureFrame</code> to use the worker and send the video frame as a bitmap to the worker. This allows the worker to use the bitmap data to draw and capture the image on the canvas.</p>
<p>We use the <code>createImageBitmap</code> API to turn the video frame into a bitmap and then send it as a transferable object. This makes it available in the worker context and usable by the OffscreenCanvas.</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">captureFrame</span>(<span class="hljs-params"></span>) </span>{
        <span class="hljs-keyword">if</span> (!stream || !worker) <span class="hljs-keyword">return</span>;
        createImageBitmap(video).then(<span class="hljs-function">(<span class="hljs-params">bitmap</span>) =&gt;</span> {
          worker.postMessage({ <span class="hljs-attr">type</span>: <span class="hljs-string">'frame'</span>, bitmap }, [bitmap]);
        });
}
</code></pre>
<p>Lastly, update the initCamera and call the initWorker function before the <code>startCapturing</code> function.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">initCamera</span>(<span class="hljs-params"></span>) </span>{
        <span class="hljs-keyword">try</span> {
          stream = <span class="hljs-keyword">await</span> navigator.mediaDevices.getUserMedia({ <span class="hljs-attr">video</span>: <span class="hljs-literal">true</span> });
          video.srcObject = stream;
          video.hidden = <span class="hljs-literal">false</span>;
          startBtn.disabled = <span class="hljs-literal">true</span>;
          initWorker(); <span class="hljs-comment">// NEW</span>
          startCapturing();
        } <span class="hljs-keyword">catch</span> (err) {
          <span class="hljs-built_in">console</span>.error(err);
          alert(<span class="hljs-string">'Unable to access camera: '</span> + err.message);
        }
}
</code></pre>
<p>Try the updated example. While capturing an image, use the input box to type as quickly as possible and observe for any lag.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1746295991506/97430e40-0960-4a69-862e-c4c32410571d.png" alt class="image--center mx-auto" /></p>
<p>Notice the difference: when the canvas renders on the main thread, the log is <code>0.80ms</code>, but in a web worker, it's <code>0.20ms</code>. This clearly demonstrates better performance. In the real world, this difference can save significant money and reduce the user churn rate.</p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>OffscreenCanvas is a helpful API that allows moving canvas tasks off the main thread, improving performance and user experience. In our example, we:</p>
<ol>
<li><p>Created a regular canvas on the main thread</p>
</li>
<li><p>Moved it to a web worker using OffscreenCanvas</p>
</li>
<li><p>Handled image rendering in the worker</p>
</li>
<li><p>Sent the result back to the main thread for display</p>
</li>
</ol>
<p>By using web workers and OffscreenCanvas, you can significantly improve performance in apps that use canvas for rendering. Try it in your next project.</p>
<p>Thanks for reading! If you found this article useful, please like, comment, and share it.</p>
<h2 id="heading-resources"><strong>Resources</strong></h2>
<ul>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas">OffscreenCanvas</a></p>
</li>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API">Web Workers API</a></p>
</li>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Transferable_objects">Transferable Objects</a></p>
</li>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Window/createImageBitmap">CreateImageBitMap</a></p>
</li>
<li><p><a target="_blank" href="https://caniuse.com/?search=OffscreenCanvas">CanIUse: OffscreenCanvas</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Why Strong Basics Matter in the AI Era]]></title><description><![CDATA[AI tools have changed how we build software. They can write code, fix bugs, and even help understand large codebases. But as AI gets better, one thing remains true: the better the input, the better the output.
To give AI the right instructions, you n...]]></description><link>https://blog.sachinchaurasiya.dev/why-strong-basics-matter-in-the-ai-era</link><guid isPermaLink="true">https://blog.sachinchaurasiya.dev/why-strong-basics-matter-in-the-ai-era</guid><category><![CDATA[AI]]></category><category><![CDATA[fundamentals]]></category><dc:creator><![CDATA[Sachin Chaurasiya]]></dc:creator><pubDate>Mon, 31 Mar 2025 17:48:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1743443051544/b069f65f-9071-46d8-b7cf-5fef5fd4052a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>AI tools have changed how we build software. They can write code, fix bugs, and even help understand large codebases. But as AI gets better, one thing remains true: <strong>the better the input, the better the output.</strong></p>
<p>To give AI the right instructions, you need to know what you want, why you want it, and how it will impact your work. That's why having strong basics in coding and problem-solving is more important than ever.</p>
<h2 id="heading-how-ai-is-changing-software-development">How AI is Changing Software Development</h2>
<p>AI has changed how developers work in several ways:</p>
<h3 id="heading-writing-code">Writing Code</h3>
<p>AI tools can write functions, classes, or components based on descriptions. They understand context and can match your coding style.</p>
<h3 id="heading-understanding-code">Understanding Code</h3>
<p>AI can analyze large codebases, explain unfamiliar code, and find patterns across different parts of your project.</p>
<h3 id="heading-finding-and-fixing-problems">Finding and Fixing Problems</h3>
<p>These tools can spot bugs, suggest fixes, and find performance issues that humans might miss.</p>
<h3 id="heading-automating-repetitive-tasks">Automating Repetitive Tasks</h3>
<p>Tasks like creating standard code, transforming data, and making tests can be automated with AI, letting developers focus on more important work.</p>
<h3 id="heading-helping-with-documentation">Helping with Documentation</h3>
<p>AI can create, summarize, and update documentation to keep information current.</p>
<p>These features boost productivity, but they depend on your ability to guide the AI effectively. Unclear instructions lead to poor solutions, which is why knowing the basics is so important.</p>
<h2 id="heading-why-the-basics-matter-more-now">Why The Basics Matter More Now</h2>
<h3 id="heading-good-in-good-out">Good In = Good Out</h3>
<p>AI works like this: what you tell it decides what you get back. Clear instructions get good answers. You need:</p>
<ul>
<li><p><strong>Basic Skills</strong>: Know how coding works so you can ask for what you need</p>
</li>
<li><p><strong>Planning Skills</strong>: Understand how parts fit together to guide AI</p>
</li>
<li><p><strong>Problem-Solving</strong>: Break big problems into small pieces AI can handle</p>
</li>
</ul>
<h3 id="heading-know-what-you-want-first">Know What You Want First</h3>
<p>Before asking AI for help, you must know:</p>
<ul>
<li><p><strong>What's the real problem?</strong> What can't be changed?</p>
</li>
<li><p><strong>Why this way?</strong> What other ways could work?</p>
</li>
<li><p><strong>Will it stay good?</strong> Is it fast? Easy to fix? Can it grow?</p>
</li>
</ul>
<p>Without these answers, you might get something that works but causes problems later.</p>
<h3 id="heading-check-ais-answers">Check AI's Answers</h3>
<p>Never just trust what AI gives you. Good basics help you:</p>
<ul>
<li><p>Find mistakes in AI's code</p>
</li>
<li><p>See security problems AI missed</p>
</li>
<li><p>Make sure the code follows good practices</p>
</li>
<li><p>Check that it actually fixes your problem</p>
</li>
</ul>
<h3 id="heading-stop-rewording-your-questions">Stop Rewording Your Questions</h3>
<p>Many people get stuck asking AI the same thing different ways. Strong basics help by:</p>
<ul>
<li><p>Letting you ask clearly the first time</p>
</li>
<li><p>Helping you understand what AI says back</p>
</li>
<li><p>Showing how to make real improvements</p>
</li>
</ul>
<h2 id="heading-how-to-use-ai-better">How to Use AI Better</h2>
<h3 id="heading-think-before-you-ask">Think Before You Ask</h3>
<p>Start with your own brain:</p>
<ol>
<li><p><strong>Know your problem</strong> before asking AI</p>
</li>
<li><p><strong>Think of some answers</strong> yourself first</p>
</li>
<li><p><strong>Pick parts</strong> where AI can help the most</p>
</li>
<li><p><strong>Gather facts</strong> that AI needs to understand</p>
</li>
</ol>
<h3 id="heading-always-double-check">Always Double-Check</h3>
<p>Never just use what AI gives you:</p>
<ul>
<li><p><strong>Try weird cases</strong> that might break it</p>
</li>
<li><p><strong>Make sure it's fast</strong>, not just working</p>
</li>
<li><p><strong>Look for safety problems</strong> AI missed</p>
</li>
<li><p><strong>Keep it simple</strong> so others can read it</p>
</li>
</ul>
<h3 id="heading-get-better-each-time">Get Better Each Time</h3>
<p>When AI gives bad answers:</p>
<ul>
<li><p>Ask why it didn't work</p>
</li>
<li><p>Learn more about your problem</p>
</li>
<li><p>Add helpful facts in your next try</p>
</li>
<li><p>Break big questions into smaller ones</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>AI is a great tool, but it can't replace knowledge and experience. To get the most out of it, focus on <strong>learning the basics, thinking critically, and improving your problem-solving skills</strong>. With strong fundamentals, AI can help you work faster and smarter.</p>
]]></content:encoded></item><item><title><![CDATA[How web worker works with a practical example]]></title><description><![CDATA[Ever noticed a webpage freezing during a heavy task? This happens because JavaScript runs on a single thread by default, causing a bad user experience. Users can't interact and have to wait until the task finishes. This problem can be solved using we...]]></description><link>https://blog.sachinchaurasiya.dev/how-web-worker-works-with-a-practical-example</link><guid isPermaLink="true">https://blog.sachinchaurasiya.dev/how-web-worker-works-with-a-practical-example</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Next.js]]></category><category><![CDATA[webworker]]></category><dc:creator><![CDATA[Sachin Chaurasiya]]></dc:creator><pubDate>Sun, 23 Feb 2025 12:42:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1740304339137/916aa4ae-9ec2-4afc-8ca4-504a75e585ae.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Ever noticed a webpage freezing during a heavy task? This happens because JavaScript runs on a single thread by default, causing a bad user experience. Users can't interact and have to wait until the task finishes. This problem can be solved using web workers. In this article, we will discuss what web workers are, why they are useful, and how to use them with a practical example by building an image compression application. Exciting, right? Let's get started.</p>
<h2 id="heading-what-are-web-workers">What are Web Workers?</h2>
<p>Web Workers let JavaScript run tasks in the background <strong>without blocking the main thread</strong>, which keeps your UI smooth and responsive. You can create them using the Web Workers API, which takes two parameters: <code>url</code> and <code>options</code>. Here is a simple example of creating a web worker.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> worker = <span class="hljs-keyword">new</span> Worker(<span class="hljs-string">'./worker.js'</span>, { <span class="hljs-attr">type</span>: <span class="hljs-string">'module'</span> });
</code></pre>
<h2 id="heading-why-use-web-workers">Why use Web Workers?</h2>
<p>As mentioned earlier, web workers run tasks in the background. Here are a few reasons to use them</p>
<ul>
<li><p>Prevents page lag during heavy computations</p>
</li>
<li><p>Handles large data processing efficiently</p>
</li>
<li><p>Improves performance for complex web applications</p>
</li>
</ul>
<h2 id="heading-how-do-they-work">How do they work?</h2>
<ol>
<li><p>The main thread <strong>creates a worker</strong> and gives it a task</p>
</li>
<li><p>The worker <strong>handles the task in the background</strong></p>
</li>
<li><p>When finished, it <strong>sends the result back</strong> to the main thread</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740305493024/1feeadef-c39e-4ee9-879f-5c65f07b8b33.png" alt class="image--center mx-auto" /></p>
<p>Alright, now we know what a web worker is, why to use them, and how they work. But that's not enough, right? So let's build the image compression application and see how to use web workers in practice.</p>
<h2 id="heading-project-setup">Project setup</h2>
<p>Create a Next.js project with TypeScript and Tailwind CSS</p>
<pre><code class="lang-bash">npx create-next-app@latest --typescript web-worker-with-example

<span class="hljs-built_in">cd</span> web-worker-with-example
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740308306689/1534324f-0ace-4064-b14d-cf4a7c4694c5.png" alt class="image--center mx-auto" /></p>
<p>To compress images in the browser, we will use the <code>@jsquash/web</code> npm library to encode and decode WebP images. This library is powered by WebAssembly, so let's install it.</p>
<pre><code class="lang-bash">npm install @jsquash/webp
</code></pre>
<p>Great, our project setup is complete. In the next section, we will create a worker script to manage image compression.</p>
<h2 id="heading-creating-worker-script">Creating worker script</h2>
<p>A worker script is a JavaScript or TypeScript file that contains the code to handle worker message events.</p>
<p>Create an <code>imageCompressionWorker.ts</code> file inside the <code>src/worker</code> folder and add the following code.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">/// &lt;reference lib="webworker" /&gt;</span>

<span class="hljs-keyword">const</span> ctx = self <span class="hljs-keyword">as</span> DedicatedWorkerGlobalScope;

<span class="hljs-keyword">import</span> { decode, encode } <span class="hljs-keyword">from</span> <span class="hljs-string">'@jsquash/webp'</span>;

ctx.onmessage = <span class="hljs-keyword">async</span> (
  event: MessageEvent&lt;{
    id: <span class="hljs-built_in">number</span>;
    imageFile: File;
    options: { quality: <span class="hljs-built_in">number</span> };
  }&gt;
) =&gt; {
  <span class="hljs-comment">// make sure the wasm is loaded</span>
  <span class="hljs-keyword">await</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'@jsquash/webp'</span>);

  <span class="hljs-keyword">const</span> { imageFile, options, id } = event.data;
  <span class="hljs-keyword">const</span> fileBuffer = <span class="hljs-keyword">await</span> imageFile.arrayBuffer();
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> imageData = <span class="hljs-keyword">await</span> decode(fileBuffer);
    <span class="hljs-keyword">const</span> compressedBuffer = <span class="hljs-keyword">await</span> encode(imageData, options);
    <span class="hljs-keyword">const</span> compressedBlob = <span class="hljs-keyword">new</span> Blob([compressedBuffer], {
      <span class="hljs-keyword">type</span>: imageFile.type,
    });
    ctx.postMessage({ id, blob: compressedBlob });
  } <span class="hljs-keyword">catch</span> (error: unknown) {
    <span class="hljs-keyword">const</span> message = error <span class="hljs-keyword">instanceof</span> <span class="hljs-built_in">Error</span> ? error.message : <span class="hljs-string">'Unknown error'</span>;
    ctx.postMessage({ id, error: message });
  }
};

<span class="hljs-keyword">export</span> {};
</code></pre>
<p>Here, we import the <code>encode</code> and <code>decode</code> methods from the <code>@jsquash/webp</code> library and use the worker global scope, <code>self</code>, to listen for messages from the main thread.</p>
<p>When a message arrives, we get the image file and options, then compress the image by first decoding and then encoding it with the quality option. Finally, we use <code>postMessage</code> to send the compressed image blob back to the main thread. If there's an error, we handle it and send the error message back using <code>postMessage</code>.</p>
<p>The worker script is ready. In the next section, we will build the Imagelist component, update the styles, update the page, and use the worker script to handle the compression.</p>
<h2 id="heading-using-the-web-worker">Using the web worker</h2>
<p>Before we start, let's update the <code>global.css</code> file with the following content and remove the default styling.</p>
<pre><code class="lang-typescript"><span class="hljs-meta">@tailwind</span> base;
<span class="hljs-meta">@tailwind</span> components;
<span class="hljs-meta">@tailwind</span> utilities;
</code></pre>
<p>Create a <code>ImageList.tsx</code> in the <code>src/components</code> folder and add the following code.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">/* eslint-disable @next/next/no-img-element */</span>
<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> ImgData = {
  id: <span class="hljs-built_in">number</span>;
  file: File;
  status: <span class="hljs-string">'compressing'</span> | <span class="hljs-string">'done'</span> | <span class="hljs-string">'error'</span>;
  originalUrl: <span class="hljs-built_in">string</span>;
  compressedUrl?: <span class="hljs-built_in">string</span>;
  error?: <span class="hljs-built_in">string</span>;
  compressedSize?: <span class="hljs-built_in">number</span>;
};

<span class="hljs-keyword">interface</span> ImageListProps {
  images: ImgData[];
}

<span class="hljs-keyword">const</span> formatBytes = (bytes: <span class="hljs-built_in">number</span>): <span class="hljs-function"><span class="hljs-params">string</span> =&gt;</span> {
  <span class="hljs-keyword">if</span> (bytes === <span class="hljs-number">0</span>) <span class="hljs-keyword">return</span> <span class="hljs-string">'0 Bytes'</span>;
  <span class="hljs-keyword">const</span> k = <span class="hljs-number">1024</span>;
  <span class="hljs-keyword">const</span> dm = <span class="hljs-number">2</span>;
  <span class="hljs-keyword">const</span> sizes = [<span class="hljs-string">'Bytes'</span>, <span class="hljs-string">'KB'</span>, <span class="hljs-string">'MB'</span>, <span class="hljs-string">'GB'</span>, <span class="hljs-string">'TB'</span>];
  <span class="hljs-keyword">const</span> i = <span class="hljs-built_in">Math</span>.floor(<span class="hljs-built_in">Math</span>.log(bytes) / <span class="hljs-built_in">Math</span>.log(k));
  <span class="hljs-keyword">return</span> <span class="hljs-built_in">parseFloat</span>((bytes / <span class="hljs-built_in">Math</span>.pow(k, i)).toFixed(dm)) + <span class="hljs-string">' '</span> + sizes[i];
};

<span class="hljs-keyword">const</span> ImageList: React.FC&lt;ImageListProps&gt; = <span class="hljs-function">(<span class="hljs-params">{ images }</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    &lt;div className=<span class="hljs-string">"mt-4"</span>&gt;
      &lt;h2 className=<span class="hljs-string">"text-xl font-semibold mb-2"</span>&gt;Image List&lt;/h2&gt;
      &lt;div className=<span class="hljs-string">"space-y-4"</span>&gt;
        {images.map(<span class="hljs-function">(<span class="hljs-params">img</span>) =&gt;</span> (
          &lt;div
            key={img.id}
            className=<span class="hljs-string">"flex flex-col md:flex-row items-center border p-4 rounded"</span>
          &gt;
            &lt;div className=<span class="hljs-string">"flex-1 flex flex-col items-center"</span>&gt;
              &lt;p className=<span class="hljs-string">"font-bold mb-2"</span>&gt;Original&lt;/p&gt;
              &lt;img
                src={img.originalUrl}
                alt=<span class="hljs-string">"Original"</span>
                className=<span class="hljs-string">"w-32 h-32 object-cover rounded border mb-2"</span>
              /&gt;
              &lt;p className=<span class="hljs-string">"text-sm"</span>&gt;Size: {formatBytes(img.file.size)}&lt;/p&gt;
            &lt;/div&gt;
            {img.status === <span class="hljs-string">'done'</span> &amp;&amp; img.compressedUrl ? (
              &lt;div className=<span class="hljs-string">"flex-1 flex flex-col items-center mt-4 md:mt-0"</span>&gt;
                &lt;p className=<span class="hljs-string">"font-bold mb-2"</span>&gt;Compressed&lt;/p&gt;
                &lt;img
                  src={img.compressedUrl}
                  alt=<span class="hljs-string">"Compressed"</span>
                  className=<span class="hljs-string">"w-32 h-32 object-cover rounded border mb-2"</span>
                /&gt;
                &lt;p className=<span class="hljs-string">"text-sm"</span>&gt;
                  Size:{<span class="hljs-string">' '</span>}
                  {img.compressedSize ? formatBytes(img.compressedSize) : <span class="hljs-string">'N/A'</span>}
                &lt;/p&gt;
                &lt;a
                  href={img.compressedUrl}
                  download={<span class="hljs-string">`<span class="hljs-subst">${img.file.name.replace(
                    <span class="hljs-regexp">/\.[^/.]+$/</span>,
                    <span class="hljs-string">''</span>
                  )}</span>-compressed.webp`</span>}
                  className=<span class="hljs-string">"mt-2 inline-block px-3 py-1 bg-blue-500 text-white rounded"</span>
                &gt;
                  Download
                &lt;/a&gt;
              &lt;/div&gt;
            ) : img.status === <span class="hljs-string">'compressing'</span> ? (
              &lt;div className=<span class="hljs-string">"flex-1 flex flex-col items-center mt-4 md:mt-0"</span>&gt;
                &lt;p className=<span class="hljs-string">"font-bold"</span>&gt;Compressing...&lt;/p&gt;
              &lt;/div&gt;
            ) : img.status === <span class="hljs-string">'error'</span> ? (
              &lt;div className=<span class="hljs-string">"flex-1 flex flex-col items-center mt-4 md:mt-0"</span>&gt;
                &lt;p className=<span class="hljs-string">"font-bold text-red-500"</span>&gt;<span class="hljs-built_in">Error</span> <span class="hljs-keyword">in</span> compression&lt;/p&gt;
              &lt;/div&gt;
            ) : <span class="hljs-literal">null</span>}
          &lt;/div&gt;
        ))}
      &lt;/div&gt;
    &lt;/div&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> ImageList;
</code></pre>
<p>The ImageList component takes one prop, <code>images</code>, which is a list of <code>ImgData</code>. It then displays the original and compressed images, showing their size and providing a download option for the compressed images.</p>
<p>Next, update the <code>app/page.tsx</code> with the following code, and let's go through the parts together.</p>
<pre><code class="lang-typescript"><span class="hljs-string">'use client'</span>;

<span class="hljs-keyword">import</span> { useState, useRef, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> ImageList, { ImgData } <span class="hljs-keyword">from</span> <span class="hljs-string">'../components/ImageList'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [images, setImages] = useState&lt;ImgData[]&gt;([]);
  <span class="hljs-keyword">const</span> [text, setText] = useState(<span class="hljs-string">''</span>);

  <span class="hljs-keyword">const</span> workerRef = useRef&lt;Worker | <span class="hljs-literal">null</span>&gt;(<span class="hljs-literal">null</span>);

  <span class="hljs-keyword">const</span> handleFileChange = <span class="hljs-function">(<span class="hljs-params">e: React.ChangeEvent&lt;HTMLInputElement&gt;</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> files = e.target.files;
    <span class="hljs-keyword">if</span> (!files || !workerRef.current) <span class="hljs-keyword">return</span>;
    <span class="hljs-keyword">const</span> filesArray = <span class="hljs-built_in">Array</span>.from(files);
    filesArray.forEach(<span class="hljs-function">(<span class="hljs-params">file, index</span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> id = <span class="hljs-built_in">Date</span>.now() + index;

      <span class="hljs-keyword">const</span> originalUrl = URL.createObjectURL(file);
      setImages(<span class="hljs-function">(<span class="hljs-params">prev</span>) =&gt;</span> [
        ...prev,
        { id, file, status: <span class="hljs-string">'compressing'</span>, originalUrl },
      ]);

      <span class="hljs-comment">// Send the file with its id to the worker.</span>
      workerRef.current!.postMessage({
        id,
        imageFile: file,
        options: { quality: <span class="hljs-number">75</span> },
      });
    });
  };

  <span class="hljs-comment">// Initialize the worker once when the component mounts.</span>
  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> worker = <span class="hljs-keyword">new</span> Worker(
      <span class="hljs-keyword">new</span> URL(<span class="hljs-string">'../worker/imageCompressionWorker.ts'</span>, <span class="hljs-keyword">import</span>.meta.url),
      { <span class="hljs-keyword">type</span>: <span class="hljs-string">'module'</span> }
    );
    workerRef.current = worker;

    <span class="hljs-comment">// Listen for messages from the worker.</span>
    worker.onmessage = <span class="hljs-function">(<span class="hljs-params">
      event: MessageEvent&lt;{ id: <span class="hljs-built_in">number</span>; blob?: Blob; error?: <span class="hljs-built_in">string</span> }&gt;
    </span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> { id, blob: compressedBlob, error } = event.data;
      setImages(<span class="hljs-function">(<span class="hljs-params">prev</span>) =&gt;</span>
        prev.map(<span class="hljs-function">(<span class="hljs-params">img</span>) =&gt;</span> {
          <span class="hljs-keyword">if</span> (img.id === id) {
            <span class="hljs-keyword">if</span> (error) <span class="hljs-keyword">return</span> { ...img, status: <span class="hljs-string">'error'</span>, error };
            <span class="hljs-keyword">const</span> compressedSize = compressedBlob!.size;
            <span class="hljs-keyword">const</span> compressedUrl = URL.createObjectURL(compressedBlob!);
            <span class="hljs-keyword">return</span> { ...img, status: <span class="hljs-string">'done'</span>, compressedUrl, compressedSize };
          }
          <span class="hljs-keyword">return</span> img;
        })
      );
    };

    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
      worker.terminate();
    };
  }, []);

  <span class="hljs-keyword">return</span> (
    &lt;div className=<span class="hljs-string">"min-h-screen p-8"</span>&gt;
      &lt;h1 className=<span class="hljs-string">"text-2xl font-bold text-center mb-4"</span>&gt;
        Image Compression <span class="hljs-keyword">with</span> Web Workers
      &lt;/h1&gt;
      &lt;div className=<span class="hljs-string">"rounded shadow p-4 mb-4 flex flex-col gap-2"</span>&gt;
        &lt;p className=<span class="hljs-string">"text-sm"</span>&gt;
          While images are compressing, you can interact <span class="hljs-keyword">with</span> the textarea below
          and observe the text being typed and UI is not frozen.
        &lt;/p&gt;
        &lt;p className=<span class="hljs-string">"text-sm"</span>&gt;
          Even you can open the dev tools and then open the performance tab and
          see the INP(Interaction to Next Paint) is very low.
        &lt;/p&gt;
        &lt;textarea
          className=<span class="hljs-string">"w-full h-32 border rounded p-2 text-black"</span>
          placeholder=<span class="hljs-string">"Type here while images are compressing..."</span>
          value={text}
          onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setText(e.target.value)}
        &gt;&lt;/textarea&gt;
      &lt;/div&gt;
      &lt;div className=<span class="hljs-string">"rounded shadow p-4"</span>&gt;
        &lt;input
          <span class="hljs-keyword">type</span>=<span class="hljs-string">"file"</span>
          multiple
          accept=<span class="hljs-string">"image/webp"</span>
          onChange={handleFileChange}
        /&gt;
        &lt;ImageList images={images} /&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>First, we import the hooks and the ImageList component, along with ImgData for type.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useState, useRef, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> ImageList, { ImgData } <span class="hljs-keyword">from</span> <span class="hljs-string">'../components/ImageList'</span>;
</code></pre>
<p>Then, we create a ref to hold the worker instance because we don't want to create the worker repeatedly with each re-render. We also want to avoid re-rendering the component if the worker instance changes.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> workerRef = useRef&lt;Worker | <span class="hljs-literal">null</span>&gt;(<span class="hljs-literal">null</span>);
</code></pre>
<p>In the useEffect, we are initializing the worker instance by using the <code>imageCompressionWorker.ts</code> worker script we created earlier.</p>
<blockquote>
<p>We use the URL API with <code>import.meta.url</code>. This makes the path relative to the current script instead of the HTML page. This way, the bundler can safely optimize, like renaming, because otherwise, the <code>worker.js</code> URL might point to a file not managed by the bundler, stopping it from making assumptions. Read more <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Worker/Worker#url">here</a>.</p>
</blockquote>
<p>Once the worker is initialized, we listen for messages from it. When we receive a message, we extract the id, blob, and error, then update the images state with the new values.</p>
<p>Finally, we clean up the worker when the component unmounts.</p>
<pre><code class="lang-typescript">useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> worker = <span class="hljs-keyword">new</span> Worker(
      <span class="hljs-keyword">new</span> URL(<span class="hljs-string">'../worker/imageCompressionWorker.ts'</span>, <span class="hljs-keyword">import</span>.meta.url),
      { <span class="hljs-keyword">type</span>: <span class="hljs-string">'module'</span> }
    );
    workerRef.current = worker;

    <span class="hljs-comment">// Listen for messages from the worker.</span>
    worker.onmessage = <span class="hljs-function">(<span class="hljs-params">
      event: MessageEvent&lt;{ id: <span class="hljs-built_in">number</span>; blob?: Blob; error?: <span class="hljs-built_in">string</span> }&gt;
    </span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> { id, blob: compressedBlob, error } = event.data;
      setImages(<span class="hljs-function">(<span class="hljs-params">prev</span>) =&gt;</span>
        prev.map(<span class="hljs-function">(<span class="hljs-params">img</span>) =&gt;</span> {
          <span class="hljs-keyword">if</span> (img.id === id) {
            <span class="hljs-keyword">if</span> (error) <span class="hljs-keyword">return</span> { ...img, status: <span class="hljs-string">'error'</span>, error };
            <span class="hljs-keyword">const</span> compressedSize = compressedBlob!.size;
            <span class="hljs-keyword">const</span> compressedUrl = URL.createObjectURL(compressedBlob!);
            <span class="hljs-keyword">return</span> { ...img, status: <span class="hljs-string">'done'</span>, compressedUrl, compressedSize };
          }
          <span class="hljs-keyword">return</span> img;
        })
      );
    };

    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
      worker.terminate();
    };
  }, []);
</code></pre>
<p>To manage image file uploads, we use the <code>handleFileChange</code> method. This method listens for the <code>onchange</code> event of the file input, processes the files, and sends them to the worker for compression.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> handleFileChange = <span class="hljs-function">(<span class="hljs-params">e: React.ChangeEvent&lt;HTMLInputElement&gt;</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> files = e.target.files;
    <span class="hljs-keyword">if</span> (!files || !workerRef.current) <span class="hljs-keyword">return</span>;
    <span class="hljs-keyword">const</span> filesArray = <span class="hljs-built_in">Array</span>.from(files);
    filesArray.forEach(<span class="hljs-function">(<span class="hljs-params">file, index</span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> id = <span class="hljs-built_in">Date</span>.now() + index;

      <span class="hljs-keyword">const</span> originalUrl = URL.createObjectURL(file);
      setImages(<span class="hljs-function">(<span class="hljs-params">prev</span>) =&gt;</span> [
        ...prev,
        { id, file, status: <span class="hljs-string">'compressing'</span>, originalUrl },
      ]);

      <span class="hljs-comment">// Send the file with its id to the worker.</span>
      workerRef.current!.postMessage({
        id,
        imageFile: file,
        options: { quality: <span class="hljs-number">75</span> },
      });
    });
  };
</code></pre>
<p>Finally, render the elements the textarea, image input, and image list.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740312099340/8c0a9973-30ca-44df-bb45-a8b6587ff623.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>User Selects Images:</strong> The user picks images using the file input, which makes the component create object URLs and mark each image as "compressing."</p>
</li>
<li><p><strong>Worker Communication:</strong> The component sends each image file (with options) to the Web Worker.</p>
</li>
<li><p><strong>Parallel Processes:</strong></p>
<ul>
<li><p><strong>Text Area Interaction:</strong> At the same time, the user can type in the text area, showing that the UI is not blocked.</p>
</li>
<li><p><strong>Image Compression:</strong> The worker compresses the image in the background.</p>
</li>
</ul>
</li>
<li><p><strong>Completion:</strong> When compression is done, the worker sends the result back to the component, which updates the UI with the compressed image while the text area keeps working smoothly.</p>
</li>
</ul>
<p>Great, everything is set up. In the next section, we will run the application and see how the web worker works.</p>
<p><img src="https://media0.giphy.com/media/v1.Y2lkPTc5MGI3NjExbjVoYnhvMWFpY25lcmpoc3luY2Ztbm94ZDdiOWFpYjg2azlhNmJqOCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/DPznISmq0hLRQqG71g/giphy.gif" alt class="image--center mx-auto" /></p>
<h2 id="heading-running-the-example">Running the example</h2>
<p>Open the terminal and run the command below, then go to <a target="_blank" href="http://localhost:3000/">http://localhost:3000/</a>.</p>
<pre><code class="lang-typescript">npm run dev
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740313421004/bc6532e8-ece4-4dc9-9fc3-0c284bbc5690.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740313327163/1fa3f3c7-6b95-4aa9-905a-0fba8ad19eda.png" alt class="image--center mx-auto" /></p>
<p>Try the live demo here: <a target="_blank" href="https://web-worker-with-example.vercel.app/">https://web-worker-with-example.vercel.app/</a></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Web workers are a great tool to improve application performance. By using Web Workers, you ensure <strong>faster, smoother, and more responsive applications</strong>. However, they should not be overused and should only be used when necessary. Also, check browser support, which is currently about 98% globally. You can check it <a target="_blank" href="https://caniuse.com/?search=webworkers">here</a>.</p>
<p>That's all for this topic. Thank you for reading! If you found this article helpful, please consider liking, commenting, and sharing it with others.</p>
<h2 id="heading-resources">Resources</h2>
<ul>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers">Using web workers</a></p>
</li>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Worker/Worker#browser_compatibility">Web worker Browser Compatibility</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/Sachin-chaurasiya/web-worker-with-example">Source Code</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/jamsinclair/jSquash/tree/main/packages/webp">Jsquash webp doc</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[How to Build a Collaborative Editor with Next.js and Liveblocks]]></title><description><![CDATA[Collaborative applications are now essential in modern software, allowing multiple users to work on the same document, design, or codebase at the same time. Think of Google Docs, Figma, or multiplayer coding platforms these tools are powerful because...]]></description><link>https://blog.sachinchaurasiya.dev/how-to-build-a-collaborative-editor-with-nextjs-and-liveblocks</link><guid isPermaLink="true">https://blog.sachinchaurasiya.dev/how-to-build-a-collaborative-editor-with-nextjs-and-liveblocks</guid><category><![CDATA[React]]></category><category><![CDATA[Next.js]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Sachin Chaurasiya]]></dc:creator><pubDate>Fri, 13 Dec 2024 07:01:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1734073061456/80dde5e4-e9e7-45f1-ada2-8f44b876a402.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Collaborative applications are now essential in modern software, allowing multiple users to work on the same document, design, or codebase at the same time. Think of Google Docs, Figma, or multiplayer coding platforms these tools are powerful because they offer <strong>real-time collaboration</strong>, where every change is instantly visible to everyone.</p>
<p>In this guide, I'll show you how to build a collaborative text editor using <strong>Next.js</strong> and <strong>Liveblocks</strong>, a library that makes real-time collaboration easy. By the end, you'll have a working editor where multiple users can edit a document, see each other's changes in real-time, and track who is online and what they are doing.</p>
<h2 id="heading-what-to-expect-from-this-guide"><strong>What to Expect from This Guide?</strong></h2>
<h3 id="heading-part-1-understanding-collaborative-editors-and-the-role-of-liveblocks"><strong>Part 1: Understanding Collaborative Editors and the Role of Liveblocks</strong></h3>
<p>In the first part, we'll look at what collaborative editors are, how they work, and the technologies behind them. We'll focus on <strong>WebSockets</strong> for real-time updates and discuss how <strong>Liveblocks</strong> makes building these features easier.</p>
<h3 id="heading-part-2-building-the-collaborative-editor-step-by-step"><strong>Part 2: Building the Collaborative Editor Step-by-Step</strong></h3>
<p>In the second part, we'll start building the editor from scratch. We'll set up the project, integrate Liveblocks, and add features like real-time editing, user presence indicators, and managing document state. By the end, you'll have a live, shareable editor to show off.</p>
<p>Ready to get started? First, let's understand the basics of collaborative editors, the technology behind them, and why Liveblocks is perfect for building them.</p>
<h2 id="heading-what-is-a-collaborative-editor">What is a Collaborative Editor?</h2>
<p>Imagine you're editing a document with a friend. You both see each other’s changes in real-time, making it feel like you're working on the same computer. That’s the magic of collaborative editors.</p>
<p>A collaborative editor is a software tool that lets multiple users work on the same document or file at the same time. Each user can make changes, and everyone sees these updates instantly. Examples include Google Docs, Notion, and Figma.</p>
<h2 id="heading-how-do-collaborative-editors-work">How Do Collaborative Editors Work?</h2>
<p>Collaborative editors use <strong>real-time communication</strong> between users and a shared server. Here’s a simple overview of how it works</p>
<ol>
<li><p>When a user edits the document, the changes are sent to the server.</p>
</li>
<li><p>The server processes these changes and sends them to all connected users.</p>
</li>
<li><p>Everyone’s view updates instantly, showing the changes in real-time.</p>
</li>
</ol>
<p>In scenarios where multiple users make changes simultaneously, the editor must effectively manage these updates to ensure consistency. This is where the technology that enables this real-time synchronization comes into play: <strong>WebSockets</strong>.</p>
<h2 id="heading-the-technology-behind-real-time-collaboration-websockets">The Technology Behind Real-Time Collaboration: WebSockets</h2>
<p>Most traditional websites use HTTP requests to talk to servers. This works well for static pages but isn't quick enough for real-time apps like collaborative editors.</p>
<p><strong>WebSockets</strong> fix this by allowing ongoing, two-way communication between the client and the server. Instead of sending separate requests for every update, a WebSocket connection stays <strong>open</strong>. This way, data can be sent and received instantly as changes occur.</p>
<p>Here's a simple explanation of how WebSocket communication works</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2bc3cf45-7764-4804-88f0-00f6b70ccd7f_1748x1024.png" alt="websocket-flow" /></p>
<ol>
<li><p>User 1 makes an edit.</p>
</li>
<li><p>The server gets the edit and sends it to both User 1 and User 2.</p>
</li>
<li><p>User 2 makes an edit, and the server sends it to both users again.</p>
</li>
</ol>
<p>This way, everyone sees the same document version in real-time.</p>
<h2 id="heading-liveblocks-simplifying-real-time-collaboration">Liveblocks: Simplifying Real-Time Collaboration</h2>
<p>Building a collaborative editor from scratch is possible, but handling real-time sync, conflict resolution, and user presence can be tricky. This is where <strong>Liveblocks</strong> helps.</p>
<p><strong>Liveblocks</strong> is a powerful library that makes building collaborative apps easier. Instead of writing all the WebSocket and state management code yourself, you can use Liveblocks’ built-in features, such as</p>
<ul>
<li><p><strong>Presence</strong>: Track who is online and what they are doing.</p>
</li>
<li><p><strong>Storage</strong>: Share and sync documents, drawings, or any content.</p>
</li>
<li><p><strong>Room Management</strong>: Manage connections and permissions for multiple users.</p>
</li>
</ul>
<p>Here’s why using Liveblocks can save you a lot of time:</p>
<ol>
<li><p><strong>Built-in Presence</strong>: See who’s editing, where their cursors are, and what they’re typing all in real-time.</p>
</li>
<li><p><strong>Conflict Handling</strong>: Easily manage updates when multiple users make changes at the same time.</p>
</li>
<li><p><strong>Simple Integration</strong>: It works seamlessly with popular frontend frameworks like Next.js, making it easier to build and scale.</p>
</li>
</ol>
<p>By using Liveblocks, you can focus on building the core features of your editor without worrying about low-level communication details.</p>
<h2 id="heading-project-setup">Project Setup</h2>
<p>Let's start by creating a new Next.js project.</p>
<pre><code class="lang-bash">npx create-next-app@latest collaborative-editor --typescript
</code></pre>
<p>After creating the project install the Liveblocks dependencies</p>
<pre><code class="lang-bash">npm install @liveblocks/client @liveblocks/node @liveblocks/react @liveblocks/yjs @tiptap/extension-collaboration @tiptap/extension-collaboration-cursor @tiptap/pm @tiptap/react @tiptap/starter-kit yjs
</code></pre>
<p>Next, create a Liveblocks config to set up the types for the user information and to display the cursor in the editor.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// liveblocks.config.ts</span>

<span class="hljs-keyword">declare</span> <span class="hljs-built_in">global</span> {
  <span class="hljs-keyword">interface</span> Liveblocks {
    Presence: { cursor: { x: <span class="hljs-built_in">number</span>; y: <span class="hljs-built_in">number</span> } | <span class="hljs-literal">null</span> };
    UserMeta: {
      id: <span class="hljs-built_in">string</span>;
      info: {
        name: <span class="hljs-built_in">string</span>;
        color: <span class="hljs-built_in">string</span>;
        picture: <span class="hljs-built_in">string</span>;
      };
    };
  }
}

<span class="hljs-keyword">export</span> {};
</code></pre>
<p>Go to <a target="_blank" href="https://liveblocks.io/">https://liveblocks.io/</a> and sign up for a free account. After signing up, you'll be taken to the dashboard where you will see two projects created for you. Select the first project, go to the <code>API keys</code> section on the left panel, and get the secret key.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728150240851/203bd5cf-9575-4e53-8b22-7b6b51f4806d.png" alt="liveblocks-dashboard1" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728150322415/edb4f666-3e3a-4ea4-bed3-3496579b728d.png" alt="liveblocks-dashboard2" /></p>
<p>Create a <code>.env</code> file and add <code>LIVEBLOCKS_SECRET_KEY</code> environment variable and secret key you copied from the API keys dashboard.</p>
<pre><code class="lang-bash">LIVEBLOCKS_SECRET_KEY=sk_dev_xxxxxxxxxx_xxxxxxx_xxx_xxxxx_x
</code></pre>
<p>Alright, the project setup is done. In the next section, we will create components for our editor.</p>
<h2 id="heading-create-components-for-the-editor">Create components for the editor</h2>
<blockquote>
<p>I won't talk about styling because this post is about building a collaborative editor with Liveblocks and Next.js. I'll share a GitHub link for the CSS and icon files.<br /><a target="_blank" href="https://github.com/Sachin-chaurasiya/collaborative-editor/tree/main/src/components">Here is the link for all components, their CSS files, and icons</a></p>
</blockquote>
<p>Let's start by creating a toolbar component and adding the following code. We will use Tiptap to build the editor because it is one of my favorite tools for creating rich text and collaborative editors.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// components/Toolbar.tsx</span>

<span class="hljs-keyword">import</span> { Editor } <span class="hljs-keyword">from</span> <span class="hljs-string">"@tiptap/react"</span>;
<span class="hljs-keyword">import</span> styles <span class="hljs-keyword">from</span> <span class="hljs-string">"./Toolbar.module.css"</span>;

<span class="hljs-keyword">type</span> Props = {
  editor: Editor | <span class="hljs-literal">null</span>;
};

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Toolbar</span>(<span class="hljs-params">{ editor }: Props</span>) </span>{
  <span class="hljs-keyword">if</span> (!editor) {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
  }

  <span class="hljs-keyword">return</span> (
    &lt;div className={styles.toolbar}&gt;
      &lt;button
        className={styles.button}
        onClick={<span class="hljs-function">() =&gt;</span> editor.chain().focus().toggleBold().run()}
        data-active={editor.isActive(<span class="hljs-string">"bold"</span>) ? <span class="hljs-string">"is-active"</span> : <span class="hljs-literal">undefined</span>}
        aria-label=<span class="hljs-string">"bold"</span>
      &gt;
        &lt;BoldIcon /&gt;
      &lt;/button&gt;
      &lt;button
        className={styles.button}
        onClick={<span class="hljs-function">() =&gt;</span> editor.chain().focus().toggleItalic().run()}
        data-active={editor.isActive(<span class="hljs-string">"italic"</span>) ? <span class="hljs-string">"is-active"</span> : <span class="hljs-literal">undefined</span>}
        aria-label=<span class="hljs-string">"italic"</span>
      &gt;
        &lt;ItalicIcon /&gt;
      &lt;/button&gt;
      &lt;button
        className={styles.button}
        onClick={<span class="hljs-function">() =&gt;</span> editor.chain().focus().toggleStrike().run()}
        data-active={editor.isActive(<span class="hljs-string">"strike"</span>) ? <span class="hljs-string">"is-active"</span> : <span class="hljs-literal">undefined</span>}
        aria-label=<span class="hljs-string">"strikethrough"</span>
      &gt;
        &lt;StrikethroughIcon /&gt;
      &lt;/button&gt;

      &lt;button
        className={styles.button}
        onClick={<span class="hljs-function">() =&gt;</span> editor.chain().focus().toggleBlockquote().run()}
        data-active={editor.isActive(<span class="hljs-string">"blockquote"</span>) ? <span class="hljs-string">"is-active"</span> : <span class="hljs-literal">undefined</span>}
        aria-label=<span class="hljs-string">"strikethrough"</span>
      &gt;
        &lt;BlockQuoteIcon /&gt;
      &lt;/button&gt;

      &lt;button
        className={styles.button}
        onClick={<span class="hljs-function">() =&gt;</span> editor.chain().focus().setHorizontalRule().run()}
        data-active={<span class="hljs-literal">undefined</span>}
        aria-label=<span class="hljs-string">"horizontal-line"</span>
      &gt;
        &lt;HorizontalLineIcon /&gt;
      &lt;/button&gt;

      &lt;button
        className={styles.button}
        onClick={<span class="hljs-function">() =&gt;</span> editor.chain().focus().toggleBulletList().run()}
        data-active={editor.isActive(<span class="hljs-string">"bulletList"</span>) ? <span class="hljs-string">"is-active"</span> : <span class="hljs-literal">undefined</span>}
        aria-label=<span class="hljs-string">"bullet-list"</span>
      &gt;
        &lt;BulletListIcon /&gt;
      &lt;/button&gt;

      &lt;button
        className={styles.button}
        onClick={<span class="hljs-function">() =&gt;</span> editor.chain().focus().toggleOrderedList().run()}
        data-active={editor.isActive(<span class="hljs-string">"orderedList"</span>) ? <span class="hljs-string">"is-active"</span> : <span class="hljs-literal">undefined</span>}
        aria-label=<span class="hljs-string">"number-list"</span>
      &gt;
        &lt;OrderedListIcon /&gt;
      &lt;/button&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>Here we are using the Tiptap editor and its formatting options like italic, bold, and lists.</p>
<p>Next, let's create an Avatars component to display all the online users in the room.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// components/Avatars.tsx</span>

<span class="hljs-keyword">import</span> { useOthers, useSelf } <span class="hljs-keyword">from</span> <span class="hljs-string">"@liveblocks/react/suspense"</span>;
<span class="hljs-keyword">import</span> styles <span class="hljs-keyword">from</span> <span class="hljs-string">"./Avatars.module.css"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Avatars</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> users = useOthers();
  <span class="hljs-keyword">const</span> currentUser = useSelf();

  <span class="hljs-keyword">return</span> (
    &lt;div className={styles.avatars}&gt;
      {users.map(<span class="hljs-function">(<span class="hljs-params">{ connectionId, info }</span>) =&gt;</span> {
        <span class="hljs-keyword">return</span> (
          &lt;Avatar key={connectionId} picture={info.picture} name={info.name} /&gt;
        );
      })}

      {currentUser &amp;&amp; (
        &lt;div className=<span class="hljs-string">"relative ml-8 first:ml-0"</span>&gt;
          &lt;Avatar
            picture={currentUser.info.picture}
            name={currentUser.info.name}
          /&gt;
        &lt;/div&gt;
      )}
    &lt;/div&gt;
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Avatar</span>(<span class="hljs-params">{ picture, name }: { picture: <span class="hljs-built_in">string</span>; name: <span class="hljs-built_in">string</span> }</span>) </span>{
  <span class="hljs-keyword">return</span> (
    &lt;div className={styles.avatar} data-tooltip={name}&gt;
      &lt;img
        src={picture}
        className={styles.avatar_picture}
        data-tooltip={name}
      /&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>Here we are using two hooks:</p>
<ul>
<li><p><code>useOthers</code>: to get the list of other users connected to the same room I'm in.</p>
</li>
<li><p><code>useSelf</code>: to get the info of my own user</p>
</li>
</ul>
<p>Now let's create an <code>ErrorListener</code> component to catch any errors happening inside the Liveblocks provider. most important one is the <code>4001</code> when user is trying to connect the room which they don’t have access to.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// components/ErrorListener.tsx</span>

<span class="hljs-string">"use client"</span>;

<span class="hljs-keyword">import</span> { useErrorListener } <span class="hljs-keyword">from</span> <span class="hljs-string">"@liveblocks/react/suspense"</span>;

<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">import</span> styles <span class="hljs-keyword">from</span> <span class="hljs-string">"./ErrorListener.module.css"</span>;
<span class="hljs-keyword">import</span> { Loading } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Loading"</span>;

<span class="hljs-keyword">const</span> ErrorListener = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [error, setError] = React.useState&lt;<span class="hljs-built_in">string</span> | <span class="hljs-literal">undefined</span>&gt;();

  useErrorListener(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
    <span class="hljs-keyword">switch</span> (error.code) {
      <span class="hljs-keyword">case</span> <span class="hljs-number">-1</span>:
        setError(<span class="hljs-string">"Could not connect to Liveblocks"</span>);

        <span class="hljs-keyword">break</span>;

      <span class="hljs-keyword">case</span> <span class="hljs-number">4001</span>:
        <span class="hljs-comment">// Could not connect because you don't have access to this room</span>
        setError(<span class="hljs-string">"You don't have access to this room"</span>);

        <span class="hljs-keyword">break</span>;

      <span class="hljs-keyword">default</span>:
        setError(<span class="hljs-string">"An unexpected error occurred"</span>);

        <span class="hljs-keyword">break</span>;
    }
  });

  <span class="hljs-keyword">return</span> error ? (
    &lt;div className={styles.container}&gt;
      &lt;div className={styles.error}&gt;{error}&lt;/div&gt;
    &lt;/div&gt;
  ) : (
    &lt;Loading /&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> ErrorListener;
</code></pre>
<p>Thanks to Liveblocks for making things easier with the <code>useErrorListener</code> hook, which handles all the error-catching logic in our collaborative application.</p>
<p>Next is the <code>ConnectToRoom</code> component, which shows the initial UI where the user enters the room name they want to join. Then, it redirects to the room page using the entered name as the roomId.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// components/ConnectToRoom.tsx</span>

<span class="hljs-string">"use client"</span>;

<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> styles <span class="hljs-keyword">from</span> <span class="hljs-string">"./ConnectToRoom.module.css"</span>;

<span class="hljs-keyword">import</span> { useRouter } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/navigation"</span>;

<span class="hljs-keyword">const</span> ConnectToRoom = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> router = useRouter();

  <span class="hljs-keyword">const</span> inputRef = React.useRef&lt;HTMLInputElement&gt;(<span class="hljs-literal">null</span>);

  <span class="hljs-keyword">const</span> connectToRoom = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> roomId = inputRef.current?.value;
    <span class="hljs-keyword">if</span> (roomId &amp;&amp; roomId.length &gt; <span class="hljs-number">0</span>) {
      <span class="hljs-keyword">await</span> (<span class="hljs-keyword">async</span> () =&gt; router.push(<span class="hljs-string">`/room?roomId=<span class="hljs-subst">${roomId}</span>`</span>))();
    }
  };

  <span class="hljs-keyword">return</span> (
    &lt;div className={styles.container}&gt;
      &lt;h1&gt;Connect to a room&lt;/h1&gt;
      &lt;p&gt;Connect to a room to start collaborating <span class="hljs-keyword">with</span> others.&lt;/p&gt;
      &lt;input
        ref={inputRef}
        <span class="hljs-keyword">type</span>=<span class="hljs-string">"text"</span>
        placeholder=<span class="hljs-string">"Room ID"</span>
        className={styles.input}
      /&gt;
      &lt;button className={styles.button} onClick={connectToRoom}&gt;
        Connect
      &lt;/button&gt;
    &lt;/div&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> ConnectToRoom;
</code></pre>
<p>Since we will get the roomId from the URL, let's create a custom hook.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// hooks/useRoomId.ts</span>

<span class="hljs-keyword">import</span> { useSearchParams } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/navigation"</span>;
<span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> useRoomId = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> searchParams = useSearchParams();

  <span class="hljs-keyword">const</span> [roomId, setRoomId] = useState&lt;<span class="hljs-built_in">string</span> | <span class="hljs-literal">null</span>&gt;(
    searchParams.get(<span class="hljs-string">"roomId"</span>)
  );

  useEffect(<span class="hljs-function">() =&gt;</span> {
    setRoomId(searchParams.get(<span class="hljs-string">"roomId"</span>));
  }, [searchParams]);

  <span class="hljs-keyword">return</span> roomId;
};
</code></pre>
<p>Lastly, there's the Editor component, which works together with Tiptap and Liveblocks to create some of the magic.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// components/Editor.tsx</span>

<span class="hljs-string">"use client"</span>;

<span class="hljs-keyword">import</span> { useEditor, EditorContent } <span class="hljs-keyword">from</span> <span class="hljs-string">"@tiptap/react"</span>;
<span class="hljs-keyword">import</span> StarterKit <span class="hljs-keyword">from</span> <span class="hljs-string">"@tiptap/starter-kit"</span>;
<span class="hljs-keyword">import</span> Collaboration <span class="hljs-keyword">from</span> <span class="hljs-string">"@tiptap/extension-collaboration"</span>;
<span class="hljs-keyword">import</span> CollaborationCursor <span class="hljs-keyword">from</span> <span class="hljs-string">"@tiptap/extension-collaboration-cursor"</span>;
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> Y <span class="hljs-keyword">from</span> <span class="hljs-string">"yjs"</span>;
<span class="hljs-keyword">import</span> { LiveblocksYjsProvider } <span class="hljs-keyword">from</span> <span class="hljs-string">"@liveblocks/yjs"</span>;
<span class="hljs-keyword">import</span> { useRoom, useSelf } <span class="hljs-keyword">from</span> <span class="hljs-string">"@liveblocks/react/suspense"</span>;
<span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { Toolbar } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Toolbar"</span>;
<span class="hljs-keyword">import</span> styles <span class="hljs-keyword">from</span> <span class="hljs-string">"./Editor.module.css"</span>;
<span class="hljs-keyword">import</span> { Avatars } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/components/Avatars"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Editor</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> room = useRoom();
  <span class="hljs-keyword">const</span> [doc, setDoc] = useState&lt;Y.Doc&gt;();
  <span class="hljs-keyword">const</span> [provider, setProvider] = useState&lt;<span class="hljs-built_in">any</span>&gt;();

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> yDoc = <span class="hljs-keyword">new</span> Y.Doc();
    <span class="hljs-keyword">const</span> yProvider = <span class="hljs-keyword">new</span> LiveblocksYjsProvider(room, yDoc);
    setDoc(yDoc);
    setProvider(yProvider);

    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
      yDoc?.destroy();
      yProvider?.destroy();
    };
  }, [room]);

  <span class="hljs-keyword">if</span> (!doc || !provider) {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
  }

  <span class="hljs-keyword">return</span> &lt;TiptapEditor doc={doc} provider={provider} /&gt;;
}

<span class="hljs-keyword">type</span> EditorProps = {
  doc: Y.Doc;
  provider: <span class="hljs-built_in">any</span>;
};

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">TiptapEditor</span>(<span class="hljs-params">{ doc, provider }: EditorProps</span>) </span>{
  <span class="hljs-keyword">const</span> userInfo = useSelf(<span class="hljs-function">(<span class="hljs-params">me</span>) =&gt;</span> me.info);

  <span class="hljs-keyword">const</span> editor = useEditor({
    editorProps: {
      attributes: {
        <span class="hljs-keyword">class</span>: styles.editor,
      },
    },
    extensions: [
      StarterKit.configure({
        history: <span class="hljs-literal">false</span>,
      }),
      Collaboration.configure({
        <span class="hljs-built_in">document</span>: doc,
      }),
      CollaborationCursor.configure({
        provider: provider,
        user: userInfo,
      }),
    ],
  });

  <span class="hljs-keyword">return</span> (
    &lt;div className={styles.container}&gt;
      &lt;div className={styles.editorHeader}&gt;
        &lt;Toolbar editor={editor} /&gt;
        &lt;Avatars /&gt;
      &lt;/div&gt;
      &lt;EditorContent editor={editor} className={styles.editorContainer} /&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728152536046/4b837b2d-84eb-4738-b596-74058d057c92.png" alt="liveblocks-with-tiptap-editor" /></p>
<p>Liveblocks gives us a YJS provider that helps define and store the editor content. Plus, Tiptap supports collaboration extensions, making it easy to set up collaboration.</p>
<p>Great, our editor is ready, but for collaboration, we need to set up the Liveblocks provider and RoomProvider. This will allow users to authenticate themselves and create a session for the room they want to join.</p>
<p>In the next section, we will discuss rooms and how Liveblocks enable real-time collaboration.</p>
<h2 id="heading-set-up-liveblocks-provider-and-room-provider">Set Up Liveblocks Provider and Room Provider</h2>
<p>Before writing the code let's first understand how Liveblocks work with the diagram.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728153780519/6eed729b-1043-4cad-b588-76230cab221c.png" alt="liveblock-flow-diagram" /></p>
<ul>
<li><p>When a user opens the app, it sends a request to find out which room they want to join. The room ID is obtained using the useRoomId hook.</p>
</li>
<li><p>After getting the roomId, the LiveblocksProvider is set up.</p>
</li>
<li><p>An authentication request is sent to <code>/api/liveblocks-auth</code> (we will set this up soon) with the roomId to check the user's access.</p>
</li>
<li><p>If the authentication is successful, the room setup starts. This includes creating a new collaborative document instance (Y.Doc) and setting up a provider (LiveblocksYjsProvider) to handle synchronization with the Liveblocks backend.</p>
</li>
<li><p>The LiveblocksYjsProvider opens a WebSocket connection with Liveblocks, joining the specified room.</p>
</li>
<li><p>The initial presence state (like cursor position and user activity) is shared with other participants in the room.</p>
</li>
<li><p><strong>User Presence</strong>: When a user joins, their presence is updated and shared with everyone in the room.</p>
</li>
<li><p><strong>Cursor Tracking</strong>: Their cursor position is tracked and shared instantly using the CollaborationCursor extension.</p>
</li>
<li><p><strong>Document Synchronization</strong>: Any changes a user makes are sent to the Liveblocks server, which updates the shared document for all users.</p>
</li>
<li><p>If <strong>User A</strong> edits the document, these changes are sent to Liveblocks as updates.</p>
</li>
<li><p>Liveblocks sends these changes to all users, keeping the document in sync.</p>
</li>
<li><p>When <strong>User B</strong> joins, their cursor and presence are shared with everyone.</p>
</li>
<li><p>When a user leaves, the Y.Doc instance and provider are removed, and the WebSocket connection is closed.</p>
</li>
<li><p>This starts a cleanup process to remove the user’s presence and updates from the room.</p>
</li>
</ul>
<p>I hope the explanation above helps you understand how Liveblocks works.</p>
<p>As mentioned, Liveblocks needs an endpoint to verify the user and start a session for the room they want to join. So, let's create an API endpoint called <code>liveblocks-auth</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// app/api/liveblocks-auth/route.ts</span>

<span class="hljs-keyword">import</span> { Liveblocks } <span class="hljs-keyword">from</span> <span class="hljs-string">"@liveblocks/node"</span>;
<span class="hljs-keyword">import</span> { NextRequest } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/server"</span>;

<span class="hljs-keyword">const</span> liveblocks = <span class="hljs-keyword">new</span> Liveblocks({
  secret: process.env.LIVEBLOCKS_SECRET_KEY!,
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">POST</span>(<span class="hljs-params">request: NextRequest</span>) </span>{
  <span class="hljs-keyword">const</span> userId = <span class="hljs-built_in">Math</span>.floor(<span class="hljs-built_in">Math</span>.random() * <span class="hljs-number">10</span>) % USER_INFO.length;

  <span class="hljs-keyword">const</span> roomId = request.nextUrl.searchParams.get(<span class="hljs-string">"roomId"</span>);

  <span class="hljs-keyword">const</span> session = liveblocks.prepareSession(<span class="hljs-string">`session-<span class="hljs-subst">${userId}</span>`</span>, {
    userInfo: USER_INFO[userId],
  });

  session.allow(roomId!, session.FULL_ACCESS);

  <span class="hljs-keyword">const</span> { body, status } = <span class="hljs-keyword">await</span> session.authorize();
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Response(body, { status });
}

<span class="hljs-keyword">const</span> USER_INFO = [
  {
    name: <span class="hljs-string">"Sachin Chaurasiya"</span>,
    color: <span class="hljs-string">"#D583F0"</span>,
    picture: <span class="hljs-string">"https://github.com/Sachin-chaurasiya.png"</span>,
  },
  {
    name: <span class="hljs-string">"Mislav Abha"</span>,
    color: <span class="hljs-string">"#F08385"</span>,
    picture: <span class="hljs-string">"https://liveblocks.io/avatars/avatar-2.png"</span>,
  },
  {
    name: <span class="hljs-string">"Tatum Paolo"</span>,
    color: <span class="hljs-string">"#F0D885"</span>,
    picture: <span class="hljs-string">"https://liveblocks.io/avatars/avatar-3.png"</span>,
  },
  {
    name: <span class="hljs-string">"Anjali Wanda"</span>,
    color: <span class="hljs-string">"#85EED6"</span>,
    picture: <span class="hljs-string">"https://liveblocks.io/avatars/avatar-4.png"</span>,
  },
  {
    name: <span class="hljs-string">"Jody Hekla"</span>,
    color: <span class="hljs-string">"#85BBF0"</span>,
    picture: <span class="hljs-string">"https://liveblocks.io/avatars/avatar-5.png"</span>,
  },
  {
    name: <span class="hljs-string">"Emil Joyce"</span>,
    color: <span class="hljs-string">"#8594F0"</span>,
    picture: <span class="hljs-string">"https://liveblocks.io/avatars/avatar-6.png"</span>,
  },
  {
    name: <span class="hljs-string">"Jory Quispe"</span>,
    color: <span class="hljs-string">"#85DBF0"</span>,
    picture: <span class="hljs-string">"https://liveblocks.io/avatars/avatar-7.png"</span>,
  },
  {
    name: <span class="hljs-string">"Quinn Elton"</span>,
    color: <span class="hljs-string">"#87EE85"</span>,
    picture: <span class="hljs-string">"https://liveblocks.io/avatars/avatar-8.png"</span>,
  },
];
</code></pre>
<p>Here, we are using fake user data, but in a real product, you would get the user data from the database and then create the session.</p>
<p>This line lets the user access the room with full permissions, which are <code>room:read</code> and <code>room:write</code>. This is just for demo purposes, but in real situations, access will depend on the user's role.</p>
<pre><code class="lang-typescript">session.allow(roomId!, session.FULL_ACCESS);
</code></pre>
<p>Next, Let's create a <code>Provider</code> component and use it in the <code>RootLayout</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// app/Providers.tsx</span>

<span class="hljs-string">"use client"</span>;

<span class="hljs-keyword">import</span> { useRoomId } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/hooks/useRoomId"</span>;
<span class="hljs-keyword">import</span> { LiveblocksProvider } <span class="hljs-keyword">from</span> <span class="hljs-string">"@liveblocks/react"</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-keyword">type</span> PropsWithChildren } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Providers</span>(<span class="hljs-params">{ children }: PropsWithChildren</span>) </span>{
  <span class="hljs-keyword">const</span> roomId = useRoomId();

  <span class="hljs-keyword">return</span> (
    &lt;LiveblocksProvider
      key={roomId}
      authEndpoint={<span class="hljs-string">`/api/liveblocks-auth?roomId=<span class="hljs-subst">${roomId}</span>`</span>}
    &gt;
      {children}
    &lt;/LiveblocksProvider&gt;
  );
}
</code></pre>
<p>Update the root layout and wrap the children with the providers.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// app/layout.tsx</span>
<span class="hljs-keyword">import</span> { Providers } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Providers"</span>;
...

<span class="hljs-keyword">return</span> (
    ...
    &lt;body&gt;
      &lt;Providers&gt;{children}&lt;/Providers&gt;
    &lt;/body&gt;
    ...
)
</code></pre>
<p>Alright, the providers are set up. Now, let's create a Room component using <code>RoomProvider</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// app/Room.tsx</span>

<span class="hljs-string">"use client"</span>;

<span class="hljs-keyword">import</span> { ReactNode } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { RoomProvider } <span class="hljs-keyword">from</span> <span class="hljs-string">"@liveblocks/react/suspense"</span>;
<span class="hljs-keyword">import</span> { ClientSideSuspense } <span class="hljs-keyword">from</span> <span class="hljs-string">"@liveblocks/react"</span>;
<span class="hljs-keyword">import</span> ErrorListener <span class="hljs-keyword">from</span> <span class="hljs-string">"@/components/ErrorListener"</span>;
<span class="hljs-keyword">import</span> { useRoomId } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/hooks/useRoomId"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Room</span>(<span class="hljs-params">{ children }: { children: ReactNode }</span>) </span>{
  <span class="hljs-keyword">const</span> roomId = useRoomId();

  <span class="hljs-keyword">return</span> (
    &lt;RoomProvider
      id={roomId ?? <span class="hljs-string">""</span>}
      initialPresence={{
        cursor: <span class="hljs-literal">null</span>,
      }}
      key={roomId}
    &gt;
      &lt;ClientSideSuspense fallback={&lt;ErrorListener /&gt;}&gt;
        {children}
      &lt;/ClientSideSuspense&gt;
    &lt;/RoomProvider&gt;
  );
}
</code></pre>
<p>Here, <code>RoomProvider</code> uses two props: <code>roomId</code> and <code>initialPresence</code> for the user's cursor, which is just the x, y position of the cursor.</p>
<p>We use <code>ClientSideSuspense</code> with the <code>ErrorListener</code> component as a fallback. If there's an error, it will show the error; otherwise, it will display a loader, meaning the providers are still loading.</p>
<p>Next, create the Room page and add the following code. It's straightforward: use the Room component to wrap the Editor so that the editor gets all the connections in that specific room.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// app/room/page.tsx</span>
<span class="hljs-string">"use client"</span>;

<span class="hljs-keyword">import</span> { Room } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/app/Room"</span>;
<span class="hljs-keyword">import</span> { Editor } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/components/Editor"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">RoomPage</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    &lt;main&gt;
      &lt;Room&gt;
        &lt;Editor /&gt;
      &lt;/Room&gt;
    &lt;/main&gt;
  );
}
</code></pre>
<p>Finally, update the Home page, which is <code>app/page.tsx</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> ConnectToRoom <span class="hljs-keyword">from</span> <span class="hljs-string">"@/components/ConnectToRoom"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    &lt;main&gt;
      &lt;ConnectToRoom /&gt;
    &lt;/main&gt;
  );
}
</code></pre>
<p>Alright, Liveblocks providers are set up correctly. Now, let's move on to the most exciting part: testing our collaborative editor. Are you excited? I am too! Let's test it in the next section.</p>
<h2 id="heading-testing">Testing</h2>
<p>Start the dev server by running the following command</p>
<pre><code class="lang-typescript">npm run dev
</code></pre>
<p>The server will start on <a target="_blank" href="http://localhost:3000">localhost:3000</a>, and you will see the "Connect to Room" interface.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728155541656/f5b0679e-a948-40aa-8c47-86e016795b4c.png" alt="connect-to-room-page" /></p>
<p>Enter the room ID, and you will be taken to the room page where you can work with other users in real time.</p>
<p>Here is an example of two users connecting to the same room, <code>local-room</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728155881616/b7154686-f7f7-4152-93cb-f3814ef06332.gif" alt="demo-gif" /></p>
<p>Great job, and congratulations on building the editor with real-time collaboration.</p>
<p>Thanks for reading to the end. If you don't want to miss interesting topics and project-building content, subscribe to the <a target="_blank" href="https://devbuddyweekly.substack.com/">Dev Buddy newsletter</a>.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this guide, we explore the power of collaborative applications, focusing on building a real-time collaborative text editor using Next.js and Liveblocks.</p>
<p>We break down the process into understanding the role of collaborative editors and setting up a project with features like real-time editing, user presence tracking, and document state management.</p>
<h2 id="heading-resources">Resources</h2>
<ul>
<li><p><a target="_blank" href="https://github.com/Sachin-chaurasiya/collaborative-editor">GitHub Repo</a></p>
</li>
<li><p>Thanks to Liveblocks for creating different examples <a target="_blank" href="https://github.com/liveblocks/liveblocks/tree/main/examples">here</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[How to Use Proxy Objects in JavaScript]]></title><description><![CDATA[Objects in JavaScript are useful data types that let us define complex data with simple key-value pairs, like a dictionary. Sometimes, you might want to change how JavaScript objects work by default. This is where Proxy Objects are helpful. In this a...]]></description><link>https://blog.sachinchaurasiya.dev/how-to-use-proxy-objects-in-javascript</link><guid isPermaLink="true">https://blog.sachinchaurasiya.dev/how-to-use-proxy-objects-in-javascript</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Sachin Chaurasiya]]></dc:creator><pubDate>Sat, 26 Oct 2024 18:10:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1729966077998/9c7eb988-7ed2-417c-93c4-bedeb8ad8315.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Objects in JavaScript are useful data types that let us define complex data with simple key-value pairs, like a dictionary. Sometimes, you might want to change how JavaScript objects work by default. This is where Proxy Objects are helpful. In this article, we will discuss what proxy objects are, why they are useful, and how to use them.</p>
<h2 id="heading-what-is-a-proxy-object">What is a Proxy Object?</h2>
<p>Before understanding what a Proxy Object is, let's look at the word <code>Proxy</code>. A proxy means something that acts like the original thing but isn't the original thing. Similarly, a Proxy Object is an object created using the original object that can intercept and change how the original object works.</p>
<p>The <code>Proxy</code> constructor takes two parameters</p>
<ul>
<li><p>target: the object you want to create a proxy for</p>
</li>
<li><p>handler: an object with operations you want to change or redefine</p>
</li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> proxyObject = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Proxy</span>(target, handler)
</code></pre>
<h2 id="heading-how-does-a-proxy-object-work"><strong>How Does a Proxy Object Work?</strong></h2>
<p>When we create Proxy Objects with a handler that has operations you want to change, it intercepts those operations, catches the call to the target object, and runs the custom logic you defined for those operations.</p>
<p>The operations are called traps, which are basically the internal methods of an object. Some of them are:</p>
<ul>
<li><p>get</p>
</li>
<li><p>set</p>
</li>
<li><p>deleteProperty</p>
</li>
</ul>
<p><img src="https://dqy38fnwh4fqs.cloudfront.net/UH9O6EPGOE7P9Q9CGBMGML6MRKMO/blog/blog-content-image-17a80f80-0420-4af7-8e9c-401c2348ce42.webp" alt /></p>
<p>Here, I have created a simple visual showing how a Proxy Object works. If we try to access or set a value, it intercepts and runs the operations (traps) defined in the handler.</p>
<p>Alright, I hope the what and how of Proxy Objects are clear. Next, we will discuss some use cases to show why Proxy Objects are useful.</p>
<h2 id="heading-use-cases-of-proxy-object">Use cases of Proxy Object</h2>
<h3 id="heading-logging">Logging</h3>
<p>Let's say you want to create a system where every time a property is accessed from the object, it logs the information.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> platform = {
  <span class="hljs-keyword">type</span>: <span class="hljs-string">"peerlist.io"</span>,
  handle: <span class="hljs-string">"sachin87"</span>,
};

<span class="hljs-keyword">const</span> proxyPlatformObj = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Proxy</span>(platform, {
  get(target, key) {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`[Info]: Accessing <span class="hljs-subst">${key}</span> at <span class="hljs-subst">${<span class="hljs-built_in">Date</span>.now()}</span>`</span>);
    <span class="hljs-keyword">return</span> target[key];
  },
});

<span class="hljs-comment">// try to access the property</span>
proxyPlatformObj.type;

<span class="hljs-comment">// [Info]: Accessing type at 1729661722827</span>

<span class="hljs-comment">// 'peerlist.io'</span>
</code></pre>
<p>This is a very simple use case, but it's useful when logging is needed, and you can expand it to do more advanced things.</p>
<p>We can also let users access properties that aren't directly available. For example, in the case above, we might want the full URL of the platform with the user handle.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> proxyPlatformObj = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Proxy</span>(platform, {
  get(target, key) {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`[Info]: Accessing <span class="hljs-subst">${key}</span> at <span class="hljs-subst">${<span class="hljs-built_in">Date</span>.now()}</span>`</span>);
    <span class="hljs-keyword">if</span> (key === <span class="hljs-string">"url"</span>) {
      <span class="hljs-keyword">return</span> <span class="hljs-string">`https://<span class="hljs-subst">${target.<span class="hljs-keyword">type</span>}</span>/<span class="hljs-subst">${target.handle}</span>`</span>;
    }
    <span class="hljs-keyword">return</span> target[key];
  },
});

<span class="hljs-comment">// try to access the url property</span>
proxyPlatformObj.url;

<span class="hljs-comment">// [Info]: Accessing url at 1729662118855</span>
<span class="hljs-comment">// 'https://peerlist.io/sachin87'</span>
</code></pre>
<h3 id="heading-validation">Validation</h3>
<p>Another use case is checking the value before adding it. For instance, let's say we want to check the handle's value before setting it. We will validate two basic conditions:</p>
<ul>
<li><p>It should be in lowercase</p>
</li>
<li><p>It can be alphanumeric</p>
</li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> proxyPlatformObj = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Proxy</span>(platform, {
  set(target, key, value) {
    <span class="hljs-keyword">if</span> (key === <span class="hljs-string">"handle"</span> &amp;&amp; !(<span class="hljs-regexp">/^[a-z0-9]+$/</span>.test(value))) {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">`[Error]: <span class="hljs-subst">${key}</span> should be in small case and can be alphanumerical`</span>);
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-keyword">return</span> <span class="hljs-built_in">Reflect</span>.set(target, key, value);
    }
  },
});

proxyPlatformObj.handle=<span class="hljs-string">"Sachin87"</span>
<span class="hljs-comment">// [Error]: handle should be in small case and can be alphanumerical</span>
</code></pre>
<p>You might be wondering what <code>Reflect.set(target, key, value)</code> is. It's a namespace object with static methods for calling JavaScript object's internal methods that can be intercepted. If you don't want validation for all properties, you can use the <code>Reflect</code> object to keep the default behavior.</p>
<p>We talked about two use cases logging and validation, but you can intercept other internal methods based on your needs.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>We talked about what a Proxy Object is, how it works, and some of its uses. A Proxy Object is helpful, but we should be aware of its downsides. We should use it only when necessary to avoid adding complexity or bugs by mistake.</p>
<p>That's all for this topic. Thank you for reading! If you found this article helpful, please consider liking, commenting, and sharing it with others.</p>
<h2 id="heading-further-reading"><strong>Further Reading</strong></h2>
<ul>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy">MDN Proxy Doc</a></p>
</li>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect">MDN Reflect Doc</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[What is Retrieval-Augmented Generation (RAG)?]]></title><description><![CDATA[In recent years, AI models like GPT-4 have become super popular for generating text that sounds human-like. These models are called Large Language Models (LLMs), and they’re great for writing articles, answering questions, and even creating stories. ...]]></description><link>https://blog.sachinchaurasiya.dev/what-is-retrieval-augmented-generation-rag</link><guid isPermaLink="true">https://blog.sachinchaurasiya.dev/what-is-retrieval-augmented-generation-rag</guid><category><![CDATA[AI]]></category><category><![CDATA[generative ai]]></category><category><![CDATA[RAG ]]></category><dc:creator><![CDATA[Sachin Chaurasiya]]></dc:creator><pubDate>Sat, 21 Sep 2024 16:42:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1726936761398/44e8d08d-1046-4117-b277-268bfc86b397.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In recent years, AI models like GPT-4 have become super popular for generating text that sounds human-like. These models are called Large Language Models (LLMs), and they’re great for writing articles, answering questions, and even creating stories. But, they have some limits. we’ll talk about how Retrieval-Augmented Generation (RAG) makes these models even better.</p>
<h2 id="heading-why-do-large-language-models-llms-have-limits">Why Do Large Language Models (LLMs) Have Limits?</h2>
<p>Even though LLMs are incredibly smart, they still have some challenges</p>
<p>1. <strong>Old Information</strong></p>
<p>LLMs only know what they were trained on. They don’t know about anything that happened after their training, so they can miss out on the latest news or facts.</p>
<p>2. <strong>Limited Memory</strong></p>
<p>If you have a long conversation or ask many questions, LLMs can <strong>forget</strong> the earlier part of the conversation because they can only hold so much information at once.</p>
<p>3. <strong>Guessing Sometimes</strong></p>
<p>Sometimes, LLMs don’t know the answer but will make something up that sounds believable. This is called <strong>hallucination</strong> and it’s a big problem if you need correct information.</p>
<h2 id="heading-what-is-retrieval-augmented-generation-rag">What is Retrieval-Augmented Generation (RAG)?</h2>
<p>RAG helps solve these issues by allowing the AI model to <strong>look up information</strong> before giving you an answer. Here’s how it works</p>
<p><strong>Step 1: Find Information</strong></p>
<p>When you ask a question, instead of only relying on what the model already knows, RAG searches for extra information from the web, databases, or other sources.</p>
<p><strong>Step 2: Combine</strong></p>
<p>After finding relevant information, RAG gives it to the AI model, which combines it with its own knowledge to come up with a response.</p>
<p><strong>Step 3: Generate Answer</strong></p>
<p>The AI then uses both the information it found and what it already knows to create a more accurate and up-to-date answer.</p>
<h2 id="heading-how-does-rag-help">How Does RAG Help?</h2>
<p>Here are some of the ways <strong>RAG</strong> improves how AI models work</p>
<p>1. <strong>Stays Up-to-Date</strong></p>
<p>RAG can look for real-time information from live sources, which means it can answer questions based on current facts.</p>
<p>2. <strong>Fewer Mistakes</strong></p>
<p>Since RAG finds real facts from external sources, it reduces the chances of the AI making up wrong information.</p>
<p>3. <strong>Handles Specific Topics</strong></p>
<p>If the AI needs to answer a question about a specific subject (like medicine or law), RAG can find data from trusted sources in that field, making the answers much more reliable.</p>
<p>In short, <strong>Retrieval-Augmented Generation (RAG)</strong> makes AI models smarter and more reliable by letting them find and use real-time information before answering. This makes it a fantastic tool for anything that needs up-to-date or factual information, like research, customer service, or learning new things.</p>
<p>RAG is an exciting development, and it shows how AI can continue to get better at understanding and helping us in more useful ways.</p>
<p>Thanks for reading till the end! I’ll see you next week with another interesting topic. Until then, happy learning!</p>
<h2 id="heading-connect-with-me">Connect with me</h2>
<ul>
<li><p><a target="_blank" href="https://www.linkedin.com/in/sachin-chaurasiya"><strong>LinkedIn</strong></a></p>
</li>
<li><p><a target="_blank" href="https://twitter.com/sachindotcom"><strong>Twitter</strong></a></p>
</li>
<li><p><a target="_blank" href="https://github.com/Sachin-chaurasiya"><strong>GitHub</strong></a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[How to Use Broadcast Channel API in React]]></title><description><![CDATA[In today’s web applications, keeping information updated across multiple tabs or windows can greatly enhance the user experience. For instance, if a user logs out in one tab, you want that action to be reflected in all other tabs. The Broadcast Chann...]]></description><link>https://blog.sachinchaurasiya.dev/how-to-use-broadcast-channel-api-in-react</link><guid isPermaLink="true">https://blog.sachinchaurasiya.dev/how-to-use-broadcast-channel-api-in-react</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[React]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Sachin Chaurasiya]]></dc:creator><pubDate>Sat, 14 Sep 2024 17:53:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1726336195058/b1802844-4939-4258-905c-f7fc68b2b346.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In today’s web applications, keeping information updated across multiple tabs or windows can greatly enhance the user experience. For instance, if a user logs out in one tab, you want that action to be reflected in all other tabs. The <strong>Broadcast Channel API</strong> makes this easy by allowing communication between different browsing contexts of the same origin. This article will guide you on how to use this API in a React application.</p>
<h2 id="heading-what-is-the-broadcast-channel-api"><strong>What is the Broadcast Channel API?</strong></h2>
<p>The <strong>Broadcast Channel API</strong> is a simple method to enable communication between different tabs, windows, or iframes of the same website. It allows you to broadcast messages to all other contexts listening on the same channel, making it ideal for real-time updates and synchronization.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb06aba45-d579-4713-bb9c-8c9b153cfe77_2058x1000.png" alt /></p>
<h2 id="heading-why-use-it"><strong>Why Use It?</strong></h2>
<p>• <strong>Real-Time Updates</strong>: Sync data like user sessions across different tabs.</p>
<p>• <strong>Ease of Integration</strong>: Simple to add to your existing React app.</p>
<p>• <strong>No Additional Libraries</strong>: Functions natively in modern browsers without extra dependencies.</p>
<h2 id="heading-setting-up-the-broadcast-channel-api-in-react"><strong>Setting Up the Broadcast Channel API in React</strong></h2>
<p>Let’s walk through how to use the Broadcast Channel API in a React application by creating a custom hook to manage communication.</p>
<h3 id="heading-create-a-custom-hook"><strong>Create a Custom Hook</strong></h3>
<p>First, create a custom hook named useBroadcastChannel to encapsulate the Broadcast Channel logic.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">const</span> useBroadcastChannel = <span class="hljs-function">(<span class="hljs-params">channelName</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> [message, setMessage] = useState(<span class="hljs-literal">null</span>);
    <span class="hljs-keyword">const</span> channel = <span class="hljs-keyword">new</span> BroadcastChannel(channelName);

    useEffect(<span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">const</span> handleMessage = <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
            setMessage(event.data);
        };

        channel.onmessage = handleMessage;

        <span class="hljs-comment">// Clean up the channel when the component unmounts</span>
        <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
            channel.close();
        };
    }, [channel]);

    <span class="hljs-keyword">const</span> sendMessage = <span class="hljs-function">(<span class="hljs-params">msg</span>) =&gt;</span> {
        channel.postMessage(msg);
    };

    <span class="hljs-keyword">return</span> { message, sendMessage };
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> useBroadcastChannel;
</code></pre>
<p>This hook creates a new BroadcastChannel, listens for incoming messages, and provides a function to send messages.</p>
<h3 id="heading-use-the-hook-in-a-react-component"><strong>Use the Hook in a React Component</strong></h3>
<p>Let’s use our custom hook in a React component to manage login sessions across different tabs.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> useBroadcastChannel <span class="hljs-keyword">from</span> <span class="hljs-string">'./useBroadcastChannel'</span>;

<span class="hljs-keyword">const</span> AuthManager = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> { message, sendMessage } = useBroadcastChannel(<span class="hljs-string">'auth_channel'</span>);

    <span class="hljs-keyword">const</span> handleLogin = <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-comment">// Notify all tabs that the user has logged in</span>
        sendMessage({ <span class="hljs-attr">type</span>: <span class="hljs-string">'login'</span>, <span class="hljs-attr">user</span>: <span class="hljs-string">'JohnDoe'</span> });
    };

    <span class="hljs-keyword">const</span> handleLogout = <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-comment">// Notify all tabs that the user has logged out</span>
        sendMessage({ <span class="hljs-attr">type</span>: <span class="hljs-string">'logout'</span> });
    };

    useEffect(<span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">if</span> (message) {
            <span class="hljs-keyword">if</span> (message.type === <span class="hljs-string">'logout'</span>) {
                alert(<span class="hljs-string">'You have been logged out in another tab!'</span>);
            }
        }
    }, [message]);

    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Authentication Manager<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{handleLogin}</span>&gt;</span>Log In<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{handleLogout}</span>&gt;</span>Log Out<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
    );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> AuthManager;
</code></pre>
<p>This AuthManager component uses the useBroadcastChannel hook to handle user authentication state across tabs. When a user logs in or out, a message is sent to all other tabs on the <code>auth_channel</code> channel. If a logout message is received in any tab, it triggers an alert to notify the user.</p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>The Broadcast Channel API provides a straightforward way to synchronize data across multiple tabs or windows in your web applications. By using a custom React hook, you can easily manage real-time messaging and improve user experience. Whether you’re handling login states or syncing other types of data, the Broadcast Channel API simplifies cross-tab communication.</p>
<h2 id="heading-further-reading"><strong>Further Reading</strong></h2>
<p>• <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_Channel_API">MDN Web Docs on Broadcast Channel API</a></p>
<h2 id="heading-connect-with-me"><strong>Connect with me</strong></h2>
<ul>
<li><p><a target="_blank" href="https://www.linkedin.com/in/sachin-chaurasiya">LinkedIn</a></p>
</li>
<li><p><a target="_blank" href="https://twitter.com/sachindotcom">Twitter</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/Sachin-chaurasiya">GitHub</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[How the Page Visibility API Improves Web Performance and User Experience]]></title><description><![CDATA[Making web applications fast and user-friendly is very important today. One useful tool for this is the Page Visibility API. This API tells developers if a web page is visible to the user or hidden in the background. It helps manage resources better ...]]></description><link>https://blog.sachinchaurasiya.dev/how-the-page-visibility-api-improves-web-performance-and-user-experience</link><guid isPermaLink="true">https://blog.sachinchaurasiya.dev/how-the-page-visibility-api-improves-web-performance-and-user-experience</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[React]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[webdev]]></category><dc:creator><![CDATA[Sachin Chaurasiya]]></dc:creator><pubDate>Wed, 26 Jun 2024 06:30:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1719329119711/ffada89e-180b-4b3c-a340-880d2d2aac68.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Making web applications fast and user-friendly is very important today. One useful tool for this is the <strong>Page Visibility API</strong>. This API tells developers if a web page is visible to the user or hidden in the background. It helps manage resources better and improve user interactions. In this article, we'll look at what the Page Visibility API is, why it matters, how to use it, some example use cases, and how to integrate it with ReactJS.</p>
<h2 id="heading-what-is-the-page-visibility-api">What is the Page Visibility API?</h2>
<p>The Page Visibility API is a web tool that lets developers check if a web page is visible or hidden. It has properties and events that notify when a page becomes visible or hidden, so developers can change how the app behaves.</p>
<h3 id="heading-key-concepts">Key Concepts</h3>
<ul>
<li><p><code>document.visibilityState</code>: This property shows if the document is visible or not. Possible values include:</p>
<ul>
<li><p><code>visible</code>: The page is visible to the user.</p>
</li>
<li><p><code>hidden</code>: The page is not visible to the user.</p>
</li>
</ul>
</li>
<li><p><code>visibilitychange</code> Event: This event occurs whenever the document's visibility state changes.</p>
</li>
</ul>
<h2 id="heading-why-use-the-page-visibility-api">Why Use the Page Visibility API?</h2>
<h3 id="heading-performance-optimization">Performance Optimization</h3>
<p>When a web page is not visible, it's smart to reduce or stop heavy tasks like animations, video playback, or data polling. This saves CPU and battery life, especially on mobile devices.</p>
<h3 id="heading-user-experience-improvement">User Experience Improvement</h3>
<p>By knowing when a user is not looking at the page, you can pause things like video playback or game animations. This way, the user won't miss any content. When the user comes back, you can resume these activities, making the experience smooth.</p>
<h3 id="heading-accurate-analytics">Accurate Analytics</h3>
<p>Tracking visibility changes helps gather more accurate usage data. Knowing when users switch tabs or minimize windows provides better insights into user behavior.</p>
<h2 id="heading-how-to-use-the-page-visibility-api">How to Use the Page Visibility API?</h2>
<p>Using the Page Visibility API in plain JavaScript is simple. Here’s a basic example</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">document</span>.addEventListener(<span class="hljs-string">'visibilitychange'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">if</span> (<span class="hljs-built_in">document</span>.visibilityState === <span class="hljs-string">'visible'</span>) {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Page is visible'</span>);
    <span class="hljs-comment">// Resume activities</span>
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Page is not visible'</span>);
    <span class="hljs-comment">// Pause activities</span>
  }
});
</code></pre>
<p>In this example, an event listener is attached to the <code>visibilitychange</code> event. Depending on the visibility state, appropriate actions are taken.</p>
<h3 id="heading-using-the-page-visibility-api-with-reactjs">Using the Page Visibility API with ReactJS</h3>
<p>To use the Page Visibility API in a React application, you can create a custom hook that encapsulates the logic.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">const</span> usePageVisibility = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [isVisible, setIsVisible] = useState(!<span class="hljs-built_in">document</span>.hidden);

  <span class="hljs-keyword">const</span> handleVisibilityChange = <span class="hljs-function">() =&gt;</span> {
    setIsVisible(!<span class="hljs-built_in">document</span>.hidden);
  };

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">document</span>.addEventListener(<span class="hljs-string">'visibilitychange'</span>, handleVisibilityChange);

    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-built_in">document</span>.removeEventListener(<span class="hljs-string">'visibilitychange'</span>, handleVisibilityChange);
    };
  }, []);

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

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> usePageVisibility;
</code></pre>
<p>You can then use this custom hook within a React component</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> usePageVisibility <span class="hljs-keyword">from</span> <span class="hljs-string">'./usePageVisibility'</span>;

<span class="hljs-keyword">const</span> MyComponent = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> isVisible = usePageVisibility();

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (isVisible) {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Component is visible'</span>);
      <span class="hljs-comment">// Resume activities</span>
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Component is not visible'</span>);
      <span class="hljs-comment">// Pause activities</span>
    }
  }, [isVisible]);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      {isVisible ? 'Page is visible' : 'Page is not visible'}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> MyComponent;
</code></pre>
<p>This approach ensures that your React components can respond to visibility changes efficiently, improving the overall user experience.</p>
<h2 id="heading-example-use-cases">Example Use Cases</h2>
<h3 id="heading-video-streaming-services">Video Streaming Services</h3>
<p>Platforms like YouTube and Netflix use the Page Visibility API to pause videos when users switch tabs or minimize the browser. This way, users don't miss any content and it saves bandwidth and system resources.</p>
<h3 id="heading-online-games">Online Games</h3>
<p>Online games can pause the game when the tab is not active. This prevents users from losing progress or missing important events in the game. It also helps save system resources and improve performance.</p>
<h3 id="heading-data-fetching-and-polling">Data Fetching and Polling</h3>
<p>Web applications that often fetch data from a server can slow down or stop polling when the page is not visible. This cuts down on unnecessary network requests and uses resources more efficiently.</p>
<h2 id="heading-best-practices">Best Practices</h2>
<h3 id="heading-performance-considerations">Performance Considerations</h3>
<p>When using the Page Visibility API, make sure that any paused activities resume efficiently. Avoid unnecessary state changes and resource loading to maintain good performance.</p>
<h3 id="heading-error-handling">Error Handling</h3>
<p>Always include error-handling mechanisms to manage unexpected issues or browser limitations. This helps maintain a smooth user experience, even in rare cases.</p>
<h3 id="heading-user-notifications">User Notifications</h3>
<p>Consider letting users know when activities are paused because of visibility changes, especially for important tasks like video calls or gaming. This transparency can boost user trust and satisfaction.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>The Page Visibility API helps make web apps better by adjusting to the user's focus. With this API, developers can improve performance, enhance user experience, and get accurate analytics. Whether you're using plain JavaScript or React, the Page Visibility API is very useful for modern web development.</p>
<p>That's all for this topic. Thank you for reading! If you found this article helpful, please consider liking, commenting, and sharing it with others.</p>
<h2 id="heading-resources">Resources</h2>
<ol>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API">MDN Web Docs on Page Visibility API</a></p>
</li>
<li><p><a target="_blank" href="https://caniuse.com/pagevisibility">Can I use: Browser Support for Page Visibility API</a></p>
</li>
<li><p><a target="_blank" href="https://acme.com/webapis/visibility.html">Test Page Visibility API</a></p>
</li>
</ol>
<h2 id="heading-connect-with-me"><strong>Connect with me</strong></h2>
<ul>
<li><p><a target="_blank" href="https://www.linkedin.com/in/sachin-chaurasiya"><strong>LinkedIn</strong></a></p>
</li>
<li><p><a target="_blank" href="https://twitter.com/sachindotcom"><strong>Twitter</strong></a></p>
</li>
<li><p><a target="_blank" href="https://github.com/Sachin-chaurasiya"><strong>GitHub</strong></a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[5 Powerful TypeScript Tricks]]></title><description><![CDATA[Unlock the full potential of TypeScript with these five powerful tricks that will improve your coding skills. From securing your types with const assertions to mastering the keyof operator, these tips will help you write cleaner, more efficient code....]]></description><link>https://blog.sachinchaurasiya.dev/5-powerful-typescript-tricks</link><guid isPermaLink="true">https://blog.sachinchaurasiya.dev/5-powerful-typescript-tricks</guid><category><![CDATA[TypeScript]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Types]]></category><dc:creator><![CDATA[Sachin Chaurasiya]]></dc:creator><pubDate>Mon, 27 May 2024 03:30:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1716623650213/c88ce8a0-d096-4999-b74c-8a5d314a64f3.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Unlock the full potential of TypeScript with these five powerful tricks that will improve your coding skills. From securing your types with const assertions to mastering the keyof operator, these tips will help you write cleaner, more efficient code.</p>
<h2 id="heading-locking-down-your-types-with-const-assertions">Locking Down Your Types with <code>const</code> Assertions</h2>
<p>Ever wanted to make sure your types stay the same throughout your code? That's where const assertions come in handy! Think of them as superglue for your types. When you use <code>as const</code>, TypeScript ensures nothing changes your types later on. It's like putting a "Do Not Touch" sign on your variables to keep them safe.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> user = {
  id: <span class="hljs-number">1</span>,
  name: <span class="hljs-string">'John Doe'</span>,
  email: <span class="hljs-string">'john.doe@example.com'</span>
} <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span>;

<span class="hljs-keyword">type</span> User = <span class="hljs-keyword">typeof</span> user;

<span class="hljs-comment">// This will cause a TypeScript error</span>
<span class="hljs-comment">// user.id = 2;</span>
</code></pre>
<h2 id="heading-creating-custom-types-with-pick">Creating Custom Types with Pick</h2>
<p>Imagine you have a large type, but you only need a few parts of it. No problem! With the <code>Pick</code> trick, you can create a new type that selects only what you need. It's like customizing your order at a restaurant – you get exactly what you want, without any extra stuff.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">interface</span> User {
  id: <span class="hljs-built_in">number</span>;
  name: <span class="hljs-built_in">string</span>;
  email: <span class="hljs-built_in">string</span>;
}

<span class="hljs-keyword">type</span> UserSummary = Pick&lt;User, <span class="hljs-string">'name'</span> | <span class="hljs-string">'email'</span>&gt;;

<span class="hljs-keyword">const</span> user: User = {
  id: <span class="hljs-number">1</span>,
  name: <span class="hljs-string">'John Doe'</span>,
  email: <span class="hljs-string">'john.doe@example.com'</span>
};

<span class="hljs-keyword">const</span> summary: UserSummary = {
  name: user.name,
  email: user.email
};
</code></pre>
<h2 id="heading-narrowing-down-your-options-with-extract">Narrowing Down Your Options with Extract</h2>
<p>Ever had a lot of choices but only needed a few specific ones? That's where <code>Extract</code> helps! It's like a magic wand that picks out exactly what you need from a list of options. Say goodbye to guesswork and hello to precision!</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">type</span> Fruit = <span class="hljs-string">'apple'</span> | <span class="hljs-string">'banana'</span> | <span class="hljs-string">'cherry'</span> | <span class="hljs-string">'date'</span>;
<span class="hljs-keyword">type</span> TropicalFruit = Extract&lt;Fruit, <span class="hljs-string">'banana'</span> | <span class="hljs-string">'date'</span>&gt;;

<span class="hljs-keyword">const</span> myFruit: TropicalFruit = <span class="hljs-string">'banana'</span>; <span class="hljs-comment">// Valid</span>
<span class="hljs-comment">// This will cause a TypeScript error</span>
<span class="hljs-comment">// const myFruit: TropicalFruit = 'apple';</span>
</code></pre>
<h2 id="heading-keeping-things-safe-and-sound-with-readonly">Keeping Things Safe and Sound with <code>Readonly</code></h2>
<p>Imagine you have some important data that should never change. That's where <code>Readonly</code> comes in! It's like putting your data in a secure vault with a strong lock. Once you make something <code>Readonly</code>, no one can change it.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> fruits: ReadonlyArray&lt;<span class="hljs-built_in">string</span>&gt; = [<span class="hljs-string">'apple'</span>, <span class="hljs-string">'banana'</span>, <span class="hljs-string">'cherry'</span>];

<span class="hljs-comment">// This will cause a TypeScript error</span>
<span class="hljs-comment">// fruits.push('date');</span>

<span class="hljs-comment">// This will also cause a TypeScript error</span>
<span class="hljs-comment">// fruits[1] = 'blueberry';</span>
</code></pre>
<h2 id="heading-mastering-the-keyof-operator">Mastering the <code>keyof</code> Operator</h2>
<p>Ever wanted to find out what keys are in an object? Meet <code>keyof</code> – your helpful tool! It shows you all the keys in an object, making it easier to work with your data.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">interface</span> User {
  id: <span class="hljs-built_in">number</span>;
  name: <span class="hljs-built_in">string</span>;
  email: <span class="hljs-built_in">string</span>;
}

<span class="hljs-keyword">type</span> UserKey = keyof User;

<span class="hljs-keyword">const</span> key: UserKey = <span class="hljs-string">'name'</span>; <span class="hljs-comment">// Valid</span>
<span class="hljs-comment">// This will cause a TypeScript error</span>
<span class="hljs-comment">// const invalidKey: UserKey = 'age';</span>
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Unlock the full power of TypeScript with five simple tricks: use const assertions to lock your types, create custom types with Pick, narrow down choices with Extract, protect data with Readonly, and use the keyof operator to easily work with object keys. These tips will help you write cleaner and more efficient code.</p>
<p>That's all for this topic. Thank you for reading! If you found this article helpful, please consider liking, commenting, and sharing it with others.</p>
<h2 id="heading-connect-with-me"><strong>Connect with me</strong></h2>
<ul>
<li><p><a target="_blank" href="https://www.linkedin.com/in/sachin-chaurasiya"><strong>LinkedIn</strong></a></p>
</li>
<li><p><a target="_blank" href="https://twitter.com/sachindotcom"><strong>Twitter</strong></a></p>
</li>
<li><p><a target="_blank" href="https://github.com/Sachin-chaurasiya"><strong>GitHub</strong></a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[How TypeScript Type Predicates Enhance Code Safety]]></title><description><![CDATA[TypeScript's type predicates are a powerful feature that improves type safety and makes code more reliable. They help confirm what a variable really is, which helps developers avoid errors and makes the code clearer. In this article, we will discuss ...]]></description><link>https://blog.sachinchaurasiya.dev/how-typescript-type-predicates-enhance-code-safety</link><guid isPermaLink="true">https://blog.sachinchaurasiya.dev/how-typescript-type-predicates-enhance-code-safety</guid><category><![CDATA[TypeScript]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[TypeSafety]]></category><dc:creator><![CDATA[Sachin Chaurasiya]]></dc:creator><pubDate>Mon, 20 May 2024 02:30:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1716016010648/0572dfbf-c90c-47e0-b1ef-ae758b49d834.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>TypeScript's type predicates are a powerful feature that improves type safety and makes code more reliable. They help confirm what a variable really is, which helps developers avoid errors and makes the code clearer. In this article, we will discuss what type predicates are and how to use them. We will also talk about their drawbacks.</p>
<h2 id="heading-what-are-type-predicates-in-typescript">What are Type Predicates in TypeScript?</h2>
<p>A type predicate is a function that returns a boolean, showing whether a variable is of a specific type.</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">isType</span>(<span class="hljs-params">arg: <span class="hljs-built_in">any</span></span>): <span class="hljs-title">arg</span> <span class="hljs-title">is</span> <span class="hljs-title">Type</span> </span>{
  <span class="hljs-comment">// logic to check if arg is of Type</span>
}
</code></pre>
<p>Here, <code>arg is Type</code> is the type predicate. It tells TypeScript that if the function returns <code>true</code>, <code>arg</code> is of type <code>Type</code>.</p>
<h2 id="heading-ensuring-data-integrity-in-api-responses">Ensuring Data Integrity in API Responses</h2>
<p>When dealing with API responses, the data might not always be in the expected format. For example, if we get user data from an API, we need to make sure it matches our <code>User</code> interface before using it.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">interface</span> User {
  id: <span class="hljs-built_in">number</span>;
  name: <span class="hljs-built_in">string</span>;
  email: <span class="hljs-built_in">string</span>;
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">isUser</span>(<span class="hljs-params">data: <span class="hljs-built_in">any</span></span>): <span class="hljs-title">data</span> <span class="hljs-title">is</span> <span class="hljs-title">User</span> </span>{
  <span class="hljs-keyword">return</span> data &amp;&amp; <span class="hljs-keyword">typeof</span> data === <span class="hljs-string">'object'</span> &amp;&amp; 
         <span class="hljs-string">'id'</span> <span class="hljs-keyword">in</span> data &amp;&amp; <span class="hljs-keyword">typeof</span> data.id === <span class="hljs-string">'number'</span> &amp;&amp;
         <span class="hljs-string">'name'</span> <span class="hljs-keyword">in</span> data &amp;&amp; <span class="hljs-keyword">typeof</span> data.name === <span class="hljs-string">'string'</span> &amp;&amp;
         <span class="hljs-string">'email'</span> <span class="hljs-keyword">in</span> data &amp;&amp; <span class="hljs-keyword">typeof</span> data.email === <span class="hljs-string">'string'</span>;
}

<span class="hljs-comment">// Simulated API response</span>
<span class="hljs-keyword">const</span> apiResponse: <span class="hljs-built_in">any</span> = {
  id: <span class="hljs-number">1</span>,
  name: <span class="hljs-string">"John Doe"</span>,
  email: <span class="hljs-string">"john.doe@example.com"</span>
};

<span class="hljs-keyword">if</span> (isUser(apiResponse)) {
  <span class="hljs-comment">// TypeScript now knows that 'apiResponse' is a 'User'</span>
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`User Name: <span class="hljs-subst">${apiResponse.name}</span>`</span>);
} <span class="hljs-keyword">else</span> {
  <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Invalid user data"</span>);
}
</code></pre>
<p>This example shows how type predicates make sure the API response matches the <code>User</code> structure before doing anything with it, preventing possible runtime errors.</p>
<h2 id="heading-handling-different-event-types-in-event-listeners">Handling Different Event Types in Event Listeners</h2>
<p>When handling different event types, it's important to know the exact type of event to handle it properly. For example, if we have a system that logs various events like <code>ClickEvent</code> and <code>KeyboardEvent</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">interface</span> ClickEvent {
  <span class="hljs-keyword">type</span>: <span class="hljs-string">"click"</span>;
  x: <span class="hljs-built_in">number</span>;
  y: <span class="hljs-built_in">number</span>;
}

<span class="hljs-keyword">interface</span> KeyboardEvent {
  <span class="hljs-keyword">type</span>: <span class="hljs-string">"keyboard"</span>;
  key: <span class="hljs-built_in">string</span>;
}

<span class="hljs-keyword">type</span> Event = ClickEvent | KeyboardEvent;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">isClickEvent</span>(<span class="hljs-params">event: Event</span>): <span class="hljs-title">event</span> <span class="hljs-title">is</span> <span class="hljs-title">ClickEvent</span> </span>{
  <span class="hljs-keyword">return</span> event.type === <span class="hljs-string">"click"</span>;
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">isKeyboardEvent</span>(<span class="hljs-params">event: Event</span>): <span class="hljs-title">event</span> <span class="hljs-title">is</span> <span class="hljs-title">KeyboardEvent</span> </span>{
  <span class="hljs-keyword">return</span> event.type === <span class="hljs-string">"keyboard"</span>;
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handleEvent</span>(<span class="hljs-params">event: Event</span>) </span>{
  <span class="hljs-keyword">if</span> (isClickEvent(event)) {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Click at coordinates: (<span class="hljs-subst">${event.x}</span>, <span class="hljs-subst">${event.y}</span>)`</span>);
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (isKeyboardEvent(event)) {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Key pressed: <span class="hljs-subst">${event.key}</span>`</span>);
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Unknown event type"</span>);
  }
}

<span class="hljs-comment">// Simulated events</span>
<span class="hljs-keyword">const</span> events: Event[] = [
  { <span class="hljs-keyword">type</span>: <span class="hljs-string">"click"</span>, x: <span class="hljs-number">100</span>, y: <span class="hljs-number">200</span> },
  { <span class="hljs-keyword">type</span>: <span class="hljs-string">"keyboard"</span>, key: <span class="hljs-string">"Enter"</span> }
];

events.forEach(handleEvent);
</code></pre>
<p>This example shows how type predicates can help handle different event types correctly, making sure the right logic is used based on the event type.</p>
<h2 id="heading-validating-configuration-objects">Validating Configuration Objects</h2>
<p>When working with configuration objects, it's important to make sure they have the right structure before using them. Let's look at an example where we check a configuration object for a web application.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">interface</span> AppConfig {
  apiUrl: <span class="hljs-built_in">string</span>;
  retryAttempts: <span class="hljs-built_in">number</span>;
  debugMode: <span class="hljs-built_in">boolean</span>;
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">isAppConfig</span>(<span class="hljs-params">config: <span class="hljs-built_in">any</span></span>): <span class="hljs-title">config</span> <span class="hljs-title">is</span> <span class="hljs-title">AppConfig</span> </span>{
  <span class="hljs-keyword">return</span> config &amp;&amp; <span class="hljs-keyword">typeof</span> config === <span class="hljs-string">'object'</span> &amp;&amp;
         <span class="hljs-string">'apiUrl'</span> <span class="hljs-keyword">in</span> config &amp;&amp; <span class="hljs-keyword">typeof</span> config.apiUrl === <span class="hljs-string">'string'</span> &amp;&amp;
         <span class="hljs-string">'retryAttempts'</span> <span class="hljs-keyword">in</span> config &amp;&amp; <span class="hljs-keyword">typeof</span> config.retryAttempts === <span class="hljs-string">'number'</span> &amp;&amp;
         <span class="hljs-string">'debugMode'</span> <span class="hljs-keyword">in</span> config &amp;&amp; <span class="hljs-keyword">typeof</span> config.debugMode === <span class="hljs-string">'boolean'</span>;
}

<span class="hljs-comment">// Simulated configuration object</span>
<span class="hljs-keyword">const</span> config: <span class="hljs-built_in">any</span> = {
  apiUrl: <span class="hljs-string">"https://api.example.com"</span>,
  retryAttempts: <span class="hljs-number">3</span>,
  debugMode: <span class="hljs-literal">true</span>
};

<span class="hljs-keyword">if</span> (isAppConfig(config)) {
  <span class="hljs-comment">// TypeScript now knows that 'config' is an 'AppConfig'</span>
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`API URL: <span class="hljs-subst">${config.apiUrl}</span>`</span>);
} <span class="hljs-keyword">else</span> {
  <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Invalid configuration object"</span>);
}
</code></pre>
<p>This example shows how type predicates can check configuration objects to make sure they have the right structure before using them in the application.</p>
<p>Alright, we discussed what type predicates are, how we can use them, and what they can do. Now, let's look at the other side: the drawbacks of type predicates.</p>
<h2 id="heading-drawbacks-of-type-predicates">Drawbacks of Type Predicates</h2>
<p>While type predicates are very useful, they have some drawbacks:</p>
<ol>
<li><p><strong>Runtime Overhead</strong>: Type predicates add runtime checks to your code. For complex types or frequent checks, this can slow down performance.</p>
</li>
<li><p><strong>Limited by JavaScript Capabilities</strong>: Type predicates can only check what JavaScript can do at runtime. They can't enforce more complex TypeScript-only types, like interfaces with methods or generic types.</p>
</li>
<li><p><strong>Code Duplication</strong>: The logic in type predicates often repeats the type definitions. This can cause problems if the type definitions are updated but the type predicates are not.</p>
</li>
<li><p><strong>False Security</strong>: If not written correctly, type predicates can give a false sense of security, leading to potential bugs if the type checks are not thorough.</p>
</li>
</ol>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Type predicates improve type safety by checking variable types, preventing errors, and making code clearer.</p>
<p>Despite some drawbacks like runtime overhead and potential code duplication, their benefits make them a valuable tool in TypeScript development.</p>
<p>That's all for this topic. Thank you for reading! If you found this article helpful, please consider liking, commenting, and sharing it with others.</p>
<h2 id="heading-resource">Resource</h2>
<ul>
<li><a target="_blank" href="https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates">Official TypeScript Doc</a></li>
</ul>
<h2 id="heading-connect-with-me"><strong>Connect with me</strong></h2>
<ul>
<li><p><a target="_blank" href="https://www.linkedin.com/in/sachin-chaurasiya"><strong>LinkedIn</strong></a></p>
</li>
<li><p><a target="_blank" href="https://twitter.com/sachindotcom"><strong>Twitter</strong></a></p>
</li>
<li><p><a target="_blank" href="https://github.com/Sachin-chaurasiya"><strong>GitHub</strong></a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[HTTP Status Codes Explained: Essential Guide for Developers]]></title><description><![CDATA[HTTP status codes are important for web development. They give key information about server requests. Knowing these codes can help you debug better and improve your app's user experience. In this guide, we’ll look at the different types of HTTP statu...]]></description><link>https://blog.sachinchaurasiya.dev/http-status-codes-explained-essential-guide-for-developers</link><guid isPermaLink="true">https://blog.sachinchaurasiya.dev/http-status-codes-explained-essential-guide-for-developers</guid><category><![CDATA[Web Development]]></category><category><![CDATA[http]]></category><category><![CDATA[server]]></category><category><![CDATA[guide]]></category><dc:creator><![CDATA[Sachin Chaurasiya]]></dc:creator><pubDate>Fri, 17 May 2024 16:34:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1715963105534/b6c43d19-d5ea-431d-982f-31f714280164.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>HTTP status codes are important for web development. They give key information about server requests. Knowing these codes can help you debug better and improve your app's user experience. In this guide, we’ll look at the different types of HTTP status codes and give examples to explain each one.</p>
<h2 id="heading-informational-responses-1xx">Informational Responses (1XX)</h2>
<p>Informational status codes show that the server got the request and is still working on it.</p>
<ul>
<li><p><strong>100 (Continue)</strong>: The server has received the request headers, and the client should continue to send the request body.</p>
</li>
<li><p><strong>102 (Processing)</strong>: The server has received the request and is working on it, but there is no response yet.</p>
</li>
</ul>
<h2 id="heading-successful-responses-2xx">Successful Responses (2XX)</h2>
<p>Successful status codes mean that the server got the request, understood it, and processed it successfully.</p>
<ul>
<li><p><strong>200 (OK)</strong>: The request was successful. This is the most common response for successful HTTP requests.</p>
</li>
<li><p><strong>201 (Created)</strong>: The request was successful, and a new resource has been created.</p>
</li>
</ul>
<h2 id="heading-redirection-messages-3xx">Redirection Messages (3XX)</h2>
<p>Redirection codes mean that the user needs to do something else to complete the request.</p>
<ul>
<li><p><strong>300 (Multiple Choices)</strong>: The request has more than one possible response. The user or user agent can choose one of them.</p>
</li>
<li><p><strong>301 (Moved Permanently)</strong>: The requested resource has been permanently moved to a new URL provided by the Location header.</p>
</li>
</ul>
<h2 id="heading-client-error-responses-4xx">Client Error Responses (4XX)</h2>
<p>Client error status codes mean the server couldn't process the request because of something wrong on the client's side.</p>
<ul>
<li><p><strong>400 (Bad Request)</strong>: The server cannot process the request because of a client error (e.g., bad request syntax).</p>
</li>
<li><p><strong>401 (Unauthorized)</strong>: The request needs user authentication.</p>
</li>
<li><p><strong>404 (Not Found)</strong>: The server cannot find the requested resource.</p>
</li>
</ul>
<h2 id="heading-server-error-responses-5xx">Server Error Responses (5XX)</h2>
<p>Server error status codes mean that the server couldn't complete a valid request.</p>
<ul>
<li><p><strong>500 (Internal Server Error)</strong>: The server ran into an unexpected problem and couldn't complete the request.</p>
</li>
<li><p><strong>502 (Bad Gateway)</strong>: The server received an invalid response from the upstream server while trying to fulfill the request.</p>
</li>
</ul>
<h2 id="heading-practical-application-of-http-status-codes">Practical Application of HTTP Status Codes</h2>
<p>Understanding these status codes is important for fixing and improving web applications. Here are some practical uses</p>
<ul>
<li><p><strong>API Integration</strong>: When integrating with APIs, handling HTTP status codes correctly ensures strong communication between services. For example, if you get a 401 Unauthorized status, prompt the user to log in again.</p>
</li>
<li><p><strong>SEO</strong>: Using the right status codes like 301 (Moved Permanently) helps keep search engine rankings by properly indicating URL changes.</p>
</li>
<li><p><strong>User Experience</strong>: Informing users with clear messages based on status codes (e.g., showing <strong>Page Not Found</strong> for a 404 status) improves user satisfaction and reduces frustration.</p>
</li>
</ul>
<h2 id="heading-resource">Resource</h2>
<ul>
<li><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status">MDN HTTP Status Codes</a></li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>HTTP status codes are essential for web communication. By knowing and using these codes correctly, developers can build more efficient, user-friendly, and error-resistant applications. Keep this guide nearby as you work on your web projects.</p>
<p>That's all for this topic. Thank you for reading! If you found this article helpful, please consider liking, commenting, and sharing it with others.</p>
<h2 id="heading-connect-with-me"><strong>Connect with me</strong></h2>
<ul>
<li><p><a target="_blank" href="https://www.linkedin.com/in/sachin-chaurasiya"><strong>LinkedIn</strong></a></p>
</li>
<li><p><a target="_blank" href="https://twitter.com/sachindotcom"><strong>Twitter</strong></a></p>
</li>
<li><p><a target="_blank" href="https://github.com/Sachin-chaurasiya"><strong>GitHub</strong></a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Simple Guide to Using Intersection Observer API with ReactJS]]></title><description><![CDATA[The Intersection Observer API is a new web tool that helps developers check when an element in the DOM comes into or leaves the viewport. This is very useful for lazy loading images, infinite scrolling, and other important features that need to know ...]]></description><link>https://blog.sachinchaurasiya.dev/simple-guide-to-using-intersection-observer-api-with-reactjs</link><guid isPermaLink="true">https://blog.sachinchaurasiya.dev/simple-guide-to-using-intersection-observer-api-with-reactjs</guid><category><![CDATA[React]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[ReactHooks]]></category><dc:creator><![CDATA[Sachin Chaurasiya]]></dc:creator><pubDate>Wed, 15 May 2024 02:30:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1715708663727/94ffb6e0-07be-4788-9ad5-062941262145.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The Intersection Observer API is a new web tool that helps developers check when an element in the DOM comes into or leaves the viewport. This is very useful for lazy loading images, infinite scrolling, and other important features that need to know when an element is visible to the user.</p>
<p>In this article, we'll learn how to use the Intersection Observer API with React, a popular JavaScript library for building user interfaces. We'll start by briefly covering the basics of the Intersection Observer API and its benefits. Then, we'll look at a practical example of using it with React to animate a component.</p>
<h2 id="heading-what-is-the-intersection-observer-api">What is the Intersection Observer API?</h2>
<p>The Intersection Observer API is a web tool that lets developers track when an element in the DOM enters or leaves the viewport or a specified parent element. Simply put, it helps us know when an element appears or disappears on the user's screen.</p>
<p>One of the main benefits of using the Intersection Observer API is that it offers a more efficient way to track the visibility of elements in the DOM compared to methods like using <code>scroll</code> or <code>resize</code> event listeners. The Intersection Observer API uses a callback-based approach, which means we can set a function to run when an element's visibility changes, instead of constantly checking visibility on each scroll or resize event.</p>
<h2 id="heading-using-the-intersection-observer-api-with-react">Using the Intersection Observer API with React</h2>
<p>To use the Intersection Observer API with React, we can make a custom hook that simplifies using the Intersection Observer API. This hook can be used in any React component to check if an element is visible and take action when it appears.</p>
<p>Here is an example of a custom <code>useElementInView</code> hook that uses the Intersection Observer API to check if an element is visible</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> useElementInView = <span class="hljs-function">(<span class="hljs-params">options</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> [isInView, setIsInView] = useState(<span class="hljs-literal">false</span>);
  <span class="hljs-keyword">const</span> targetRef = useRef(<span class="hljs-literal">null</span>);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> observer = <span class="hljs-keyword">new</span> IntersectionObserver(<span class="hljs-function">(<span class="hljs-params">entries</span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> [entry] = entries;
      setIsInView(entry.isIntersecting);
    }, options);

    <span class="hljs-keyword">if</span> (targetRef.current) {
      observer.observe(targetRef.current);
    }

    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-keyword">if</span> (targetRef.current) {
        observer.unobserve(targetRef.current);
      }
    };
  }, [options]);

  <span class="hljs-keyword">return</span> [targetRef, isInView];
};
</code></pre>
<p>This hook creates an IntersectionObserver instance and watches the target element. When the target element becomes visible within the root element, it updates the state with the visibility info.</p>
<p>To use this hook in a React component, you can pass the options to the hook and get the returned ref and visibility info</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> MyComponent = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [targetRef, isInView] = useElementInView({ <span class="hljs-attr">threshold</span>: <span class="hljs-number">0.5</span> });

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">{targetRef}</span>&gt;</span>
      {isInView ? 'Element is in view!' : 'Element is not in view.'}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};
</code></pre>
<p>Alright, we have discussed what the Intersection Observer is and how it works. We created a hook called <code>useElementInView</code>, and in the next section, we will use this hook to animate a component.</p>
<h2 id="heading-scroll-to-reveal-using-the-useelementinview-hook">Scroll to reveal using the <code>useElementInView</code> hook</h2>
<p>We will create a component that will animate an element when it comes into view, essentially a scroll-to-reveal animation.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> useElementInView <span class="hljs-keyword">from</span> <span class="hljs-string">'./useElementInView'</span>;

<span class="hljs-keyword">const</span> AnimatedComponent = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [targetRef, isInView] = useElementInView({ <span class="hljs-attr">threshold</span>: <span class="hljs-number">0.1</span> });

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>
      <span class="hljs-attr">ref</span>=<span class="hljs-string">{targetRef}</span>
      <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span>
        <span class="hljs-attr">opacity:</span> <span class="hljs-attr">isInView</span> ? <span class="hljs-attr">1</span> <span class="hljs-attr">:</span> <span class="hljs-attr">0</span>,
        <span class="hljs-attr">transition:</span> '<span class="hljs-attr">opacity</span> <span class="hljs-attr">0.5s</span> <span class="hljs-attr">ease-out</span>',
      }}
    &gt;</span>
      I fade in when scrolled into view!
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> AnimatedComponent;
</code></pre>
<p>In this example, the <code>AnimatedComponent</code> starts with an opacity of 0, making it invisible. When at least 10% of the component scrolls into view (as set by the <code>threshold</code> option), the <code>isInView</code> state changes to <code>true</code>, and the component fades in with an opacity of 1.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this article, we explore the Intersection Observer API and how to use it with React to track when elements are visible in the viewport. We cover the basics of the API, its advantages over older methods, and show a practical example by creating a custom React hook, <code>useElementInView</code>, to animate components when they come into view.</p>
<p>That's all for this topic. Thank you for reading! If you found this article helpful, please consider liking, commenting, and sharing it with others.</p>
<h2 id="heading-connect-with-me"><strong>Connect with me</strong></h2>
<ul>
<li><p><a target="_blank" href="https://www.linkedin.com/in/sachin-chaurasiya"><strong>LinkedIn</strong></a></p>
</li>
<li><p><a target="_blank" href="https://twitter.com/sachindotcom"><strong>Twitter</strong></a></p>
</li>
<li><p><a target="_blank" href="https://github.com/Sachin-chaurasiya"><strong>GitHub</strong></a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[React Error Boundary: A Guide to Gracefully Handling Errors]]></title><description><![CDATA[React revolves around JavaScript, and as the application expands, certain components may become error-prone, leading to a blank page problem. To tackle this, we should incorporate Error Boundaries. These boundaries will display an alternative UI when...]]></description><link>https://blog.sachinchaurasiya.dev/react-error-boundary-a-guide-to-gracefully-handling-errors</link><guid isPermaLink="true">https://blog.sachinchaurasiya.dev/react-error-boundary-a-guide-to-gracefully-handling-errors</guid><category><![CDATA[React]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[error handling]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Sachin Chaurasiya]]></dc:creator><pubDate>Sun, 12 May 2024 04:09:45 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1715486381285/a25ccdfa-5052-4541-a4f6-ccaf80b50124.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>React revolves around JavaScript, and as the application expands, certain components may become error-prone, leading to a blank page problem. To tackle this, we should incorporate Error Boundaries. These boundaries will display an alternative UI when an error occurs, allowing the user to navigate back or retry. This article will explore how we can gracefully handle errors in React by using error boundaries.</p>
<h2 id="heading-what-is-an-error-boundary">What is an error boundary?</h2>
<p>Think of error boundaries in React as safety nets. They're like guardians watching over a group of performers (the components) on a stage (the app). If any performer falls down <strong>(a JavaScript error occurs)</strong>, the safety net (the error boundary) catches them. It then logs what happened and puts up a poster (the fallback UI) until the performer is ready to get back on stage.</p>
<h2 id="heading-implementing-error-boundaries-in-react">Implementing Error Boundaries in React</h2>
<p>React supports two types of components: class-based and functional components. However, if we want to implement an error boundary, we can't do it with a functional component; we need to create a class-based component for the error boundary. But don't worry, the React community is extensive, and many third-party packages are available to help you use a React error boundary component directly instead of creating it yourself.</p>
<p>We will use the <code>react-error-boundary</code> package, which provides an ErrorBoundary component that catches errors and displays the fallback UI. It offers various methods to show the fallback UI.</p>
<p>Let's begin by installing the <code>react-error-boundary</code> as a dependency in our React project.</p>
<pre><code class="lang-bash">yarn add react-error-boundary

OR

npm install react-error-boundary

OR

pnpm add react-error-boundary
</code></pre>
<p>After installing the dependency, create the fallback component that you want to display when an error occurs.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { FallbackProps } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-error-boundary"</span>;
<span class="hljs-keyword">import</span> ErrorFallbackIcon <span class="hljs-keyword">from</span> <span class="hljs-string">"./error-fallback.svg"</span>;

<span class="hljs-keyword">const</span> ErrorFallback: React.FC&lt;FallbackProps&gt; = <span class="hljs-function">(<span class="hljs-params">{
  error,
  resetErrorBoundary,
}</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    &lt;div className=<span class="hljs-string">"error-boundary-fallback-wrapper"</span>&gt;
      &lt;ErrorFallbackIcon /&gt;
      &lt;h1&gt;Something went wrong&lt;/h1&gt;
      &lt;p&gt;{error.message}&lt;/p&gt;
      &lt;button onClick={resetErrorBoundary}&gt;Home&lt;/button&gt;
    &lt;/div&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> ErrorFallback;
</code></pre>
<p>The fallback component accepts two props.</p>
<ul>
<li><p><code>error</code> : error object that contains the error message.</p>
</li>
<li><p><code>resetErrorBoundary</code> : a callback function to reset the error boundary and retry rendering.</p>
</li>
</ul>
<p>Alright, we have the fallback component ready. Now, let's wrap our application with the <code>ErrorBoundary</code> component provided by <code>react-error-boundary</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> { ErrorBoundary } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-error-boundary'</span>;
<span class="hljs-keyword">import</span> { useHistory } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>;
<span class="hljs-keyword">import</span> ErrorFallback <span class="hljs-keyword">from</span> <span class="hljs-string">'./ErrorFallback'</span>;

<span class="hljs-keyword">interface</span> Props {
  children: React.ReactNode;
}

<span class="hljs-keyword">const</span> App: React.FC&lt;Props&gt; = <span class="hljs-function">(<span class="hljs-params">{ children }</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> history = useHistory();

  <span class="hljs-keyword">const</span> handleErrorReset = <span class="hljs-function">() =&gt;</span> {
    history.push(<span class="hljs-string">"/"</span>);
  };

  <span class="hljs-keyword">return</span> (
    &lt;ErrorBoundary FallbackComponent={ErrorFallback} onReset={handleErrorReset}&gt;
      {children}
    &lt;/ErrorBoundary&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p>The <code>ErrorBoundary</code> component accepts several props, but the important ones are:</p>
<ul>
<li><p><code>FallbackComponent</code> : Fallback component to render when an error occurs.</p>
</li>
<li><p><code>onReset</code> : callback function to reset the state of the application so that the error doesn't happen again.</p>
</li>
</ul>
<p>Okay, our application is now wrapped with an error boundary. As a regular user, if you interact with the application and encounter an error on a specific page causing a crash, instead of seeing a blank page, you will be directed to a fallback page. This page will display the exact error message and provide you with the option to navigate back.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715486165963/863247b7-d670-40cb-8104-05f5c77f110e.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Implementing error boundaries in React using the <code>react-error-boundary</code> package can help in gracefully handling errors and providing a fallback UI for users when errors occur. By incorporating error boundaries, developers can ensure a better user experience by preventing blank pages and offering options to navigate back or retry.</p>
<p>That's all for this topic. Thank you for reading! If you found this article helpful, please consider liking, commenting, and sharing it with others.</p>
<h2 id="heading-resources">Resources</h2>
<ul>
<li><p><a target="_blank" href="https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary">Official doc</a></p>
</li>
<li><p><a target="_blank" href="https://www.npmjs.com/package/react-error-boundary">React Error Boundary npm</a></p>
</li>
</ul>
<h2 id="heading-connect-with-me"><strong>Connect with me</strong></h2>
<ul>
<li><p><a target="_blank" href="https://www.linkedin.com/in/sachin-chaurasiya"><strong>LinkedIn</strong></a></p>
</li>
<li><p><a target="_blank" href="https://twitter.com/sachindotcom"><strong>Twitter</strong></a></p>
</li>
<li><p><a target="_blank" href="https://github.com/Sachin-chaurasiya"><strong>GitHub</strong></a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Build and Send Emails Using React and TypeScript]]></title><description><![CDATA[Emails are widely used for communication, whether it's expressing thoughts, sharing product information, sending content to newsletter subscribers, sharing event details, and more.
Crafting and sending emails isn't straightforward. You have to design...]]></description><link>https://blog.sachinchaurasiya.dev/build-and-send-emails-using-react-and-typescript</link><guid isPermaLink="true">https://blog.sachinchaurasiya.dev/build-and-send-emails-using-react-and-typescript</guid><category><![CDATA[TypeScript]]></category><category><![CDATA[React]]></category><category><![CDATA[react-email]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[resend]]></category><dc:creator><![CDATA[Sachin Chaurasiya]]></dc:creator><pubDate>Sat, 16 Mar 2024 15:06:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1710584063908/7e7789ae-f71d-4630-aebc-d37b59d9767d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Emails are widely used for communication, whether it's expressing thoughts, sharing product information, sending content to newsletter subscribers, sharing event details, and more.</p>
<p>Crafting and sending emails isn't straightforward. You have to design templates for various emails using an old-fashioned table structure, which can make emails less intuitive. Additionally, you need to set up a server to send emails, and there are other issues like emails ending up in spam folders or being blocked by the recipient's service.</p>
<p>But don't worry, in this article, we'll explore how to create and send emails using React and TypeScript. Exciting, isn't it? Let's dive in!</p>
<p>React can't send emails on its own. So, we'll use <strong>Resend</strong>, a great tool for sending emails. We'll also use <strong>React Email</strong> to make our email designs look really good.</p>
<p>We'll make a simple <strong>Contact Us</strong> form. Then, we'll design an email layout just for that form. Lastly, we'll set it up so that when someone fills out the contact form, an email gets sent.</p>
<h2 id="heading-setup-project">Setup Project</h2>
<p>We will use Next.js for scaffolding our project, so please run the following command in your terminal.</p>
<blockquote>
<p>Make sure you have Node installed, version 18.18.2 or higher.</p>
</blockquote>
<pre><code class="lang-bash"><span class="hljs-comment"># yarn</span>
npx create-next-app@latest build-send-emails-react-typescript

<span class="hljs-comment"># navigate to project folder</span>
<span class="hljs-built_in">cd</span> build-send-emails-react-typescript
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710592748690/4c666d65-d1ab-4bf3-a660-307c1dfbb382.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-resend-and-react-email">Resend and React Email</h3>
<p>Sign up for a free account on <a target="_blank" href="https://resend.com/signup">Resend</a> and get your API keys.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710587699858/adae7b22-6bfe-4803-915c-d21a0285d13f.png" alt class="image--center mx-auto" /></p>
<p>After you sign up, go to the <strong>API keys</strong> on the left side of the screen. Then, create a new API key and copy it.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710587888006/cf17071a-2d9f-41dd-88fa-feebe0db13d4.png" alt class="image--center mx-auto" /></p>
<p>I named it <code>build-send-emails-react-typescript</code> , but you can choose any name you like. It's totally up to you!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710588002924/85f793c4-74b3-402f-a755-e712894ab637.png" alt class="image--center mx-auto" /></p>
<p>Create <code>.env</code> file in the project root folder and add your API key.</p>
<pre><code class="lang-bash">RESEND_API_KEY=XXX-XXX-XXX
</code></pre>
<p>Install the Resend Node.js SDK.</p>
<pre><code class="lang-bash">yarn add resend
</code></pre>
<p>Then, install React Email and React Email Components.</p>
<pre><code class="lang-bash">yarn add react-email @react-email/components -E
</code></pre>
<p>Great, we've set up our project! Next, we'll create a template for an email that we will send to users when they fill out the <strong>Contact Us</strong> form.</p>
<h2 id="heading-email-template">Email Template</h2>
<p>Create a file <code>emails/contact-us.tsx</code> in the <code>src</code> folder. Then, copy and paste the code provided below into that file.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> {
  Body,
  Container,
  Head,
  Html,
  Markdown,
  Preview,
  Text,
} <span class="hljs-keyword">from</span> <span class="hljs-string">'@react-email/components'</span>;
<span class="hljs-keyword">import</span> { CSSProperties } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">interface</span> ContactUsEmailProps {
  name: <span class="hljs-built_in">string</span>;
  email: <span class="hljs-built_in">string</span>;
  message: <span class="hljs-built_in">string</span>;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> ContactUsEmail = <span class="hljs-function">(<span class="hljs-params">{
  name,
  email,
  message,
}: ContactUsEmailProps</span>) =&gt;</span> (
  &lt;Html&gt;
    &lt;Head /&gt;
    &lt;Preview&gt;Thanks <span class="hljs-keyword">for</span> contacting us {name}!&lt;/Preview&gt;
    &lt;Body style={main}&gt;
      &lt;Container style={container}&gt;
        &lt;Text style={paragraph}&gt;Hi {name},&lt;/Text&gt;
        &lt;Text style={paragraph}&gt;We have received your message&lt;/Text&gt;
        &lt;Markdown
          markdownContainerStyles={{
            boxShadow: <span class="hljs-string">'0 0 10px rgba(0, 0, 0, 0.05)'</span>,
            borderRadius: <span class="hljs-string">'8px'</span>,
            padding: <span class="hljs-string">'20px'</span>,
            backgroundColor: <span class="hljs-string">'#f3f4f6'</span>,
            border: <span class="hljs-string">'1px solid #e5e7eb'</span>,
          }}
        &gt;
          {message}
        &lt;/Markdown&gt;
        &lt;Text style={paragraph}&gt;
          We will get back to you <span class="hljs-keyword">as</span> soon <span class="hljs-keyword">as</span> possible at {email}.
        &lt;/Text&gt;
      &lt;/Container&gt;
    &lt;/Body&gt;
  &lt;/Html&gt;
);

<span class="hljs-keyword">const</span> main: CSSProperties = {
  backgroundColor: <span class="hljs-string">'#ffffff'</span>,
  borderRadius: <span class="hljs-string">'8px'</span>,
  border: <span class="hljs-string">'1px solid #e5e7eb'</span>,
  boxShadow: <span class="hljs-string">'0 0 10px rgba(0, 0, 0, 0.05)'</span>,
  fontFamily:
    <span class="hljs-string">'-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif'</span>,
};

<span class="hljs-keyword">const</span> container: CSSProperties = {
  margin: <span class="hljs-string">'0 auto'</span>,
  padding: <span class="hljs-string">'20px 0 48px'</span>,
};

<span class="hljs-keyword">const</span> paragraph: CSSProperties = {
  fontSize: <span class="hljs-string">'16px'</span>,
  lineHeight: <span class="hljs-string">'26px'</span>,
};
</code></pre>
<p>In this code, we're using components from a <strong>react-email</strong> to design our email template.</p>
<p>This text will show up in the recipient's inbox.</p>
<pre><code class="lang-typescript">&lt;Preview&gt;Thanks <span class="hljs-keyword">for</span> contacting us {name}!&lt;/Preview&gt;
</code></pre>
<p>We're allowing markdown for the message, so we're using a component called Markdown from <strong>react-email</strong>.</p>
<pre><code class="lang-typescript">&lt;Markdown
          markdownContainerStyles={{
            boxShadow: <span class="hljs-string">'0 0 10px rgba(0, 0, 0, 0.05)'</span>,
            borderRadius: <span class="hljs-string">'8px'</span>,
            padding: <span class="hljs-string">'20px'</span>,
            backgroundColor: <span class="hljs-string">'#f3f4f6'</span>,
            border: <span class="hljs-string">'1px solid #e5e7eb'</span>,
          }}
        &gt;
          {message}
        &lt;/Markdown&gt;
</code></pre>
<p>Alright, we have created the email template for our form. In the next section, we will create an API route for sending emails.</p>
<h2 id="heading-api-route-for-sending-email">API Route For Sending Email</h2>
<p>Create a file <code>api/email/route.ts</code> in the <code>src/app</code> folder. Then, copy and paste the code provided below into that file.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Resend } <span class="hljs-keyword">from</span> <span class="hljs-string">'resend'</span>;
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> { ContactUsEmail } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../../emails/contact-us'</span>;

<span class="hljs-keyword">const</span> resend = <span class="hljs-keyword">new</span> Resend(process.env.RESEND_API_KEY);

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">POST</span>(<span class="hljs-params">req: Request</span>) </span>{
  <span class="hljs-keyword">const</span> payload = <span class="hljs-keyword">await</span> req.json();

  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> { data, error } = <span class="hljs-keyword">await</span> resend.emails.send({
      <span class="hljs-keyword">from</span>: <span class="hljs-string">'your-email@domain.com'</span>,
      to: payload.email,
      subject: <span class="hljs-string">`Thank you for contacting us, <span class="hljs-subst">${payload.name}</span>`</span>,
      react: ContactUsEmail(payload),
    });

    <span class="hljs-keyword">if</span> (error) {
      <span class="hljs-keyword">return</span> Response.json({ error });
    }

    <span class="hljs-keyword">return</span> Response.json({ data });
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-keyword">return</span> Response.json({ error });
  }
}
</code></pre>
<p>This will create the resend instance which will have all the helper methods to send email.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> resend = <span class="hljs-keyword">new</span> Resend(process.env.RESEND_API_KEY);
</code></pre>
<p>Next, we have a POST function to handle the POST request on the <code>api/email</code> route.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">POST</span>(<span class="hljs-params">req: Request</span>) </span>{
  <span class="hljs-keyword">const</span> payload = <span class="hljs-keyword">await</span> req.json();
  ...
}
</code></pre>
<p>This code manages everything needed to send an email. It specifies:</p>
<ul>
<li><p><code>from</code>: Your email address, from which the email will be sent.</p>
</li>
<li><p><code>to</code>: The recipient's email address.</p>
</li>
<li><p><code>subject</code>: The subject of the email.</p>
</li>
<li><p><code>react</code>: The React component we previously created.</p>
</li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> { data, error } = <span class="hljs-keyword">await</span> resend.emails.send({
      <span class="hljs-keyword">from</span>: <span class="hljs-string">'your-email@domain.com'</span>,
      to: payload.email,
      subject: <span class="hljs-string">`Thank you for contacting us, <span class="hljs-subst">${payload.name}</span>`</span>,
      react: ContactUsEmail(payload),
    });
</code></pre>
<blockquote>
<p>To test the form, you can use the testing domain provided by Resend in the "from" field, which is <a target="_blank" href="mailto:onboarding@resend.dev">onboarding@resend.dev</a>. However, it will only allow sending emails to the email address linked with your Resend account.</p>
</blockquote>
<p>Resend offers the option to set up your domain for sending emails to any email address. You can do this by going to the <strong>Domains</strong> section in the left-side menu.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710596797037/f1e60a79-a21e-4aee-88e9-2e80725064d8.png" alt class="image--center mx-auto" /></p>
<p>Awesome, we've set up the API route for sending emails! Next, we'll create a Contact Us form and style it using Tailwind CSS to make it look nice.</p>
<h2 id="heading-contact-us-form">Contact Us Form</h2>
<p>We'll make a super simple form with just three fields:</p>
<ul>
<li><p>name</p>
</li>
<li><p>email</p>
</li>
<li><p>message.</p>
</li>
</ul>
<p>Create a <code>components/contact-us.tsx</code> file inside the <code>src</code> directory and add the following code.</p>
<pre><code class="lang-typescript"><span class="hljs-string">'use client'</span>;

<span class="hljs-keyword">import</span> React, { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">interface</span> FormState {
  name: <span class="hljs-built_in">string</span>;
  email: <span class="hljs-built_in">string</span>;
  message: <span class="hljs-built_in">string</span>;
}

<span class="hljs-keyword">const</span> ContactUs: React.FC = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [formState, setFormState] = useState&lt;FormState&gt;({
    name: <span class="hljs-string">''</span>,
    email: <span class="hljs-string">''</span>,
    message: <span class="hljs-string">''</span>,
  });
  <span class="hljs-keyword">const</span> [errors, setErrors] = useState&lt;FormState&gt;({
    name: <span class="hljs-string">''</span>,
    email: <span class="hljs-string">''</span>,
    message: <span class="hljs-string">''</span>,
  });

  <span class="hljs-keyword">const</span> [isSubmitting, setIsSubmitting] = useState(<span class="hljs-literal">false</span>);

  <span class="hljs-keyword">const</span> validateEmail = <span class="hljs-function">(<span class="hljs-params">email: <span class="hljs-built_in">string</span></span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> re = <span class="hljs-regexp">/\S+@\S+\.\S+/</span>;
    <span class="hljs-keyword">return</span> re.test(email);
  };

  <span class="hljs-keyword">const</span> handleChange = <span class="hljs-function">(<span class="hljs-params">
    e: React.ChangeEvent&lt;HTMLInputElement | HTMLTextAreaElement&gt;
  </span>) =&gt;</span> {
    setFormState({ ...formState, [e.target.name]: e.target.value });
  };

  <span class="hljs-keyword">const</span> handleSubmit = <span class="hljs-keyword">async</span> (e: React.FormEvent) =&gt; {
    e.preventDefault();

    <span class="hljs-keyword">try</span> {
      setIsSubmitting(<span class="hljs-literal">true</span>);
      <span class="hljs-keyword">const</span> errors = { name: <span class="hljs-string">''</span>, email: <span class="hljs-string">''</span>, message: <span class="hljs-string">''</span> };

      <span class="hljs-keyword">if</span> (formState.name === <span class="hljs-string">''</span>) {
        errors.name = <span class="hljs-string">'Name is required'</span>;
      }

      <span class="hljs-keyword">if</span> (formState.email === <span class="hljs-string">''</span> || !validateEmail(formState.email)) {
        errors.email = <span class="hljs-string">'Valid email is required'</span>;
      }

      <span class="hljs-keyword">if</span> (formState.message === <span class="hljs-string">''</span>) {
        errors.message = <span class="hljs-string">'Message is required'</span>;
      }

      setErrors(errors);

      <span class="hljs-keyword">if</span> (!errors.name &amp;&amp; !errors.email &amp;&amp; !errors.message) {
         <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">'/api/email'</span>, {
          method: <span class="hljs-string">'POST'</span>,
          headers: {
            <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>,
          },
          body: <span class="hljs-built_in">JSON</span>.stringify(formState),
        });
        setFormState({ name: <span class="hljs-string">''</span>, email: <span class="hljs-string">''</span>, message: <span class="hljs-string">''</span> });
      }
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-comment">// handle error here</span>
    } <span class="hljs-keyword">finally</span> {
      setIsSubmitting(<span class="hljs-literal">false</span>);
    }
  };

  <span class="hljs-keyword">return</span> (
    &lt;form
      className=<span class="hljs-string">"flex flex-col gap-4 justify-center"</span>
      onSubmit={handleSubmit}
    &gt;
      &lt;div className=<span class="hljs-string">"flex flex-col gap-1"</span>&gt;
        &lt;input
          className=<span class="hljs-string">"w-full rounded-md border-2 border-slate-300 px-2 py-1 outline-purple-500"</span>
          <span class="hljs-keyword">type</span>=<span class="hljs-string">"text"</span>
          name=<span class="hljs-string">"name"</span>
          value={formState.name}
          onChange={handleChange}
          placeholder=<span class="hljs-string">"Name"</span>
        /&gt;
        {errors.name &amp;&amp; &lt;p className=<span class="hljs-string">"text-sm text-red-400"</span>&gt;{errors.name}&lt;/p&gt;}
      &lt;/div&gt;

      &lt;div className=<span class="hljs-string">"flex flex-col gap-1"</span>&gt;
        &lt;input
          className=<span class="hljs-string">"w-full rounded-md border-2 border-slate-300 px-2 py-1 outline-purple-500"</span>
          <span class="hljs-keyword">type</span>=<span class="hljs-string">"email"</span>
          name=<span class="hljs-string">"email"</span>
          value={formState.email}
          onChange={handleChange}
          placeholder=<span class="hljs-string">"Email"</span>
        /&gt;
        {errors.email &amp;&amp; &lt;p className=<span class="hljs-string">"text-sm text-red-400"</span>&gt;{errors.email}&lt;/p&gt;}
      &lt;/div&gt;

      &lt;div className=<span class="hljs-string">"flex flex-col gap-1"</span>&gt;
        &lt;textarea
          className=<span class="hljs-string">"w-full rounded-md border-2 border-slate-300 px-2 py-1 outline-purple-500"</span>
          name=<span class="hljs-string">"message"</span>
          value={formState.message}
          onChange={handleChange}
          placeholder=<span class="hljs-string">"Message"</span>
          rows={<span class="hljs-number">6</span>}
        /&gt;
        {errors.message &amp;&amp; (
          &lt;p className=<span class="hljs-string">"text-sm text-red-400"</span>&gt;{errors.message}&lt;/p&gt;
        )}
      &lt;/div&gt;

      &lt;button
        disabled={isSubmitting}
        className=<span class="hljs-string">"rounded-md bg-purple-500 text-white px-2 py-1 block"</span>
        <span class="hljs-keyword">type</span>=<span class="hljs-string">"submit"</span>
      &gt;
        {isSubmitting ? <span class="hljs-string">'Submitting...'</span> : <span class="hljs-string">'Submit'</span>}
      &lt;/button&gt;
    &lt;/form&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> ContactUs;
</code></pre>
<p>This code sets up a basic Contact Us form with validation. When the user submits the form, it first checks if the data entered is valid. If everything looks good, it then sends an email to the user by making an API call.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">await</span> fetch(<span class="hljs-string">'/api/email'</span>, {
          method: <span class="hljs-string">'POST'</span>,
          headers: {
            <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>,
          },
          body: <span class="hljs-built_in">JSON</span>.stringify(formState),
 });
</code></pre>
<p>To see the demo, let's submit the form. Start the server and go to <a target="_blank" href="http://localhost:3000/">http://localhost:3000/</a> in your web browser.</p>
<pre><code class="lang-bash">yarn run dev
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710600750896/7f0e9954-5427-40f2-8699-bade11927863.gif" alt class="image--center mx-auto" /></p>
<p>Here's an example of an email that has been sent.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710600590900/aef4f7f6-0899-4d77-b608-3c93c190233d.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>We created a basic <strong>Contact Us</strong> form and incorporated <strong>Resend</strong> and <strong>React Email</strong> to send emails using only React and TypeScript.</p>
<p>You can experiment with creating more user-friendly templates using other components provided by react-email, such as images, headings, buttons, code blocks, and more.</p>
<p>That's all for this topic. Thank you for reading! If you found this article helpful, please consider liking, commenting, and sharing it with others.</p>
<h2 id="heading-resources">Resources</h2>
<ul>
<li><p><a target="_blank" href="https://react.email/docs/introduction"><strong>React Email</strong></a></p>
</li>
<li><p><a target="_blank" href="https://resend.com/docs/introduction">Resend</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/Sachin-chaurasiya/build-send-emails-react-typescript">Source Code</a></p>
</li>
</ul>
<h2 id="heading-connect-with-me"><strong>Connect with me</strong></h2>
<ul>
<li><p><a target="_blank" href="https://www.linkedin.com/in/sachin-chaurasiya"><strong>LinkedIn</strong></a></p>
</li>
<li><p><a target="_blank" href="https://twitter.com/sachindotcom"><strong>Twitter</strong></a></p>
</li>
<li><p><a target="_blank" href="https://github.com/Sachin-chaurasiya"><strong>GitHub</strong></a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Type Safety in TypeScript - Unknown vs Any]]></title><description><![CDATA[TypeScript is very popular because it adds static typing to JavaScript. This makes the code safer and better quality, which JavaScript developers like. In TypeScript, two types that are often used are unknown and any. They might look the same at firs...]]></description><link>https://blog.sachinchaurasiya.dev/type-safety-in-typescript-unknown-vs-any</link><guid isPermaLink="true">https://blog.sachinchaurasiya.dev/type-safety-in-typescript-unknown-vs-any</guid><category><![CDATA[TypeScript]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Types]]></category><dc:creator><![CDATA[Sachin Chaurasiya]]></dc:creator><pubDate>Fri, 08 Mar 2024 17:40:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1709918050418/3e1eb65d-9276-443f-8c30-b71aa5d07356.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>TypeScript</strong> is very popular because it adds static typing to <strong>JavaScript</strong>. This makes the code safer and better quality, which JavaScript developers like. In TypeScript, two types that are often used are <strong>unknown</strong> and <strong>any</strong>. They might look the same at first, but they are used for different things. In this article, we will look at <strong>unknown</strong> and <strong>any</strong> in more detail and show how they are used with examples.</p>
<h2 id="heading-exploring-unknown"><strong>Exploring unknown</strong></h2>
<p>The <strong>unknown</strong> type is a safer choice than <strong>any</strong>. When a variable is of type <strong>unknown</strong>, TypeScript makes you check the type before you can use that variable. This makes developers clearly state their assumptions, which helps to avoid errors when the code is running.</p>
<p>Think about a situation where you're using data from an API, and you are not sure what the data looks like. Using the <strong>unknown</strong> helps you deal with this uncertainty in a safe way.</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">processData</span>(<span class="hljs-params">data: unknown</span>): <span class="hljs-title">string</span> </span>{
  <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> data === <span class="hljs-string">'string'</span>) {
    <span class="hljs-keyword">return</span> data.toUpperCase();
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-comment">// Handle other cases appropriately</span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">"Invalid data"</span>;
  }
}

<span class="hljs-comment">// No compilation error</span>
<span class="hljs-keyword">const</span> result = processData(<span class="hljs-string">"Hello, TypeScript!"</span>);
</code></pre>
<p>In simpler terms, using <strong>unknown</strong> makes sure you double-check what kind of data you have before you do anything with it. This helps prevent mistakes that could happen while the program is running.</p>
<h2 id="heading-exploring-any"><strong>Exploring any</strong></h2>
<p>On the other hand, <strong>any</strong> is the most easy-going type in TypeScript. It skips type checking, letting variables of this type to be set to anything without making the code fail to compile. While this freedom can be handy, it means you lose the advantages of static typing.</p>
<p>Think about a situation where you want as much flexibility as possible, and you're sure that your code is type-safe</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">processDynamicData</span>(<span class="hljs-params">data: <span class="hljs-built_in">any</span></span>): <span class="hljs-title">string</span> </span>{
  <span class="hljs-comment">// No compilation error</span>
  <span class="hljs-keyword">return</span> data.toUpperCase(); 
}

<span class="hljs-comment">// No compilation error, works as expected</span>
<span class="hljs-keyword">const</span> result1 = processDynamicData(<span class="hljs-string">"Hello, TypeScript!"</span>); 
<span class="hljs-built_in">console</span>.log(result1); <span class="hljs-comment">// Outputs: "HELLO, TYPESCRIPT!"</span>

<span class="hljs-comment">// No compilation error, but will cause a runtime error</span>
<span class="hljs-keyword">const</span> result2 = processDynamicData(<span class="hljs-number">12345</span>); 
<span class="hljs-built_in">console</span>.log(result2); <span class="hljs-comment">// Error: data.toUpperCase is not a function</span>
</code></pre>
<p>In simpler terms, using <strong>any</strong> means you don't have to check the type of data, but it can cause problems if the data type is not what you expected.</p>
<h2 id="heading-when-to-use-unknown"><strong>When to use unknown</strong></h2>
<p>Use <strong>unknown</strong> when you're not sure what kinda variable is, and you want to make sure it's the right kind before you do something with it. This makes your code safer and easier to understand, especially when you're working with data from outside or inputs that can change.</p>
<h2 id="heading-when-to-use-any"><strong>When to use any</strong></h2>
<p>Consider using <strong>any</strong> when you need maximum flexibility and are confident that type of safety won't be compromised. However, use <strong>any</strong> sparingly, as its overuse can lead to a loss of the benefits provided by TypeScript's static typing.</p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>When using TypeScript, you can use <strong>unknown</strong> when you want to be very careful with your code and make sure everything is the right type. <strong>any</strong> is used when you want to be more flexible, but this can make your code less safe. You should think carefully about which one to use, depending on what your code needs and how confident you are about the types and safety of your code.</p>
<p>That's all for this topic. Thank you for reading! If you found this article helpful, please consider liking, commenting, and sharing it with others.</p>
<h2 id="heading-connect-with-me"><strong>Connect with me</strong></h2>
<ul>
<li><p><a target="_blank" href="https://www.linkedin.com/in/sachin-chaurasiya"><strong>LinkedIn</strong></a></p>
</li>
<li><p><a target="_blank" href="https://twitter.com/sachindotcom"><strong>Twitter</strong></a></p>
</li>
<li><p><a target="_blank" href="https://github.com/Sachin-chaurasiya">GitHub</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Building a SQL Expert Bot: A Step-by-Step Guide with Vercel AI SDK and OpenAI API]]></title><description><![CDATA[OpenAI has brought the revolution in the AI field by creating the ChatGPT and now we can say the actual AI era has started and everyone from individuals to businesses using AI.
OpenAI has even created an API to build custom AI solutions like chatbots...]]></description><link>https://blog.sachinchaurasiya.dev/building-a-sql-expert-bot-a-step-by-step-guide-with-vercel-ai-sdk-and-openai-api</link><guid isPermaLink="true">https://blog.sachinchaurasiya.dev/building-a-sql-expert-bot-a-step-by-step-guide-with-vercel-ai-sdk-and-openai-api</guid><category><![CDATA[Next.js]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[AI]]></category><category><![CDATA[Vercel]]></category><category><![CDATA[openai]]></category><dc:creator><![CDATA[Sachin Chaurasiya]]></dc:creator><pubDate>Sun, 03 Mar 2024 03:37:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1709394301970/5f3a4779-1a53-49eb-a9ca-dbff406fb87a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>OpenAI has brought the revolution in the AI field by creating the ChatGPT and now we can say the actual AI era has started and everyone from individuals to businesses using AI.</p>
<p>OpenAI has even created an API to build custom AI solutions like chatbots, assistants, and more. We can access the APIs using SDKs provided by OpenAI for different programming languages. There are also wrappers built on top of the API to help you build the interface easily.</p>
<p>Vercel has built an AI SDK for constructing the streaming user interface with TypeScript and JavaScript. The best part is that it is open-source and has support for Vercel Edge runtime.</p>
<p>In this article, we will build a SQL Expert ChatBot using the OpenAI API and Vercel AI SDK. We will discuss streaming responses, custom prompts, and much more.</p>
<h2 id="heading-setup-openai-account">Setup OpenAI Account</h2>
<p>First, we will need to create an account on OpenAI and get an API key. You can sign up for a free account on OpenAI's website. Once you're logged in, go to the <code>API keys</code> section in the menu on the left side of the screen. From there, you can create a new API key. I have created one and named it <code>vercel-ai-sdk</code>, but you can choose any name you like.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709435521070/3455d296-ea6a-4f8c-b800-095285be818f.png" alt class="image--center mx-auto" /></p>
<p>Okay, now that we have the OpenAI API key, let's move on to the next section where we will talk about the Vercel AI SDK and how to set it up on your computer.</p>
<h2 id="heading-setup-vercel-ai-sdk">Setup Vercel AI SDK</h2>
<p>The <a target="_blank" href="https://sdk.vercel.ai/docs">Vercel AI SDK</a> is built for OpenAI APIs and includes a range of tools for utilizing OpenAI APIs.</p>
<p>To begin, let's create a Next.js application and install the dependencies <code>ai</code> for the Vercel AI SDK and <code>openai</code> for the OpenAI API client.</p>
<pre><code class="lang-bash">pnpm dlx create-next-app ai-sql-expert
<span class="hljs-built_in">cd</span> ai-sql-expert
pnpm install ai openai
</code></pre>
<p>Make sure to have the same configuration as shown in the image below</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709397853508/8cd2f4ad-619f-4b80-8ad6-ccf0145b398b.png" alt class="image--center mx-auto" /></p>
<p>Create a <code>.env</code> file in your project root and add your OpenAI API Key</p>
<pre><code class="lang-bash">OPENAI_API_KEY=xxxxxxxxx
</code></pre>
<p>Great, now that we have set up the OpenAI and Vercel AI SDKs, let's proceed to create API routes and a user interface for our SQL Expert Bot.</p>
<h2 id="heading-create-api-routes">Create API Routes</h2>
<p>We will set up an API route to manage user message requests. When a user sends a message, the OpenAI API will process it and send a response back to Next.js.</p>
<p>Create a file named <code>route.ts</code> inside the <code>api/chat</code> folder in your project's <code>src/app</code> directory. Then, add the following code snippet</p>
<pre><code class="lang-typescript"># app/api/chat/route.ts

<span class="hljs-keyword">import</span> OpenAI <span class="hljs-keyword">from</span> <span class="hljs-string">'openai'</span>;
<span class="hljs-keyword">import</span> { OpenAIStream, StreamingTextResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">'ai'</span>;

<span class="hljs-keyword">const</span> openai = <span class="hljs-keyword">new</span> OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> runtime = <span class="hljs-string">'edge'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">POST</span>(<span class="hljs-params">req: Request</span>) </span>{
  <span class="hljs-keyword">const</span> { messages } = <span class="hljs-keyword">await</span> req.json();

  <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> openai.chat.completions.create({
    model: <span class="hljs-string">'gpt-3.5-turbo'</span>,
    stream: <span class="hljs-literal">true</span>,
    messages,
  });

  <span class="hljs-keyword">const</span> stream = OpenAIStream(response);
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> StreamingTextResponse(stream);
}
</code></pre>
<p>This section is for setting up the OpenAI instance by providing the API key.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> openai = <span class="hljs-keyword">new</span> OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
});
</code></pre>
<p>By adding this line, we are instructing Next.js to utilize edge runtime for processing the API requests.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> runtime = <span class="hljs-string">'edge'</span>;
</code></pre>
<p>The <code>POST</code> function extracts the <code>messages</code> field from the JSON payload sent in the request.</p>
<p>It then uses the <code>openai.chat.completions.create</code> method to send these messages to OpenAI's <code>GPT-3.5-turbo</code> model for processing. This method creates completions (responses) based on the provided messages.</p>
<p>The <code>stream</code> parameter is set to <code>true</code>, indicating that the responses should be streamed back to the client in real time.</p>
<p>After receiving the response from OpenAI, the code converts it into a friendly text stream using a function called <code>OpenAIStream</code>.</p>
<p>Finally, the function constructs a response using the <code>StreamingTextResponse</code> class and returns it. This response contains the streamed text generated by OpenAI in response to the user's messages.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">POST</span>(<span class="hljs-params">req: Request</span>) </span>{
  <span class="hljs-keyword">const</span> { messages } = <span class="hljs-keyword">await</span> req.json();

  <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> openai.chat.completions.create({
    model: <span class="hljs-string">'gpt-3.5-turbo'</span>,
    stream: <span class="hljs-literal">true</span>,
    messages,
  });

  <span class="hljs-keyword">const</span> stream = OpenAIStream(response);
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> StreamingTextResponse(stream);
}
</code></pre>
<p>Okay, we have created the API route to handle user requests and process messages with the OpenAI API. Next, we will move on to creating the user interface for our SQL Expert Bot.</p>
<h2 id="heading-create-the-user-interface">Create The User Interface</h2>
<p>Before adding code for the user interface we will add the contants for initial chat messages which we will be using as custom prompt for our bot to set it's behaviour.</p>
<p>Create <code>constant/chat.constants.ts</code> in the project's <code>src/app</code> directory. Then, add the following code snippet</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Message } <span class="hljs-keyword">from</span> <span class="hljs-string">'ai/react'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> INITIAL_MESSAGES: Message[] = [
  {
    id: <span class="hljs-string">''</span>,
    role: <span class="hljs-string">'system'</span>,
    content: <span class="hljs-string">`You are an SQL expert. you can write generate SQL queries for any given problem statement.
        Make sure to return the query with proper formatting and indentation.
        Make sure to return the query with proper explanation and comments.
        If you are not able to solve the problem, you can ask for more details.
        If user is not able to understand the query, you can explain the query in simple words.
        If user is providing wrong prompt, you can ask for correct prompt.
        `</span>,
  },
];
</code></pre>
<p>We are providing instructions to the system on how to behave in various scenarios, determining the appropriate responses, and specifying how these responses should be delivered.</p>
<p>Next, add the following code snippet in <code>src/app/pages.tsx</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-string">'use client'</span>;

<span class="hljs-keyword">import</span> { useChat } <span class="hljs-keyword">from</span> <span class="hljs-string">'ai/react'</span>;
<span class="hljs-keyword">import</span> Markdown <span class="hljs-keyword">from</span> <span class="hljs-string">'react-markdown'</span>;
<span class="hljs-keyword">import</span> { INITIAL_MESSAGES } <span class="hljs-keyword">from</span> <span class="hljs-string">'./constant/chat.constants'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Chat</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> { messages, input, handleInputChange, handleSubmit } = useChat({
    initialMessages: INITIAL_MESSAGES,
  });
  <span class="hljs-keyword">return</span> (
    &lt;div&gt;
      &lt;div className=<span class="hljs-string">"text-center py-8"</span>&gt;
        &lt;h2 className=<span class="hljs-string">"text-center text-2xl font-bold mb-2"</span>&gt;SQL Expert&lt;/h2&gt;
        &lt;p&gt;
          Welcome to the SQL Expert. You can ask <span class="hljs-built_in">any</span> SQL related questions and
          expert will help you out.
        &lt;/p&gt;
      &lt;/div&gt;

      &lt;div className=<span class="hljs-string">"flex flex-col w-full max-w-2xl pb-24 mx-auto stretch gap-4"</span>&gt;
        {messages
          .filter(<span class="hljs-function">(<span class="hljs-params">m</span>) =&gt;</span> m.role !== <span class="hljs-string">'system'</span>)
          .map(<span class="hljs-function">(<span class="hljs-params">m</span>) =&gt;</span> (
            &lt;div
              key={m.id}
              className=<span class="hljs-string">"bg-gray-100 p-4 rounded flex gap-2 flex-col"</span>
            &gt;
              &lt;span className=<span class="hljs-string">"font-medium"</span>&gt;
                {m.role === <span class="hljs-string">'user'</span> ? <span class="hljs-string">'You'</span> : <span class="hljs-string">'Expert'</span>}
              &lt;/span&gt;
              &lt;Markdown&gt;{m.content}&lt;/Markdown&gt;
            &lt;/div&gt;
          ))}

        &lt;form
          className=<span class="hljs-string">"flex gap-4 fixed bottom-0 w-full mb-8"</span>
          onSubmit={handleSubmit}
        &gt;
          &lt;input
            autoFocus
            className=<span class="hljs-string">"p-2 border border-gray-300 rounded shadow-xl outline-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 flex-grow max-w-xl"</span>
            value={input}
            placeholder=<span class="hljs-string">"Ask your SQL related question.."</span>
            onChange={handleInputChange}
          /&gt;

          &lt;button
            className=<span class="hljs-string">"border p-2 px-4 rounded shadow-xl border-gray-300 bg-purple-500 text-white"</span>
            <span class="hljs-keyword">type</span>=<span class="hljs-string">"submit"</span>
          &gt;
            Send
          &lt;/button&gt;
        &lt;/form&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>We will use a client component to display messages and a form for users to send their messages.</p>
<pre><code class="lang-typescript"><span class="hljs-string">'use client'</span>;
</code></pre>
<p>Since the API will return the response with markdown content, we will utilize <code>react-markdown</code> to parse the markdown content.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> Markdown <span class="hljs-keyword">from</span> <span class="hljs-string">'react-markdown'</span>;
</code></pre>
<p>The <code>useChat</code> hook helps you create a chat interface for your chatbot app. It makes it simple to show messages from your AI provider, handle chat input, and update the UI when new messages come in. It accepts a bunch of options, and for our case, we are using the <code>initialMessages</code> option to set the system's behavior.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useChat } <span class="hljs-keyword">from</span> <span class="hljs-string">'ai/react'</span>;

<span class="hljs-keyword">const</span> { messages, input, handleInputChange, handleSubmit } = useChat({
    initialMessages: INITIAL_MESSAGES,
  });
</code></pre>
<p>This part is for displaying the messages we receive from the API. We have set a custom prompt for the <code>system</code>, so we will filter it out and only show the <code>user</code> and <code>expert</code> messages.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-col w-full max-w-2xl pb-24 mx-auto stretch gap-4"</span>&gt;</span>
        {messages
          .filter((m) =&gt; m.role !== 'system')
          .map((m) =&gt; (
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span>
              <span class="hljs-attr">key</span>=<span class="hljs-string">{m.id}</span>
              <span class="hljs-attr">className</span>=<span class="hljs-string">"bg-gray-100 p-4 rounded flex gap-2 flex-col"</span>
            &gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"font-medium"</span>&gt;</span>
                {m.role === 'user' ? 'You' : 'Expert'}
              <span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">Markdown</span>&gt;</span>{m.content}<span class="hljs-tag">&lt;/<span class="hljs-name">Markdown</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
          ))}
...
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p>Finally, we have the form for users to send their messages. We are using the <code>handleSubmit</code> function provided by the <code>useChat</code> hook to listen for the form submit event.</p>
<p>For the input field, we are using <code>handleInputChange</code> and <code>input</code> to control the input value.</p>
<p>Then, we have a button for submitting the form.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-col w-full max-w-2xl pb-24 mx-auto stretch gap-4"</span>&gt;</span>
        ...
        <span class="hljs-tag">&lt;<span class="hljs-name">form</span>
          <span class="hljs-attr">className</span>=<span class="hljs-string">"flex gap-4 fixed bottom-0 w-full mb-8"</span>
          <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{handleSubmit}</span>
        &gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
            <span class="hljs-attr">autoFocus</span>
            <span class="hljs-attr">className</span>=<span class="hljs-string">"p-2 border border-gray-300 rounded shadow-xl outline-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 flex-grow max-w-xl"</span>
            <span class="hljs-attr">value</span>=<span class="hljs-string">{input}</span>
            <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"Ask your SQL related question.."</span>
            <span class="hljs-attr">onChange</span>=<span class="hljs-string">{handleInputChange}</span>
          /&gt;</span>

          <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
            <span class="hljs-attr">className</span>=<span class="hljs-string">"border p-2 px-4 rounded shadow-xl border-gray-300 bg-purple-500 text-white"</span>
            <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span>
          &gt;</span>
            Send
          <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p>Thanks to Vercel AI SDK for providing a hook like <code>useChat</code> that takes care of everything from displaying messages to managing user input.</p>
<p>Great! We have built the user interface for our SQL Expert bot. Now, in the next section, we will start the development server and test it out.</p>
<h2 id="heading-test-the-bot">Test The Bot</h2>
<p>To start the development server, run the following command in your terminal</p>
<pre><code class="lang-bash">pnpm run dev

OR

yarn run dev

OR

npm run dev
</code></pre>
<p>Visit <a target="_blank" href="http://localhost:3000">http://localhost:3000</a> in your browser to view the application.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709435456702/4b4ec4a8-4c2c-4153-afb6-2f5c5cc493a7.png" alt class="image--center mx-auto" /></p>
<p>You can try out the bot with the provided sample prompts below</p>
<pre><code class="lang-bash">1. <span class="hljs-string">"Create a SQL query to find the total number of orders placed by each customer in the past year, ordered by the highest number of orders."</span>

2. <span class="hljs-string">"Write a SQL query to identify customers who have spent more than <span class="hljs-variable">$1000</span> in total on purchases within the last 3 months."</span>

3. <span class="hljs-string">"Design a SQL query to calculate the average time it takes for an order to be fulfilled from the moment it's placed, considering the timestamps of order placement and fulfillment."</span>

4. <span class="hljs-string">"Develop a SQL query to list the products with the highest profit margin, considering both the cost price and the selling price."</span>

5. <span class="hljs-string">"Construct a SQL query to find the monthly revenue trend for the past year, broken down by month."</span>

6. <span class="hljs-string">"Create a SQL query to identify customers who have not made a purchase in the last 6 months."</span>

7. <span class="hljs-string">"Write a SQL query to find the top 5 categories with the highest number of products sold in the last quarter."</span>

8. <span class="hljs-string">"Design a SQL query to identify orders with items that are out of stock at the time of purchase."</span>

9. <span class="hljs-string">"Develop a SQL query to calculate the total revenue generated from repeat customers versus new customers in the last month."</span>

10. <span class="hljs-string">"Construct a SQL query to identify any anomalies or irregularities in order quantities compared to historical averages for each product."</span>
</code></pre>
<p>Let's see the demo with the prompt provided below</p>
<pre><code class="lang-plaintext">Imagine you're tasked with optimizing the database performance of a large e-commerce platform. Write a SQL query to identify the top 10 selling products by revenue over the past month, considering both quantity sold and unit price.
</code></pre>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/oyeBhh5wR_M">https://youtu.be/oyeBhh5wR_M</a></div>
<p> </p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Awesome! Now that we have a functioning SQL Expert Bot, feel free to experiment with it and customize it to suit your requirements. That's all for this topic. Thank you for reading! If you found this article helpful, please consider liking, commenting, and sharing it with others.</p>
<h2 id="heading-resources">Resources</h2>
<ul>
<li><p><a target="_blank" href="https://github.com/Sachin-chaurasiya/ai-sql-expert">SQL Expert GitHub Repository</a></p>
</li>
<li><p><a target="_blank" href="https://sdk.vercel.ai/docs">Vercel AI SDK Docs</a></p>
</li>
<li><p><a target="_blank" href="https://sdk.vercel.ai/docs/api-reference/use-chat">useChat Hook Docs</a></p>
</li>
</ul>
<h2 id="heading-connect-with-me"><strong>Connect with me 👋</strong></h2>
<ul>
<li><p><a target="_blank" href="https://www.linkedin.com/in/sachin-chaurasiya"><strong>LinkedIn</strong></a></p>
</li>
<li><p><a target="_blank" href="https://twitter.com/sachindotcom"><strong>Twitter</strong></a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Hashnode Blog GitHub Action - fetch and display the latest blogs in a nice format]]></title><description><![CDATA[As a developer, it is essential to have a GitHub account and profile to showcase your work and skills. You can include information such as your introduction, current projects, skills, contributions, and more. However, you can also leverage the power ...]]></description><link>https://blog.sachinchaurasiya.dev/hashnode-blog-github-action-fetch-and-display-the-latest-blogs-in-a-nice-format</link><guid isPermaLink="true">https://blog.sachinchaurasiya.dev/hashnode-blog-github-action-fetch-and-display-the-latest-blogs-in-a-nice-format</guid><category><![CDATA[APIHackathon]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[github-actions]]></category><category><![CDATA[APIs]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Sachin Chaurasiya]]></dc:creator><pubDate>Thu, 01 Feb 2024 17:04:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1706686343036/9cc246a6-598f-4a90-9287-d9e7d211e0f8.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As a developer, it is essential to have a GitHub account and profile to showcase your work and skills. You can include information such as your introduction, current projects, skills, contributions, and more. However, you can also leverage the power of Hashnode to share your latest blogs and increase your visibility. By doing this, your followers can stay updated on your knowledge-sharing activities and learn from your expertise.</p>
<p>I felt the same and added my latest blogs to my GitHub profile. As we know, GitHub supports <strong>markdown</strong> for profile <strong>README</strong>, so I have manually added my latest five blogs to my profile. However, this process became tedious and repetitive because I had to manually update my profile README whenever I published a new blog.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/Sachin-chaurasiya">https://github.com/Sachin-chaurasiya</a></div>
<p> </p>
<p>I started looking into how I can automate this process and have the latest updated blogs on my profile. At the same time, Hashnode announced that they have a huge update in their APIs, and new APIs are publicly available. I knew that if I wanted to automate something on GitHub, GitHub Actions was the best solution.</p>
<p>I already knew what GitHub Actions are and how they work; however, I did not know how to build one. I was very curious to create my own GitHub Action and make it available to the community so that others could also use it. Therefore, I decided to invest my time in learning how to build my own GitHub Actions that would help me fetch the latest post from my Hashnode publication and then display it on my Profile README.</p>
<p><img src="https://i.giphy.com/3KCOFfdqmptLi.webp" alt class="image--center mx-auto" /></p>
<blockquote>
<p>For those of you who don't know what GitHub Actions are, you can read my blog here.</p>
</blockquote>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://openreplay.hashnode.dev/a-practical-guide-to-github-actions">https://openreplay.hashnode.dev/a-practical-guide-to-github-actions</a></div>
<p> </p>
<p>One more reason to build something with Hashnode APIs is the <a target="_blank" href="https://hashnode.com/hackathons/apihackathon">Hashnode APIs Hackathon</a>. In this article, I will discuss what Hashnode Blog GitHub action is, the problem it solves, its functionality, how I built it, the challenges I faced, how I solved them, and more.</p>
<h2 id="heading-what-is-hashnode-blog-github-action">What is Hashnode blog GitHub Action?</h2>
<p>It's a GitHub action to fetch and display your latest blog from Hashnode in a visually pleasing manner. If you are writing on Hashnode (if you don't have a blog yet, <a target="_blank" href="https://hashnode.com/unlock-blog">set it up here</a>) and want to showcase your latest blogs on your profile README, you can use the Hashnode Blog Action to achieve the same.</p>
<p>Configuring the Hashnode Blog GitHub Action is straightforward, and it supports the following configuration options.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Option</td><td>Description</td><td>Default</td><td>Required</td></tr>
</thead>
<tbody>
<tr>
<td><code>HASHNODE_PUBLICATION_NAME</code></td><td>Your hashnode publication name, Example: <a target="_blank" href="http://blog.hashnode.dev">blog.hashnode.dev</a></td><td>null</td><td>true</td></tr>
<tr>
<td><code>POST_COUNT</code></td><td>The number of posts counts</td><td>6</td><td>false</td></tr>
<tr>
<td><code>FORMAT</code></td><td>Output format (table, list, card, stacked)</td><td><code>table</code></td><td>false</td></tr>
<tr>
<td><code>FILE</code></td><td>Filename to save the output</td><td><a target="_blank" href="http://README.md"><code>README.md</code></a></td><td>false</td></tr>
<tr>
<td><code>DEBUG</code></td><td>Run action in debug mode</td><td>false</td><td>false</td></tr>
</tbody>
</table>
</div><p>You can create a GitHub workflow as shown below to execute the Hashnode Blog Action.</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="c3296a3d4526c0aaaf6112d018bd206f"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/Sachin-chaurasiya/c3296a3d4526c0aaaf6112d018bd206f" class="embed-card">https://gist.github.com/Sachin-chaurasiya/c3296a3d4526c0aaaf6112d018bd206f</a></div><p> </p>
<p>You can replace <a target="_blank" href="http://blog.sachinchaurasiya.dev"><code>blog.sachinchaurasiya.dev</code></a> with your publication name, and you can also add the <code>FORMAT</code>, <code>FILE</code>, and <code>POST_COUNT</code> options. If you do not provide values for these options, the default values will take effect.</p>
<p>Examples of different available FORMAT options.</p>
<h3 id="heading-list">List</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706720716272/b61717fe-dba5-42db-bf12-6a5d81058d30.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-stacked">Stacked</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706720728923/f56a581a-7b1c-4feb-9dd6-87e29cdd096c.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-table">Table</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706809466609/4dae0329-3d47-4a7b-9aa4-0a3d781a73df.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-card">Card</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706809487408/45fc61b9-8cbb-4928-ad9d-00994efa39b9.png" alt class="image--center mx-auto" /></p>
<p>Now that you understand the Hashnode Blog GitHub action, its functionality, and how it works, the upcoming section will delve into how I built it, the challenges I encountered, and how I resolved them.</p>
<h2 id="heading-how-i-built-the-hashnode-blog-github-action">How I Built the Hashnode Blog GitHub Action?</h2>
<p>While learning about GitHub Actions, I came across the <a target="_blank" href="https://github.com/actions">GitHub Actions Org</a>, and they have a bunch of templates for building custom GitHub actions. So, I started searching for a template that has TypeScript support, ensuring type safety to write bug-free code. I found the <code>typescript-action</code> template that includes support for tests, linter, versioning, and more.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/actions/typescript-action">https://github.com/actions/typescript-action</a></div>
<p> </p>
<p>I clicked on the <code>Use this template</code> button, and within minutes, I had a basic GitHub action with TypeScript support. I then started adding code for my action logic.</p>
<p>First, I defined all the configuration and meta-information for my action in, including the name, description, author information, branding information, inputs, Node.js version, and entry point.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">'Hashnode Blog Action'</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">'Action to pull your latest blog and show it in a nice format.'</span>
<span class="hljs-attr">author:</span> <span class="hljs-string">'Sachin-chaurasiya'</span>

<span class="hljs-attr">branding:</span>
  <span class="hljs-attr">icon:</span> <span class="hljs-string">'hash'</span>
  <span class="hljs-attr">color:</span> <span class="hljs-string">'blue'</span>

<span class="hljs-attr">inputs:</span>
  <span class="hljs-attr">HASHNODE_PUBLICATION_NAME:</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">'Your hashnode publication name, Example: blog.hashnode.dev'</span>
    <span class="hljs-attr">required:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">POST_COUNT:</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">'Number of posts count'</span>
    <span class="hljs-attr">required:</span> <span class="hljs-literal">false</span>
    <span class="hljs-attr">default:</span> <span class="hljs-number">6</span>
  <span class="hljs-attr">FORMAT:</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">'Output format (table, list, card)'</span>
    <span class="hljs-attr">required:</span> <span class="hljs-literal">false</span>
    <span class="hljs-attr">default:</span> <span class="hljs-string">'table'</span>
  <span class="hljs-attr">FILE:</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">'File name to save the output'</span>
    <span class="hljs-attr">required:</span> <span class="hljs-literal">false</span>
    <span class="hljs-attr">default:</span> <span class="hljs-string">'README.md'</span>
  <span class="hljs-attr">DEBUG:</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">'Debug mode'</span>
    <span class="hljs-attr">required:</span> <span class="hljs-literal">false</span>
    <span class="hljs-attr">default:</span> <span class="hljs-literal">false</span>

<span class="hljs-attr">runs:</span>
  <span class="hljs-attr">using:</span> <span class="hljs-string">node20</span>
  <span class="hljs-attr">main:</span> <span class="hljs-string">dist/index.js</span>
</code></pre>
<p>Then, I started working on logic to fetch the posts from Hashnode using a GraphQL query. I have not used any third-party library for querying Hashnode APIs, I just utilized the JavaScript Fetch API.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { HashNodeArticleResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">'HashNodeTypes'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> BASE_URL = <span class="hljs-string">'https://gql.hashnode.com/'</span>

<span class="hljs-keyword">const</span> getQuery = (publicationName: <span class="hljs-built_in">string</span>, limit: <span class="hljs-built_in">number</span>): <span class="hljs-function"><span class="hljs-params">string</span> =&gt;</span> {
  <span class="hljs-keyword">return</span> <span class="hljs-string">`{
  publication(host: "<span class="hljs-subst">${publicationName}</span>") {
    posts(first: <span class="hljs-subst">${limit}</span>) {
      totalDocuments
      pageInfo {
        hasNextPage
        endCursor
      }
      edges {
        node {
          url
          title
          brief
          slug
          publishedAt
          coverImage {
            url
          }
          reactionCount
          replyCount
        }
      }
    }
  }
}`</span>
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> fetchPosts = <span class="hljs-keyword">async</span> (
  publicationName: <span class="hljs-built_in">string</span>,
  limit: <span class="hljs-built_in">number</span>
): <span class="hljs-built_in">Promise</span>&lt;HashNodeArticleResponse&gt; =&gt; {
  <span class="hljs-keyword">const</span> query = getQuery(publicationName, limit)
  <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(BASE_URL, {
    method: <span class="hljs-string">'POST'</span>,
    headers: {
      <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>
    },
    body: <span class="hljs-built_in">JSON</span>.stringify({ query })
  })
  <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> response.json()

  <span class="hljs-keyword">return</span> data
}
</code></pre>
<p>Then, I worked on utilities such as <code>date</code> and <code>formats</code>. Format utilities are designed to process the response from the Hashnode API posts and return the Markdown string for the requested output format.</p>
<p>For example, the piece of code below will process the list of posts and return the <code>table</code> Markdown format.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> createMarkdownTable = (postList: PostNode[]): <span class="hljs-function"><span class="hljs-params">string</span> =&gt;</span> {
  <span class="hljs-keyword">let</span> tableContent = <span class="hljs-string">'&lt;table&gt;'</span>

  postList.forEach(<span class="hljs-function"><span class="hljs-params">post</span> =&gt;</span> {
    <span class="hljs-keyword">const</span> { title, brief, coverImage, url } = post

    tableContent += <span class="hljs-string">'&lt;tr&gt;'</span>
    tableContent += <span class="hljs-string">`&lt;td&gt;&lt;img src="<span class="hljs-subst">${coverImage.url}</span>" alt="<span class="hljs-subst">${title}</span>"&gt;&lt;/td&gt;`</span>
    tableContent += <span class="hljs-string">`&lt;td&gt;&lt;a href="<span class="hljs-subst">${url}</span>"&gt;&lt;strong&gt;<span class="hljs-subst">${title}</span>&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;<span class="hljs-subst">${brief}</span>&lt;/td&gt;`</span>
    tableContent += <span class="hljs-string">'&lt;/tr&gt;'</span>
  })

  tableContent += <span class="hljs-string">'&lt;/table&gt;'</span>

  <span class="hljs-keyword">return</span> tableContent
}
</code></pre>
<p>Last but not least, I worked on the entry point of the action, which is the <code>main.ts</code> file. This file includes logic for obtaining inputs, fetching posts, processing the posts, obtaining the output in markdown format, and then finding and replacing that content within a specified regex pattern. Finally, it commits and pushes the changes to the user file repository.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> core <span class="hljs-keyword">from</span> <span class="hljs-string">'@actions/core'</span>
<span class="hljs-keyword">import</span> { fetchPosts } <span class="hljs-keyword">from</span> <span class="hljs-string">'./hashnodeQuery'</span>
<span class="hljs-keyword">import</span> fs <span class="hljs-keyword">from</span> <span class="hljs-string">'fs'</span>
<span class="hljs-keyword">import</span> commitFile <span class="hljs-keyword">from</span> <span class="hljs-string">'./commitFiles'</span>
<span class="hljs-keyword">import</span> { getFormattedContent } <span class="hljs-keyword">from</span> <span class="hljs-string">'./utils/formatUtils'</span>
<span class="hljs-keyword">import</span> { ContentFormat } <span class="hljs-keyword">from</span> <span class="hljs-string">'HashNodeTypes'</span>

<span class="hljs-keyword">const</span> SECTION_REGEX =
  <span class="hljs-regexp">/^(&lt;!--(?:\s|)HASHNODE_BLOG:(?:START|start)(?:\s|)--&gt;)(?:\n|)([\s\S]*?)(?:\n|)(&lt;!--(?:\s|)HASHNODE_BLOG:(?:END|end)(?:\s|)--&gt;)$/gm</span>

<span class="hljs-comment">/**
 * The main function for the action.
 * @returns {Promise&lt;void&gt;} Resolves when the action is complete.
 */</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">run</span>(<span class="hljs-params"></span>): <span class="hljs-title">Promise</span>&lt;<span class="hljs-title">void</span>&gt; </span>{
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> publicationName: <span class="hljs-built_in">string</span> = core.getInput(<span class="hljs-string">'HASHNODE_PUBLICATION_NAME'</span>)
    <span class="hljs-keyword">const</span> postCount: <span class="hljs-built_in">number</span> = <span class="hljs-built_in">parseInt</span>(core.getInput(<span class="hljs-string">'POST_COUNT'</span>))
    <span class="hljs-keyword">const</span> outputFileName: <span class="hljs-built_in">string</span> = core.getInput(<span class="hljs-string">'FILE'</span>)
    <span class="hljs-keyword">const</span> format: ContentFormat = (core.getInput(<span class="hljs-string">'FORMAT'</span>) ??
      <span class="hljs-string">'table'</span>) <span class="hljs-keyword">as</span> ContentFormat
    <span class="hljs-keyword">const</span> isDebug: <span class="hljs-built_in">boolean</span> = core.getInput(<span class="hljs-string">'DEBUG'</span>) === <span class="hljs-string">'true'</span>

    <span class="hljs-comment">// fetch posts from hashnode</span>
    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetchPosts(publicationName, postCount)
    <span class="hljs-keyword">const</span> posts = response.data.publication.posts.edges.map(<span class="hljs-function"><span class="hljs-params">edge</span> =&gt;</span> edge.node)

    <span class="hljs-keyword">const</span> filePath = <span class="hljs-string">`<span class="hljs-subst">${process.env.GITHUB_WORKSPACE}</span>/<span class="hljs-subst">${outputFileName}</span>`</span>
    <span class="hljs-keyword">const</span> fileContent = fs.readFileSync(filePath, <span class="hljs-string">'utf8'</span>)

    <span class="hljs-keyword">const</span> output = getFormattedContent(posts, format)

    <span class="hljs-keyword">const</span> result = fileContent
      .toString()
      .replace(SECTION_REGEX, <span class="hljs-string">`$1\n<span class="hljs-subst">${output}</span>\n$3`</span>)

    fs.writeFileSync(filePath, result, <span class="hljs-string">'utf8'</span>)

    <span class="hljs-comment">// commit changes to the file when not in debug mode</span>
    <span class="hljs-keyword">if</span> (!isDebug) {
      <span class="hljs-comment">// eslint-disable-next-line github/no-then</span>
      <span class="hljs-keyword">await</span> commitFile().catch(<span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> {
        core.error(err)
        core.info(err.stack)
        process.exit(err.code || <span class="hljs-number">-1</span>)
      })
    }
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-comment">// Fail the workflow run if an error occurs</span>
    <span class="hljs-keyword">if</span> (error <span class="hljs-keyword">instanceof</span> <span class="hljs-built_in">Error</span>) core.setFailed(error.message)
  }
}
</code></pre>
<p>Now that you have an understanding of how I built the action, let's delve into the challenges I faced in the next section.</p>
<h2 id="heading-challenges-faced">Challenges Faced</h2>
<p>The <code>typescript-action</code> template had a lot of pre-configured workflows. So, when I tried to build the action for the first time, it gave me a bunch of errors.</p>
<p><img src="https://i.giphy.com/bEVKYB487Lqxy.webp" alt="TV gif. Kermit the Frog from The Muppets chews on his frog fingers as if he has fingernails and trembles in fear. " class="image--center mx-auto" /></p>
<p>The second challenge was committing and pushing the changes to the user's file repository. This was, I would say, an exciting challenge, as it was my first time committing and pushing changes programmatically in GitHub.</p>
<p>I thought, No problem! I'm an engineer, and it's my job to face challenges and find solutions. Now, let me explain how I tackled these challenges.</p>
<h2 id="heading-how-i-solved-the-challenges">How I Solved the Challenges</h2>
<p>To overcome the initial challenge, I carefully read the guide in the template's README and adjusted or changed the workflows to fit my requirements. The key takeaway here is that reading the available documentation can be helpful.</p>
<p>The second challenge was a bit tricky, but I believed that every problem has a solution. I began by searching on Google to find out how to automatically commit and push changes to a GitHub repository. Unfortunately, I couldn't find helpful resources. So, I turned to ChatGPT and GitHub Copilot for assistance. They guided me towards using the GitHub Action bot config to commit and push changes. Check out the bash file for the details on how to do this for the user's file repository.</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/bin/sh</span>

<span class="hljs-built_in">set</span> -e

<span class="hljs-keyword">if</span> [ -z <span class="hljs-string">"<span class="hljs-variable">$GITHUB_TOKEN</span>"</span> ]; <span class="hljs-keyword">then</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"🚩  GITHUB_TOKEN Not Found. Please Set It As ENV Variable"</span>
    <span class="hljs-built_in">exit</span> 1
<span class="hljs-keyword">fi</span>

git config --global user.email <span class="hljs-string">"githubactionbot+hashnode@gmail.com"</span>
git config --global user.name <span class="hljs-string">"Hashnode Bot"</span>

DEST_FILE=<span class="hljs-string">"<span class="hljs-variable">${INPUT_FILE}</span>"</span>

GIT_URL=<span class="hljs-string">"https://x-access-token:<span class="hljs-variable">${GITHUB_TOKEN}</span>@github.com/<span class="hljs-variable">${GITHUB_REPOSITORY}</span>.git"</span>

git add <span class="hljs-string">"<span class="hljs-variable">${GITHUB_WORKSPACE}</span>/<span class="hljs-variable">${DEST_FILE}</span>"</span> -f

<span class="hljs-keyword">if</span> [ <span class="hljs-string">"<span class="hljs-subst">$(git status --porcelain)</span>"</span> != <span class="hljs-string">""</span> ]; <span class="hljs-keyword">then</span>
    git commit -m <span class="hljs-string">"📚 Latest Blog Updated"</span>
<span class="hljs-keyword">else</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"  ✅ Blog List Upto Date"</span>
<span class="hljs-keyword">fi</span>

git push <span class="hljs-string">"<span class="hljs-variable">$GIT_URL</span>"</span>
</code></pre>
<p>As this is not something that will be generated on every new build, I directly placed it in the <code>dist</code> directory.</p>
<p>Then, wrote a simple utility function to execute this Bash file and commit and push the changes.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { spawn } <span class="hljs-keyword">from</span> <span class="hljs-string">'child_process'</span>
<span class="hljs-keyword">import</span> path <span class="hljs-keyword">from</span> <span class="hljs-string">'path'</span>

<span class="hljs-keyword">const</span> exec = <span class="hljs-keyword">async</span> (cmd: <span class="hljs-built_in">string</span>, args: <span class="hljs-built_in">string</span>[] = []): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">number</span>&gt; =&gt;
  <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> app = spawn(cmd, args, { stdio: <span class="hljs-string">'inherit'</span> })
    app.on(<span class="hljs-string">'close'</span>, <span class="hljs-function">(<span class="hljs-params">code: <span class="hljs-built_in">number</span></span>) =&gt;</span> {
      <span class="hljs-keyword">if</span> (code !== <span class="hljs-number">0</span>) {
        <span class="hljs-keyword">const</span> err = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`Invalid status code: <span class="hljs-subst">${code}</span>`</span>)
        <span class="hljs-built_in">Object</span>.defineProperty(err, <span class="hljs-string">'code'</span>, { value: code })
        <span class="hljs-keyword">return</span> reject(err)
      }
      <span class="hljs-keyword">return</span> resolve(code)
    })
    app.on(<span class="hljs-string">'error'</span>, reject)
  })

<span class="hljs-keyword">const</span> main = <span class="hljs-keyword">async</span> (): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">number</span>&gt; =&gt; {
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> exec(<span class="hljs-string">'bash'</span>, [path.join(__dirname, <span class="hljs-string">'./commit.sh'</span>)])
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> main
</code></pre>
<p>This utility function uses the <code>spawn</code> function to execute bash commands.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>I hope you find this article genuinely helpful, and that you've learned something from it. I've released the Hashnode blog GitHub action on the GitHub marketplace, so feel free to try it out and share your feedback. It's open source, so pull requests are welcome. You can report bugs, request features, and more.</p>
<p>Show your support for the project by giving it a star ⭐️.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/Sachin-chaurasiya/hashnode-blog-action">https://github.com/Sachin-chaurasiya/hashnode-blog-action</a></div>
<p> </p>
<p>Lastly, I want to express my gratitude to Hashnode for providing this fantastic opportunity to create something using Hashnode's brand-new set of APIs.</p>
<p>And that’s it for this topic. Thank you for reading.</p>
<h2 id="heading-connect-with-me"><strong>Connect with me 👋</strong></h2>
<ul>
<li><p><a target="_blank" href="https://www.linkedin.com/in/sachin-chaurasiya"><strong>LinkedIn</strong></a></p>
</li>
<li><p><a target="_blank" href="https://twitter.com/sachindotcom"><strong>Twitter</strong></a></p>
</li>
</ul>
]]></content:encoded></item></channel></rss>