<?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[Gauravbytes | Backend Engineer & AI Agent Builder]]></title><description><![CDATA[I build. I break. I document.

Backend engineer & AI agent tinkerer writing about Node.js APIs, SaaS architecture, and the messy reality of shipping products alone.

Follow along as I figure things out in public.]]></description><link>https://gauravbytes.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/616e87a8f1f4c944cc6b49a1/62b66582-c854-4bcb-b2f6-2b2879623cff.png</url><title>Gauravbytes | Backend Engineer &amp; AI Agent Builder</title><link>https://gauravbytes.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Wed, 03 Jun 2026 10:30:01 GMT</lastBuildDate><atom:link href="https://gauravbytes.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[These AI Memory Types Decide Whether Your Agent Is Smart or Useless]]></title><description><![CDATA[Everyone is building AI agents right now.
But most of them have one major problem:
They forget everything.
You ask the agent something… then ask a follow-up question… and suddenly it behaves like it h]]></description><link>https://gauravbytes.dev/these-ai-memory-types-decide-whether-your-agent-is-smart-or-useless</link><guid isPermaLink="true">https://gauravbytes.dev/these-ai-memory-types-decide-whether-your-agent-is-smart-or-useless</guid><category><![CDATA[AI]]></category><category><![CDATA[llm]]></category><category><![CDATA[aiagents]]></category><category><![CDATA[generative ai]]></category><category><![CDATA[memory]]></category><category><![CDATA[RAG ]]></category><dc:creator><![CDATA[Gaurav Kumar]]></dc:creator><pubDate>Mon, 25 May 2026 04:30:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/616e87a8f1f4c944cc6b49a1/f54921a0-c3ad-4550-91a0-37a72fcb5123.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Everyone is building AI agents right now.</p>
<p>But most of them have one major problem:</p>
<p>They forget everything.</p>
<p>You ask the agent something… then ask a follow-up question… and suddenly it behaves like it has amnesia.</p>
<p>That’s because many developers focus only on:</p>
<ul>
<li><p>prompts,</p>
</li>
<li><p>tools,</p>
</li>
<li><p>workflows,</p>
</li>
<li><p>and LLM selection…</p>
</li>
</ul>
<p>…but completely ignore <strong>memory systems</strong>.</p>
<p>The reality is:</p>
<blockquote>
<p>Memory is what transforms an LLM into an actual AI agent.</p>
</blockquote>
<p>Without memory:</p>
<ul>
<li><p>agents feel dumb,</p>
</li>
<li><p>conversations break,</p>
</li>
<li><p>personalization disappears,</p>
</li>
<li><p>and long-running tasks become impossible.</p>
</li>
</ul>
<p>Before implementing any AI agent, you must understand the different memory types, when to use them, and what should (and should not) be stored.</p>
<p>In this article, we’ll break down:</p>
<ul>
<li><p>Short-term memory</p>
</li>
<li><p>Long-term memory</p>
</li>
<li><p>Semantic memory</p>
</li>
<li><p>Episodic memory</p>
</li>
<li><p>Working memory</p>
</li>
<li><p>Retrieval memory And how modern AI systems like OpenAI and Anthropic likely structure memory internally.</p>
</li>
</ul>
<hr />
<h2>Why Memory Matters in AI Agents</h2>
<p>Imagine hiring a human assistant who:</p>
<ul>
<li><p>forgets your name every minute,</p>
</li>
<li><p>never remembers previous tasks,</p>
</li>
<li><p>and resets after every conversation.</p>
</li>
</ul>
<p>That assistant would be useless.</p>
<p>Yet that’s exactly how many AI agents behave today.</p>
<p>Memory enables agents to:</p>
<ul>
<li><p>maintain conversation context,</p>
</li>
<li><p>remember user preferences,</p>
</li>
<li><p>learn from previous interactions,</p>
</li>
<li><p>improve future responses,</p>
</li>
<li><p>and perform long-running autonomous tasks.</p>
</li>
</ul>
<p>This is the difference between:</p>
<ul>
<li><p>a chatbot,</p>
</li>
<li><p>and a real AI assistant.</p>
</li>
</ul>
<hr />
<h2>1. Short-Term Memory (Conversation Memory)</h2>
<h3>What It Is</h3>
<p>Short-term memory stores:</p>
<ul>
<li><p>recent messages,</p>
</li>
<li><p>current task context,</p>
</li>
<li><p>active reasoning steps,</p>
</li>
<li><p>and temporary conversation state.</p>
</li>
</ul>
<p>This memory usually lives inside:</p>
<ul>
<li><p>the context window,</p>
</li>
<li><p>Redis,</p>
</li>
<li><p>in-memory cache,</p>
</li>
<li><p>or session storage.</p>
</li>
</ul>
<h3>Example</h3>
<p>User says:</p>
<blockquote>
<p>“Help me write a LinkedIn post about AI agents.”</p>
</blockquote>
<p>Then later:</p>
<blockquote>
<p>“Make it shorter.”</p>
</blockquote>
<p>The AI needs short-term memory to understand: “it” = the LinkedIn post.</p>
<p>Without it, the agent gets confused.</p>
<h3>Common Mistake</h3>
<p>Many developers dump entire conversations into prompts.</p>
<p>This:</p>
<ul>
<li><p>increases token cost,</p>
</li>
<li><p>slows responses,</p>
</li>
<li><p>and eventually exceeds context limits.</p>
</li>
</ul>
<p>Good agents summarize and compress short-term memory over time.</p>
<hr />
<h2>2. Long-Term Memory (Persistent Memory)</h2>
<h3>What It Is</h3>
<p>Long-term memory stores information across sessions.</p>
<p>This includes:</p>
<ul>
<li><p>user preferences,</p>
</li>
<li><p>goals,</p>
</li>
<li><p>writing style,</p>
</li>
<li><p>past projects,</p>
</li>
<li><p>recurring workflows,</p>
</li>
<li><p>and important facts.</p>
</li>
</ul>
<p>Usually stored in:</p>
<ul>
<li><p>vector databases,</p>
</li>
<li><p>PostgreSQL,</p>
</li>
<li><p>graph databases,</p>
</li>
<li><p>or knowledge stores.</p>
</li>
</ul>
<h3>Example</h3>
<p>If a user always writes:</p>
<ul>
<li><p>backend engineering blogs,</p>
</li>
<li><p>AI tutorials,</p>
</li>
<li><p>and YouTube scripts…</p>
</li>
</ul>
<p>…the agent can remember this and personalize future outputs automatically.</p>
<p>That’s how assistants start feeling “smart.”</p>
<hr />
<h2>3. Semantic Memory</h2>
<h3>What It Is</h3>
<p>Semantic memory stores facts and knowledge.</p>
<p>Think of it like:</p>
<ul>
<li><p>concepts,</p>
</li>
<li><p>relationships,</p>
</li>
<li><p>expertise,</p>
</li>
<li><p>and learned information.</p>
</li>
</ul>
<h3>Example</h3>
<p>The agent remembers:</p>
<ul>
<li><p>LangChain is an AI framework,</p>
</li>
<li><p>PostgreSQL is a database,</p>
</li>
<li><p>Redis is used for caching.</p>
</li>
</ul>
<p>This is knowledge-based memory.</p>
<p>Humans use semantic memory too.</p>
<hr />
<h2>4. Episodic Memory</h2>
<h3>What It Is</h3>
<p>Episodic memory stores experiences and past interactions.</p>
<p>Instead of remembering facts, the agent remembers events.</p>
<h3>Example</h3>
<p>The agent remembers:</p>
<blockquote>
<p>“Last week the user struggled with Docker networking.”</p>
</blockquote>
<p>That historical experience helps future responses.</p>
<p>This is one of the most important memory types for personalization.</p>
<hr />
<h2>5. Working Memory</h2>
<h3>What It Is</h3>
<p>Working memory is temporary reasoning memory.</p>
<p>It exists only while solving a task.</p>
<p>The agent may store:</p>
<ul>
<li><p>intermediate reasoning,</p>
</li>
<li><p>calculations,</p>
</li>
<li><p>plans,</p>
</li>
<li><p>or execution steps.</p>
</li>
</ul>
<p>Once the task finishes, this memory may disappear.</p>
<h3>Example</h3>
<p>While generating SQL:</p>
<ol>
<li><p>Understand schema</p>
</li>
<li><p>Build query</p>
</li>
<li><p>Validate query</p>
</li>
<li><p>Execute safely</p>
</li>
</ol>
<p>The intermediate steps live in working memory.</p>
<hr />
<h2>6. Retrieval Memory (RAG Memory)</h2>
<h3>What It Is</h3>
<p>Instead of storing everything directly in prompts, the agent retrieves relevant information when needed.</p>
<p>This powers:</p>
<ul>
<li><p>RAG systems,</p>
</li>
<li><p>document agents,</p>
</li>
<li><p>and enterprise AI assistants.</p>
</li>
</ul>
<h3>Example</h3>
<p>User asks:</p>
<blockquote>
<p>“Summarize my uploaded PDF.”</p>
</blockquote>
<p>The agent:</p>
<ol>
<li><p>retrieves relevant chunks,</p>
</li>
<li><p>injects them into context,</p>
</li>
<li><p>then answers.</p>
</li>
</ol>
<p>This avoids context overload.</p>
<hr />
<h2>The Real Challenge: What Should Be Stored?</h2>
<p>This is where most AI agent systems fail.</p>
<p>Not everything deserves long-term memory.</p>
<p>Good memory systems decide:</p>
<ul>
<li><p>what to remember,</p>
</li>
<li><p>what to forget,</p>
</li>
<li><p>and what to summarize.</p>
</li>
</ul>
<h3>Good Things to Store</h3>
<ul>
<li><p>User preferences</p>
</li>
<li><p>Long-term goals</p>
</li>
<li><p>Writing style</p>
</li>
<li><p>Repeated workflows</p>
</li>
<li><p>Important project details</p>
</li>
</ul>
<h3>Bad Things to Store</h3>
<ul>
<li><p>Temporary emotions</p>
</li>
<li><p>One-time requests</p>
</li>
<li><p>Sensitive information</p>
</li>
<li><p>Random conversational noise</p>
</li>
</ul>
<hr />
<h2>A Practical AI Agent Memory Architecture</h2>
<p>A production-grade AI agent often looks like this:</p>
<img src="https://cdn.hashnode.com/uploads/covers/616e87a8f1f4c944cc6b49a1/ebcaef62-63ef-4c1f-ac14-21db77664094.jpg" alt="" style="display:block;margin:0 auto" />

<p>This layered architecture is what makes modern AI assistants scalable.</p>
<hr />
<h2>How Modern AI Systems Likely Handle Memory</h2>
<p>Companies like OpenAI and Anthropic likely use:</p>
<ul>
<li><p>session memory,</p>
</li>
<li><p>persistent user memory,</p>
</li>
<li><p>retrieval systems,</p>
</li>
<li><p>summarization pipelines,</p>
</li>
<li><p>and memory ranking systems.</p>
</li>
</ul>
<p>The hard part is not storing memory.</p>
<p>The hard part is:</p>
<ul>
<li><p>deciding importance,</p>
</li>
<li><p>retrieval timing,</p>
</li>
<li><p>compression,</p>
</li>
<li><p>and relevance filtering.</p>
</li>
</ul>
<p>Memory engineering is becoming its own field.</p>
<hr />
<h2>Final Thoughts</h2>
<p>Most developers think AI agents are about:</p>
<ul>
<li><p>better prompts,</p>
</li>
<li><p>bigger models,</p>
</li>
<li><p>or more tools.</p>
</li>
</ul>
<p>But memory is what actually creates continuity, intelligence, and personalization.</p>
<p>The future of AI agents won’t belong to the models with the largest context windows.</p>
<p>It will belong to agents that:</p>
<ul>
<li><p>remember intelligently,</p>
</li>
<li><p>forget strategically,</p>
</li>
<li><p>and learn continuously.</p>
</li>
</ul>
<p>If you truly want to build production-grade AI agents…</p>
<p>start with memory architecture first.</p>
]]></content:encoded></item><item><title><![CDATA[The 5 Cron Jobs That Save Backend Servers From Disaster]]></title><description><![CDATA[Most backend systems don’t fail in dramatic ways. They fail quietly—when no one is watching. A missed backup, a full disk, or a stuck background job is often all it takes to turn a stable system into ]]></description><link>https://gauravbytes.dev/5-cron-jobs-that-save-backend-servers-from-disaster</link><guid isPermaLink="true">https://gauravbytes.dev/5-cron-jobs-that-save-backend-servers-from-disaster</guid><category><![CDATA[cronjob]]></category><category><![CDATA[Devops]]></category><category><![CDATA[System Design]]></category><category><![CDATA[automation]]></category><category><![CDATA[backend]]></category><dc:creator><![CDATA[Gaurav Kumar]]></dc:creator><pubDate>Wed, 20 May 2026 04:30:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/616e87a8f1f4c944cc6b49a1/0c6dfd2c-ebd4-43c2-8e6b-703c56274288.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Most backend systems don’t fail in dramatic ways. They fail quietly—when no one is watching. A missed backup, a full disk, or a stuck background job is often all it takes to turn a stable system into a production incident.</p>
<p>That’s why cron jobs matter more than most developers realize. They are the invisible safety net keeping systems alive in production.</p>
<p>Below are 5 cron jobs every backend server should have.</p>
<h2>#1. Automated Backups</h2>
<p>Regularly backing up your data is non-negotiable. This is arguably one of the most critical uses of cron jobs in any backend system.</p>
<p>In production, things rarely break on purpose—they break due to small, unexpected mistakes. A deleted record, a faulty deployment, or even a storage corruption issue can wipe out critical data in seconds.</p>
<p>A backup cron job ensures that no matter what happens, there is always a recent version of your data that can be restored. It quietly runs in the background, taking snapshots of your database and storing them safely in external storage.</p>
<h2>#2. Log Cleanup &amp; Rotation</h2>
<p>Logs are extremely useful until they aren’t. Over time, they quietly grow into massive files that start consuming disk space without any warning.</p>
<p>A log cleanup cron job ensures that old logs are either deleted or compressed regularly. Without this, servers can suddenly run out of storage and crash unexpectedly.</p>
<p>This job is less about performance optimization and more about survival. It keeps your system stable by preventing silent resource exhaustion.</p>
<h2>#3. Server Health Checks</h2>
<p>Your server rarely tells you when it’s about to fail—it just does. That’s why continuous health checks are essential.</p>
<p>This cron job periodically checks CPU usage, memory consumption, disk space, and service availability. If anything crosses safe limits, it triggers alerts before users are affected.</p>
<p>It acts like a silent observer, constantly watching the system so you don’t have to manually inspect it every few hours.</p>
<h2>#4. Cache Cleanup</h2>
<p>Every backend system generates temporary files—uploads, cached responses, processing artifacts, and more. Most of these are never cleaned automatically.</p>
<p>Over time, these files accumulate and slowly degrade system performance or fill up storage completely.</p>
<p>A cleanup cron job ensures that anything temporary truly stays temporary. It removes stale files and keeps the system lean, fast, and predictable.</p>
<h2>#5. Retry Failed Jobs</h2>
<p>Not everything works on the first attempt. API calls fail, webhooks timeout, and background jobs occasionally break due to external dependencies.</p>
<p>Instead of losing that work permanently, a retry cron job reprocesses failed tasks at regular intervals.</p>
<p>This is what makes modern backend systems resilient. It ensures that temporary failures don’t turn into permanent data loss or broken workflows.</p>
<h2>Final Thoughts</h2>
<p>Cron jobs are often overlooked because they don’t feel “core” to the product. But in reality, they are what make production systems stable, reliable, and self-healing.</p>
<p>Most backend failures don’t come from code—they come from missing automation.</p>
<p>And these 5 cron jobs quietly prevent that from happening.</p>
]]></content:encoded></item><item><title><![CDATA[Build a  PostgreSQL AI Agent Using LangChain + Ollama ]]></title><description><![CDATA[🔥 Introduction
What if you could query your database like this:

"Show me top 10 users by revenue"

…and get instant results—without writing SQL?
Welcome to the world of AI-powered database agents.
I]]></description><link>https://gauravbytes.dev/build-a-postgresql-ai-agent-using-langchain-ollama</link><guid isPermaLink="true">https://gauravbytes.dev/build-a-postgresql-ai-agent-using-langchain-ollama</guid><category><![CDATA[AI]]></category><category><![CDATA[langchain]]></category><category><![CDATA[PostgreSQL]]></category><category><![CDATA[Python]]></category><category><![CDATA[Machine Learning]]></category><category><![CDATA[llm]]></category><category><![CDATA[ollama]]></category><category><![CDATA[developers]]></category><dc:creator><![CDATA[Gaurav Kumar]]></dc:creator><pubDate>Mon, 11 May 2026 04:30:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/616e87a8f1f4c944cc6b49a1/bfdaf57d-f520-4f28-80a2-8691650735b5.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>🔥 Introduction</h2>
<p>What if you could query your database like this:</p>
<blockquote>
<p><em>"Show me top 10 users by revenue"</em></p>
</blockquote>
<p>…and get instant results—without writing SQL?</p>
<p>Welcome to the world of <strong>AI-powered database agents</strong>.</p>
<p>In this tutorial, you'll learn how to build a <strong>secure PostgreSQL AI agent</strong> using:</p>
<ul>
<li><p>🧩 <strong>LangChain</strong> — for agent orchestration and tool chaining</p>
</li>
<li><p>🦙 <strong>Ollama</strong> — to run a local LLM with zero API cost</p>
</li>
<li><p>🐘 <strong>PostgreSQL</strong> — as the target database</p>
</li>
<li><p>🛡️ <strong>Custom SQL safety guard</strong> — to block destructive queries</p>
</li>
</ul>
<p>By the end, you'll have a production-ready AI database assistant that understands natural language and safely executes SQL queries.</p>
<blockquote>
<p>💻 <strong>Source Code:</strong> <a href="https://github.com/icon-gaurav/postgres-agent">https://github.com/icon-gaurav/postgres-agent</a></p>
</blockquote>
<hr />
<h2>🤖 What is a PostgreSQL AI Agent?</h2>
<p>A <strong>PostgreSQL AI Agent</strong> is an LLM-powered system that:</p>
<ul>
<li><p>Converts natural language → SQL queries</p>
</li>
<li><p>Executes queries on PostgreSQL</p>
</li>
<li><p>Returns structured results</p>
</li>
</ul>
<blockquote>
<p>👉 Think of it as ChatGPT for your database, but controlled and safe.</p>
</blockquote>
<hr />
<h2>⚙️ Tech Stack</h2>
<table>
<thead>
<tr>
<th>Tool</th>
<th>Purpose</th>
</tr>
</thead>
<tbody><tr>
<td><a href="https://python.langchain.com">LangChain</a></td>
<td>AI agent orchestration, tool use</td>
</tr>
<tr>
<td><a href="https://ollama.com">Ollama</a></td>
<td>Local LLM inference, no API key required</td>
</tr>
<tr>
<td><a href="https://pypi.org/project/langchain-ollama/">langchain-ollama</a></td>
<td>LangChain ↔ Ollama integration</td>
</tr>
<tr>
<td><a href="https://pypi.org/project/psycopg2/">psycopg2</a></td>
<td>PostgreSQL database adapter for Python</td>
</tr>
<tr>
<td>Python</td>
<td>Core backend runtime</td>
</tr>
</tbody></table>
<hr />
<h2>🧱 Architecture</h2>
<img src="https://cdn.hashnode.com/uploads/covers/616e87a8f1f4c944cc6b49a1/e4f8ba23-4815-415e-8275-175b7f19216d.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h2>🔌 Step 1: PostgreSQL Connection</h2>
<p>First, configure your database connection using <code>psycopg2</code>:</p>
<pre><code class="language-python">import psycopg2
 
DB_CONFIG = {
    "host": "localhost",
    "port": 5432,
    "database": "postgres",
    "user": "postgres",
    "password": "root",
}
 
def get_connection():
    return psycopg2.connect(**DB_CONFIG)
</code></pre>
<blockquote>
<p>🔐 <strong>Security tip:</strong> In production, load credentials from environment variables or a secrets manager — never hardcode passwords.</p>
</blockquote>
<hr />
<h2>🛠️ Step 2: Create Database Tools</h2>
<p>LangChain agents interact with the world through <strong>tools</strong> — Python functions decorated with <code>@tool</code> that the LLM can invoke by name.</p>
<h3>📋 List Tables Tool</h3>
<pre><code class="language-python">@tool
def list_tables() -&gt; str:
    """List all tables in the database."""
    conn = get_connection()
    try:
        cur = conn.cursor()
        cur.execute("""
            SELECT table_name FROM information_schema.tables
            WHERE table_schema = 'public'
        """)
        tables = [row[0] for row in cur.fetchall()]
        return f"Tables: {', '.join(tables)}" if tables else "No tables found."
    finally:
        conn.close()
</code></pre>
<p>This enables <strong>dynamic schema discovery</strong> — the agent doesn't need hardcoded table names.</p>
<h3>📑 Get Table Schema</h3>
<pre><code class="language-python">@tool
@tool
def get_table_schema(table_name: str) -&gt; str:
    """Get the schema (columns and types) of a specific table."""
    conn = get_connection()
    try:
        cur = conn.cursor()
        cur.execute("""
            SELECT column_name, data_type, is_nullable
            FROM information_schema.columns
            WHERE table_schema = 'public' AND table_name = %s
            ORDER BY ordinal_position
        """, (table_name,))
        columns = cur.fetchall()
        if not columns:
            return f"Table '{table_name}' not found."
        schema = "\n".join([f"  {col[0]} ({col[1]}, nullable={col[2]})" for col in columns])
        return f"Schema for '{table_name}':\n{schema}"
    finally:
        conn.close()
</code></pre>
<p>Allows the agent to understand:</p>
<ul>
<li><p>Column names</p>
</li>
<li><p>Data types</p>
</li>
<li><p>Constraints</p>
</li>
</ul>
<h3>⚡ Execute SQL Tool</h3>
<pre><code class="language-python">@tool
def execute_sql(query: str) -&gt; str:
    """Execute a SQL query against the PostgreSQL database and return results. Use this for SELECT queries."""
    is_safe, reason = validate_read_only_sql(query)
    if not is_safe:
        return f"Safety Guard: Blocked query. {reason}"

    conn = get_connection()
    try:
        cur = conn.cursor()
        cur.execute(query)
        if cur.description:
            columns = [desc[0] for desc in cur.description]
            rows = cur.fetchall()
            if not rows:
                return "Query returned no results."
            result = " | ".join(columns) + "\n"
            result += "\n".join([" | ".join(str(v) for v in row) for row in rows[:50]])
            if len(rows) &gt; 50:
                result += f"\n... ({len(rows)} total rows)"
            return result
        else:
            conn.commit()
            return f"Query executed successfully. Rows affected: {cur.rowcount}"
    except Exception as e:
        conn.rollback()
        return f"SQL Error: {e}"
    finally:
        conn.close()
</code></pre>
<p>This is the <strong>core execution layer</strong>.</p>
<hr />
<h2>Step 3: SQL Safety Guard — Prevent Destructive Queries</h2>
<p>Allowing an LLM to run arbitrary SQL is a critical security risk. The <strong>SQL safety guard</strong> validates every query before execution.</p>
<h3>Read-Only Allowlist</h3>
<p>Only these SQL statement types are permitted:</p>
<table>
<thead>
<tr>
<th>Allowed</th>
<th>Blocked</th>
</tr>
</thead>
<tbody><tr>
<td><code>SELECT</code></td>
<td><code>INSERT</code></td>
</tr>
<tr>
<td><code>WITH</code> (CTEs)</td>
<td><code>UPDATE</code></td>
</tr>
<tr>
<td><code>SHOW</code></td>
<td><code>DELETE</code></td>
</tr>
<tr>
<td><code>EXPLAIN</code></td>
<td><code>DROP</code></td>
</tr>
<tr>
<td>—</td>
<td><code>ALTER</code></td>
</tr>
<tr>
<td>—</td>
<td><code>TRUNCATE</code></td>
</tr>
</tbody></table>
<h3>🧼 Normalize Queries</h3>
<p>We remove:</p>
<ul>
<li><p>Comments</p>
</li>
<li><p>Strings</p>
</li>
<li><p>Hidden injections</p>
</li>
</ul>
<blockquote>
<p>👉 This ensures <strong>safe AI execution</strong> in production environments.</p>
</blockquote>
<hr />
<h2>🧠 Step 4: Setup Ollama (Local LLM)</h2>
<p><a href="https://ollama.com">Ollama</a> lets you download and run large language models entirely on your own machine — no cloud account, no API key, no usage fees.</p>
<blockquote>
<p>📚 <strong>Official Resources:</strong></p>
<ul>
<li><p>🌐 Website: <a href="https://ollama.com">ollama.com</a></p>
</li>
<li><p>📖 Documentation: <a href="https://docs.ollama.com">docs.ollama.com</a></p>
</li>
<li><p>🗂️ Model Library: <a href="https://ollama.com/library">ollama.com/library</a></p>
</li>
<li><p>🐙 GitHub: <a href="https://github.com/ollama/ollama">github.com/ollama/ollama</a></p>
</li>
</ul>
</blockquote>
<hr />
<h3>🔽 Pulling a Model</h3>
<p>Once Ollama is running, pull the model used in this project:</p>
<pre><code class="language-bash">ollama pull qwen2.5:7b
</code></pre>
<p>You can verify it's available with:</p>
<pre><code class="language-bash">ollama list
</code></pre>
<p>Browse all available models at <a href="https://ollama.com/library">ollama.com/library</a>. Some good alternatives for SQL agents:</p>
<table>
<thead>
<tr>
<th>Model</th>
<th>Command</th>
<th>Notes</th>
</tr>
</thead>
<tbody><tr>
<td>Qwen 2.5 7B</td>
<td><code>ollama pull qwen2.5:7b</code></td>
<td>Used in this tutorial</td>
</tr>
<tr>
<td>Llama 3.1 8B</td>
<td><code>ollama pull llama3.1</code></td>
<td>Strong general-purpose model</td>
</tr>
<tr>
<td>DeepSeek-R1 7B</td>
<td><code>ollama pull deepseek-r1</code></td>
<td>Good reasoning ability</td>
</tr>
<tr>
<td>Mistral 7B</td>
<td><code>ollama pull mistral</code></td>
<td>Fast and lightweight</td>
</tr>
</tbody></table>
<hr />
<h3>📦 Installing the LangChain Ollama Package</h3>
<p>The LangChain integration for Ollama lives in the dedicated <a href="https://pypi.org/project/langchain-ollama/"><code>langchain-ollama</code></a> package:</p>
<pre><code class="language-bash">pip install langchain-ollama
</code></pre>
<blockquote>
<p>📖 <strong>Package References:</strong></p>
<ul>
<li><p>📦 PyPI: <a href="https://pypi.org/project/langchain-ollama/">pypi.org/project/langchain-ollama</a></p>
</li>
<li><p>🔗 LangChain Docs: <a href="https://docs.langchain.com/oss/python/integrations/chat/ollama">docs.langchain.com — ChatOllama</a></p>
</li>
<li><p>📐 API Reference: <a href="https://reference.langchain.com/python/langchain-ollama">reference.langchain.com/python/langchain-ollama</a></p>
</li>
</ul>
</blockquote>
<h3>⚙️ Configuring ChatOllama</h3>
<p>Now initialize the LLM in your Python code:</p>
<pre><code class="language-python">from langchain_ollama import ChatOllama
 
llm = ChatOllama(model="qwen2.5:7b", temperature=0)
</code></pre>
<p>Setting <code>temperature=0</code> makes the model deterministic — essential for reliable SQL generation. You can tune other key parameters as needed:</p>
<table>
<thead>
<tr>
<th>Parameter</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody><tr>
<td><code>model</code></td>
<td>required</td>
<td>Model name from <code>ollama list</code></td>
</tr>
<tr>
<td><code>temperature</code></td>
<td><code>0.8</code></td>
<td>Creativity — use <code>0</code> for SQL tasks</td>
</tr>
<tr>
<td><code>num_predict</code></td>
<td><code>128</code></td>
<td>Max tokens to generate</td>
</tr>
<tr>
<td><code>base_url</code></td>
<td><code>http://localhost:11434</code></td>
<td>Ollama server URL</td>
</tr>
</tbody></table>
<p><strong>Why Ollama?</strong></p>
<ul>
<li><p>✅ No API cost</p>
</li>
<li><p>✅ Runs fully locally — data never leaves your machine</p>
</li>
<li><p>✅ Privacy-friendly — ideal for sensitive database workloads</p>
</li>
<li><p>✅ Fast inference with GPU support</p>
</li>
<li><p>✅ Supports dozens of open-source models</p>
</li>
</ul>
<hr />
<h2>🔗 Step 5: Create LangChain Agent</h2>
<pre><code class="language-python">tools = [list_tables, get_table_schema, execute_sql]
agent = create_agent(llm, tools)
</code></pre>
<p>LangChain allows the AI to:</p>
<ul>
<li><p>Decide which tool to use</p>
</li>
<li><p>Chain multiple steps</p>
</li>
<li><p>Reason dynamically</p>
</li>
</ul>
<hr />
<h2>💬 Step 6: Interactive Chat Loop</h2>
<pre><code class="language-python">while True:
    user_input = input("\nYou: ").strip()
    if user_input.lower() in ("exit", "quit"):
        print("Goodbye!")
        break
    if not user_input:
        continue
</code></pre>
<p>This makes your agent:</p>
<ul>
<li><p>Conversational</p>
</li>
<li><p>Stateful</p>
</li>
<li><p>Easy to debug</p>
</li>
</ul>
<hr />
<h2>🧾Step 7: Debugging &amp; Observability</h2>
<p>Visibility into what the agent is doing is essential for development. This helper function prints each tool call and its result:</p>
<pre><code class="language-python">def print_turn_details(messages: list[BaseMessage]) -&gt; None:
    final_response = ""

    for message in messages:
        if isinstance(message, AIMessage):
            for tool_call in message.tool_calls:
                tool_name = tool_call.get("name", "unknown_tool")
                tool_args = format_tool_payload(tool_call.get("args", {}))
                print(f"\nTool call: {tool_name}({tool_args})")

            content = format_content(message.content).strip()
            if content:
                final_response = content

        elif isinstance(message, ToolMessage):
            tool_name = getattr(message, "name", None) or "tool"
            tool_output = format_content(message.content).strip() or "(no output)"
            print(f"\nTool response [{tool_name}]: {tool_output}")

    if final_response:
        print(f"\nAgent: {final_response}")
    else:
        print("\nAgent: I couldn't generate a response.")
</code></pre>
<p>Shows:</p>
<ul>
<li><p>Tool calls</p>
</li>
<li><p>Tool outputs</p>
</li>
<li><p>Final response</p>
</li>
</ul>
<blockquote>
<p>👉 This is extremely useful for <strong>debugging agent behavior</strong>.</p>
</blockquote>
<hr />
<h2>🧪 Example Queries</h2>
<p>Try asking:</p>
<ul>
<li><p><code>"List all tables"</code></p>
</li>
<li><p><code>"Show schema of users table"</code></p>
</li>
<li><p><code>"Get top 5 users by revenue"</code></p>
</li>
<li><p><code>"How many orders were placed last month?"</code></p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/616e87a8f1f4c944cc6b49a1/0865e8a5-269f-4bce-a094-e963a49d5aed.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h2>Real-World Use Cases for a Natural Language Database Agent</h2>
<p>This architecture can power a wide range of applications:</p>
<p><strong>📊 AI-Powered Analytics Dashboards</strong> — let non-technical stakeholders query live data in plain English without learning SQL.</p>
<p><strong>💬 Internal Data Chatbots</strong> — embed in Slack or Microsoft Teams so product and ops teams can self-serve data questions.</p>
<p><strong>🧾 Automated Reporting</strong> — schedule the agent to answer recurring questions and generate daily or weekly reports.</p>
<p><strong>🏢 SaaS Admin Panels</strong> — give your ops team a natural language interface to your product database.</p>
<p><strong>🤖 AI Copilots for Data Teams</strong> — speed up analyst workflows by auto-generating SQL drafts from plain-English specs.</p>
<hr />
<h2>🎯 Conclusion</h2>
<p>You've built more than just a demo. This is a <strong>secure, extensible AI database agent</strong> that can be used in real-world applications.</p>
<p><strong>Key Takeaways:</strong></p>
<ul>
<li><p>LangChain simplifies agent workflows</p>
</li>
<li><p>Ollama enables local LLM execution</p>
</li>
<li><p>SQL safety is critical</p>
</li>
<li><p>Tool-based architecture = powerful AI agents</p>
</li>
</ul>
<p>The core insight: wrapping your database in typed, well-described LangChain tools gives the LLM exactly the context it needs to generate correct SQL — without ever exposing raw database access.</p>
<blockquote>
<p><a href="https://github.com/icon-gaurav/postgres-agent">📦 Full Source Code</a> — complete working implementation from this tutorial</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[The Day I Stopped Building Alone: OpenClaw as My Virtual Team]]></title><description><![CDATA[A candid look at using AI agents to build a SaaS product — what works, what doesn't, and why I'm never going back to building solo.

The Problem With Building Alone
Before I talk about OpenClaw, let m]]></description><link>https://gauravbytes.dev/the-day-i-stopped-building-alone-openclaw-as-my-virtual-team</link><guid isPermaLink="true">https://gauravbytes.dev/the-day-i-stopped-building-alone-openclaw-as-my-virtual-team</guid><category><![CDATA[openclaw]]></category><category><![CDATA[ai agents]]></category><category><![CDATA[Productivity]]></category><category><![CDATA[AI]]></category><category><![CDATA[#ai-tools]]></category><category><![CDATA[Orchestration]]></category><category><![CDATA[solopreneur ]]></category><category><![CDATA[Build In Public]]></category><category><![CDATA[SaaS]]></category><dc:creator><![CDATA[Gaurav Kumar]]></dc:creator><pubDate>Mon, 09 Mar 2026 03:30:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/616e87a8f1f4c944cc6b49a1/f151d1ab-a51b-4c1a-8041-87113353e973.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>A candid look at using AI agents to build a SaaS product — what works, what doesn't, and why I'm never going back to building solo.</em></p>
<hr />
<h2><strong>The Problem With Building Alone</strong></h2>
<p>Before I talk about <a href="https://openclaw.ai/">OpenClaw</a>, let me paint the picture.</p>
<p>When you're building a SaaS product by yourself, you're not just a developer. You're the researcher, the architect, the QA engineer, the SEO specialist, and — if you're lucky — the person who actually writes code. Every decision defaults to you. Every skill gap becomes a blocker.</p>
<p>Want to add authentication? Better research OAuth providers, understand security best practices, and implement it right. Need to figure out email deliverability? Time to go down a 3-hour rabbit hole. Building alone means you're constantly context-switching between "figuring things out" and "actually building."</p>
<p>I know because I've been there. Building <a href="https://www.shiftmailer.com/">ShiftMailer</a> — my product for AI-powered email marketing — meant I had to wear all these hats. And honestly? It was exhausting.</p>
<h2><strong>Then Came OpenClaw</strong></h2>
<p>OpenClaw isn't just another AI chatbot. It's an AI agent framework that can actually <em>do</em> things — read files, run commands, search the web, analyze code, and coordinate with other tools. It's like having a team member who doesn't sleep, doesn't complain, and can spin up new skills on demand.</p>
<p>Here's what I learned after using it for ShiftMailer development.</p>
<img src="https://cdn.hashnode.com/uploads/covers/616e87a8f1f4c944cc6b49a1/cb6dbe8b-efc4-4904-a2d8-c1757b40925c.png" alt="" style="display:block;margin:0 auto" />

<h2><strong>What Actually Works</strong></h2>
<h3><strong>1. Research That Goes Beyond Google</strong></h3>
<p>I used to spend hours researching trends, comparing tools, and validating ideas. Now I can ask OpenClaw to:</p>
<ul>
<li><p>Find current trends in email marketing automation</p>
</li>
<li><p>Compare pricing models of competitors</p>
</li>
<li><p>Research technical decisions (like multi-tenancy approaches)</p>
</li>
</ul>
<p>It doesn't just give me links — it synthesizes information and gives me actionable insights. This alone saved me days of scattered research.</p>
<h3><strong>2. Code That I Still Own</strong></h3>
<p>Here's something important: I <em>know</em> how to code. I'm a backend engineer. But that doesn't mean I want to write every boilerplate or debug every edge case alone.</p>
<p>OpenClaw helps me:</p>
<ul>
<li><p><strong>Write faster</strong> — It handles the repetitive stuff so I focus on the interesting parts</p>
</li>
<li><p><strong>Review my code</strong> — Fresh eyes catch bugs I missed</p>
</li>
<li><p><strong>Explore new patterns</strong> — I can ask "how would you approach this with Node.js streams?" and get working examples</p>
</li>
</ul>
<p>It's not replacing my skills. It's amplifying them.</p>
<h3><strong>3. Real-World Analysis</strong></h3>
<p>One surprise: OpenClaw analyzed my website's SEO and gave me specific suggestions. Not generic advice — actual, implementable recommendations based on my content. That's not something I expected an AI agent to do well, but it handled it.</p>
<h2><strong>The Honest Take: What Doesn't Work</strong></h2>
<p>I promised you an honest review, so here it is:</p>
<h3><strong>Orchestration is Hard</strong></h3>
<p>OpenClaw is great at analyzing requirements and using individual tools well. But when it comes to coordinating complex multi-step workflows? Sometimes it struggles. Things that would be trivial for a human — "okay, first do A, then B, but only if C worked" — can get messy.</p>
<p>This means I'm still the conductor. The agent executes well, but I'm the one keeping the orchestra in sync.</p>
<h3><strong>Security: You Gotta Be Careful</strong></h3>
<p>Here's the thing: OpenClaw has access to my files, my environment, my code. That's powerful, but it's also a responsibility.</p>
<p>For now, I'm careful about:</p>
<ul>
<li><p>Not giving agents unrestricted external access</p>
</li>
<li><p>Reviewing code before shipping</p>
</li>
<li><p>Keeping sensitive configs isolated</p>
</li>
</ul>
<p>This isn't a criticism of OpenClaw — it's just smart practice. When you're delegating to an AI, trust but verify.</p>
<h2><strong>The Comparison: Traditional Cofounder vs. AI Agent</strong></h2>
<p>People often ask: "Isn't this like having a cofounder?"</p>
<p>Not really. Here's the difference:</p>
<table>
<thead>
<tr>
<th><strong>Traditional Cofounder</strong></th>
<th><strong>AI Agent (OpenClaw)</strong></th>
</tr>
</thead>
<tbody><tr>
<td>Has fixed skills</td>
<td>Can spin up new skills on demand</td>
</tr>
<tr>
<td>Needs alignment, meetings, context</td>
<td>Instant context, no hand-holding</td>
</tr>
<tr>
<td>Sleeps, has bad days, costs equity</td>
<td>Always available, improves over time</td>
</tr>
<tr>
<td>Human judgment on tough calls</td>
<td>Follows instructions, but needs oversight</td>
</tr>
</tbody></table>
<p>With a traditional cofounder, I'd need to research every aspect myself, find different people for different skills, and coordinate schedules. With OpenClaw, I can say "I need research on X" and get it done — then switch to "help me debug this API" without friction.</p>
<h2><strong>What This Means for Solo Builders</strong></h2>
<p>If you're building alone, here's what I'd tell you:</p>
<p><strong>AI agents aren't magic.</strong> They're tools. And like any tool, they have limits. But if you learn to work with them — to prompt well, to review outputs, to stay in the loop — you can do way more than you could alone.</p>
<p><a href="https://www.shiftmailer.com/">ShiftMailer</a> exists today because I stopped trying to do everything myself. I found a way to leverage AI that works <em>with</em> my skills, not instead of them.</p>
<h2><strong>What's Next</strong></h2>
<p>I'm still figuring out the orchestration piece. I'm still being careful about security. But the gap between "idea" and "working product" has shrunk dramatically.</p>
<p>If you're a solo builder on the fence about AI agents — try it. Start small. See what works for your workflow. You might be surprised what you can ship when you're not alone in the room.</p>
<hr />
<p><em>Have questions about building with AI agents or want to share your story? Let's connect — find me on</em> <a href="https://x.com/gauravk_tweet">X</a> <em>or check out</em> <a href="https://gauravbytes.hashnode.dev"><em>gauravbytes.hashnode.dev</em></a><em>.</em></p>
]]></content:encoded></item><item><title><![CDATA[Building Sequential AI Agents with Vercel AI SDK (Multi-Step LLM Workflows)]]></title><description><![CDATA[Most AI agents today are just a single prompt and a single response.
That approach works—until you need structure, reliability, or production-grade workflows.
In this post, we’ll explore sequential AI agents, how they differ from normal AI agents, an...]]></description><link>https://gauravbytes.dev/building-sequential-ai-agents-with-vercel-ai-sdk-multi-step-llm-workflows</link><guid isPermaLink="true">https://gauravbytes.dev/building-sequential-ai-agents-with-vercel-ai-sdk-multi-step-llm-workflows</guid><category><![CDATA[AI]]></category><category><![CDATA[llm]]></category><category><![CDATA[Vercel]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[#ai-tools]]></category><category><![CDATA[agentic AI]]></category><category><![CDATA[software development]]></category><dc:creator><![CDATA[Gaurav Kumar]]></dc:creator><pubDate>Mon, 19 Jan 2026 09:16:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1768766741571/0aca3271-3cef-49b8-8884-8831f0f36fdf.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Most AI agents today are just a single prompt and a single response.</p>
<p>That approach works—until you need structure, reliability, or production-grade workflows.</p>
<p>In this post, we’ll explore <strong>sequential AI agents</strong>, how they differ from normal AI agents, and how you can build a multi-step AI workflow using the <strong>Vercel AI SDK</strong>.</p>
<h2 id="heading-what-is-an-ai-agent">What Is an AI Agent?</h2>
<p>An AI agent is a system that:</p>
<ol>
<li><p>Takes an input (user query, event, or data)</p>
</li>
<li><p>Uses an LLM to reason or generate output</p>
</li>
<li><p>Optionally calls tools, APIs, or functions</p>
</li>
<li><p>Returns a result or performs an action</p>
</li>
</ol>
<p>In many applications, this entire process happens <strong>in one step</strong>.</p>
<p>Example:</p>
<blockquote>
<p>User asks: <em>“Write a product update email”</em><br />→ LLM generates the email in a single response</p>
</blockquote>
<p>This works well for simple tasks—but it starts breaking down as complexity grows.</p>
<h2 id="heading-normal-ai-agent-single-step-agent">Normal AI Agent (Single-Step Agent)</h2>
<p>A <strong>normal AI agent</strong> typically follows this flow:</p>
<pre><code class="lang-typescript">Input → LLM → Output
</code></pre>
<h3 id="heading-characteristics">Characteristics</h3>
<ul>
<li><p>Single prompt</p>
</li>
<li><p>Single LLM call</p>
</li>
<li><p>Minimal or no intermediate state</p>
</li>
<li><p>Fast and cheap</p>
</li>
</ul>
<h3 id="heading-example-use-cases">Example Use Cases</h3>
<ul>
<li><p>Chatbots</p>
</li>
<li><p>Text rewriting</p>
</li>
<li><p>Summarization</p>
</li>
<li><p>Simple content generation</p>
</li>
</ul>
<h3 id="heading-limitations">Limitations</h3>
<ul>
<li><p>Hard to enforce structure</p>
</li>
<li><p>No explicit reasoning steps</p>
</li>
<li><p>Poor control over multi-stage workflows</p>
</li>
<li><p>Difficult to debug or extend</p>
</li>
</ul>
<p>When tasks require <strong>planning, validation, transformation, or multiple roles</strong>, a single-step agent becomes fragile.</p>
<h2 id="heading-what-is-a-sequential-ai-agent">What Is a Sequential AI Agent?</h2>
<p>A <strong>sequential AI agent</strong> breaks a task into <strong>multiple ordered steps</strong>, where:</p>
<ul>
<li><p>Each step has a clear responsibility</p>
</li>
<li><p>Output of one step becomes input for the next</p>
</li>
<li><p>Context accumulates across steps</p>
</li>
</ul>
<pre><code class="lang-typescript">Input
  ↓
Step <span class="hljs-number">1</span> (Planner Agent)
  ↓
Step <span class="hljs-number">2</span> (Executor Agent)
  ↓
Step <span class="hljs-number">3</span> (Refiner / Validator Agent)
  ↓
Final Output
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768765239209/5c84630e-268e-4da6-b924-0c1da9df9743.png" alt="Source : https://www.cybage.com/blog/building-intelligent-ai-systems-understanding-agentic-ai-and-design-patterns" class="image--center mx-auto" /></p>
<p>Instead of asking the model to do everything at once, we <strong>guide it through a pipeline</strong>.</p>
<h2 id="heading-normal-agent-vs-sequential-agent">Normal Agent vs Sequential Agent</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Aspect</td><td>Normal Agent</td><td>Sequential Agent</td></tr>
</thead>
<tbody>
<tr>
<td>LLM Calls</td><td>One</td><td>Multiple</td></tr>
<tr>
<td>Structure</td><td>Implicit</td><td>Explicit</td></tr>
<tr>
<td>Control</td><td>Low</td><td>High</td></tr>
<tr>
<td>Debuggability</td><td>Hard</td><td>Easy</td></tr>
<tr>
<td>Cost</td><td>Lower</td><td>Higher</td></tr>
<tr>
<td>Scalability</td><td>Limited</td><td>High</td></tr>
</tbody>
</table>
</div><p>Sequential agents trade <strong>simplicity</strong> for <strong>control and reliability</strong>.</p>
<h2 id="heading-when-are-sequential-ai-agents-beneficial">When Are Sequential AI Agents Beneficial?</h2>
<p>Sequential agents are ideal when:</p>
<h3 id="heading-1-tasks-have-clear-phases">1. Tasks Have Clear Phases</h3>
<p>Example:</p>
<ul>
<li><p>Planning</p>
</li>
<li><p>Writing</p>
</li>
<li><p>Reviewing</p>
</li>
<li><p>Formatting</p>
</li>
</ul>
<h3 id="heading-2-output-must-follow-strict-structure">2. Output Must Follow Strict Structure</h3>
<ul>
<li><p>Emails</p>
</li>
<li><p>Reports</p>
</li>
<li><p>JSON schemas</p>
</li>
<li><p>Code generation</p>
</li>
</ul>
<h3 id="heading-3-different-roles-are-needed">3. Different “Roles” Are Needed</h3>
<ul>
<li><p>Product marketer</p>
</li>
<li><p>Engineer</p>
</li>
<li><p>Editor</p>
</li>
</ul>
<h3 id="heading-4-you-want-deterministic-pipelines">4. You Want Deterministic Pipelines</h3>
<ul>
<li><p>SaaS features</p>
</li>
<li><p>Automations</p>
</li>
<li><p>Multi-tenant systems</p>
</li>
</ul>
<p>This is why sequential agents work extremely well for:</p>
<ul>
<li><p>Product update emails</p>
</li>
<li><p>CRM workflows</p>
</li>
<li><p>Content pipelines</p>
</li>
<li><p>Data extraction and transformation</p>
</li>
</ul>
<h2 id="heading-designing-a-sequential-agent-conceptually">Designing a Sequential Agent (Conceptually)</h2>
<p>Let’s say we want to build a <strong>content generation agent</strong>.</p>
<h3 id="heading-step-1-planner-agent">Step 1: Planner Agent</h3>
<p>Responsibility:</p>
<ul>
<li><p>Analyze the input</p>
</li>
<li><p>Break it into structured sections</p>
</li>
</ul>
<p>Output:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"sections"</span>: [<span class="hljs-string">"Introduction"</span>, <span class="hljs-string">"Key Points"</span>, <span class="hljs-string">"Conclusion"</span>]
}
</code></pre>
<h3 id="heading-step-2-writer-agent">Step 2: Writer Agent</h3>
<p>Responsibility:</p>
<ul>
<li>Generate content for each section</li>
</ul>
<p>Input:</p>
<ul>
<li><p>Original user input</p>
</li>
<li><p>Planner output</p>
</li>
</ul>
<h3 id="heading-step-3-refiner-agent">Step 3: Refiner Agent</h3>
<p>Responsibility:</p>
<ul>
<li><p>Improve tone</p>
</li>
<li><p>Fix grammar</p>
</li>
<li><p>Enforce constraints</p>
</li>
</ul>
<p>Each step is <strong>predictable and replaceable</strong>.</p>
<h2 id="heading-implementing-a-sequential-ai-agent-with-vercel-ai-sdk">Implementing a Sequential AI Agent with Vercel AI SDK</h2>
<h3 id="heading-step-1-create-the-planner-agent">Step 1: Create the Planner Agent</h3>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { generateText } <span class="hljs-keyword">from</span> <span class="hljs-string">"ai"</span>;
<span class="hljs-keyword">import</span> { openai } <span class="hljs-keyword">from</span> <span class="hljs-string">"@ai-sdk/openai"</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">plannerAgent</span>(<span class="hljs-params">input: <span class="hljs-built_in">string</span></span>) </span>{
  <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> generateText({
    model: openai(<span class="hljs-string">"gpt-4.1"</span>),
    prompt: <span class="hljs-string">`Analyze the input and create a structured plan.\n\nInput: <span class="hljs-subst">${input}</span>`</span>,
  });

  <span class="hljs-keyword">return</span> result.text;
}
</code></pre>
<h3 id="heading-step-2-create-the-writer-agent">Step 2: Create the Writer Agent</h3>
<pre><code class="lang-ts"><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">writerAgent</span>(<span class="hljs-params">plan: <span class="hljs-built_in">string</span>, input: <span class="hljs-built_in">string</span></span>) </span>{
  <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> generateText({
    model: openai(<span class="hljs-string">"gpt-4.1"</span>),
    prompt: <span class="hljs-string">`Using the following plan, write detailed content.\n\nPlan:\n<span class="hljs-subst">${plan}</span>\n\nInput:\n<span class="hljs-subst">${input}</span>`</span>,
  });

  <span class="hljs-keyword">return</span> result.text;
}
</code></pre>
<h3 id="heading-step-3-create-the-refiner-agent">Step 3: Create the Refiner Agent</h3>
<pre><code class="lang-ts"><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">refinerAgent</span>(<span class="hljs-params">content: <span class="hljs-built_in">string</span></span>) </span>{
  <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> generateText({
    model: openai(<span class="hljs-string">"gpt-4.1"</span>),
    prompt: <span class="hljs-string">`Refine the following content for clarity and tone.\n\n<span class="hljs-subst">${content}</span>`</span>,
  });

  <span class="hljs-keyword">return</span> result.text;
}
</code></pre>
<h3 id="heading-step-4-orchestrate-the-sequential-flow">Step 4: Orchestrate the Sequential Flow</h3>
<pre><code class="lang-ts"><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">sequentialAgent</span>(<span class="hljs-params">input: <span class="hljs-built_in">string</span></span>) </span>{
  <span class="hljs-keyword">const</span> plan = <span class="hljs-keyword">await</span> plannerAgent(input);
  <span class="hljs-keyword">const</span> draft = <span class="hljs-keyword">await</span> writerAgent(plan, input);
  <span class="hljs-keyword">const</span> finalOutput = <span class="hljs-keyword">await</span> refinerAgent(draft);

  <span class="hljs-keyword">return</span> finalOutput;
}
</code></pre>
<p>This orchestration is the <strong>heart of a sequential agent</strong>.</p>
<h2 id="heading-benefits-of-this-approach">Benefits of This Approach</h2>
<ul>
<li><p>Clear separation of responsibilities</p>
</li>
<li><p>Easier debugging (inspect each step)</p>
</li>
<li><p>Reusable agents</p>
</li>
<li><p>Better output consistency</p>
</li>
<li><p>Safer production usage</p>
</li>
</ul>
<p>This is especially useful when building <strong>AI-powered SaaS features</strong>, not demos.</p>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>Sequential AI agents represent a shift from <em>“ask the model to do everything”</em> to <em>“designing AI workflows.”</em></p>
<p>With the <a target="_blank" href="https://vercel.com/docs/ai-sdk">Vercel AI SDK</a>, building these workflows feels natural and maintainable.</p>
<p>If you’re building:</p>
<ul>
<li><p>AI-first products</p>
</li>
<li><p>Content pipelines</p>
</li>
<li><p>Internal tooling</p>
</li>
</ul>
<p>…sequential agents will give you <strong>control, clarity, and confidence</strong>.</p>
<p>If you’re interested, the next step could be:</p>
<ul>
<li><p>Adding validation agents</p>
</li>
<li><p>Parallel agents</p>
</li>
<li><p>Streaming intermediate steps</p>
</li>
<li><p>Persisting agent state</p>
</li>
</ul>
<p>That’s where AI engineering starts to feel like real software engineering 🚀</p>
]]></content:encoded></item><item><title><![CDATA[🛍️ Building an AI-Powered E-commerce Chatbot Using Vercel AI SDK and Gemini]]></title><description><![CDATA[E-commerce is rapidly transforming — and AI-powered shopping assistants are becoming the new default buying experience.Think about it:

Instead of browsing 20 product pages, users simply ask:  “Show me red shoes under ₹1500.”

Instead of navigating a...]]></description><link>https://gauravbytes.dev/building-an-ai-powered-e-commerce-chatbot-using-vercel-ai-sdk-and-gemini</link><guid isPermaLink="true">https://gauravbytes.dev/building-an-ai-powered-e-commerce-chatbot-using-vercel-ai-sdk-and-gemini</guid><category><![CDATA[AI]]></category><category><![CDATA[ai agents]]></category><category><![CDATA[gemini]]></category><category><![CDATA[llm]]></category><category><![CDATA[ecommerce]]></category><category><![CDATA[chatbot]]></category><category><![CDATA[Vercel]]></category><category><![CDATA[Next.js]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Node.js]]></category><dc:creator><![CDATA[Gaurav Kumar]]></dc:creator><pubDate>Fri, 28 Nov 2025 18:18:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1764186329330/a6c58ccb-8946-4e3d-a530-e28c43084d3c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>E-commerce is rapidly transforming — and AI-powered shopping assistants are becoming the new default buying experience.<br />Think about it:</p>
<ul>
<li><p>Instead of browsing <strong>20 product pages</strong>, users simply ask:<br />  <em>“Show me red shoes under ₹1500.”</em></p>
</li>
<li><p>Instead of navigating a 5-step checkout, they say:<br />  <em>“Add the second one to my cart and checkout.”</em></p>
</li>
</ul>
<p>To explore this future, I built a <strong>fully functional AI E-Commerce Chatbot</strong> using <strong>Vercel AI SDK + Gemini</strong>, equipped with tools like:</p>
<ul>
<li><p><strong>Fetch Catalog</strong></p>
</li>
<li><p><strong>Add to Cart</strong></p>
</li>
<li><p><strong>Checkout Cart</strong></p>
</li>
</ul>
<p>And a <strong>UI that responds with card-style product previews</strong>, add-to-cart confirmation messages, and interactive checkout prompts.</p>
<p>This blog is a <strong>complete to-do guide</strong> to help developers build the same chatbot from scratch.</p>
<p>Let’s start building it</p>
<h2 id="heading-define-tools-for-gemini">🧩 Define Tools for Gemini</h2>
<p>Gemini supports <strong>function calling</strong>, so we define our tools. We will create functions that accept some parameters and return a result based on them. Here, we are using an API call to achieve this, but you can implement any business logic in these functions.</p>
<p><strong>Catalog tool</strong></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">fetchCatalog</span>(<span class="hljs-params">{query, maxPrice}: { query: <span class="hljs-built_in">string</span>; maxPrice: <span class="hljs-built_in">number</span> }</span>) </span>{
    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> fetch(
        <span class="hljs-string">`<span class="hljs-subst">${process.env.NEXT_PUBLIC_BASE_URL}</span>/api/catalog?query=<span class="hljs-subst">${query}</span>&amp;maxPrice=<span class="hljs-subst">${maxPrice}</span>`</span>
    );
    <span class="hljs-keyword">if</span> (!res.ok) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Failed to fetch catalog"</span>);
    <span class="hljs-keyword">return</span> res.json();
}
</code></pre>
<p><strong>Cart tool</strong></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">addToCart</span>(<span class="hljs-params">productId: <span class="hljs-built_in">string</span>, quantity: <span class="hljs-built_in">number</span></span>) </span>{
    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">`<span class="hljs-subst">${process.env.NEXT_PUBLIC_BASE_URL}</span>/api/cart`</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({product_id: productId, qty: quantity}),
    });
    <span class="hljs-keyword">if</span> (!res.ok) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Failed to add to cart"</span>);
    <span class="hljs-keyword">return</span> res.json();
}
</code></pre>
<p><strong>Checkout Tool</strong></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">checkoutCart</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">`<span class="hljs-subst">${process.env.NEXT_PUBLIC_BASE_URL}</span>/api/checkout`</span>, {
        method: <span class="hljs-string">"POST"</span>,
    });
    <span class="hljs-keyword">if</span> (!res.ok) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Failed to checkout"</span>);
    <span class="hljs-keyword">return</span> res.json();
}
</code></pre>
<h1 id="heading-create-the-tool-enabled-chat-api"><strong>🛠️ Create the Tool-Enabled Chat API</strong></h1>
<p>This is the heart of the chatbot.</p>
<h3 id="heading-define-the-prompt-for-our-ai-agent">Define the prompt for our AI agent</h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> SYSTEM_PROMPT = <span class="hljs-string">`
You are a shopping assistant AI integrated with tools.

You can help the user browse products, add them to a cart, and checkout.

### Available tools:
1. **fetchCatalog()**
   - Retrieves all products from the catalog.
   - Always call this tool to see what products are available before suggesting items.

2. **addToCart(productId: string)**
   - Adds the specified product to the user's cart.
   - Call this when the user asks to "add", "buy", or "I want this".

3. **checkoutCart()**
   - Processes the order and simulates checkout.
   - Only call this if the user explicitly says "checkout", "place order", or "buy now".

---

### Rules of behavior:
- Never invent products. Only use the results returned by 'fetchCatalog'.
- Always confirm product details (name, price, stock) when suggesting items.
- If the user is vague (e.g., "show me something cool"), fetch the catalog and then suggest a few options.
- Be conversational and friendly, but clearly indicate actions you’re taking.
- After a successful checkout, thank the user and end the flow.

---

### Examples:

**User:** "Show me headphones under $150"  
**Assistant reasoning:** Call 'fetchCatalog', filter results by category + price, then present matching products.  

**User:** "Yes, add the headphones to my cart"  
**Assistant reasoning:** Call 'addToCart(productId)' for that item then show the current cart.  

**User:** "Checkout now"  
**Assistant reasoning:** Call 'checkoutCart' and return the order confirmation.
`</span>
</code></pre>
<h3 id="heading-api-endpoint-to-use-our-chatbot"><strong>API endpoint to use our chatbot</strong></h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> {convertToModelMessages, streamText, UIMessage} <span class="hljs-keyword">from</span> <span class="hljs-string">"ai"</span>;
<span class="hljs-keyword">import</span> {addToCart, checkoutCart, fetchCatalog} <span class="hljs-keyword">from</span> <span class="hljs-string">"@/lib/tools"</span>;
<span class="hljs-keyword">import</span> {google} <span class="hljs-keyword">from</span> <span class="hljs-string">"@ai-sdk/google"</span>;
<span class="hljs-keyword">import</span> {NextRequest, NextResponse} <span class="hljs-keyword">from</span> <span class="hljs-string">"next/server"</span>;
<span class="hljs-keyword">import</span> {z} <span class="hljs-keyword">from</span> <span class="hljs-string">'zod'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> runtime = <span class="hljs-string">"edge"</span>;
<span class="hljs-keyword">const</span> gemini = google(<span class="hljs-string">"models/gemini-2.5-flash-lite"</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: NextRequest</span>) </span>{
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> {messages}: { messages: UIMessage[] } = <span class="hljs-keyword">await</span> req.json()

        <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> streamText({
            model: gemini,
            system: SYSTEM_PROMPT,
            messages: convertToModelMessages(messages),
            tools: {
                fetchCatalog: {
                    description: <span class="hljs-string">"Retrieves all products from the catalog."</span>,
                    inputSchema: z.object({
                        query: z.string().optional().default(<span class="hljs-string">""</span>),
                        maxPrice: z.number().optional().default(<span class="hljs-number">1000</span>)
                    }),
                    execute: <span class="hljs-keyword">async</span> ({query, maxPrice}) =&gt; {
                        <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> fetchCatalog({query, maxPrice});
                    }
                },
                addToCart: {
                    description: <span class="hljs-string">"Adds the specified product to the user's cart."</span>,
                    inputSchema: z.object({
                        productId: z.string(),
                        quantity: z.number().optional().default(<span class="hljs-number">1</span>)
                    }),
                    execute: <span class="hljs-keyword">async</span> ({productId, quantity}) =&gt; {
                        <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> addToCart(productId, quantity);
                    }
                },
                checkoutCart: {
                    description: <span class="hljs-string">"Process checkout and initiate payment"</span>,
                    inputSchema: z.object({}),
                    execute: <span class="hljs-keyword">async</span> () =&gt; {
                        <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> checkoutCart();
                    }
                }
            }
        });


        <span class="hljs-keyword">return</span> result.toUIMessageStreamResponse({
            onError: errorHandler
        });
    } <span class="hljs-keyword">catch</span> (err) {
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error : "</span>, err);
        <span class="hljs-keyword">return</span> NextResponse.json({error: <span class="hljs-string">"Internal server error"</span>}, {status: <span class="hljs-number">500</span>});
    }
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">errorHandler</span>(<span class="hljs-params">error: unknown</span>) </span>{
    <span class="hljs-keyword">if</span> (error == <span class="hljs-literal">null</span>) {
        <span class="hljs-keyword">return</span> <span class="hljs-string">'unknown error'</span>;
    }

    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> error === <span class="hljs-string">'string'</span>) {
        <span class="hljs-keyword">return</span> error;
    }

    <span class="hljs-keyword">if</span> (error <span class="hljs-keyword">instanceof</span> <span class="hljs-built_in">Error</span>) {
        <span class="hljs-keyword">return</span> error.message;
    }

    <span class="hljs-keyword">return</span> <span class="hljs-built_in">JSON</span>.stringify(error);
}
</code></pre>
<h1 id="heading-build-ui-with-card-components"><strong>🎨 Build UI With Card Components</strong></h1>
<p>When the LLM returns tool results, your UI displays them as <strong>cards</strong> so we will implement these components in react</p>
<h3 id="heading-product-card"><strong>🟥 Product Card</strong></h3>
<pre><code class="lang-typescript"><span class="hljs-comment">// components/ProductCard.tsx</span>
<span class="hljs-string">"use client"</span>;

<span class="hljs-keyword">import</span> {useState} <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</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">ProductCard</span>(<span class="hljs-params">{product, addToCart}: { product: <span class="hljs-built_in">any</span>, addToCart: <span class="hljs-built_in">any</span> }</span>) </span>{
    <span class="hljs-keyword">const</span> [adding, setAdding] = useState(<span class="hljs-literal">false</span>);
    <span class="hljs-keyword">const</span> [added, setAdded] = useState(<span class="hljs-literal">false</span>);

    <span class="hljs-keyword">const</span> handleAddToCart = <span class="hljs-keyword">async</span> () =&gt; {
        setAdding(<span class="hljs-literal">true</span>);
        addToCart(product?.id, product?.name)
        setAdded(<span class="hljs-literal">true</span>)
        setAdding(<span class="hljs-literal">false</span>);
    };

    <span class="hljs-keyword">return</span> (
        &lt;div className=<span class="hljs-string">"border rounded-xl p-4 shadow-md flex flex-col items-center gap-2 bg-white"</span>&gt;
            &lt;img
                src={product.image}
                alt={product.name}
                className=<span class="hljs-string">"w-32 h-32 object-cover rounded-lg"</span>
            /&gt;
            &lt;h3 className=<span class="hljs-string">"text-lg font-semibold"</span>&gt;{product.name}&lt;/h3&gt;
            &lt;p className=<span class="hljs-string">"text-gray-600"</span>&gt;₹{product?.price}&lt;/p&gt;
            &lt;button
                onClick={handleAddToCart}
                disabled={adding || added}
                className=<span class="hljs-string">"bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 disabled:bg-gray-400"</span>
            &gt;
                {adding ? <span class="hljs-string">"Adding..."</span> : added ? <span class="hljs-string">"Added"</span> : <span class="hljs-string">"Add to Cart"</span>}
            &lt;/button&gt;
        &lt;/div&gt;
    );
}
</code></pre>
<h3 id="heading-cart-card"><strong>🟦 Cart Card</strong></h3>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>;
<span class="hljs-keyword">import</span> {CartItem} <span class="hljs-keyword">from</span> <span class="hljs-string">"@/lib/cartStore"</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">Cart</span>(<span class="hljs-params">{items = []}: { items?: <span class="hljs-built_in">any</span> }</span>) </span>{
    <span class="hljs-keyword">const</span> total = items?.reduce(<span class="hljs-function">(<span class="hljs-params">sum:<span class="hljs-built_in">number</span>, i:CartItem</span>) =&gt;</span> sum + i.price * i.qty, <span class="hljs-number">0</span>);
    <span class="hljs-keyword">return</span> (
        &lt;div className=<span class="hljs-string">"p-4 bg-white rounded-xl shadow-md w-[320px]"</span>&gt;
            &lt;h2 className=<span class="hljs-string">"text-lg font-bold mb-3"</span>&gt;🛒 Your Cart&lt;/h2&gt;

            {items.length === <span class="hljs-number">0</span> &amp;&amp; &lt;p className=<span class="hljs-string">"text-gray-500"</span>&gt;Cart is empty&lt;/p&gt;}

            &lt;ul className=<span class="hljs-string">"space-y-3"</span>&gt;
                {items.map(<span class="hljs-function">(<span class="hljs-params">item:CartItem</span>) =&gt;</span> (
                    &lt;li
                        key={item.productId}
                        className=<span class="hljs-string">"flex items-center justify-between gap-2 border-b pb-2"</span>
                    &gt;
                        &lt;div className=<span class="hljs-string">"flex items-center gap-2"</span>&gt;
                            {item.image &amp;&amp; (
                                &lt;img
                                    src={item.image}
                                    alt={item.name}
                                    className=<span class="hljs-string">"w-10 h-10 rounded"</span>
                                /&gt;
                            )}
                            &lt;div&gt;
                                &lt;p className=<span class="hljs-string">"font-medium"</span>&gt;{item.name}&lt;/p&gt;
                                &lt;p className=<span class="hljs-string">"text-sm text-gray-500"</span>&gt;
                                    ₹{item?.price} × {item.qty} =  ${item?.price * item.qty}
                                &lt;/p&gt;
                            &lt;/div&gt;

                        &lt;/div&gt;

                    &lt;/li&gt;
                ))}
            &lt;/ul&gt;

            {items.length &gt; <span class="hljs-number">0</span> &amp;&amp; (
                &lt;div className=<span class="hljs-string">"pt-3 flex justify-between font-bold"</span>&gt;
                    &lt;span&gt;Total -&amp;nbsp;&lt;/span&gt;
                    &lt;span&gt;₹{total ? total.toFixed(<span class="hljs-number">2</span>) : <span class="hljs-string">"0.00"</span>}&lt;/span&gt;
                &lt;/div&gt;
            )}
        &lt;/div&gt;
    );
}
</code></pre>
<h3 id="heading-checkout-card"><strong>🟩 Checkout Card</strong></h3>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>;
<span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> {OrderItem} <span class="hljs-keyword">from</span> <span class="hljs-string">"@/lib/orderStore"</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">Checkout</span>(<span class="hljs-params">{order}: { order: OrderItem | <span class="hljs-literal">null</span> }</span>) </span>{
    <span class="hljs-keyword">const</span> [step, setStep] = useState&lt;<span class="hljs-string">"details"</span> | <span class="hljs-string">"payment"</span> | <span class="hljs-string">"success"</span>&gt;(<span class="hljs-string">"details"</span>);
    <span class="hljs-comment">// mock "processing payment"</span>
    <span class="hljs-keyword">const</span> handlePayment = <span class="hljs-function">() =&gt;</span> {
        setStep(<span class="hljs-string">"payment"</span>);
        <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> setStep(<span class="hljs-string">"success"</span>), <span class="hljs-number">2000</span>);
    };

    <span class="hljs-keyword">return</span> (
        &lt;div className=<span class="hljs-string">"p-6 max-w-md mx-auto bg-white rounded-xl shadow-lg"</span>&gt;
            &lt;h2 className=<span class="hljs-string">"text-xl font-bold mb-4"</span>&gt;Checkout&lt;/h2&gt;

            {step === <span class="hljs-string">"details"</span> &amp;&amp; (
                &lt;div&gt;
                    &lt;h3 className=<span class="hljs-string">"font-semibold mb-2"</span>&gt;Shipping Info&lt;/h3&gt;
                    &lt;form className=<span class="hljs-string">"space-y-3"</span>&gt;
                        &lt;input
                            <span class="hljs-keyword">type</span>=<span class="hljs-string">"text"</span>
                            placeholder=<span class="hljs-string">"Full Name"</span>
                            className=<span class="hljs-string">"w-full border rounded px-3 py-2"</span>
                        /&gt;
                        &lt;input
                            <span class="hljs-keyword">type</span>=<span class="hljs-string">"text"</span>
                            placeholder=<span class="hljs-string">"Address"</span>
                            className=<span class="hljs-string">"w-full border rounded px-3 py-2"</span>
                        /&gt;
                        &lt;input
                            <span class="hljs-keyword">type</span>=<span class="hljs-string">"text"</span>
                            placeholder=<span class="hljs-string">"City"</span>
                            className=<span class="hljs-string">"w-full border rounded px-3 py-2"</span>
                        /&gt;
                        &lt;input
                            <span class="hljs-keyword">type</span>=<span class="hljs-string">"text"</span>
                            placeholder=<span class="hljs-string">"ZIP Code"</span>
                            className=<span class="hljs-string">"w-full border rounded px-3 py-2"</span>
                        /&gt;
                    &lt;/form&gt;

                    &lt;div className=<span class="hljs-string">"mt-4 border-t pt-3"</span>&gt;
                        &lt;p className=<span class="hljs-string">"flex justify-between"</span>&gt;
                            &lt;span className=<span class="hljs-string">"font-medium"</span>&gt;Total&lt;/span&gt;
                            &lt;span&gt;₹{order?.totalAmount}&lt;/span&gt;
                        &lt;/p&gt;
                    &lt;/div&gt;

                    &lt;button
                        onClick={handlePayment}
                        className=<span class="hljs-string">"mt-4 w-full bg-blue-500 text-white py-2 rounded"</span>
                    &gt;
                        Proceed to Payment
                    &lt;/button&gt;
                &lt;/div&gt;
            )}

            {step === <span class="hljs-string">"payment"</span> &amp;&amp; (
                &lt;div className=<span class="hljs-string">"text-center"</span>&gt;
                    &lt;p className=<span class="hljs-string">"text-gray-600"</span>&gt;Processing Payment...&lt;/p&gt;
                    &lt;div className=<span class="hljs-string">"mt-3 animate-spin rounded-full h-10 w-10 border-4 border-blue-500 border-t-transparent mx-auto"</span>&gt;&lt;/div&gt;
                &lt;/div&gt;
            )}

            {step === <span class="hljs-string">"success"</span> &amp;&amp; (
                &lt;div className=<span class="hljs-string">"text-center"</span>&gt;
                    &lt;h3 className=<span class="hljs-string">"text-green-600 font-bold text-lg"</span>&gt;✅ Payment Successful!&lt;/h3&gt;
                    &lt;p className=<span class="hljs-string">"mt-2 text-gray-600"</span>&gt;
                        Thank you <span class="hljs-keyword">for</span> your purchase. Your order will be shipped soon.
                    &lt;/p&gt;
                &lt;/div&gt;
            )}
        &lt;/div&gt;
    );
}
</code></pre>
<h1 id="heading-integrate-tool-responses-in-ui"><strong>💬Integrate Tool Responses in UI</strong></h1>
<p>Your chat page processes three types of messages:</p>
<ul>
<li><p>normal model text</p>
</li>
<li><p>tool calls</p>
</li>
<li><p>tool responses</p>
</li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-comment">// app/page.tsx</span>
<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-sdk/react"</span>;
<span class="hljs-keyword">import</span> {DefaultChatTransport} <span class="hljs-keyword">from</span> <span class="hljs-string">"ai"</span>;
<span class="hljs-keyword">import</span> {useState} <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> {Bot, User} <span class="hljs-keyword">from</span> <span class="hljs-string">"lucide-react"</span>;
<span class="hljs-keyword">import</span> ProductCard <span class="hljs-keyword">from</span> <span class="hljs-string">"@/components/ProductCard"</span>;
<span class="hljs-keyword">import</span> Cart <span class="hljs-keyword">from</span> <span class="hljs-string">"@/components/CartCard"</span>;
<span class="hljs-keyword">import</span> Checkout <span class="hljs-keyword">from</span> <span class="hljs-string">"@/components/Checkout"</span>;
<span class="hljs-keyword">import</span> {OrderItem} <span class="hljs-keyword">from</span> <span class="hljs-string">"@/lib/orderStore"</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> {messages, sendMessage, status} = useChat({
        transport: <span class="hljs-keyword">new</span> DefaultChatTransport({
            api: <span class="hljs-string">'/api/chat'</span>,
        }),
    });
    <span class="hljs-keyword">const</span> [input, setInput] = useState(<span class="hljs-string">''</span>);

    <span class="hljs-keyword">return</span> (
        &lt;main className=<span class="hljs-string">"flex flex-col items-center pt-4 min-h-screen bg-white"</span>&gt;
            &lt;div className=<span class="hljs-string">"w-[70vw] bg-white rounded-lg p-6"</span>&gt;
                &lt;h1 className=<span class="hljs-string">"text-2xl font-bold mb-4 text-center"</span>&gt;
                    🛍️ Shopping Assistant
                &lt;/h1&gt;

                {<span class="hljs-comment">/* Chat messages */</span>}
                &lt;div className=<span class="hljs-string">"space-y-4 h-[calc(100vh-200px)] w-full overflow-y-auto p-4 rounded-lg mb-4"</span>&gt;
                    {messages.map(<span class="hljs-function">(<span class="hljs-params">m</span>) =&gt;</span> (
                        &lt;div
                            key={m.id}
                            className={<span class="hljs-string">`flex flex-start items-start gap-3 items-center`</span>}
                        &gt;
                            {m.role === <span class="hljs-string">"user"</span> ? (
                                &lt;div className=<span class="hljs-string">"flex items-center gap-2"</span>&gt;
                                    &lt;div className=<span class="hljs-string">"bg-blue-500 text-white p-2 rounded-full"</span>&gt;
                                        &lt;User className=<span class="hljs-string">"w-5 h-5"</span>/&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;) : (
                                &lt;div className=<span class="hljs-string">"flex items-center gap-2"</span>&gt;
                                    &lt;div className=<span class="hljs-string">"bg-gray-600 text-white p-2 rounded-full"</span>&gt;
                                        &lt;Bot className=<span class="hljs-string">"w-5 h-5"</span>/&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            )}
                            &lt;div
                                className={<span class="hljs-string">`w-full flex py-[5px] px-[10px] rounded-[10px] <span class="hljs-subst">${m.role === <span class="hljs-string">"user"</span> ? <span class="hljs-string">"bg-blue-100"</span> : <span class="hljs-string">"bg-gray-200"</span>}</span>`</span>}&gt;
                                {m.parts.map(<span class="hljs-function">(<span class="hljs-params">part, index</span>) =&gt;</span> {
                                        <span class="hljs-keyword">switch</span> (part?.type) {
                                            <span class="hljs-keyword">case</span> <span class="hljs-string">'step-start'</span>:
                                                <span class="hljs-comment">// show step boundaries as horizontal lines:</span>
                                                <span class="hljs-keyword">return</span> index &gt; <span class="hljs-number">0</span> ? (
                                                    &lt;div key={index} className=<span class="hljs-string">"text-gray-500"</span>&gt;
                                                        &lt;hr className=<span class="hljs-string">"my-2 border-gray-300"</span>/&gt;
                                                    &lt;/div&gt;
                                                ) : <span class="hljs-literal">null</span>;
                                            <span class="hljs-keyword">case</span> <span class="hljs-string">'tool-fetchCatalog'</span>:
                                                <span class="hljs-keyword">switch</span> (part?.state) {
                                                    <span class="hljs-keyword">case</span> <span class="hljs-string">'input-streaming'</span>:
                                                        <span class="hljs-keyword">return</span> &lt;span className=<span class="hljs-string">"italic text-gray-600"</span>
                                                                     key={<span class="hljs-string">`tool-<span class="hljs-subst">${index}</span>`</span>}&gt; 🛍️ Fetching products… &lt;/span&gt;;
                                                    <span class="hljs-keyword">case</span> <span class="hljs-string">'input-available'</span>:
                                                        <span class="hljs-keyword">return</span> &lt;span className=<span class="hljs-string">"italic text-gray-600"</span>
                                                                     key={<span class="hljs-string">`tool-<span class="hljs-subst">${index}</span>`</span>}&gt; 🛍️ Fetching products… &lt;/span&gt;;
                                                    <span class="hljs-keyword">case</span> <span class="hljs-string">'output-available'</span>:
                                                        <span class="hljs-keyword">let</span> products = part.output <span class="hljs-keyword">as</span> <span class="hljs-built_in">any</span>

                                                        <span class="hljs-keyword">return</span> products?.length &gt; <span class="hljs-number">0</span> ?
                                                           &lt;div className={<span class="hljs-string">"flex flex-wrap gap-4"</span>} key={<span class="hljs-string">"products-list"</span>}&gt;
                                                                {products?.map(<span class="hljs-function">(<span class="hljs-params">product: <span class="hljs-built_in">any</span></span>) =&gt;</span> {
                                                                    <span class="hljs-keyword">return</span> &lt;ProductCard product={product} addToCart={<span class="hljs-keyword">async</span> (productId:<span class="hljs-built_in">string</span>, productName:<span class="hljs-built_in">string</span>) =&gt; {
                                                                        <span class="hljs-keyword">await</span> sendMessage({text: <span class="hljs-string">`Add <span class="hljs-subst">${productName}</span> to my cart`</span>})
                                                                    }}
                                                                                        key={product.id}/&gt;
                                                                })}
                                                                    &lt;/div&gt;
                                                            :
                                                            &lt;div key={<span class="hljs-string">`tool-<span class="hljs-subst">${index}</span>`</span>} className=<span class="hljs-string">"text-gray-600"</span>&gt;No
                                                                products found.&lt;/div&gt;
                                                    <span class="hljs-keyword">case</span> <span class="hljs-string">'output-error'</span>:
                                                        <span class="hljs-keyword">return</span> &lt;div key={<span class="hljs-string">`tool-<span class="hljs-subst">${index}</span>`</span>}&gt;<span class="hljs-built_in">Error</span>: {part.errorText}&lt;/div&gt;;
                                                }
                                                <span class="hljs-keyword">break</span>;
                                            <span class="hljs-keyword">case</span> <span class="hljs-string">'tool-addToCart'</span>:
                                                <span class="hljs-keyword">switch</span> (part?.state) {
                                                    <span class="hljs-keyword">case</span> <span class="hljs-string">'input-streaming'</span>:
                                                        <span class="hljs-keyword">return</span> &lt;span className=<span class="hljs-string">"italic text-gray-600"</span>
                                                                     key={<span class="hljs-string">`tool-<span class="hljs-subst">${index}</span>`</span>}&gt;
                                                            ➕ Adding item to cart…
                                                        &lt;/span&gt;;
                                                    <span class="hljs-keyword">case</span> <span class="hljs-string">'input-available'</span>:
                                                        <span class="hljs-keyword">return</span> &lt;span className=<span class="hljs-string">"italic text-gray-600"</span>
                                                                     key={<span class="hljs-string">`tool-<span class="hljs-subst">${index}</span>`</span>}&gt;
                                                            ➕ Adding item to cart…
                                                        &lt;/span&gt;;
                                                    <span class="hljs-keyword">case</span> <span class="hljs-string">'output-available'</span>:
                                                        <span class="hljs-keyword">return</span> &lt;Cart key={<span class="hljs-string">`tool-<span class="hljs-subst">${index}</span>}`</span> } items={part.output <span class="hljs-keyword">as</span> <span class="hljs-built_in">any</span>[]}/&gt;;
                                                    <span class="hljs-keyword">case</span> <span class="hljs-string">'output-error'</span>:
                                                        <span class="hljs-keyword">return</span> &lt;div key={<span class="hljs-string">`tool-<span class="hljs-subst">${index}</span>`</span>}&gt;<span class="hljs-built_in">Error</span>: {part.errorText}&lt;/div&gt;;
                                                }
                                                <span class="hljs-keyword">break</span>;
                                            <span class="hljs-keyword">case</span> <span class="hljs-string">'tool-checkoutCart'</span>:
                                                <span class="hljs-keyword">const</span> order = part?.output <span class="hljs-keyword">as</span> OrderItem
                                                <span class="hljs-keyword">switch</span> (part?.state) {
                                                    <span class="hljs-keyword">case</span> <span class="hljs-string">'input-streaming'</span>:
                                                        <span class="hljs-keyword">return</span> &lt;span className=<span class="hljs-string">"italic text-gray-600"</span>
                                                                     key={<span class="hljs-string">`tool-<span class="hljs-subst">${index}</span>`</span>}&gt;💳 Processing checkout…&lt;/span&gt;;
                                                    <span class="hljs-keyword">case</span> <span class="hljs-string">'input-available'</span>:
                                                        <span class="hljs-keyword">return</span> &lt;span className=<span class="hljs-string">"italic text-gray-600"</span>
                                                                     key={<span class="hljs-string">`tool-<span class="hljs-subst">${index}</span>`</span>}&gt;💳 Processing checkout…&lt;/span&gt;;
                                                    <span class="hljs-keyword">case</span> <span class="hljs-string">'output-available'</span>:
                                                        <span class="hljs-keyword">return</span> &lt;Checkout order={order} key={<span class="hljs-string">`tool-<span class="hljs-subst">${index}</span>`</span>}/&gt;;
                                                    <span class="hljs-keyword">case</span> <span class="hljs-string">'output-error'</span>:
                                                        <span class="hljs-keyword">return</span> &lt;div key={<span class="hljs-string">`tool-<span class="hljs-subst">${index}</span>`</span>}&gt;<span class="hljs-built_in">Error</span>: {part.errorText}&lt;/div&gt;;
                                                }
                                                <span class="hljs-keyword">break</span>;

                                            <span class="hljs-keyword">case</span> <span class="hljs-string">'text'</span>:
                                                <span class="hljs-keyword">return</span> &lt;span key={index}&gt;{part.text}&lt;/span&gt;;
                                        }
                                    }
                                )}
                            &lt;/div&gt;
                        &lt;/div&gt;
                    ))}
                    {status === <span class="hljs-string">'submitted'</span> &amp;&amp; &lt;div className=<span class="hljs-string">"text-gray-500"</span>&gt;Thinking...&lt;/div&gt;}
                &lt;/div&gt;

                {<span class="hljs-comment">/* Input box */</span>}
                &lt;form onSubmit={<span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> {
                    e.preventDefault();
                    <span class="hljs-keyword">if</span> (input.trim()) {
                        sendMessage({text: input});
                        setInput(<span class="hljs-string">''</span>);
                    }
                }} className=<span class="hljs-string">"flex gap-2"</span>&gt;
                    &lt;input
                        className=<span class="hljs-string">"flex-1 border border-gray-300 shadow-lg rounded-lg px-4 py-2 focus:outline-none focus:ring-1 focus:ring-gray-400"</span>
                        value={input}
                        disabled={status !== <span class="hljs-string">'ready'</span>}
                        placeholder=<span class="hljs-string">"Ask about products... e.g. 'Show me shoes under 200 rupees'"</span>
                        onChange={<span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> setInput(e.target.value)}
                    /&gt;
                    &lt;button
                        <span class="hljs-keyword">type</span>=<span class="hljs-string">"submit"</span>
                        className=<span class="hljs-string">"bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 shadow-lg disabled:opacity-50"</span>
                        disabled={status !== <span class="hljs-string">'ready'</span> || !input.trim()}
                    &gt;
                        Ask
                    &lt;/button&gt;
                &lt;/form&gt;
            &lt;/div&gt;
        &lt;/main&gt;
    );
}
</code></pre>
<h1 id="heading-result">Result</h1>
<h3 id="heading-list-all-products">List all products</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764181143368/b75816ea-bef9-4f19-ae3e-58634c79a442.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-filter-products-based-on-price">Filter products based on price</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764181216423/77f04e4c-fc33-4b5b-8ea3-450ccb617dce.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-add-to-cart-using-text">Add to cart using text</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764181277059/f8455632-e95e-402e-94e3-ee95b693ac15.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-checkout-feature-using-text-that-allows-user-to-pay-using-preferred-method">Checkout feature using text that allows user to pay using preferred method</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764185232878/b0c99f37-b8bf-4ccb-a035-2872a8857a4b.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-what-we-just-built">🎯 <strong>What We Just Built</strong></h1>
<p>With <strong>Gemini tool calling + Vercel AI SDK + Next.js</strong>, you now have an:</p>
<ul>
<li><p>AI-powered <strong>shopping assistant</strong></p>
</li>
<li><p>Interactive <strong>product catalog chatbot</strong></p>
</li>
<li><p>Cart-enabled <strong>conversational checkout</strong></p>
</li>
<li><p>Real-time <strong>streaming chat UI</strong></p>
</li>
<li><p>Modern e-commerce experience using <strong>React card components</strong></p>
</li>
</ul>
<h1 id="heading-next-steps">🚀 <strong>Next Steps</strong></h1>
<p>Add more advanced features:</p>
<ul>
<li><p>🔍 Vector search (Supabase, Pinecone)</p>
</li>
<li><p>🔧 Tool-based filtering (by price, brand, ratings)</p>
</li>
<li><p>👤 Personalized recommendations</p>
</li>
<li><p>💳 Real checkout integration</p>
</li>
<li><p>📦 Track order status</p>
</li>
</ul>
<h1 id="heading-conclusion">🏁 <strong>Conclusion</strong></h1>
<p>Using <strong>Vercel AI SDK + Google Gemini</strong>, we've created a fully interactive <strong>e-commerce chatbot</strong> that transforms the shopping experience. This innovation allows users to discover products, add items to their cart, and complete the checkout process all within a conversational interface. This development is a significant advancement in the tech world, offering a seamless and engaging way to shop online.</p>
<p>Here is the live version: <a target="_blank" href="https://shopping-assistant-kappa.vercel.app/">DEMO</a></p>
<p>Source code - <a target="_blank" href="https://github.com/icon-gaurav/ecom-chatbot">Github</a></p>
]]></content:encoded></item><item><title><![CDATA[Building an Ethereum dApp with Next.js, Wagmi, and MetaMask]]></title><description><![CDATA[Over the last few years, decentralized applications (dApps) have become one of the most exciting areas in Web3. Instead of relying on centralized servers, dApps interact directly with blockchains—allowing users to own their assets, sign transactions,...]]></description><link>https://gauravbytes.dev/building-an-ethereum-dapp-with-nextjs-wagmi-and-metamask</link><guid isPermaLink="true">https://gauravbytes.dev/building-an-ethereum-dapp-with-nextjs-wagmi-and-metamask</guid><category><![CDATA[dapps]]></category><category><![CDATA[Ethereum]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Next.js]]></category><category><![CDATA[Metamask]]></category><category><![CDATA[Web3]]></category><category><![CDATA[defi]]></category><category><![CDATA[Blockchain]]></category><category><![CDATA[#wagmi]]></category><category><![CDATA[crypto]]></category><category><![CDATA[sepolia]]></category><category><![CDATA[Smart Contracts]]></category><dc:creator><![CDATA[Gaurav Kumar]]></dc:creator><pubDate>Mon, 25 Aug 2025 11:04:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1755811467976/1cadd7ee-e625-43ea-8927-0acac2e8c02b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Over the last few years, decentralized applications (dApps) have become one of the most exciting areas in Web3. Instead of relying on centralized servers, dApps interact directly with blockchains—allowing users to <strong>own their assets, sign transactions, and engage with trustless systems</strong>.</p>
<p>In this tutorial, we’ll walk through building a simple Ethereum dApp using <strong>Next.js, Wagmi, and MetaMask</strong>. Our application will let users:</p>
<ul>
<li><p>Connect their MetaMask wallet</p>
</li>
<li><p>Send ETH to another address on a testnet (Sepolia)</p>
</li>
<li><p>View their recent transactions in a paginated format</p>
</li>
</ul>
<p>This project is designed as a starting point for anyone who wants to dive into Ethereum development and understand how wallet connections, transactions, and blockchain interactions work in practice.</p>
<p>We’ll use <strong>MetaMask</strong> as the crypto wallet for connecting and signing transactions, <strong>Wagmi</strong> (a React hooks library) for interacting with Ethereum, and <strong>Next.js</strong> for building the frontend of our dApp.</p>
<p>By the end of this guide, you’ll have a fully functional Ethereum dApp running on a testnet—ready to extend into something bigger, like token transfers or DeFi features.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before we dive into building the dApp, here’s what you’ll need to get started:</p>
<h3 id="heading-1-basic-knowledge">1. Basic Knowledge</h3>
<ul>
<li><p>Familiarity with <strong>React</strong> and <strong>Next.js</strong> fundamentals</p>
</li>
<li><p>A basic understanding of <strong>Ethereum</strong> and how transactions work</p>
</li>
<li><p>Some exposure to <strong>JavaScript/TypeScript</strong></p>
</li>
</ul>
<h3 id="heading-2-installed-tools">2. Installed Tools</h3>
<ul>
<li><p><strong>Node.js &amp; npm/yarn</strong> – To run the Next.js project</p>
</li>
<li><p><strong>MetaMask</strong> – Installed as a browser extension or installed on mobile (for connecting and approving transactions)</p>
</li>
<li><p><strong>VS Code (or any IDE)</strong> – To write and manage your project code</p>
</li>
</ul>
<h3 id="heading-3-ethereum-test-network-setup">3. Ethereum Test Network Setup</h3>
<p>We’ll use the <strong>Sepolia testnet</strong> for this project. That way, you don’t spend real ETH while testing.</p>
<p>👉 Steps:</p>
<ol>
<li><p>Install <strong>MetaMask</strong> if you haven’t already.</p>
</li>
<li><p>Switch your MetaMask network to <strong>Sepolia Test Network</strong>.</p>
</li>
<li><p>Get some free test ETH from a <strong>Sepolia Faucet</strong> (just Google <em>Sepolia faucet</em> and request test ETH).</p>
</li>
</ol>
<p>💡 <strong>Note:</strong> If you’re having trouble getting test ETH, you can also reach out to me via [<a target="_blank" href="mailto:icon.gaurav806@gmail.com">email</a>], and I can transfer some Sepolia ETH to your wallet for testing.</p>
<h3 id="heading-4-libraries-well-use">4. Libraries We’ll Use</h3>
<ul>
<li><p><strong>Next.js</strong> – React framework for building the frontend</p>
</li>
<li><p><strong>Wagmi</strong> – React hooks for Ethereum (easy wallet connection + transaction handling)</p>
</li>
<li><p><strong>Viem</strong> – A low-level Ethereum library used under the hood by Wagmi</p>
</li>
<li><p><strong>Tailwind CSS</strong> – For styling (optional, but makes UI much easier)</p>
</li>
</ul>
<p>Once you have these prerequisites in place, you’ll be ready to start coding your dApp 🚀</p>
<h2 id="heading-project-setup">🚀 Project Setup</h2>
<p>Let’s set up our Ethereum dApp from scratch. We’ll be using <strong>Next.js</strong> for the frontend, <strong>Wagmi</strong> for wallet interactions, and <strong>MetaMask</strong> as our crypto wallet provider.</p>
<h3 id="heading-1-create-a-nextjs-app">1. Create a Next.js App</h3>
<p>First, create a new Next.js project using the official CLI:</p>
<pre><code class="lang-bash">npx create-next-app eth-dapp
<span class="hljs-built_in">cd</span> eth-dapp
</code></pre>
<h3 id="heading-2-install-dependencies">2. Install Dependencies</h3>
<p>We’ll need <strong>Wagmi</strong>, <strong>viem</strong>, and <strong>ethers</strong> to connect with Ethereum.</p>
<pre><code class="lang-bash">npm install wagmi viem ethers
</code></pre>
<p>If you want pretty logging (optional), install <strong>pino-pretty</strong>:</p>
<pre><code class="lang-bash">npm install pino-pretty
</code></pre>
<h3 id="heading-3-configure-wagmi">3. Configure Wagmi</h3>
<p>Inside your project, set up the Wagmi client in a <code>Web3Provider.tsx</code> file. This will allow us to connect to Ethereum testnets like <strong>Sepolia</strong>.</p>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>
<span class="hljs-keyword">import</span> {createConfig, http, WagmiProvider} <span class="hljs-keyword">from</span> <span class="hljs-string">'wagmi'</span>
<span class="hljs-keyword">import</span> {mainnet, sepolia} <span class="hljs-keyword">from</span> <span class="hljs-string">'wagmi/chains'</span>
<span class="hljs-keyword">import</span> {QueryClient, QueryClientProvider} <span class="hljs-keyword">from</span> <span class="hljs-string">"@tanstack/react-query"</span>;
<span class="hljs-keyword">import</span> {metaMask} <span class="hljs-keyword">from</span> <span class="hljs-string">"@wagmi/connectors"</span>;
<span class="hljs-keyword">import</span> {injected} <span class="hljs-keyword">from</span> <span class="hljs-string">"wagmi/connectors"</span>;

<span class="hljs-keyword">const</span> queryClient = <span class="hljs-keyword">new</span> QueryClient();
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> config = createConfig({
    chains: [mainnet, sepolia],
    connectors: [
        metaMask(),
        injected(),
    ],
    transports: {
        [mainnet.id]: http(),
        [sepolia.id]: http(),
    },
})

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Web3Provider</span>(<span class="hljs-params">{children}: { children: React.ReactNode }</span>) </span>{
    <span class="hljs-keyword">return</span> &lt;WagmiProvider config={config}&gt;
        &lt;QueryClientProvider client={queryClient}&gt;
            {children}
        &lt;<span class="hljs-regexp">/QueryClientProvider&gt;&lt;/</span>WagmiProvider&gt;;
}
</code></pre>
<p>Now wrap your app in this provider (<code>app/layout.tsx</code> in Next.js 13+):</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> {Web3Provider} <span class="hljs-keyword">from</span> <span class="hljs-string">"@/lib/web3/Web3Provider"</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">RootLayout</span>(<span class="hljs-params">{ children }: { children: React.ReactNode }</span>) </span>{
  <span class="hljs-keyword">return</span> (
    &lt;html lang=<span class="hljs-string">"en"</span>&gt;
      &lt;body&gt;
            &lt;Web3Provider&gt;{children}&lt;/Web3Provider&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  );
}
</code></pre>
<h3 id="heading-4-install-metamask">4. Install MetaMask</h3>
<p>Make sure you have the <a target="_blank" href="https://metamask.io/">MetaMask extension</a> installed in your browser or in your phone.</p>
<ul>
<li><p>Switch to the <strong>Sepolia Testnet</strong> in MetaMask.</p>
</li>
<li><p>Request some test ETH from a <a target="_blank" href="https://sepoliafaucet.com/">Sepolia Faucet</a>.</p>
</li>
</ul>
<p>💡 <em>If you’re unable to get test ETH, feel free to email me at</em> <strong><em>[</em></strong><a target="_blank" href="mailto:icon.gaurav806@gmail.com">icon.gaurav806@gmail.com</a><strong><em>]</em></strong> <em>and I can transfer some to your wallet for testing.</em></p>
<p>👉 That’s our base setup. Next, we’ll build the <strong>Wallet Connection component</strong> so users can connect their MetaMask wallet to the dApp.</p>
<h2 id="heading-connecting-your-wallet">🔗 Connecting Your Wallet</h2>
<p>Now that our project is set up with <strong>Next.js</strong> and <strong>Wagmi</strong>, let’s add a <strong>Wallet Connect button</strong> so users can link their MetaMask wallet to the dApp.</p>
<h3 id="heading-1-create-a-wallet-connect-component">1. Create a Wallet Connect Component</h3>
<p>Inside <code>components/WalletConnect.tsx</code>, add the following:</p>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>
<span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>
<span class="hljs-keyword">import</span> {useAccount, useConnect, useDisconnect} <span class="hljs-keyword">from</span> <span class="hljs-string">"wagmi"</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">WalletConnect</span>(<span class="hljs-params">{ onAddWallet }: {
    onAddWallet: (address: <span class="hljs-built_in">string</span>) =&gt; <span class="hljs-built_in">void</span>
}</span>) </span>{
    <span class="hljs-keyword">const</span> {connectAsync, connectors, status , reset, error} = useConnect();
    <span class="hljs-keyword">const</span> { disconnect } = useDisconnect();
    <span class="hljs-keyword">const</span> handleConnect = <span class="hljs-keyword">async</span> () =&gt; {
        <span class="hljs-keyword">try</span> {
            disconnect(); <span class="hljs-comment">// Disconnect any existing connection</span>
            <span class="hljs-keyword">const</span> connector = connectors[<span class="hljs-number">0</span>]; <span class="hljs-comment">// Assuming the first connector is the one we want</span>
            <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> connectAsync({ connector });
            onAddWallet(data?.accounts?.[<span class="hljs-number">0</span>]);
        } <span class="hljs-keyword">catch</span> (error) {
            reset()
            <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Failed to connect wallet:"</span>, error);
            <span class="hljs-built_in">console</span>.log(error)
        }
    }


    <span class="hljs-keyword">return</span> (
        &lt;div className=<span class="hljs-string">"max-w-md mx-auto mt-10 p-6 bg-white rounded-2xl shadow-md"</span>&gt;
            &lt;h2 className=<span class="hljs-string">"text-xl font-semibold text-gray-800 mb-4"</span>&gt;Add Wallet&lt;/h2&gt;

            &lt;div className=<span class="hljs-string">"text-center"</span>&gt;
                &lt;button
                    onClick={handleConnect}
                    className=<span class="hljs-string">"w-full py-2 px-4 bg-green-600 text-white rounded-lg hover:bg-green-700 transition"</span>
                &gt;
                    {status === <span class="hljs-string">'pending'</span>? <span class="hljs-string">'Connecting...'</span> :<span class="hljs-string">'Connect Using External Wallet'</span>}
                &lt;/button&gt;

                {error &amp;&amp; &lt;p className=<span class="hljs-string">"text-red-600 mt-2"</span>&gt;{error.message}&lt;/p&gt;}
            &lt;/div&gt;
        &lt;/div&gt;
    )
}
</code></pre>
<h3 id="heading-2-add-it-to-the-homepage">2. Add It to the Homepage</h3>
<p>Open <code>app/page.tsx</code> and include the WalletConnect component:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> WalletConnect <span class="hljs-keyword">from</span> <span class="hljs-string">"@/components/WalletConnect"</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;div className=<span class="hljs-string">"flex flex-col items-center justify-center px-6"</span>&gt;

            {<span class="hljs-comment">/* ===== Hero Section with Wallet Connect ===== */</span>}
            &lt;section className=<span class="hljs-string">"text-center mt-20 max-w-3xl"</span>&gt;
                &lt;h1 className=<span class="hljs-string">"text-5xl font-extrabold text-gray-900 mb-6"</span>&gt;
                    Manage Your Crypto <span class="hljs-keyword">with</span> Ease 🚀
                &lt;/h1&gt;
                &lt;p className=<span class="hljs-string">"text-lg text-gray-600 mb-8"</span>&gt;
                    Create wallets, send &amp; receive Ethereum, and track transactions —
                    all <span class="hljs-keyword">in</span> one secure, easy-to-use app.
                &lt;/p&gt;

                {<span class="hljs-comment">/* Connect CTA */</span>}
                &lt;div className=<span class="hljs-string">"mt-6"</span>&gt;
                    &lt;WalletConnect onAddWallet={<span class="hljs-function">() =&gt;</span> redirect(<span class="hljs-string">'/dashboard'</span>)}/&gt;
                    &lt;p className=<span class="hljs-string">"text-sm text-gray-500 mt-3"</span>&gt;
                        Don’t have a wallet yet? Install{<span class="hljs-string">" "</span>}
                        &lt;Link
                            href=<span class="hljs-string">"https://metamask.io/download/"</span>
                            target=<span class="hljs-string">"_blank"</span>
                            rel=<span class="hljs-string">"noopener noreferrer"</span>
                            className=<span class="hljs-string">"text-blue-600 underline"</span>
                        &gt;
                            MetaMask
                        &lt;/Link&gt;{<span class="hljs-string">" "</span>}
                        to get started.
                    &lt;/p&gt;
                &lt;/div&gt;
            &lt;/section&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<h3 id="heading-3-test-it">3. Test It</h3>
<ul>
<li>Run your dev server:</li>
</ul>
<pre><code class="lang-bash">npm run dev
</code></pre>
<ul>
<li><p>Open <a target="_blank" href="http://localhost:3000"><code>http://localhost:3000</code></a>.</p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755808946974/b257da30-a2a7-472e-939d-c042843bf1f9.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
<ul>
<li><p>Click <strong>Connect Wallet</strong> → MetaMask should pop up asking for approval.</p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755808983904/ba423a13-dee3-421d-91eb-47a90177118c.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Once connected, you’ll see get redirected to dashboard page.</p>
</li>
</ul>
<p>✅ At this stage, your dApp can connect and disconnect a wallet.<br />Next, we’ll extend this by <strong>fetching account details and showing balances</strong>.</p>
<h2 id="heading-fetching-wallet-details-amp-balances">💰 Fetching Wallet Details &amp; Balances</h2>
<p>Once users connect their wallet, we can show them their <strong>Ethereum address</strong>, <strong>balance</strong>, and even a quick copy button. This helps them confirm they are on the right account before sending transactions.</p>
<h3 id="heading-1-fetch-account-details-with-wagmi">1. Fetch Account Details with Wagmi</h3>
<p>Wagmi provides hooks like <code>useAccount</code> and <code>useBalance</code> to make this easy. Inside <code>app/dashboard/page.tsx</code>, add the following:</p>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>;

<span class="hljs-keyword">import</span> {useAccount, useBalance} <span class="hljs-keyword">from</span> <span class="hljs-string">"wagmi"</span>;
<span class="hljs-keyword">import</span> {useState} <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> {Copy, Send, Wallet, ExternalLink, X} <span class="hljs-keyword">from</span> <span class="hljs-string">"lucide-react"</span>;
<span class="hljs-keyword">import</span> {redirect} <span class="hljs-keyword">from</span> <span class="hljs-string">"next/navigation"</span>;
<span class="hljs-keyword">import</span> SendTransaction <span class="hljs-keyword">from</span> <span class="hljs-string">"@/components/SendTransaction"</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">DashboardPage</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> {address, isConnected} = useAccount();
    <span class="hljs-keyword">const</span> {data: balance} = useBalance({address});


    <span class="hljs-keyword">const</span> handleCopy = <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">if</span> (address) {
            navigator.clipboard.writeText(address);
            alert(<span class="hljs-string">"Wallet address copied!"</span>);
        }
    };

    <span class="hljs-keyword">if</span> (!isConnected) {
        <span class="hljs-keyword">return</span> (
            &lt;div className=<span class="hljs-string">"flex items-center justify-center min-h-screen p-4"</span>&gt;
                &lt;div className=<span class="hljs-string">"p-6 border rounded-lg shadow-md text-center w-full max-w-sm"</span>&gt;
                    &lt;p className=<span class="hljs-string">"text-lg font-semibold"</span>&gt;No wallet connected&lt;/p&gt;
                    &lt;p className=<span class="hljs-string">"text-sm text-gray-500 mt-2"</span>&gt;
                        Please connect your wallet to view your dashboard.
                    &lt;/p&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        );
    }

    <span class="hljs-keyword">return</span> (
        &lt;div className=<span class="hljs-string">"p-4 sm:p-8 space-y-6 sm:space-y-8"</span>&gt;
            &lt;h1 className=<span class="hljs-string">"text-2xl sm:text-3xl font-bold"</span>&gt;Dashboard&lt;/h1&gt;

            {<span class="hljs-comment">/* Wallet Info */</span>}
            &lt;div className=<span class="hljs-string">"border rounded-lg shadow-md p-4 sm:p-6 flex items-center justify-between gap-4"</span>&gt;
                &lt;div className=<span class="hljs-string">"flex items-center gap-4 w-full"</span>&gt;
                    &lt;Wallet className=<span class="hljs-string">"w-8 h-8 sm:w-10 sm:h-10 text-blue-600"</span>/&gt;
                    &lt;div className=<span class="hljs-string">"min-w-0 flex-1"</span>&gt;
                        &lt;p className=<span class="hljs-string">"text-sm text-gray-500"</span>&gt;Connected Wallet&lt;/p&gt;
                        &lt;p className=<span class="hljs-string">"font-semibold text-sm sm:text-base break-all "</span>&gt;{address}&lt;/p&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
                &lt;button
                    onClick={handleCopy}
                    className=<span class="hljs-string">"p-2 border rounded-lg hover:bg-gray-100 flex-shrink-0"</span>
                &gt;
                    &lt;Copy className=<span class="hljs-string">"w-4 h-4"</span>/&gt;
                &lt;/button&gt;
            &lt;/div&gt;


            {<span class="hljs-comment">/* Balance Info */</span>}
            &lt;div className=<span class="hljs-string">"border rounded-lg shadow-md p-4 sm:p-6"</span>&gt;
                &lt;p className=<span class="hljs-string">"text-sm text-gray-500"</span>&gt;Total Balance&lt;/p&gt;
                &lt;p className=<span class="hljs-string">"text-xl sm:text-2xl font-bold mt-2"</span>&gt;
                    {balance ? <span class="hljs-string">`<span class="hljs-subst">${balance.formatted}</span> <span class="hljs-subst">${balance.symbol}</span>`</span> : <span class="hljs-string">"Loading..."</span>}
                &lt;/p&gt;
            &lt;/div&gt;


        &lt;/div&gt;
    );
}
</code></pre>
<h3 id="heading-2-test-it">2. Test It</h3>
<ul>
<li><p>Connect your wallet.</p>
</li>
<li><p>Head to <code>/dashboard</code>.</p>
</li>
<li><p>You should now see your <strong>wallet address</strong>, a <strong>copy icon</strong>, and your <strong>ETH balance</strong>.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755809501585/14908e93-66d1-4dcf-b0dd-c3ccfbd8d190.png" alt class="image--center mx-auto" /></p>
<p>✅ With this, your dApp shows <strong>real-time account info</strong> after connection.<br />Next up, we’ll implement the <strong>Send ETH feature</strong> with a modal for transfers.</p>
<h2 id="heading-sending-eth-with-a-transaction-modal">🚀 Sending ETH with a Transaction Modal</h2>
<p>One of the key features of our dApp is the ability to <strong>send ETH</strong> directly from the dashboard. For this, we’ll build a <strong>modal form</strong> where users can:</p>
<ul>
<li><p>Enter a recipient’s address</p>
</li>
<li><p>Enter the amount to send</p>
</li>
<li><p>Confirm the transfer in MetaMask</p>
</li>
</ul>
<p>We’ll use <code>wagmi</code>’s <strong>useSendTransaction</strong> hook along with <code>viem</code>’s <code>parseEther</code> utility.</p>
<h3 id="heading-1-send-transaction-component">1. Send Transaction Component</h3>
<p>Inside <code>components/SendTransaction.tsx</code>, add the following:</p>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>;

<span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { useSendTransaction, useWaitForTransactionReceipt } <span class="hljs-keyword">from</span> <span class="hljs-string">"wagmi"</span>;
<span class="hljs-keyword">import</span> { parseEther } <span class="hljs-keyword">from</span> <span class="hljs-string">"viem"</span>;
<span class="hljs-keyword">import</span> Link <span class="hljs-keyword">from</span> <span class="hljs-string">"next/link"</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">SendTransaction</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> [to, setTo] = useState&lt;<span class="hljs-string">`0x<span class="hljs-subst">${<span class="hljs-built_in">string</span>}</span>`</span> | <span class="hljs-string">""</span>&gt;(<span class="hljs-string">""</span>);
    <span class="hljs-keyword">const</span> [amount, setAmount] = useState(<span class="hljs-string">""</span>);

    <span class="hljs-keyword">const</span> { data: txHash, isPending, sendTransaction, error } = useSendTransaction();
    <span class="hljs-keyword">const</span> { isLoading: isConfirming, isSuccess: isConfirmed } =
        useWaitForTransactionReceipt({
            hash: txHash, <span class="hljs-comment">// hash of transaction</span>
        });

    <span class="hljs-keyword">const</span> handleSend = <span class="hljs-keyword">async</span> () =&gt; {
        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">await</span> sendTransaction({
                to: to <span class="hljs-keyword">as</span> <span class="hljs-string">`0x<span class="hljs-subst">${<span class="hljs-built_in">string</span>}</span>`</span>,
                value: parseEther(amount), <span class="hljs-comment">// convert ETH string to wei</span>
            });
        } <span class="hljs-keyword">catch</span> (err) {
            <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Transaction failed:"</span>, err);
        }
    };

    <span class="hljs-keyword">return</span> (
        &lt;div className=<span class="hljs-string">"p-4 max-w-md mx-auto space-y-4 rounded"</span>&gt;
            &lt;h2 className=<span class="hljs-string">"text-lg font-semibold"</span>&gt;Send ETH&lt;/h2&gt;

            &lt;input
                <span class="hljs-keyword">type</span>=<span class="hljs-string">"text"</span>
                placeholder=<span class="hljs-string">"Recipient address (0x...)"</span>
                value={to}
                onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setTo(e.target.value <span class="hljs-keyword">as</span> <span class="hljs-string">`0x<span class="hljs-subst">${<span class="hljs-built_in">string</span>}</span>`</span> | <span class="hljs-string">""</span>)}
                className=<span class="hljs-string">"w-full p-2 border rounded"</span>
            /&gt;

            &lt;input
                <span class="hljs-keyword">type</span>=<span class="hljs-string">"text"</span>
                placeholder=<span class="hljs-string">"Amount (ETH)"</span>
                value={amount}
                onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setAmount(e.target.value)}
                className=<span class="hljs-string">"w-full p-2 border rounded"</span>
            /&gt;

            &lt;button
                onClick={handleSend}
                disabled={isPending}
                className=<span class="hljs-string">"px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:bg-gray-400"</span>
            &gt;
                {isPending ? <span class="hljs-string">"Sending..."</span> : <span class="hljs-string">"Send"</span>}
            &lt;/button&gt;

            {<span class="hljs-comment">/* Transaction States */</span>}
            {isPending &amp;&amp; &lt;p className=<span class="hljs-string">"text-yellow-600"</span>&gt;⏳ Transaction is being sent...&lt;/p&gt;}
            {isConfirming &amp;&amp; &lt;p className=<span class="hljs-string">"text-blue-600"</span>&gt;⏳ Waiting <span class="hljs-keyword">for</span> confirmation...&lt;/p&gt;}
            {isConfirmed &amp;&amp; (
                &lt;p className=<span class="hljs-string">"text-green-600"</span>&gt;
                    ✅ Transaction confirmed!
                &lt;/p&gt;
            )}

            {<span class="hljs-comment">/* Transaction Details */</span>}
            {txHash &amp;&amp; (
                &lt;div className=<span class="hljs-string">"mt-2 space-y-2"</span>&gt;
                    &lt;p className=<span class="hljs-string">"text-sm break-all"</span>&gt;
                        🔗 Tx Hash: {txHash}
                    &lt;/p&gt;
                    &lt;Link
                        href={<span class="hljs-string">`https://sepolia.etherscan.io/tx/<span class="hljs-subst">${txHash}</span>`</span>}
                        target=<span class="hljs-string">"_blank"</span>
                        className=<span class="hljs-string">"underline text-blue-600"</span>
                    &gt;
                        View on Etherscan
                    &lt;/Link&gt;
                    &lt;br /&gt;
                    &lt;Link
                        href={<span class="hljs-string">`/dashboard`</span>}
                        className=<span class="hljs-string">"underline text-purple-600"</span>
                    &gt;
                        Go to Dashboard
                    &lt;/Link&gt;
                &lt;/div&gt;
            )}

            {error &amp;&amp; (
                &lt;p className=<span class="hljs-string">"text-sm text-red-600"</span>&gt;
                    ❌ <span class="hljs-built_in">Error</span>: {error.message}
                &lt;/p&gt;
            )}
        &lt;/div&gt;
    );
}
</code></pre>
<h3 id="heading-2-add-modal-to-dashboard">2. Add Modal to Dashboard</h3>
<p>We’ll trigger the modal using a <strong>Send Money</strong> button.</p>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>;

<span class="hljs-keyword">import</span> {useAccount, useBalance} <span class="hljs-keyword">from</span> <span class="hljs-string">"wagmi"</span>;
<span class="hljs-keyword">import</span> {useState} <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> {Copy, Send, Wallet, ExternalLink, X} <span class="hljs-keyword">from</span> <span class="hljs-string">"lucide-react"</span>;
<span class="hljs-keyword">import</span> {redirect} <span class="hljs-keyword">from</span> <span class="hljs-string">"next/navigation"</span>;
<span class="hljs-keyword">import</span> SendTransaction <span class="hljs-keyword">from</span> <span class="hljs-string">"@/components/SendTransaction"</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">DashboardPage</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> {address, isConnected} = useAccount();
    <span class="hljs-keyword">const</span> {data: balance} = useBalance({address});

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

    <span class="hljs-keyword">const</span> handleCopy = <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">if</span> (address) {
            navigator.clipboard.writeText(address);
            alert(<span class="hljs-string">"Wallet address copied!"</span>);
        }
    };

    <span class="hljs-keyword">if</span> (!isConnected) {
        <span class="hljs-keyword">return</span> (
            &lt;div className=<span class="hljs-string">"flex items-center justify-center min-h-screen p-4"</span>&gt;
                &lt;div className=<span class="hljs-string">"p-6 border rounded-lg shadow-md text-center w-full max-w-sm"</span>&gt;
                    &lt;p className=<span class="hljs-string">"text-lg font-semibold"</span>&gt;No wallet connected&lt;/p&gt;
                    &lt;p className=<span class="hljs-string">"text-sm text-gray-500 mt-2"</span>&gt;
                        Please connect your wallet to view your dashboard.
                    &lt;/p&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        );
    }

    <span class="hljs-keyword">return</span> (
        &lt;div className=<span class="hljs-string">"p-4 sm:p-8 space-y-6 sm:space-y-8"</span>&gt;
            &lt;h1 className=<span class="hljs-string">"text-2xl sm:text-3xl font-bold"</span>&gt;Dashboard&lt;/h1&gt;

            {<span class="hljs-comment">/* Wallet Info */</span>}
            &lt;div className=<span class="hljs-string">"border rounded-lg shadow-md p-4 sm:p-6 flex items-center justify-between gap-4"</span>&gt;
                &lt;div className=<span class="hljs-string">"flex items-center gap-4 w-full"</span>&gt;
                    &lt;Wallet className=<span class="hljs-string">"w-8 h-8 sm:w-10 sm:h-10 text-blue-600"</span>/&gt;
                    &lt;div className=<span class="hljs-string">"min-w-0 flex-1"</span>&gt;
                        &lt;p className=<span class="hljs-string">"text-sm text-gray-500"</span>&gt;Connected Wallet&lt;/p&gt;
                        &lt;p className=<span class="hljs-string">"font-semibold text-sm sm:text-base break-all "</span>&gt;{address}&lt;/p&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
                &lt;button
                    onClick={handleCopy}
                    className=<span class="hljs-string">"p-2 border rounded-lg hover:bg-gray-100 flex-shrink-0"</span>
                &gt;
                    &lt;Copy className=<span class="hljs-string">"w-4 h-4"</span>/&gt;
                &lt;/button&gt;
            &lt;/div&gt;


            {<span class="hljs-comment">/* Balance Info */</span>}
            &lt;div className=<span class="hljs-string">"border rounded-lg shadow-md p-4 sm:p-6"</span>&gt;
                &lt;p className=<span class="hljs-string">"text-sm text-gray-500"</span>&gt;Total Balance&lt;/p&gt;
                &lt;p className=<span class="hljs-string">"text-xl sm:text-2xl font-bold mt-2"</span>&gt;
                    {balance ? <span class="hljs-string">`<span class="hljs-subst">${balance.formatted}</span> <span class="hljs-subst">${balance.symbol}</span>`</span> : <span class="hljs-string">"Loading..."</span>}
                &lt;/p&gt;
            &lt;/div&gt;

            {<span class="hljs-comment">/* Actions */</span>}
            &lt;div className=<span class="hljs-string">"flex flex-col gap-4 sm:flex-row"</span>&gt;
                &lt;button
                    onClick={<span class="hljs-function">() =&gt;</span> setIsModalOpen(<span class="hljs-literal">true</span>)}
                    className=<span class="hljs-string">"flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 w-full sm:w-auto"</span>
                &gt;
                    &lt;Send className=<span class="hljs-string">"w-4 h-4"</span>/&gt; Send Money
                &lt;/button&gt;

            &lt;/div&gt;

            {<span class="hljs-comment">/* Send Money Modal */</span>}
            {isModalOpen &amp;&amp; (
                &lt;div className=<span class="hljs-string">"fixed inset-0 flex items-center justify-center bg-black/40 p-4"</span>&gt;
                    &lt;div className=<span class="hljs-string">"bg-white rounded-lg shadow-lg w-full max-w-md p-6 relative"</span>&gt;
                        &lt;button
                            onClick={<span class="hljs-function">() =&gt;</span> setIsModalOpen(<span class="hljs-literal">false</span>)}
                            className=<span class="hljs-string">"absolute top-2 right-2 p-1 rounded hover:bg-gray-100"</span>
                        &gt;
                            &lt;X className=<span class="hljs-string">"w-5 h-5"</span>/&gt;
                        &lt;/button&gt;
                        &lt;SendTransaction/&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
            )}
        &lt;/div&gt;
    );
}
</code></pre>
<h3 id="heading-3-test-the-flow">3. Test the Flow</h3>
<ol>
<li><p>Connect your wallet</p>
</li>
<li><p>Enter a <strong>recipient address</strong> and <strong>amount</strong></p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755809935391/f2f16ac9-0c39-41aa-af96-e54f757cc225.png" alt class="image--center mx-auto" /></p>
<p> 🎉 ETH is transferred on the <strong>testnet</strong></p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755809982212/263e6076-6a7d-4860-b322-5d4cd9ef58a4.png" alt class="image--center mx-auto" /></p>
<p> Confirm the transaction in MetaMask or in Etherscan</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755810181634/b59b04be-2b45-4c08-ae7a-fbc922238987.png" alt class="image--center mx-auto" /></p>
<p>✅ Now your dApp supports <strong>ETH transfers</strong> via a clean modal UI.</p>
<p>Next, we’ll build the <strong>Transactions Page</strong> to view past transfers with pagination.</p>
<h2 id="heading-viewing-past-transactions">📜 Viewing Past Transactions</h2>
<p>Once users can send ETH, the next logical step is to <strong>view their past transactions</strong>. This helps them keep track of transfers, amounts, and recipients directly from our dApp.</p>
<p>We’ll use <strong>Etherscan APIs</strong> (or any testnet block explorer API) to fetch transaction history for the connected wallet. For simplicity, we’ll show a <strong>paginated table</strong> of transactions.</p>
<h3 id="heading-1-transactions-page">1. Transactions Page</h3>
<p>Inside <code>app/transactions.tsx</code>, add the following:</p>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>;

<span class="hljs-keyword">import</span> {useAccount} <span class="hljs-keyword">from</span> <span class="hljs-string">"wagmi"</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> {ExternalLink} <span class="hljs-keyword">from</span> <span class="hljs-string">"lucide-react"</span>;

<span class="hljs-keyword">interface</span> Transaction {
    hash: <span class="hljs-built_in">string</span>;
    <span class="hljs-keyword">from</span>: <span class="hljs-built_in">string</span>;
    to: <span class="hljs-built_in">string</span>;
    value: <span class="hljs-built_in">string</span>;
    timeStamp: <span class="hljs-built_in">number</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">TransactionsPage</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> {address, isConnected} = useAccount();
    <span class="hljs-keyword">const</span> [transactions, setTransactions] = useState&lt;Transaction[]&gt;([]);
    <span class="hljs-keyword">const</span> [page, setPage] = useState(<span class="hljs-number">1</span>);
    <span class="hljs-keyword">const</span> [loading, setLoading] = useState(<span class="hljs-literal">false</span>);

    <span class="hljs-comment">// replace this with your provider API</span>
    <span class="hljs-keyword">const</span> fetchTransactions = <span class="hljs-keyword">async</span> (pageNumber: <span class="hljs-built_in">number</span>) =&gt; {
        <span class="hljs-keyword">if</span> (!address) <span class="hljs-keyword">return</span>;
        setLoading(<span class="hljs-literal">true</span>);

        <span class="hljs-keyword">try</span> {
            <span class="hljs-comment">// Example using Etherscan API (you can use Alchemy/Moralis/Blockscout too)</span>
            <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> fetch(
                <span class="hljs-string">`https://api-sepolia.etherscan.io/api?module=account&amp;action=txlist&amp;address=<span class="hljs-subst">${address}</span>&amp;startblock=0&amp;endblock=99999999&amp;page=<span class="hljs-subst">${pageNumber}</span>&amp;offset=5&amp;sort=desc&amp;apikey=<span class="hljs-subst">${process.env.NEXT_PUBLIC_ETHERSCAN_API_KEY}</span>`</span>
            );
            <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> res.json();
            setTransactions(data.result);

        } <span class="hljs-keyword">catch</span> (err) {
            <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error fetching transactions:"</span>, err);
        } <span class="hljs-keyword">finally</span> {
            setLoading(<span class="hljs-literal">false</span>);
        }
    };

    useEffect(<span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">if</span> (isConnected) {
            fetchTransactions(page);
        }
    }, [page, isConnected]);

    <span class="hljs-keyword">if</span> (!isConnected) {
        <span class="hljs-keyword">return</span> (
            &lt;div className=<span class="hljs-string">"flex items-center justify-center min-h-screen"</span>&gt;
                &lt;div className=<span class="hljs-string">"p-6 border rounded-lg shadow-md text-center"</span>&gt;
                    &lt;p className=<span class="hljs-string">"text-lg font-semibold"</span>&gt;No wallet connected&lt;/p&gt;
                    &lt;p className=<span class="hljs-string">"text-sm text-gray-500 mt-2"</span>&gt;
                        Connect your wallet to view transactions.
                    &lt;/p&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        );
    }

    <span class="hljs-keyword">return</span> (
        &lt;div className=<span class="hljs-string">"p-8 w-full"</span>&gt;
            &lt;h1 className=<span class="hljs-string">"text-2xl font-bold mb-6"</span>&gt;Transactions&lt;/h1&gt;

            {loading ? (
                &lt;p&gt;Loading transactions...&lt;/p&gt;
            ) : (
                &lt;div className=<span class="hljs-string">"overflow-x-auto border rounded-lg"</span>&gt;
                    &lt;table className=<span class="hljs-string">"min-w-full text-sm"</span>&gt;
                        &lt;thead className=<span class="hljs-string">"bg-gray-100"</span>&gt;
                        &lt;tr&gt;
                            &lt;th className=<span class="hljs-string">"px-4 py-2 text-left"</span>&gt;Sender&lt;/th&gt;
                            &lt;th className=<span class="hljs-string">"px-4 py-2 text-left"</span>&gt;Receiver&lt;/th&gt;
                            &lt;th className=<span class="hljs-string">"px-4 py-2"</span>&gt;Type&lt;/th&gt;
                            &lt;th className=<span class="hljs-string">"px-4 py-2"</span>&gt;<span class="hljs-built_in">Date</span>&lt;/th&gt;
                            &lt;th className=<span class="hljs-string">"px-4 py-2"</span>&gt;Amount (ETH)&lt;/th&gt;
                            &lt;th className=<span class="hljs-string">"px-4 py-2"</span>&gt;Status&lt;/th&gt;
                        &lt;/tr&gt;
                        &lt;/thead&gt;
                        &lt;tbody&gt;
                        {transactions.map(<span class="hljs-function">(<span class="hljs-params">tx</span>) =&gt;</span> {
                            <span class="hljs-keyword">const</span> <span class="hljs-keyword">type</span> =
                                tx.from.toLowerCase() === address?.toLowerCase()
                                    ? <span class="hljs-string">"Sent"</span>
                                    : <span class="hljs-string">"Received"</span>;

                            <span class="hljs-keyword">const</span> date = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(
                                <span class="hljs-built_in">parseInt</span>(tx.timeStamp.toString()) * <span class="hljs-number">1000</span>
                            ).toLocaleString();

                            <span class="hljs-keyword">const</span> amount = (<span class="hljs-built_in">parseFloat</span>(tx.value) / <span class="hljs-number">1e18</span>).toFixed(<span class="hljs-number">5</span>);

                            <span class="hljs-keyword">return</span> (
                                &lt;tr key={tx.hash} className=<span class="hljs-string">"border-t hover:bg-gray-50"</span>&gt;
                                    &lt;td className=<span class="hljs-string">"px-4 py-2"</span>&gt;{tx.from}&lt;/td&gt;
                                    &lt;td className=<span class="hljs-string">"px-4 py-2"</span>&gt;{tx.to}&lt;/td&gt;
                                    &lt;td className=<span class="hljs-string">"px-4 py-2 text-center"</span>&gt;{<span class="hljs-keyword">type</span>}&lt;/td&gt;
                                    &lt;td className=<span class="hljs-string">"px-4 py-2"</span>&gt;{date}&lt;/td&gt;
                                    &lt;td className=<span class="hljs-string">"px-4 py-2"</span>&gt;{amount}&lt;/td&gt;
                                    &lt;td className=<span class="hljs-string">"px-4 py-2 text-center"</span>&gt;
                                        &lt;a
                                            href={<span class="hljs-string">`https://sepolia.etherscan.io/tx/<span class="hljs-subst">${tx.hash}</span>`</span>}
                                            target=<span class="hljs-string">"_blank"</span>
                                            rel=<span class="hljs-string">"noopener noreferrer"</span>
                                            className=<span class="hljs-string">"inline-flex items-center gap-1 text-blue-600 hover:underline"</span>
                                        &gt;
                                            View &lt;ExternalLink className=<span class="hljs-string">"w-4 h-4"</span>/&gt;
                                        &lt;/a&gt;
                                    &lt;/td&gt;
                                &lt;/tr&gt;
                            );
                        })}
                        &lt;/tbody&gt;
                    &lt;/table&gt;
                &lt;/div&gt;
            )}

            {<span class="hljs-comment">/* Pagination Controls */</span>}
            &lt;div className=<span class="hljs-string">"flex justify-between mt-4"</span>&gt;
                &lt;button
                    disabled={page === <span class="hljs-number">1</span>}
                    onClick={<span class="hljs-function">() =&gt;</span> setPage(<span class="hljs-function">(<span class="hljs-params">p</span>) =&gt;</span> <span class="hljs-built_in">Math</span>.max(p - <span class="hljs-number">1</span>, <span class="hljs-number">1</span>))}
                    className=<span class="hljs-string">"px-4 py-2 border rounded disabled:opacity-50"</span>
                &gt;
                    Previous
                &lt;/button&gt;
                &lt;span className=<span class="hljs-string">"px-4 py-2"</span>&gt;Page {page}&lt;/span&gt;
                &lt;button
                    onClick={<span class="hljs-function">() =&gt;</span> setPage(<span class="hljs-function">(<span class="hljs-params">p</span>) =&gt;</span> p + <span class="hljs-number">1</span>)}
                    className=<span class="hljs-string">"px-4 py-2 border rounded"</span>
                &gt;
                    Next
                &lt;/button&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    );
}
</code></pre>
<h3 id="heading-2-key-features">2. Key Features</h3>
<ul>
<li><p><strong>Etherscan API integration</strong> – fetches real transaction data</p>
</li>
<li><p><strong>Pagination support</strong> – only 5 transactions per page</p>
</li>
<li><p><strong>Clickable transaction hashes</strong> – link to Sepolia Etherscan explorer</p>
</li>
<li><p><strong>Formatted ETH values &amp; timestamps</strong></p>
</li>
</ul>
<h3 id="heading-3-user-flow">3. User Flow</h3>
<ol>
<li><p>User navigates to <strong>Transactions Page</strong></p>
</li>
<li><p>dApp fetches their <strong>latest transactions</strong> from Sepolia testnet</p>
</li>
<li><p>Users can browse through pages for full history</p>
</li>
<li><p>Each transaction is clickable → Opens in <strong>Etherscan</strong></p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755810778355/78a8d169-6048-4aa7-907b-e3fc9de9bfd2.png" alt class="image--center mx-auto" /></p>
<p>In this guide, we built a simple yet powerful <strong>Ethereum dApp</strong> using <strong>Next.js, Wagmi, and MetaMask</strong>.<br />We walked through:</p>
<ul>
<li><p>🔗 <strong>Connecting a crypto wallet</strong> with MetaMask</p>
</li>
<li><p>👛 <strong>Checking wallet balances</strong> on the Sepolia testnet</p>
</li>
<li><p>💸 <strong>Sending ETH transactions</strong> directly from the browser</p>
</li>
<li><p>📜 <strong>Viewing transaction history</strong> with pagination and Etherscan links</p>
</li>
</ul>
<p>This project gives you a <strong>solid foundation</strong> for building decentralized applications. From here, you can expand in many exciting directions:</p>
<h3 id="heading-possible-next-steps">🚀 Possible Next Steps</h3>
<ul>
<li><p><strong>Token Transfers (ERC-20):</strong> Add support for sending tokens like USDC or DAI.</p>
</li>
<li><p><strong>Smart Contracts:</strong> Deploy your own contracts and interact with them.</p>
</li>
<li><p><strong>NFT Support (ERC-721/1155):</strong> Mint, transfer, and display NFTs in your dApp.</p>
</li>
<li><p><strong>Improved UI/UX:</strong> Add notifications, confirmations, and a polished dashboard.</p>
</li>
<li><p><strong>Security Features:</strong> Integrate rate limiting, error handling, and input validation.</p>
</li>
</ul>
<p>👉 This dApp is just the starting point. With these building blocks, you can craft anything from a <strong>DeFi dashboard</strong> to a <strong>full-scale Web3 application</strong>.</p>
<hr />
<p>🌐 <strong>Try it out yourself</strong><br />You can explore the live version here: <a target="_blank" href="https://crypto-wallet-management.vercel.app/"><strong>Deployed dApp</strong></a></p>
<p>💻 <strong>View or fork the code</strong><br />Check out the full source code on GitHub: <a target="_blank" href="https://github.com/icon-gaurav/crypto-wallet-management"><strong>GitHub Repo</strong></a></p>
<hr />
<h3 id="heading-whats-next-in-our-blog-series">🔮 <strong>What’s Next in Our Blog Series?</strong></h3>
<p>In the next post, we’ll explore <strong>integrating smart contracts into our dApp</strong> so that users can interact with decentralized logic directly from the UI.</p>
]]></content:encoded></item><item><title><![CDATA[How I Created an MCP Server for PostgreSQL to Power AI Agents — Components, Architecture & Real Testing]]></title><description><![CDATA[Recently, I built a fully functional MCP (Model Control Plane) server powered by PostgreSQL—something I found incredibly useful when trying to make LLMs interact intelligently with structured data. If you've ever struggled to bolt on a REST API for e...]]></description><link>https://gauravbytes.dev/how-i-created-an-mcp-server-for-postgresql-to-power-ai-agents-components-architecture-and-real-testing</link><guid isPermaLink="true">https://gauravbytes.dev/how-i-created-an-mcp-server-for-postgresql-to-power-ai-agents-components-architecture-and-real-testing</guid><category><![CDATA[mcp]]></category><category><![CDATA[PostgreSQL]]></category><category><![CDATA[AI]]></category><category><![CDATA[agentic AI]]></category><category><![CDATA[claude.ai]]></category><category><![CDATA[Python]]></category><category><![CDATA[llm]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Gaurav Kumar]]></dc:creator><pubDate>Tue, 13 May 2025 08:05:22 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1746992148580/1aa31433-dc65-43cb-8eaf-1335c35a0f52.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Recently, I built a fully functional MCP (Model Control Plane) server powered by PostgreSQL—something I found incredibly useful when trying to make LLMs interact intelligently with structured data. If you've ever struggled to bolt on a REST API for every new resource or wished your AI agent could just “understand” your backend without glue code, this article is for you. Traditional APIs feel like a bottleneck when building modern, adaptive systems—especially when you’re trying to connect them to large language models or automation flows. That’s where MCP shines.</p>
<p>Instead of rigid endpoints and verbose documentation, MCP offers a dynamic, model-based architecture that’s inherently more compatible with how LLMs operate. In this guide, I’ll show you how to build an MCP server backed by PostgreSQL using FastMCP and psycopg2, explain the pain points it solves and walk you through the key steps to get it running—so you can focus on building smarter systems without drowning in boilerplate. Let’s dive in.</p>
<h1 id="heading-what-is-an-mcp-server"><strong>What is an MCP Server?</strong></h1>
<p>Model Context Protocol (MCP) is an open-source set of rules, developed by Anthropic, that establishes a standard way for applications to provide relevant context to Large Language Models (LLMs).</p>
<p>Envision MCP as a universal connector, much like a USB-C port for AI. Just as USB-C allows various devices to connect to a wide range of accessories in a standardized manner, MCP enables AI models to seamlessly interface with diverse data sources and tools. This standardization simplifies the process of building AI agents and complex workflows, offering pre-built integrations and the flexibility to work with different LLM providers while prioritizing data security within your own infrastructure.</p>
<p>Without interfaces like MCP, LLMs are limited to their built-in capabilities and training data. With MCP, they can be empowered to:</p>
<ul>
<li><p>Read files and databases</p>
</li>
<li><p>Execute commands</p>
</li>
<li><p>Access APIs</p>
</li>
<li><p>Interact with local tools</p>
</li>
<li><p>And more!</p>
</li>
</ul>
<p>All of this happens with user oversight and permission, making it both powerful and secure.</p>
<h1 id="heading-mcp-architecture">MCP Architecture</h1>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1746294509997/436dab47-ec18-4b70-a603-ec8b30f14994.png" alt class="image--center mx-auto" /></p>
<p><strong>MCP</strong> has the following components</p>
<ul>
<li><p><strong>MCP Hosts</strong>: Programs like Claude Desktop, IDEs, or AI tools that want to access data through MCP</p>
</li>
<li><p><strong>MCP Clients</strong>: Protocol clients that maintain 1:1 connections with servers</p>
</li>
<li><p><strong>MCP Servers</strong>: Lightweight programs that each expose specific capabilities through the standardized Model Context Protocol</p>
</li>
<li><p><strong>Local Data Sources</strong>: Your computer’s files, databases, and services that MCP servers can securely access</p>
</li>
<li><p><strong>Remote Services</strong>: External systems available over the internet (e.g., through APIs) that MCP servers can connect to</p>
</li>
</ul>
<h1 id="heading-core-mcp-concepts"><strong>Core MCP Concepts</strong></h1>
<p><strong>MCP</strong> servers can provide three main types of capabilities:</p>
<ol>
<li><p><strong>Resources</strong>: File-like data that can be read by clients (like API responses or file contents)</p>
</li>
<li><p><strong>Tools</strong>: Functions that can be called by the LLM (with user approval)</p>
</li>
<li><p><strong>Prompts</strong>: Pre-written templates that help users accomplish specific tasks</p>
</li>
</ol>
<h2 id="heading-mcp-resource">MCP Resource</h2>
<p>Resources are MCP’s way of exposing read-only data to LLMs. A resource is anything that has content that can be read, such as:</p>
<ul>
<li><p>Files on your computer</p>
</li>
<li><p>Database records</p>
</li>
<li><p>API responses</p>
</li>
<li><p>Application data</p>
</li>
<li><p>System information</p>
</li>
</ul>
<p>Each resource has:</p>
<ul>
<li><p>A unique URI (like [<code>file:///example.txt</code>](file:///example.txt) or <code>database://users/123</code>)</p>
</li>
<li><p>A display name</p>
</li>
<li><p>Optional metadata (description, MIME type)</p>
</li>
<li><p>Content (text or binary data)</p>
</li>
</ul>
<h2 id="heading-mcp-tools"><strong>MCP Tools</strong></h2>
<p>Tools are executable functions that LLMs can call to perform actions or retrieve dynamic information. Unlike resources, which are read-only, and prompts, which structure LLM interactions, tools allow LLMs to actively do things like calculate values, make API calls, or modify data.</p>
<p>Tools enable LLMs to interact with systems and perform actions.</p>
<h2 id="heading-what-are-mcp-prompts"><strong>What are MCP Prompts?</strong></h2>
<p>Prompts in MCP are structured templates that servers provide to standardize interactions with language models. Unlike resources which provide data, or tools which execute actions, prompts define reusable message sequences and workflows that help guide LLM behavior in consistent, predictable ways.</p>
<p>In this article we are going to create mcp server for postgreSQL step by step</p>
<h1 id="heading-setting-up-your-development-environment">Setting Up Your Development Environment</h1>
<p>First, let’s install <code>uv</code> and set up our Python project and environment:</p>
<pre><code class="lang-powershell">powershell <span class="hljs-literal">-ExecutionPolicy</span> ByPass <span class="hljs-literal">-c</span> <span class="hljs-string">"irm https://astral.sh/uv/install.ps1 | iex"</span>
</code></pre>
<p>Make sure to restart your terminal afterwards to ensure that the <code>uv</code> command gets picked up.</p>
<h2 id="heading-create-project-directory">Create Project Directory</h2>
<p>Now, let’s create and set up our project:</p>
<pre><code class="lang-powershell"><span class="hljs-comment"># Create a new directory for our project</span>
uv init mcp<span class="hljs-literal">-server</span>
<span class="hljs-built_in">cd</span> mcp<span class="hljs-literal">-server</span>

<span class="hljs-comment"># Create virtual environment and activate it</span>
uv venv
.venv\Scripts\activate

<span class="hljs-comment"># Install dependencies</span>
uv add mcp[<span class="hljs-type">cli</span>] httpx

<span class="hljs-comment"># Create our server file</span>
<span class="hljs-built_in">new-item</span> server.py
</code></pre>
<p>Now let’s dive into building your server.</p>
<h2 id="heading-building-your-server">Building your server</h2>
<p>Update the <code>server.py</code> according to the following code:</p>
<ol>
<li><p>Import the packages</p>
</li>
<li><p>Database wrapper class for database connection and database queries</p>
</li>
<li><p>Define App Context</p>
</li>
<li><p>Define app lifespan and pass this lifespan to MCP instance along with MCP Server name</p>
</li>
</ol>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> contextlib <span class="hljs-keyword">import</span> asynccontextmanager
<span class="hljs-keyword">from</span> collections.abc <span class="hljs-keyword">import</span> AsyncIterator
<span class="hljs-keyword">from</span> dataclasses <span class="hljs-keyword">import</span> dataclass

<span class="hljs-keyword">import</span> asyncpg
<span class="hljs-keyword">from</span> mcp.server.fastmcp <span class="hljs-keyword">import</span> FastMCP, Context

<span class="hljs-comment"># Async PostgreSQL wrapper using asyncpg</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Database</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, pool: asyncpg.Pool</span>):</span>
        self.pool = pool

<span class="hljs-meta">    @classmethod</span>
    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">connect</span>(<span class="hljs-params">cls</span>) -&gt; "Database":</span>
        pool = <span class="hljs-keyword">await</span> asyncpg.create_pool(
            user=<span class="hljs-string">"#user"</span>,
            password=<span class="hljs-string">"#your_password"</span>,
            database=<span class="hljs-string">"Your_database_name"</span>,
            host=<span class="hljs-string">"#database_host"</span>,
            port=<span class="hljs-string">'#Port_number'</span>,
        )
        <span class="hljs-keyword">return</span> cls(pool)

    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">disconnect</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-keyword">await</span> self.pool.close()

    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">query</span>(<span class="hljs-params">self, query: str</span>) -&gt; list:</span>
        <span class="hljs-keyword">async</span> <span class="hljs-keyword">with</span> self.pool.acquire() <span class="hljs-keyword">as</span> conn:
            <span class="hljs-keyword">try</span>:
                <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> conn.fetch(query)
            <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
                print(<span class="hljs-string">f"Query error: <span class="hljs-subst">{e}</span>"</span>)
                <span class="hljs-keyword">return</span> []

    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">fetch_schema</span>(<span class="hljs-params">self</span>) -&gt; list:</span>
        query = <span class="hljs-string">"""
        SELECT table_name FROM information_schema.tables
        WHERE table_schema = 'public'
        """</span>
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> self.query(query)


<span class="hljs-meta">@dataclass</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AppContext</span>:</span>
    db: Database


<span class="hljs-meta">@asynccontextmanager</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">app_lifespan</span>(<span class="hljs-params">server: FastMCP</span>) -&gt; AsyncIterator[AppContext]:</span>
    db = <span class="hljs-keyword">await</span> Database.connect()
    <span class="hljs-keyword">try</span>:
        <span class="hljs-keyword">yield</span> AppContext(db=db)
    <span class="hljs-keyword">finally</span>:
        <span class="hljs-keyword">await</span> db.disconnect()


mcp = FastMCP(<span class="hljs-string">"PostgresMCPServer"</span>, lifespan=app_lifespan)
</code></pre>
<p>The <strong>FastMCP</strong> class uses Python type hints and docstrings to automatically generate tool definitions, making it easy to create and maintain MCP tools.</p>
<h2 id="heading-implementing-mcp-tools"><strong>Implementing MCP tools</strong></h2>
<p>The tool execution handler is responsible for actually executing the logic of each tool. Let’s add it:</p>
<pre><code class="lang-python"><span class="hljs-meta">@mcp.tool("fetch_schema")</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">fetch_schema</span>(<span class="hljs-params">ctx: Context</span>) -&gt; str:</span>
    db = ctx.request_context.lifespan_context.db
    schema = <span class="hljs-keyword">await</span> db.fetch_schema()
    <span class="hljs-keyword">return</span> str([record[<span class="hljs-string">"table_name"</span>] <span class="hljs-keyword">for</span> record <span class="hljs-keyword">in</span> schema])


<span class="hljs-meta">@mcp.tool("fetch_all_tables")</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">fetch_all_tables</span>(<span class="hljs-params">ctx: Context</span>) -&gt; str:</span>
    db = ctx.request_context.lifespan_context.db
    query = <span class="hljs-string">"SELECT * FROM information_schema.tables WHERE table_schema='public'"</span>
    tables = <span class="hljs-keyword">await</span> db.query(query)
    <span class="hljs-keyword">return</span> str(tables)


<span class="hljs-meta">@mcp.tool("run_query")</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">run_query</span>(<span class="hljs-params">ctx: Context, query: str</span>) -&gt; str:</span>
    db = ctx.request_context.lifespan_context.db
    result = <span class="hljs-keyword">await</span> db.query(query)
    <span class="hljs-keyword">return</span> str(result)
</code></pre>
<h2 id="heading-httpsmodelcontextprotocolioquickstartserverrunning-the-serverrunning-the-server"><a target="_blank" href="https://modelcontextprotocol.io/quickstart/server#running-the-server"><strong>​</strong></a><strong>Running the server</strong></h2>
<p>Finally, let’s initialize and run the server:</p>
<pre><code class="lang-python"><span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    <span class="hljs-comment"># Initialize and run the server</span>
    mcp.run()
</code></pre>
<p>Your server is complete! Run <code>uv run server.py</code> to confirm that everything’s working.</p>
<p>Let’s now test your server from an existing MCP host, Claude for Desktop.</p>
<h1 id="heading-testing-your-server-with-claude-for-desktop"><strong>Testing your server with Claude for Desktop</strong></h1>
<p>There are several ways to test your MCP server. One way is with Claude Desktop, and you can also use the MCP Inspector tool to test all capabilities during development.</p>
<h2 id="heading-setting-up-claude-desktop"><strong>Setting Up Claude Desktop</strong></h2>
<p>Here are the steps for setting up an MCP server in Claude Desktop:</p>
<ol>
<li><p>Install Claude for Desktop if you haven’t already</p>
</li>
<li><p>Open Claude and access Settings</p>
<p> You can access it in your [path of claude desktop installation]<strong>/claude_desktop_config.json</strong></p>
</li>
<li><p>Edit configuration</p>
<pre><code class="lang-json"> {
   <span class="hljs-attr">"mcpServers"</span>: {
     <span class="hljs-attr">"PostgresMCPServer"</span>: {
       <span class="hljs-attr">"command"</span>: <span class="hljs-string">"uv"</span>,
       <span class="hljs-attr">"args"</span>: [
         <span class="hljs-string">"run"</span>,
         <span class="hljs-string">"--with"</span>,
         <span class="hljs-string">"mcp[cli]"</span>,
         <span class="hljs-string">"mcp"</span>,
         <span class="hljs-string">"run"</span>,
         <span class="hljs-string">"C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\server.py"</span>
       ]
     }
   }
 }
</code></pre>
</li>
</ol>
<p>This tells Claude for Desktop:</p>
<ol>
<li><p>There’s an MCP server named “weather”</p>
</li>
<li><p>To launch it by running <code>uv command</code> with the following <code>arguments</code></p>
</li>
</ol>
<p>Save the file, and restart <strong>Claude for Desktop</strong>.</p>
<h2 id="heading-test-with-claude-desktop"><strong>Test with Claude Desktop</strong></h2>
<p>Let’s make sure Claude for Desktop is picking up the 3 tools we’ve exposed in our <code>PostgresMCPServer</code> server. You can do this by looking for the setting icon</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1746987377731/b1237596-594a-4224-8f9c-c86b6a60cae7.png" alt class="image--center mx-auto" /></p>
<p>After clicking on the setting icon, you should see the MCP server listed:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1746987466775/2d4411d5-d79b-4a5a-8b41-fc3b8f2dd047.png" alt class="image--center mx-auto" /></p>
<p>After clicking the <code>PostgresMCPServer</code>, you should see the MCP tools listed:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1746987583736/22a85c19-108a-40da-8b2d-6ccfb62ea513.png" alt class="image--center mx-auto" /></p>
<p>If you can see the tools here, you can now test your server</p>
<p>We will test the server using the following commands:</p>
<ol>
<li><p>Retrieve information on all tables within the database.</p>
</li>
<li><p>Use Claude Desktop to summarize the relationships between the tables.</p>
</li>
<li><p>Use Claude to generate a bar graph illustrating the comparison between events and registration counts.</p>
</li>
</ol>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://vimeo.com/1083801920/d9ec481fed?ts=0&amp;share=copy">https://vimeo.com/1083801920/d9ec481fed?ts=0&amp;share=copy</a></div>
<p> </p>
<p>Finally…</p>
<p>Building an MCP server powered by PostgreSQL offers a dynamic and efficient way to integrate large language models with structured data. By leveraging the Model Context Protocol, developers can create adaptive systems that bypass the limitations of traditional APIs, allowing AI agents to interact seamlessly with various data sources and tools. This approach not only simplifies the development process but also enhances the capabilities of AI models, enabling them to perform complex tasks with greater ease and security. By following the steps outlined in this guide, you can set up your own MCP server and unlock new possibilities for smarter, more responsive systems.</p>
]]></content:encoded></item><item><title><![CDATA[🚀 Understanding GraphQL Federation in Microservices Architecture]]></title><description><![CDATA[As applications grow in complexity, microservices become the go-to architectural pattern. But with them comes a new challenge: API sprawl. Each service manages its own schema, leading to a tangled mess of REST endpoints or isolated GraphQL APIs.
Ente...]]></description><link>https://gauravbytes.dev/understanding-graphql-federation-in-microservices-architecture</link><guid isPermaLink="true">https://gauravbytes.dev/understanding-graphql-federation-in-microservices-architecture</guid><category><![CDATA[GraphQL]]></category><category><![CDATA[Microservices]]></category><category><![CDATA[APIs]]></category><category><![CDATA[subgraph]]></category><category><![CDATA[Apollo GraphQL]]></category><category><![CDATA[backend]]></category><category><![CDATA[gateway]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[System Design]]></category><dc:creator><![CDATA[Gaurav Kumar]]></dc:creator><pubDate>Tue, 22 Apr 2025 05:23:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1745222657126/9f8c4e67-957d-4211-a1f0-6a9dd42ef8e1.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As applications grow in complexity, microservices become the go-to architectural pattern. But with them comes a new challenge: <strong>API sprawl</strong>. Each service manages its own schema, leading to a tangled mess of REST endpoints or isolated GraphQL APIs.</p>
<p><strong>Enter GraphQL Federation</strong>—a powerful solution that lets you compose a unified GraphQL API from multiple microservices, while keeping each service independently developed and deployed.</p>
<p>In this article, we’ll break down what GraphQL Federation is, how it works, and why it’s a game-changer for modern backend systems.</p>
<h1 id="heading-what-is-graphql-federation">🧩 What Is GraphQL Federation?</h1>
<p><strong>GraphQL Federation</strong> is a technique introduced by <strong>Apollo</strong> that allows multiple GraphQL services (called <em>subgraphs</em>) to be merged into a single, unified API gateway (called the <em>federated gateway</em>).</p>
<p>It solves a critical issue in microservice architectures: how to let teams work independently on their own GraphQL schemas, while still offering a seamless client experience through a <strong>single graph</strong>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1745221622982/008c9db7-593c-4826-abce-457f21b748de.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-key-components">Key Components:</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Component</td><td>Description</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Subgraph</strong></td><td>An individual GraphQL service with part of the overall schema</td></tr>
<tr>
<td><strong>Federated Gateway</strong></td><td>The GraphQL server that stitches all subgraphs into one unified schema</td></tr>
<tr>
<td><strong>@key</strong> Directive</td><td>Used to define the primary key for an entity shared across subgraphs</td></tr>
<tr>
<td><strong>@requires / @provides</strong></td><td>Manage dependency fields between subgraphs</td></tr>
</tbody>
</table>
</div><h1 id="heading-core-concepts-explained">🔍 Core Concepts Explained</h1>
<h3 id="heading-1-entity-resolution-with-key">1. <strong>Entity Resolution with @key</strong></h3>
<p>If multiple services work on the same entity (e.g., <code>User</code>), the <code>@key</code> directive tells the gateway how to resolve it:</p>
<pre><code class="lang-graphql"><span class="hljs-keyword">type</span> User <span class="hljs-meta">@key</span>(<span class="hljs-symbol">fields:</span> <span class="hljs-string">"id"</span>) {
  <span class="hljs-symbol">id:</span> ID!
  <span class="hljs-symbol">name:</span> String
}
</code></pre>
<h3 id="heading-2-service-extension-with-extends">2. <strong>Service Extension with @extends</strong></h3>
<p>A service can add fields to an entity defined in another service:</p>
<pre><code class="lang-graphql"><span class="hljs-comment"># In the reviews subgraph</span>
extend <span class="hljs-keyword">type</span> User <span class="hljs-meta">@key</span>(<span class="hljs-symbol">fields:</span> <span class="hljs-string">"id"</span>) {
  <span class="hljs-symbol">id:</span> ID! 
  <span class="hljs-symbol">reviews:</span> [Review]
}
</code></pre>
<h3 id="heading-3-gateway-composition">3. <strong>Gateway Composition</strong></h3>
<p>The gateway uses service definitions from each subgraph (via introspection or static configs) and composes them into a single schema that the client can query.</p>
<h1 id="heading-real-world-example-e-commerce-platform">💡 Real-World Example: E-Commerce Platform</h1>
<p>Let’s say you're building an e-commerce platform with the following services:</p>
<ul>
<li><p><strong>User Service</strong>: Manages user data.</p>
</li>
<li><p><strong>Product Service</strong>: Manages products available in the store.</p>
</li>
<li><p><strong>Order Service</strong>: Manages customer orders and ties users to the products they purchase.</p>
</li>
</ul>
<p>With <strong>GraphQL Federation</strong>, each service defines its part of the schema and can <strong>extend entities</strong> from other services to build a unified graph.</p>
<h3 id="heading-schema-in-user-service">🔹 Schema in User Service:</h3>
<pre><code class="lang-graphql"><span class="hljs-keyword">type</span> User <span class="hljs-meta">@key</span>(<span class="hljs-symbol">fields:</span> <span class="hljs-string">"id"</span>) {
  <span class="hljs-symbol">id:</span> ID!
  <span class="hljs-symbol">name:</span> String
  <span class="hljs-symbol">email:</span> String
}
</code></pre>
<p>This defines the <code>User</code> entity and its primary key (<code>id</code>). It can be referenced by other services.</p>
<h3 id="heading-schema-in-product-service">🔹 Schema in Product Service:</h3>
<pre><code class="lang-graphql"><span class="hljs-keyword">type</span> Product <span class="hljs-meta">@key</span>(<span class="hljs-symbol">fields:</span> <span class="hljs-string">"id"</span>) {
  <span class="hljs-symbol">id:</span> ID!
  <span class="hljs-symbol">name:</span> String
  <span class="hljs-symbol">price:</span> Float
}
</code></pre>
<p>Each product has its own ID, name, and price.</p>
<h3 id="heading-schema-in-order-service">🔹 Schema in Order Service:</h3>
<pre><code class="lang-graphql"><span class="hljs-keyword">type</span> Order <span class="hljs-meta">@key</span>(<span class="hljs-symbol">fields:</span> <span class="hljs-string">"id"</span>) {
   <span class="hljs-symbol">id:</span> ID!
   <span class="hljs-symbol">quantity:</span> Int
   <span class="hljs-symbol">orderDate:</span> String
   <span class="hljs-symbol">user:</span>User <span class="hljs-meta">@provides</span>(<span class="hljs-symbol">fields:</span> <span class="hljs-string">"id"</span>)
   <span class="hljs-symbol">product:</span>Product <span class="hljs-meta">@provides</span>(<span class="hljs-symbol">fields:</span> <span class="hljs-string">"id"</span>)
   <span class="hljs-symbol">total:</span> Float
}

extend <span class="hljs-keyword">type</span> User <span class="hljs-meta">@key</span>(<span class="hljs-symbol">fields:</span> <span class="hljs-string">"id"</span>) {
  <span class="hljs-symbol">id:</span> ID! 
}

extend <span class="hljs-keyword">type</span> Product <span class="hljs-meta">@key</span>(<span class="hljs-symbol">fields:</span> <span class="hljs-string">"id"</span>) {
  <span class="hljs-symbol">id:</span> ID! 
}
</code></pre>
<p>This service defines the <code>Order</code> entity, and extends both <code>User</code> and <code>Product</code> entities to show which users placed orders and which products were purchased.</p>
<h3 id="heading-unified-query-at-the-gateway">🧪 Unified Query at the Gateway:</h3>
<p>Once the services are federated into a single gateway, clients can query them as one unified schema:</p>
<pre><code class="lang-graphql"><span class="hljs-keyword">query</span> {
  orders {
    id
    quantity
    orderDate
    user {
      id
      name
      email
    }
    product {
      id
      name
      price
    }
  }
}
</code></pre>
<p>This single query fetches user details, their orders, and associated product details—<strong>even though the data is spread across three separate services</strong>.</p>
<h3 id="heading-how-federation-makes-this-work">🚦 How Federation Makes This Work</h3>
<ul>
<li><p>The <strong>User Service</strong> owns the <code>User</code> type.</p>
</li>
<li><p>The <strong>Product Service</strong> owns the <code>Product</code> type.</p>
</li>
<li><p>The <strong>Order Service</strong> stitches everything together by referencing <code>User</code> and <code>Product</code> entities using the <code>@extends</code> directive.</p>
</li>
</ul>
<p>This setup allows each team to focus on their domain, deploy independently, and still contribute to a <strong>shared graph</strong> that feels seamless to the client.</p>
<h1 id="heading-setting-up-apollo-federation-simplified-steps">⚙️ Setting Up Apollo Federation (Simplified Steps)</h1>
<p>To federate your services using Apollo Federation, follow these steps:</p>
<h3 id="heading-step-1-set-up-each-subgraph-user-product-order">🔧 Step 1: Set Up Each Subgraph (User, Product, Order)</h3>
<p>Each service is an <strong>independent GraphQL server</strong> using the <code>@apollo/subgraph</code> package.</p>
<h4 id="heading-11-install-dependencies">1.1 Install Dependencies</h4>
<pre><code class="lang-bash">npm install @apollo/subgraph graphql
</code></pre>
<h3 id="heading-user-service">👤 <strong>User Service</strong></h3>
<h4 id="heading-schema-user-schemagraphql">Schema (<code>user-schema.graphql</code>)</h4>
<pre><code class="lang-graphql"><span class="hljs-keyword">type</span> User <span class="hljs-meta">@key</span>(<span class="hljs-symbol">fields:</span> <span class="hljs-string">"id"</span>) {
  <span class="hljs-symbol">id:</span> ID!
  <span class="hljs-symbol">name:</span> String
  <span class="hljs-symbol">email:</span> String
}
</code></pre>
<h4 id="heading-server-setup-indexjs">Server Setup (<code>index.js</code>)</h4>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> { ApolloServer } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'@apollo/server'</span>);
<span class="hljs-keyword">const</span> { buildSubgraphSchema } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'@apollo/subgraph'</span>);
<span class="hljs-keyword">const</span> { startStandaloneServer } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'@apollo/server/standalone'</span>);
<span class="hljs-keyword">const</span> gql = <span class="hljs-built_in">require</span>(<span class="hljs-string">'graphql-tag'</span>);

<span class="hljs-keyword">const</span> typeDefs = gql(<span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>).readFileSync(<span class="hljs-string">'./user-schema.graphql'</span>, <span class="hljs-string">'utf-8'</span>));
<span class="hljs-keyword">const</span> resolvers = {
  <span class="hljs-attr">User</span>: {
    __resolveReference(user) {
      <span class="hljs-keyword">return</span> users.find(<span class="hljs-function"><span class="hljs-params">u</span> =&gt;</span> u.id === user.id);
    },
  },
};

<span class="hljs-keyword">const</span> server = <span class="hljs-keyword">new</span> ApolloServer({
  <span class="hljs-attr">schema</span>: buildSubgraphSchema([{ typeDefs, resolvers }]),
});

startStandaloneServer(server, { <span class="hljs-attr">listen</span>: { <span class="hljs-attr">port</span>: <span class="hljs-number">4001</span> } });
</code></pre>
<hr />
<h3 id="heading-product-service">📦 <strong>Product Service</strong></h3>
<h4 id="heading-schema-product-schemagraphql">Schema (<code>product-schema.graphql</code>)</h4>
<pre><code class="lang-graphql"><span class="hljs-keyword">type</span> Product <span class="hljs-meta">@key</span>(<span class="hljs-symbol">fields:</span> <span class="hljs-string">"id"</span>) {
  <span class="hljs-symbol">id:</span> ID!
  <span class="hljs-symbol">name:</span> String
  <span class="hljs-symbol">price:</span> Float
}
</code></pre>
<h4 id="heading-server-setup-indexjs-1">Server Setup (<code>index.js</code>)</h4>
<p>Use the same <code>@apollo/subgraph</code> setup, listening on port <code>4002</code>.</p>
<h3 id="heading-order-service">📑 <strong>Order Service</strong></h3>
<p>This one <strong>extends</strong> both <code>User</code> and <code>Product</code> entities.</p>
<h4 id="heading-schema-order-schemagraphql">Schema (<code>order-schema.graphql</code>)</h4>
<pre><code class="lang-graphql"><span class="hljs-keyword">type</span> Order <span class="hljs-meta">@key</span>(<span class="hljs-symbol">fields:</span> <span class="hljs-string">"id"</span>) {
  <span class="hljs-symbol">id:</span> ID!
  <span class="hljs-symbol">user:</span> User
  <span class="hljs-symbol">products:</span> [Product]
  <span class="hljs-symbol">total:</span> Float
}

extend <span class="hljs-keyword">type</span> User <span class="hljs-meta">@key</span>(<span class="hljs-symbol">fields:</span> <span class="hljs-string">"id"</span>) {
  <span class="hljs-symbol">id:</span> ID!
  <span class="hljs-symbol">orders:</span> [Order]
}

extend <span class="hljs-keyword">type</span> Product <span class="hljs-meta">@key</span>(<span class="hljs-symbol">fields:</span> <span class="hljs-string">"id"</span>) {
  <span class="hljs-symbol">id:</span> ID!
  <span class="hljs-symbol">purchasedBy:</span> [User]
}
</code></pre>
<h4 id="heading-server-setup">Server Setup</h4>
<p>Use the same <code>@apollo/subgraph</code> setup, listening on port <code>4003</code>.</p>
<h3 id="heading-step-2-set-up-apollo-gateway">🛠 Step 2: Set Up Apollo Gateway</h3>
<p>This service <strong>composes the subgraphs</strong> and exposes one unified schema.</p>
<h4 id="heading-install-required-packages">Install Required Packages</h4>
<pre><code class="lang-bash">npm install @apollo/gateway graphql @apollo/server
</code></pre>
<h4 id="heading-gateway-setup-gatewayjs">Gateway Setup (<code>gateway.js</code>)</h4>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> { ApolloServer } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'@apollo/server'</span>);
<span class="hljs-keyword">const</span> { ApolloGateway } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'@apollo/gateway'</span>);
<span class="hljs-keyword">const</span> { startStandaloneServer } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'@apollo/server/standalone'</span>);

<span class="hljs-keyword">const</span> gateway = <span class="hljs-keyword">new</span> ApolloGateway({
  <span class="hljs-attr">serviceList</span>: [
    { <span class="hljs-attr">name</span>: <span class="hljs-string">'user'</span>, <span class="hljs-attr">url</span>: <span class="hljs-string">'http://localhost:4001'</span> },
    { <span class="hljs-attr">name</span>: <span class="hljs-string">'product'</span>, <span class="hljs-attr">url</span>: <span class="hljs-string">'http://localhost:4002'</span> },
    { <span class="hljs-attr">name</span>: <span class="hljs-string">'order'</span>, <span class="hljs-attr">url</span>: <span class="hljs-string">'http://localhost:4003'</span> },
  ],
});

<span class="hljs-keyword">const</span> server = <span class="hljs-keyword">new</span> ApolloServer({ gateway, <span class="hljs-attr">subscriptions</span>: <span class="hljs-literal">false</span> });

startStandaloneServer(server, { <span class="hljs-attr">listen</span>: { <span class="hljs-attr">port</span>: <span class="hljs-number">4000</span> } }).then(<span class="hljs-function">(<span class="hljs-params">{ url }</span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`🚀 Gateway ready at <span class="hljs-subst">${url}</span>`</span>);
});
</code></pre>
<hr />
<h3 id="heading-step-3-start-all-services">🔁 Step 3: Start All Services</h3>
<p>In separate terminal tabs or scripts:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Terminal 1</span>
node user/index.js

<span class="hljs-comment"># Terminal 2</span>
node product/index.js

<span class="hljs-comment"># Terminal 3</span>
node order/index.js

<span class="hljs-comment"># Terminal 4</span>
node gateway.js
</code></pre>
<p>Now your GraphQL API is live at <a target="_blank" href="http://localhost:4000/graphql"><code>http://localhost:4000/graphql</code></a> and unified!</p>
<h3 id="heading-step-4-query-the-federated-schema">🧪 Step 4: Query the Federated Schema</h3>
<p>Test it with a powerful, nested query:</p>
<pre><code class="lang-graphql"><span class="hljs-keyword">query</span> {
  orders {
    id
    quantity
    orderDate
    user {
      id
      name
      email
    }
    product {
      id
      name
      price
    }
  }
}
</code></pre>
<p>Even though <code>user</code>, <code>order</code>, and <code>product</code> are handled by <strong>separate microservices</strong>, this <strong>single query works flawlessly</strong> via federation!</p>
<h1 id="heading-pros-and-cons-of-using-graphql-federation">⚖️ Pros and Cons of Using GraphQL Federation</h1>
<p>Before adopting GraphQL Federation in your architecture, it’s important to weigh its advantages and potential challenges. Here’s a balanced look:</p>
<h3 id="heading-pros-of-graphql-federation">✅ Pros of GraphQL Federation</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Benefit</td><td>Description</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Modular Architecture</strong></td><td>Each subgraph service is owned and maintained by individual teams. This promotes autonomy and scalability.</td></tr>
<tr>
<td><strong>Single Unified Graph</strong></td><td>The client interacts with one clean, unified API—regardless of how many services are involved behind the scenes.</td></tr>
<tr>
<td><strong>Independent Deployment</strong></td><td>Subgraphs can be deployed independently without needing to rebuild or restart the gateway or other services.</td></tr>
<tr>
<td><strong>Schema Collaboration</strong></td><td>Teams can contribute to shared entities (e.g., <code>User</code>, <code>Product</code>) using directives like <code>@extends</code> and <code>@key</code>, enabling tight yet controlled coupling.</td></tr>
<tr>
<td><strong>Optimized Developer Experience</strong></td><td>Great tools from Apollo like <code>Rover</code>, schema registry, and schema checks make collaboration and CI/CD smooth.</td></tr>
<tr>
<td><strong>Frontend Simplicity</strong></td><td>Frontend developers can query complex relationships in a single request, improving developer productivity and reducing over-fetching.</td></tr>
</tbody>
</table>
</div><h3 id="heading-cons-of-graphql-federation">⚠️ Cons of GraphQL Federation</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Limitation</td><td>Description</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Increased Operational Complexity</strong></td><td>Managing multiple subgraph services and the gateway adds overhead to DevOps and deployment pipelines.</td></tr>
<tr>
<td><strong>Cross-Team Coordination</strong></td><td>Schema design requires coordination across teams when extending shared entities, especially in larger orgs.</td></tr>
<tr>
<td><strong>Performance Bottlenecks</strong></td><td>Poorly designed federated queries can result in N+1 problems or excessive network calls between services. Caching and batching become more critical.</td></tr>
<tr>
<td><strong>Learning Curve</strong></td><td>Developers must understand GraphQL directives (<code>@key</code>, <code>@extends</code>, etc.), entity resolution, and subgraph architecture.</td></tr>
<tr>
<td><strong>Debugging Can Be Tricky</strong></td><td>When things break, it can be harder to trace errors across subgraphs and the gateway.</td></tr>
<tr>
<td><strong>Gateway is a Single Point of Failure</strong></td><td>Unless properly scaled and load-balanced, the Apollo Gateway can become a bottleneck or SPOF (single point of failure).</td></tr>
</tbody>
</table>
</div><h3 id="heading-when-to-use-graphql-federation">🧠 When to Use GraphQL Federation</h3>
<p>Use Federation when:</p>
<ul>
<li><p>You have multiple teams working on different domains (e.g., user, orders, products).</p>
</li>
<li><p>You want to avoid monolithic GraphQL servers.</p>
</li>
<li><p>You’re already on a microservices architecture and need a clean API layer.</p>
</li>
<li><p>You need to enable schema composition and controlled entity extension.</p>
</li>
</ul>
<p>Avoid Federation when:</p>
<ul>
<li><p>You have a small team or a small monolithic app.</p>
</li>
<li><p>Your microservices don't share much schema or are loosely coupled.</p>
</li>
<li><p>You’re not familiar with GraphQL or don’t have time to invest in tooling/setup.</p>
</li>
</ul>
<h1 id="heading-conclusion-future-proof-your-backend">🎯 Conclusion: Future-Proof Your Backend</h1>
<p>GraphQL Federation brings structure, scalability, and clarity to the chaos of microservice APIs. It aligns perfectly with the modular development goals of backend teams, while keeping the developer experience smooth on both server and client ends.</p>
<p>If you're building a microservices architecture or already using GraphQL, <strong>federation is worth exploring</strong>—especially with tools like Apollo Federation leading the way.</p>
<p>👉 <strong>Ready to federate your GraphQL APIs?</strong><br />Start small—break one schema into subgraphs and try running a gateway locally. You’ll see the power of Federation in action.</p>
<p><strong>Don’t forget to share this post with your team and bookmark it for reference!</strong></p>
]]></content:encoded></item><item><title><![CDATA[Using DataLoader to Batch and Optimize Database Queries in GraphQL ⚡]]></title><description><![CDATA[What is DataLoader?
DataLoader is a generic utility developed by Facebook for batching and caching database queries efficiently in GraphQL applications. It helps in reducing redundant queries and solving the N+1 query problem by grouping multiple que...]]></description><link>https://gauravbytes.dev/using-dataloader-to-batch-and-optimize-database-queries-in-graphql</link><guid isPermaLink="true">https://gauravbytes.dev/using-dataloader-to-batch-and-optimize-database-queries-in-graphql</guid><category><![CDATA[Node.js]]></category><category><![CDATA[GraphQL]]></category><category><![CDATA[APIs]]></category><category><![CDATA[performance]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Express]]></category><category><![CDATA[backend]]></category><category><![CDATA[software development]]></category><category><![CDATA[SQL]]></category><category><![CDATA[MongoDB]]></category><category><![CDATA[prisma]]></category><category><![CDATA[Sequelize]]></category><dc:creator><![CDATA[Gaurav Kumar]]></dc:creator><pubDate>Mon, 17 Mar 2025 15:59:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1742067484075/8c085cad-4915-4b16-a088-03ff760b6d95.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-what-is-dataloader">What is DataLoader?</h1>
<p>DataLoader is a generic utility developed by Facebook for batching and caching database queries efficiently in GraphQL applications. It helps in reducing redundant queries and solving the <strong>N+1 query problem</strong> by grouping multiple queries into a single batch request.</p>
<h3 id="heading-key-features-of-dataloader">Key Features of DataLoader:</h3>
<ol>
<li><p><strong>Batching</strong>: Combines multiple database requests into a single query to optimize performance.</p>
</li>
<li><p><strong>Caching</strong>: Stores results to prevent redundant database calls in the same request cycle.</p>
</li>
<li><p><strong>Asynchronous Execution</strong>: Uses promises to handle multiple database requests efficiently.</p>
</li>
</ol>
<p>By integrating DataLoader into GraphQL resolvers, we can significantly improve the efficiency and scalability of our APIs.</p>
<h1 id="heading-why-dataloader-is-necessary">Why DataLoader is Necessary 🧐</h1>
<p>GraphQL APIs provide flexibility in data fetching, allowing clients to request only the necessary data. However, this flexibility can lead to the <strong>N+1 query problem</strong>, a common performance issue where multiple queries to related data cause database inefficiencies.</p>
<h1 id="heading-the-n1-query-problem">The N+1 Query Problem 🏗️</h1>
<p>In a GraphQL resolver, if fetching related data requires separate queries for each parent record, it results in an exponential increase in database calls. Consider an example:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> resolvers = {
  <span class="hljs-attr">Query</span>: {
    <span class="hljs-attr">users</span>: <span class="hljs-keyword">async</span> () =&gt; <span class="hljs-keyword">await</span> db.User.findAll(),
  },
  <span class="hljs-attr">User</span>: {
    <span class="hljs-attr">posts</span>: <span class="hljs-keyword">async</span> (parent) =&gt; <span class="hljs-keyword">await</span> db.Post.findAll({ <span class="hljs-attr">where</span>: { <span class="hljs-attr">userId</span>: parent.id } })
  }
};
</code></pre>
<p>If we fetch 10 users along with their posts, the resolver first queries for users (<code>1 query</code>), then for each user, it fetches their posts (<code>10 additional queries</code>). This results in <strong>11 queries</strong> instead of an optimal <strong>2 queries</strong>.</p>
<h2 id="heading-how-dataloader-solves-this-problem">How DataLoader Solves This Problem</h2>
<p><a target="_blank" href="https://github.com/graphql/dataloader">DataLoader</a> batches multiple queries into a single request and caches results to avoid redundant calls. It allows GraphQL resolvers to efficiently fetch related data in bulk. 📦🔗⚡</p>
<h1 id="heading-step-by-step-implementation">Step-by-Step Implementation📝</h1>
<h3 id="heading-1-install-dataloader">1. Install DataLoader 🔧</h3>
<p>If not already installed, add DataLoader to your project:</p>
<pre><code class="lang-sh">npm install dataloader
</code></pre>
<h3 id="heading-2-create-a-dataloader-for-batching-queries">2. Create a DataLoader for Batching Queries 📊</h3>
<p>In your GraphQL setup, define a DataLoader instance to batch and cache database queries.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> DataLoader = <span class="hljs-built_in">require</span>(<span class="hljs-string">'dataloader'</span>);
<span class="hljs-keyword">const</span> db = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./models'</span>);

<span class="hljs-keyword">const</span> userPostLoader = <span class="hljs-keyword">new</span> DataLoader(<span class="hljs-keyword">async</span> (userIds) =&gt; {
  <span class="hljs-keyword">const</span> posts = <span class="hljs-keyword">await</span> db.Post.findAll({ <span class="hljs-attr">where</span>: { <span class="hljs-attr">userId</span>: userIds } });

  <span class="hljs-keyword">const</span> postsByUserId = userIds.map(<span class="hljs-function"><span class="hljs-params">id</span> =&gt;</span> posts.filter(<span class="hljs-function"><span class="hljs-params">post</span> =&gt;</span> post.userId === id));
  <span class="hljs-keyword">return</span> postsByUserId;
});
</code></pre>
<h3 id="heading-3-integrate-dataloader-in-resolvers">3. Integrate DataLoader in Resolvers 🔌</h3>
<p>Modify the resolver to use DataLoader instead of making separate queries.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> resolvers = {
  <span class="hljs-attr">Query</span>: {
    <span class="hljs-attr">users</span>: <span class="hljs-keyword">async</span> () =&gt; <span class="hljs-keyword">await</span> db.User.findAll(),
  },
  <span class="hljs-attr">User</span>: {
    <span class="hljs-attr">posts</span>: <span class="hljs-function">(<span class="hljs-params">parent, args, context</span>) =&gt;</span> context.userPostLoader.load(parent.id)
  }
};
</code></pre>
<h3 id="heading-4-attach-dataloader-to-context">4. Attach DataLoader to Context 🔗</h3>
<p>Ensure DataLoader is available in each request by adding it to the context in your GraphQL server setup.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> { ApolloServer } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'apollo-server'</span>);

<span class="hljs-keyword">const</span> server = <span class="hljs-keyword">new</span> ApolloServer({
  typeDefs,
  resolvers,
  <span class="hljs-attr">context</span>: <span class="hljs-function">() =&gt;</span> ({
    <span class="hljs-attr">userPostLoader</span>: userPostLoader
  })
});
</code></pre>
<h1 id="heading-verifying-performance-improvements">Verifying Performance Improvements 📊</h1>
<p>Before implementing DataLoader, let's analyze the number of queries being made. Consider the following GraphQL query:</p>
<pre><code class="lang-graphql"><span class="hljs-keyword">query</span> {
  users {
    id
    name
    posts {
      id
      title
    }
  }
}
</code></pre>
<h3 id="heading-without-dataloader">Without DataLoader : ❌</h3>
<ol>
<li><p><strong>Query to fetch users</strong> (1 query)</p>
<pre><code class="lang-sql"> <span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> <span class="hljs-keyword">users</span>;
</code></pre>
</li>
<li><p><strong>Query for each user's posts</strong> (10 queries if there are 10 users)</p>
<pre><code class="lang-sql"> <span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> posts <span class="hljs-keyword">WHERE</span> userId = <span class="hljs-number">1</span>;
 <span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> posts <span class="hljs-keyword">WHERE</span> userId = <span class="hljs-number">2</span>;
 ...
 <span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> posts <span class="hljs-keyword">WHERE</span> userId = <span class="hljs-number">10</span>;
</code></pre>
</li>
</ol>
<p>Total queries: <strong>11</strong></p>
<h3 id="heading-with-dataloader">With DataLoader : ✅</h3>
<ol>
<li><p><strong>Query to fetch users</strong> (1 query)</p>
<pre><code class="lang-sql"> <span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> <span class="hljs-keyword">users</span>;
</code></pre>
</li>
<li><p><strong>Single batched query to fetch all posts at once</strong> (1 query)</p>
<pre><code class="lang-sql"> <span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> posts <span class="hljs-keyword">WHERE</span> userId <span class="hljs-keyword">IN</span> (<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,<span class="hljs-number">4</span>,<span class="hljs-number">5</span>,<span class="hljs-number">6</span>,<span class="hljs-number">7</span>,<span class="hljs-number">8</span>,<span class="hljs-number">9</span>,<span class="hljs-number">10</span>);
</code></pre>
</li>
</ol>
<p>Total queries: <strong>2</strong></p>
<h3 id="heading-performance-gains">Performance Gains : 🚀</h3>
<p>By reducing the number of queries from 11 to 2, DataLoader significantly reduces database load and improves response time. The reduction in queries is more noticeable as the dataset grows, ensuring better scalability and performance efficiency. Before DataLoader, fetching posts for 10 users resulted in <strong>11 queries</strong>. With DataLoader, it reduces to <strong>2 queries</strong>:</p>
<ol>
<li><p>Fetch all users</p>
</li>
<li><p>Fetch all posts in a single batch query</p>
</li>
</ol>
<h1 id="heading-integrating-dataloader-with-prismasequelizemongodb">Integrating DataLoader with Prisma/Sequelize/MongoDB 🛠️</h1>
<h3 id="heading-using-dataloader-with-prisma">Using DataLoader with Prisma</h3>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> userPostLoader = <span class="hljs-keyword">new</span> DataLoader(<span class="hljs-keyword">async</span> (userIds) =&gt; {
  <span class="hljs-keyword">const</span> posts = <span class="hljs-keyword">await</span> prisma.post.findMany({
    <span class="hljs-attr">where</span>: { <span class="hljs-attr">userId</span>: { <span class="hljs-attr">in</span>: userIds } },
  });

  <span class="hljs-keyword">const</span> postsByUserId = userIds.map(<span class="hljs-function"><span class="hljs-params">id</span> =&gt;</span> posts.filter(<span class="hljs-function"><span class="hljs-params">post</span> =&gt;</span> post.userId === id));
  <span class="hljs-keyword">return</span> postsByUserId;
});
</code></pre>
<h3 id="heading-using-dataloader-with-sequelize">Using DataLoader with Sequelize</h3>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> userPostLoader = <span class="hljs-keyword">new</span> DataLoader(<span class="hljs-keyword">async</span> (userIds) =&gt; {
  <span class="hljs-keyword">const</span> posts = <span class="hljs-keyword">await</span> Post.findAll({
    <span class="hljs-attr">where</span>: { <span class="hljs-attr">userId</span>: userIds },
  });

  <span class="hljs-keyword">return</span> userIds.map(<span class="hljs-function"><span class="hljs-params">id</span> =&gt;</span> posts.filter(<span class="hljs-function"><span class="hljs-params">post</span> =&gt;</span> post.userId === id));
});
</code></pre>
<h3 id="heading-using-dataloader-with-mongodb-mongoose">Using DataLoader with MongoDB (Mongoose)</h3>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> userPostLoader = <span class="hljs-keyword">new</span> DataLoader(<span class="hljs-keyword">async</span> (userIds) =&gt; {
  <span class="hljs-keyword">const</span> posts = <span class="hljs-keyword">await</span> Post.find({ <span class="hljs-attr">userId</span>: { <span class="hljs-attr">$in</span>: userIds } });

  <span class="hljs-keyword">return</span> userIds.map(<span class="hljs-function"><span class="hljs-params">id</span> =&gt;</span> posts.filter(<span class="hljs-function"><span class="hljs-params">post</span> =&gt;</span> post.userId.toString() === id.toString()));
});
</code></pre>
<h1 id="heading-impact-of-using-dataloader">Impact of Using DataLoader</h1>
<ol>
<li><p><strong>Improved Performance</strong>: Reduces database queries, significantly optimizing response times.</p>
</li>
<li><p><strong>Better Scalability</strong>: Handles large GraphQL queries efficiently, making the API more scalable.</p>
</li>
<li><p><strong>Reduced Database Load</strong>: Batching reduces the number of queries, minimizing database stress.</p>
</li>
<li><p><strong>Caching Benefits</strong>: Avoids redundant database calls by caching previously fetched data.</p>
</li>
</ol>
<h1 id="heading-conclusion">Conclusion 💡</h1>
<p>Using DataLoader in GraphQL APIs is essential for optimizing database queries and solving the N+1 query problem. By batching requests and caching results, it significantly enhances performance and scalability. Whether you are using Prisma, Sequelize, or MongoDB, integrating DataLoader is a best practice for efficient GraphQL APIs.</p>
]]></content:encoded></item><item><title><![CDATA[Integrating Metrics and Analytics with Custom GraphQL Plugins : Enhance GraphQL APIs]]></title><description><![CDATA[Modern web applications demand flexible and high-performing APIs, and GraphQL has become the go-to solution for managing data flow between clients and servers. However, as applications grow in complexity, developers need a way to extend the functiona...]]></description><link>https://gauravbytes.dev/integrating-metrics-and-analytics-with-custom-graphql-plugins-enhance-graphql-apis</link><guid isPermaLink="true">https://gauravbytes.dev/integrating-metrics-and-analytics-with-custom-graphql-plugins-enhance-graphql-apis</guid><category><![CDATA[analytics]]></category><category><![CDATA[metrics]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[APIs]]></category><category><![CDATA[monitoring]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[GraphQL]]></category><category><![CDATA[Apollo GraphQL]]></category><dc:creator><![CDATA[Gaurav Kumar]]></dc:creator><pubDate>Wed, 01 Jan 2025 05:52:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1735634262478/a56460cf-0236-4221-b42f-ddcd4ec5c002.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Modern web applications demand flexible and high-performing APIs, and GraphQL has become the go-to solution for managing data flow between clients and servers. However, as applications grow in complexity, developers need a way to extend the functionality of their GraphQL servers to meet unique requirements such as logging, monitoring, authentication, and more.</p>
<p>Enter <strong>GraphQL plugins</strong>—a powerful mechanism in Apollo Server that allows developers to customize and enhance their API functionality with ease.</p>
<p>In this blog, we’ll explore what GraphQL plugins are, set up a basic Apollo Server, and walk through creating a custom plugin to log query response times. We’ll also delve into advanced use cases and best practices for building scalable and efficient GraphQL APIs with Apollo Server plugins. Let’s dive in!</p>
<h2 id="heading-what-are-graphql-plugins">What Are GraphQL Plugins?</h2>
<p>GraphQL plugins in Apollo Server allow developers to inject custom logic into the server’s lifecycle. Whether it’s tracking performance, managing request authentication, or adding additional logging, plugins empower developers to fine-tune their GraphQL servers for specific needs.</p>
<h2 id="heading-lifecycle-hooks"><strong>Lifecycle Hooks</strong></h2>
<p>Plugins in Apollo Server operate on a series of lifecycle hooks. These hooks allow developers to tap into various stages of a GraphQL operation, such as:</p>
<ol>
<li><p><code>requestDidStart</code>: Triggered when a new GraphQL request is received.</p>
</li>
<li><p><code>didResolveOperation</code>: Triggered after the server successfully parses and validates the query.</p>
</li>
<li><p><code>executionDidStart</code>: Triggered before the query execution begins.</p>
</li>
<li><p><code>willSendResponse</code>: Triggered right before the server sends the response to the client.</p>
</li>
</ol>
<p>By leveraging these hooks, you can implement custom behaviors like query logging, error tracking, or request throttling.</p>
<p>The following diagram illustrates the sequence of events that fire for each request. Each of these events is documented in <a target="_blank" href="https://www.apollographql.com/docs/apollo-server/integrations/plugins-event-reference/">Apollo Server plugin events</a> <a target="_blank" href="https://www.apollographql.com/docs/apollo-server/integrations/plugins-event-reference/">and available on apollo web</a>site.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1735631131223/f3d85c3d-eb97-4d8f-96c3-d75221b496f7.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-setting-up-apollo-server">Setting Up Apollo Server</h2>
<p>Before we jump into creating a custom plugin, let’s set up a basic Apollo Server with a simple schema and resolver. If you don’t already have Apollo Server installed, start by setting it up:</p>
<h3 id="heading-step-1-install-dependencies"><strong>Step 1: Install Dependencies</strong></h3>
<p>Run the following command to install the necessary packages:</p>
<pre><code class="lang-bash">npm install @apollo/server graphql
</code></pre>
<h3 id="heading-step-2-define-a-basic-schema"><strong>Step 2: Define a Basic Schema</strong></h3>
<p>Create a file named <code>schema.js</code> with the following content:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// we store posts list in a separate file</span>
<span class="hljs-keyword">import</span> {posts} <span class="hljs-keyword">from</span> <span class="hljs-string">"../../utils/data"</span>

<span class="hljs-keyword">const</span> typeDefs = <span class="hljs-string">`#graphql
    type Query {
        posts:[Post]
    }

    type Post {
        id: ID!
        title: String!
        content: String!
    }
`</span>;

<span class="hljs-keyword">const</span> resolvers = {
  <span class="hljs-attr">Query</span>: {
    <span class="hljs-attr">posts</span>: <span class="hljs-function">() =&gt;</span> {
            <span class="hljs-keyword">return</span> posts;
        },
  },
};

<span class="hljs-built_in">module</span>.exports = { typeDefs, resolvers };
</code></pre>
<h3 id="heading-step-3-set-up-the-apollo-server"><strong>Step 3: Set Up the Apollo Server</strong></h3>
<p>Create a file named <code>server.js</code> and set up Apollo Server:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> { ApolloServer } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'@apollo/server'</span>);
<span class="hljs-keyword">const</span> { typeDefs, resolvers } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./schema'</span>);

<span class="hljs-keyword">const</span> server = <span class="hljs-keyword">new</span> ApolloServer({
  typeDefs,
  resolvers,
});

server.listen().then(<span class="hljs-function">(<span class="hljs-params">{ url }</span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`🚀 Server ready at <span class="hljs-subst">${url}</span>`</span>);
});
</code></pre>
<p>Run the server using the following command:</p>
<pre><code class="lang-bash">node server.js
</code></pre>
<p>At this point, you have a basic Apollo Server running locally.</p>
<h2 id="heading-creating-a-custom-plugin-for-response-time-logging">Creating a Custom Plugin for Response Time Logging</h2>
<p>Now that we have a working Apollo Server, let’s create a custom plugin to log the response time of each GraphQL query.</p>
<h3 id="heading-step-1-understanding-the-plugin-lifecycle"><strong>Step 1: Understanding the Plugin Lifecycle</strong></h3>
<p>To measure response time, we’ll use the <code>requestDidStart</code> and <code>willSendResponse</code> hooks. Here’s the flow:</p>
<ol>
<li><p>Record the start time when the request begins.</p>
</li>
<li><p>Calculate the duration when the response is about to be sent.</p>
</li>
</ol>
<h3 id="heading-step-2-implementing-the-plugin"><strong>Step 2: Implementing the Plugin</strong></h3>
<p>Create a file named <code>loggingPlugin.js</code> with the following content:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> loggingPlugin = {
  <span class="hljs-keyword">async</span> requestDidStart(requestContext) {
    <span class="hljs-keyword">const</span> start = <span class="hljs-built_in">Date</span>.now();
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Query received: <span class="hljs-subst">${requestContext.request.query}</span>`</span>);

    <span class="hljs-keyword">return</span> {
      <span class="hljs-keyword">async</span> willSendResponse() {
        <span class="hljs-keyword">const</span> duration = <span class="hljs-built_in">Date</span>.now() - start;
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Response sent after <span class="hljs-subst">${duration}</span>ms`</span>);
      },
    };
  },
};

<span class="hljs-built_in">module</span>.exports = { loggingPlugin };
</code></pre>
<h3 id="heading-step-3-integrating-the-plugin"><strong>Step 3: Integrating the Plugin</strong></h3>
<p>Modify your <code>server.js</code> file to include the custom plugin:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> { ApolloServer } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'@apollo/server'</span>);
<span class="hljs-keyword">const</span> { typeDefs, resolvers } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./schema'</span>);
<span class="hljs-keyword">const</span> { loggingPlugin } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./loggingPlugin'</span>);

<span class="hljs-keyword">const</span> server = <span class="hljs-keyword">new</span> ApolloServer({
  typeDefs,
  resolvers,
  <span class="hljs-attr">plugins</span>: [loggingPlugin],
});

server.listen().then(<span class="hljs-function">(<span class="hljs-params">{ url }</span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`🚀 Server ready at <span class="hljs-subst">${url}</span>`</span>);
});
</code></pre>
<p>Restart the server, and you’ll see query logs along with response times in the console.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1735631996618/4ef9dda1-89c7-4834-9863-8bd1af98351f.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-advanced-use-cases-for-apollo-server-plugins">Advanced Use Cases for Apollo Server Plugins</h2>
<p>Plugins offer immense flexibility, enabling you to build advanced features for your GraphQL APIs. Here are a few examples:</p>
<h3 id="heading-1-extending-the-logging-plugin"><strong>1. Extending the Logging Plugin</strong></h3>
<p>Enhance the plugin to include user-specific details or operation names in the logs. For instance:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Operation: <span class="hljs-subst">${requestContext.operationName}</span>`</span>);
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">`User ID: <span class="hljs-subst">${requestContext.context.userId}</span>`</span>);
</code></pre>
<h3 id="heading-2-enforcing-security-and-rate-limiting"><strong>2. Enforcing Security and Rate Limiting</strong></h3>
<p>Implement rate limiting or request validation to prevent abuse. For example:</p>
<ul>
<li><p>Track the number of queries.</p>
</li>
<li><p>Reject requests exceeding a predefined threshold.</p>
</li>
</ul>
<pre><code class="lang-javascript"><span class="hljs-keyword">if</span> (queryCount &gt; MAX_QUERIES) {
  <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Rate limit exceeded.'</span>);
}
</code></pre>
<h3 id="heading-3-monitoring-and-analytics"><strong>3. Monitoring and Analytics</strong></h3>
<p>Send query performance metrics to external tools like Prometheus, Grafana, or DataDog for detailed monitoring and analytics.</p>
<h3 id="heading-4-dynamic-schema-modifications"><strong>4. Dynamic Schema Modifications</strong></h3>
<p>Build a plugin that dynamically modifies the schema at runtime based on user roles or feature flags.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Apollo Server plugins are a powerful tool for enhancing the functionality and performance of your GraphQL APIs. From logging response times to enforcing security and enabling advanced monitoring, plugins offer endless possibilities to tailor your server to your application’s needs.</p>
<p>By following this guide, you now have the skills to create custom plugins and integrate them seamlessly into your Apollo Server. Experiment with different use cases and share your innovations with the community.</p>
<p>For further learning, check out the <a target="_blank" href="https://www.apollographql.com/docs/apollo-server/">Apollo Server documentation</a>. Happy coding! 🚀</p>
<p>If you found this guide helpful, share it with your network and let us know how you’re using Apollo Server plugins in your projects. Stay tuned for more tutorials on building scalable GraphQL APIs!</p>
]]></content:encoded></item><item><title><![CDATA[Ensuring API Reliability: Metrics, Tools, and Best Practices]]></title><description><![CDATA[APIs serve as the backbone of modern applications, acting as the crucial link that enables seamless communication between various services and systems. Ensuring the reliability of these APIs is essential for maintaining high levels of user satisfacti...]]></description><link>https://gauravbytes.dev/ensuring-api-reliability-metrics-tools-and-best-practices</link><guid isPermaLink="true">https://gauravbytes.dev/ensuring-api-reliability-metrics-tools-and-best-practices</guid><category><![CDATA[APIs]]></category><category><![CDATA[monitoring]]></category><category><![CDATA[Databases]]></category><category><![CDATA[scalability]]></category><category><![CDATA[development]]></category><category><![CDATA[backend]]></category><category><![CDATA[tools]]></category><category><![CDATA[performance]]></category><category><![CDATA[Security]]></category><dc:creator><![CDATA[Gaurav Kumar]]></dc:creator><pubDate>Mon, 25 Nov 2024 07:13:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1732012567530/f92eaa8f-f7c9-4fc4-82aa-49e89cbab47c.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>APIs serve as the backbone of modern applications, acting as the crucial link that enables seamless communication between various services and systems. Ensuring the reliability of these APIs is essential for maintaining high levels of user satisfaction, achieving scalability, and ensuring operational efficiency. In this article, we will delve into the fundamental aspects required to ensure API reliability. We will explore the essential metrics that need to be tracked to assess the performance and stability of APIs. Additionally, we will discuss how to effectively monitor these metrics using powerful tools such as <strong>Prometheus</strong>. By understanding and implementing these practices, developers can enhance the robustness of their APIs, leading to improved application performance and a better user experience.</p>
<h3 id="heading-essential-elements-for-ensuring-api-reliability"><strong>Essential Elements for Ensuring API Reliability</strong></h3>
<ol>
<li><p><strong>Performance Monitoring</strong></p>
<ul>
<li><p>Ensure APIs respond quickly and handle concurrent requests efficiently.</p>
</li>
<li><p>Optimize for low latency and high throughput.</p>
</li>
</ul>
</li>
<li><p><strong>Scalability</strong></p>
<ul>
<li><p>Design APIs to handle increasing loads without degradation.</p>
</li>
<li><p>Use load balancers, auto-scaling groups, and caching mechanisms.</p>
</li>
</ul>
</li>
<li><p><strong>Error Handling</strong></p>
<ul>
<li><p>Implement comprehensive error logging and monitoring.</p>
</li>
<li><p>Provide clear error responses for better client-side debugging.</p>
</li>
</ul>
</li>
<li><p><strong>Security</strong></p>
<ul>
<li><p>Secure APIs with authentication, authorization, and encryption.</p>
</li>
<li><p>Monitor for unusual activity or unauthorized access attempts.</p>
</li>
</ul>
</li>
<li><p><strong>Availability</strong></p>
<ul>
<li><p>Aim for high uptime with robust failover mechanisms.</p>
</li>
<li><p>Use redundancy at the network, server, and data levels.</p>
</li>
</ul>
</li>
</ol>
<h3 id="heading-key-metrics-to-monitor-for-api-success"><strong>Key Metrics to Monitor for API Success</strong></h3>
<ol>
<li><p><strong>Performance Metrics</strong></p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732012896194/e7df149f-88b7-42e3-b692-88a6218affcc.webp" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>Response Time (Latency):</strong> Average, 95th percentile (P95), and 99th percentile (P99) latencies.</p>
</li>
<li><p><strong>Request Rate (RPS):</strong> Total requests per second handled by the API.</p>
</li>
<li><p><strong>Error Rate:</strong> Percentage of requests resulting in errors (4xx or 5xx responses).</p>
</li>
<li><p><strong>Cache Hit Rate:</strong> Frequency of cache hits versus total cache requests.</p>
</li>
</ul>
</li>
<li><p><strong>Infrastructure Metrics</strong></p>
<ul>
<li><p><strong>CPU and Memory Usage:</strong> Resource consumption patterns under load.</p>
</li>
<li><p><strong>Disk I/O:</strong> Storage throughput for reading and writing data.</p>
</li>
<li><p><strong>Thread and Connection Pool Usage:</strong> Health of connection pools and threads.</p>
</li>
</ul>
</li>
<li><p><strong>Reliability Metrics</strong></p>
<ul>
<li><p><strong>Uptime:</strong> Measure of API availability, often reflected as an SLA.</p>
</li>
<li><p><strong>Dependency Latency:</strong> Response time for third-party APIs the service relies on.</p>
</li>
<li><p><strong>Timeouts and Retries:</strong> Frequency of timed-out requests and retries.</p>
</li>
</ul>
</li>
<li><p><strong>Usage Metrics</strong></p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732013224567/577e0b11-279b-4e80-ba1c-b0baa7a8d1e9.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>Endpoint Popularity:</strong> Most accessed API endpoints.</p>
</li>
<li><p><strong>User Activity Patterns:</strong> Trends in API usage over time.</p>
</li>
<li><p><strong>Rate Limit Violations:</strong> Incidents where clients exceed allowed limits.</p>
</li>
</ul>
</li>
<li><p><strong>Security Metrics</strong></p>
<ul>
<li><p><strong>Authentication Failures:</strong> Invalid login attempts or token issues.</p>
</li>
<li><p><strong>Unusual IP Activity:</strong> Unexpected access patterns from specific IPs.</p>
</li>
<li><p><strong>Data Integrity Issues:</strong> Monitoring anomalies in data processing or storage.</p>
</li>
</ul>
</li>
</ol>
<h3 id="heading-how-to-monitor-these-metrics">How to Monitor These Metrics</h3>
<p>These tools provide complete solutions for monitoring performance, resource use, and reliability metrics.</p>
<ul>
<li><p><strong>Datadog:</strong> Comprehensive monitoring and alerting with integrated APM and logging.</p>
</li>
<li><p><strong>New Relic:</strong> APM with powerful diagnostics and distributed tracing.</p>
</li>
<li><p><strong>AWS CloudWatch:</strong> Built-in monitoring for AWS-based infrastructure and APIs.</p>
</li>
</ul>
<p>These tools stand out due to their wide adoption, robust features, and the ability to address various aspects of API monitoring, from performance metrics to log analysis.</p>
<h3 id="heading-best-practices-for-api-monitoring"><strong>Best Practices for API Monitoring</strong></h3>
<ol>
<li><p><strong>Use Distributed Tracing:</strong> Tools like Jaeger or Zipkin help trace requests across services, identifying bottlenecks.</p>
</li>
<li><p><strong>Implement Logging:</strong> Use structured logging to capture detailed request/response data for troubleshooting.</p>
</li>
<li><p><strong>Automate Alerts:</strong> Set up alerts for anomalies like high error rates, increased latency, or resource exhaustion.</p>
</li>
<li><p><strong>Conduct Regular Load Testing:</strong> Use tools like Apache JMeter or k6 to simulate traffic and identify scaling issues.</p>
</li>
<li><p><strong>Continuously Refine Metrics:</strong> Regularly review and update monitored metrics to align with evolving business needs.</p>
</li>
</ol>
<h3 id="heading-conclusion"><strong>Conclusion</strong></h3>
<p>In conclusion, ensuring API reliability is a multifaceted endeavor that requires careful attention to performance, scalability, error handling, security, and availability. By monitoring key metrics such as response time, error rate, and resource usage, developers can gain valuable insights into the health and performance of their APIs. Utilizing powerful monitoring tools like Prometheus, Datadog, and AWS CloudWatch can aid in effectively tracking these metrics and identifying potential issues before they impact users. Adopting best practices such as distributed tracing, structured logging, and regular load testing further enhances the robustness of APIs. By implementing these strategies, developers can significantly improve application performance, leading to a more reliable and satisfying user experience.</p>
]]></content:encoded></item><item><title><![CDATA[Automate Your API Testing: Integrate Postman with GitHub Actions for Seamless CI/CD]]></title><description><![CDATA[Introduction
Postman is a robust API development environment that enables you to create, send, test, and document APIs efficiently. GitHub Actions is a CI/CD platform designed to automate the testing and deployment of your code. By integrating Postma...]]></description><link>https://gauravbytes.dev/automate-your-api-testing-integrate-postman-with-github-actions-for-seamless-cicd</link><guid isPermaLink="true">https://gauravbytes.dev/automate-your-api-testing-integrate-postman-with-github-actions-for-seamless-cicd</guid><category><![CDATA[Postman]]></category><category><![CDATA[Testing]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[github-actions]]></category><category><![CDATA[automation]]></category><category><![CDATA[ci-cd]]></category><category><![CDATA[Continuous Integration]]></category><category><![CDATA[Automated Testing]]></category><category><![CDATA[APIs]]></category><dc:creator><![CDATA[Gaurav Kumar]]></dc:creator><pubDate>Wed, 04 Sep 2024 08:07:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1725390224751/01788ebe-0adb-48c4-a0f4-7d89d28a4966.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction"><strong>Introduction</strong></h2>
<p>Postman is a robust API development environment that enables you to create, send, test, and document APIs efficiently. GitHub Actions is a CI/CD platform designed to automate the testing and deployment of your code. By integrating Postman with GitHub Actions, you can seamlessly automate your API testing as part of your development workflow.</p>
<h2 id="heading-prerequisites"><strong>Prerequisites:</strong></h2>
<ul>
<li><p>A GitHub account and repository.</p>
</li>
<li><p>A Postman collection containing your API tests.</p>
</li>
<li><p>A Postman environment containing the necessary variables for your tests (e.g., API keys, base URLs).</p>
</li>
</ul>
<h2 id="heading-steps"><strong>Steps:</strong></h2>
<ol>
<li><p><strong>Create a Postman Collection:</strong></p>
<ul>
<li><p>In Postman, create a new collection and add your API test cases.</p>
</li>
<li><p>Configure the necessary environment variables in your Postman environment.</p>
</li>
</ul>
</li>
<li><p><strong>Generate Postman API Key:</strong></p>
<ul>
<li><p>Go to the settings page and then API keys to generate a new API key</p>
</li>
<li><p>Save this API key to GitHub repository secrets</p>
</li>
</ul>
</li>
</ol>
<p>    <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1725388184733/f084a48c-1e47-468d-8107-c40dd7400693.png" alt class="image--center mx-auto" /></p>
<ol start="3">
<li><p><strong>Create a GitHub Actions Workflow:</strong></p>
<ul>
<li><p>In your GitHub repository, create a new file named <code>.github/workflows/postman-tests.yml</code>.</p>
</li>
<li><p>Paste the following code into the file, replacing the placeholders with your specific values:</p>
<pre><code class="lang-yaml">  <span class="hljs-attr">name:</span> <span class="hljs-string">Automated</span> <span class="hljs-string">Testing</span> <span class="hljs-string">using</span> <span class="hljs-string">Postman</span> <span class="hljs-string">CLI</span>

  <span class="hljs-attr">on:</span>
    <span class="hljs-attr">push:</span>
      <span class="hljs-attr">branches:</span> [<span class="hljs-string">main</span>]

  <span class="hljs-attr">jobs:</span>
    <span class="hljs-attr">automated-api-tests:</span>
      <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
      <span class="hljs-attr">steps:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">Postman</span> <span class="hljs-string">CLI</span>
          <span class="hljs-attr">run:</span> <span class="hljs-string">|
            curl -o- "https://dl-cli.pstmn.io/install/linux64.sh" | sh
</span>        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Login</span> <span class="hljs-string">to</span> <span class="hljs-string">Postman</span> <span class="hljs-string">CLI</span>
          <span class="hljs-attr">run:</span> <span class="hljs-string">postman</span> <span class="hljs-string">login</span> <span class="hljs-string">--with-api-key</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.POSTMAN_API_KEY</span> <span class="hljs-string">}}</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Run</span> <span class="hljs-string">API</span> <span class="hljs-string">tests</span>
          <span class="hljs-attr">run:</span> <span class="hljs-string">|</span>
            <span class="hljs-string">postman</span> <span class="hljs-string">collection</span> <span class="hljs-string">run</span> <span class="hljs-string">"[collection_id]"</span> <span class="hljs-string">-e</span> <span class="hljs-string">"[environment_id]"</span>
</code></pre>
</li>
</ul>
</li>
<li><p><strong>Commit and Push:</strong></p>
<ul>
<li>Commit and push the <code>.github/workflows/postman-tests.yml</code> file to your GitHub repository.</li>
</ul>
</li>
</ol>
<h2 id="heading-explanation"><strong>Explanation:</strong></h2>
<ul>
<li><p>The workflow is triggered by pushing events to the <code>main</code> branch.</p>
</li>
<li><p>It installs the Postman CLI for Linux</p>
</li>
<li><p>It uses the <code>postman login</code> command to authenticate with the Postman CLI</p>
</li>
<li><p>It runs the Postman collection specified by the <code>[collection_id]</code> placeholder (replace it with your actual collection ID). It also uses the environment defined by the <code>[environment_id]</code> placeholder (replace it with your actual environment ID) for variables.</p>
</li>
</ul>
<h2 id="heading-additional-considerations"><strong>Additional Considerations:</strong></h2>
<ul>
<li><p>You can customize the workflow to run tests on different branches, trigger on pull requests, or use different runners.</p>
</li>
<li><p>Consider using environment variables to store sensitive information (e.g., API keys) securely.</p>
</li>
<li><p>Explore other features of GitHub Actions, such as caching, artifacts, and secrets, to enhance your workflow.</p>
</li>
</ul>
<p>By following these steps, you can effectively automate the testing of your APIs using Postman and GitHub Actions, ensuring that your code changes are thoroughly tested before deployment.</p>
]]></content:encoded></item><item><title><![CDATA[Building Robust Webhook Services in Node.js: Best Practices and Techniques]]></title><description><![CDATA[Traditionally, applications have relied on polling mechanisms to fetch updates from external systems. This approach was inefficient, consuming resources and often leading to delays in receiving critical information. Webhooks revolutionized this by in...]]></description><link>https://gauravbytes.dev/building-robust-webhook-services-in-nodejs-best-practices-and-techniques</link><guid isPermaLink="true">https://gauravbytes.dev/building-robust-webhook-services-in-nodejs-best-practices-and-techniques</guid><category><![CDATA[webhooks]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[Microservices]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[APIs]]></category><category><![CDATA[Express]]></category><category><![CDATA[Security]]></category><dc:creator><![CDATA[Gaurav Kumar]]></dc:creator><pubDate>Tue, 06 Aug 2024 06:50:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1721031215472/a39579ac-fbe5-48d3-b96d-8a083c2030cb.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Traditionally, applications have relied on polling mechanisms to fetch updates from external systems. This approach was inefficient, consuming resources and often leading to delays in receiving critical information. Webhooks revolutionized this by introducing a push-based model, where external systems proactively send data to your application when specific events occur.</p>
<h2 id="heading-what-is-a-webhook"><strong>What is a webhook?</strong></h2>
<p>A webhook is essentially an HTTP callback triggered by a specific event in a software application. When a defined event happens, the source system sends an HTTP POST payload to a pre-defined URL, notifying your application about the occurrence.</p>
<h2 id="heading-why-use-webhooks"><strong>Why use webhooks?</strong></h2>
<p>Webhooks offer several advantages over traditional polling methods:</p>
<ul>
<li><p><strong>Real-time updates:</strong> Receive information instantly when events happen, eliminating delays.</p>
</li>
<li><p><strong>Reduced server load:</strong> No need for constant polling, saving resources.</p>
</li>
<li><p><strong>Efficient resource utilization:</strong> Trigger actions only when necessary, based on specific events.</p>
</li>
</ul>
<h2 id="heading-real-world-examples"><strong>Real-world examples:</strong></h2>
<ul>
<li><p>E-commerce platforms sending order confirmation or shipment updates to your application</p>
</li>
<li><p>Payment gateways notifying you about successful or failed transactions</p>
</li>
<li><p>Collaboration tools sending notifications about file changes or comments</p>
</li>
</ul>
<h2 id="heading-how-webhooks-work"><strong>How webhooks work:</strong></h2>
<ol>
<li><p><strong>Event triggering:</strong> An event occurs in the source system (e.g., order placed, payment processed).</p>
</li>
<li><p><strong>Webhook request:</strong> The source system sends an HTTP POST request to the pre-defined webhook URL.</p>
</li>
<li><p><strong>Payload delivery:</strong> The request includes data about the event in the request body (often in JSON format).</p>
</li>
<li><p><strong>Webhook endpoint:</strong> Your application receives the request and processes the data accordingly.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722513135262/ef577ddb-2981-4b5f-a354-3e68f6a411ed.png" alt class="image--center mx-auto" /></p>
<p>By understanding the fundamentals of webhooks, we're ready to dive into building a basic webhook service in Node.js.</p>
<h2 id="heading-setting-up-your-nodejs-project-for-webhooks"><strong>Setting Up Your Node.js Project for Webhooks</strong></h2>
<p>To get started, we'll need a Node.js project. Use <code>npm init -y</code> to quickly create a new project directory. Next, install the required dependencies:</p>
<pre><code class="lang-javascript">npm install express body-parser cors
</code></pre>
<ul>
<li><p><strong>Express.js:</strong> For building the web server.</p>
</li>
<li><p><strong>body-parser:</strong> For parsing incoming request bodies.</p>
</li>
<li><p><strong>cors:</strong> For handling Cross-Origin Resource Sharing (CORS) if needed.</p>
</li>
</ul>
<p>Now the next steps include creating CRUD operations to store webhooks that need to be triggered when an event happened</p>
<h2 id="heading-implementing-crud-operations-for-webhook-storage"><strong>Implementing CRUD Operations for Webhook Storage</strong></h2>
<ol>
<li><p>Setup MongoDB instance using Mongoose: Create a file named <code>db.js</code> to connect to your MongoDB database:</p>
<pre><code class="lang-javascript"> <span class="hljs-keyword">const</span> mongoose = <span class="hljs-built_in">require</span>(<span class="hljs-string">'mongoose'</span>);

 <span class="hljs-keyword">const</span> connectDB = <span class="hljs-keyword">async</span> () =&gt; {
   <span class="hljs-keyword">try</span> {
     <span class="hljs-keyword">await</span> mongoose.connect(<span class="hljs-string">'mongodb://localhost:27017/webhook_service'</span>, {
       <span class="hljs-attr">useNewUrlParser</span>: <span class="hljs-literal">true</span>,
       <span class="hljs-attr">useUnifiedTopology</span>: <span class="hljs-literal">true</span>,
     });
     <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'MongoDB connected successfully'</span>);
   } <span class="hljs-keyword">catch</span> (error) {
     <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'MongoDB connection error:'</span>, error);
     process.exit(<span class="hljs-number">1</span>); <span class="hljs-comment">// Exit process on connection failure</span>
   }
 };

 <span class="hljs-built_in">module</span>.exports = connectDB;
</code></pre>
</li>
<li><p>Define Webhook schema :</p>
<p> Create a file named <code>Webhook.js</code> to define the Mongoose model for your webhook data:</p>
<pre><code class="lang-javascript"> <span class="hljs-keyword">const</span> mongoose = <span class="hljs-built_in">require</span>(<span class="hljs-string">'mongoose'</span>);

 <span class="hljs-keyword">const</span> WebhookSchema = <span class="hljs-keyword">new</span> mongoose.Schema({
   <span class="hljs-attr">url</span>: {
     <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>,
     <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>,
   },
   <span class="hljs-attr">headers</span>: {
     <span class="hljs-attr">type</span>: <span class="hljs-built_in">Object</span>,
     <span class="hljs-attr">default</span>: {},
   },
   <span class="hljs-attr">events</span>: {
     <span class="hljs-attr">type</span>: [<span class="hljs-built_in">String</span>], <span class="hljs-comment">// Array of strings representing subscribed events</span>
     <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>,
   },
   <span class="hljs-attr">createdAt</span>: {
     <span class="hljs-attr">type</span>: <span class="hljs-built_in">Date</span>,
     <span class="hljs-attr">default</span>: <span class="hljs-built_in">Date</span>.now,
   },
  <span class="hljs-comment">// Additional considerations:</span>

   <span class="hljs-attr">secret</span>: {
     <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>,
     <span class="hljs-attr">default</span>: <span class="hljs-string">''</span>, <span class="hljs-comment">// Optional secret key for authentication</span>
   },
   <span class="hljs-attr">isActive</span>: {
     <span class="hljs-attr">type</span>: <span class="hljs-built_in">Boolean</span>,
     <span class="hljs-attr">default</span>: <span class="hljs-literal">true</span>, <span class="hljs-comment">// Flag indicating if the webhook is currently active</span>
   },
   <span class="hljs-attr">description</span>: {
     <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>,
     <span class="hljs-attr">default</span>: <span class="hljs-string">''</span>, <span class="hljs-comment">// Optional description of the webhook's purpose</span>
   },

 });

 <span class="hljs-built_in">module</span>.exports = mongoose.model(<span class="hljs-string">'Webhook'</span>, WebhookSchema);
</code></pre>
</li>
<li><p>Define endpoint to list all webhooks</p>
<pre><code class="lang-javascript"> <span class="hljs-comment">// Read (GET) all webhooks</span>
 app.get(<span class="hljs-string">'/webhooks'</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
   <span class="hljs-keyword">try</span> {
     <span class="hljs-keyword">const</span> webhooks = <span class="hljs-keyword">await</span> Webhook.find();
     res.json(webhooks);
   } <span class="hljs-keyword">catch</span> (error) {
     <span class="hljs-built_in">console</span>.error(error);
     res.status(<span class="hljs-number">500</span>).send(<span class="hljs-string">'Server Error'</span>);
   }
 });
</code></pre>
</li>
<li><p>Define endpoints to store a webhook in the database</p>
<pre><code class="lang-javascript"> <span class="hljs-comment">// Create (POST) a new webhook</span>
 app.post(<span class="hljs-string">'/webhooks'</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
   <span class="hljs-keyword">try</span> {
     <span class="hljs-keyword">const</span> newWebhook = <span class="hljs-keyword">new</span> Webhook(req.body);
     <span class="hljs-keyword">const</span> savedWebhook = <span class="hljs-keyword">await</span> newWebhook.save();
     res.status(<span class="hljs-number">201</span>).json(savedWebhook);
   } <span class="hljs-keyword">catch</span> (error) {
     <span class="hljs-built_in">console</span>.error(error);
     res.status(<span class="hljs-number">500</span>).send(<span class="hljs-string">'Server Error'</span>);
   }
 });
</code></pre>
</li>
<li><p>Define an endpoint to fetch a single webhook using the id</p>
<pre><code class="lang-javascript"> <span class="hljs-comment">// Read (GET) a single webhook by ID</span>
 app.get(<span class="hljs-string">'/webhooks/:id'</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
   <span class="hljs-keyword">try</span> {
     <span class="hljs-keyword">const</span> webhook = <span class="hljs-keyword">await</span> Webhook.findById(req.params.id);
     <span class="hljs-keyword">if</span> (!webhook) {
       <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">404</span>).send(<span class="hljs-string">'Webhook not found'</span>);
     }
     res.json(webhook);
   } <span class="hljs-keyword">catch</span> (error) {
     <span class="hljs-built_in">console</span>.error(error);
     res.status(<span class="hljs-number">500</span>).send(<span class="hljs-string">'Server Error'</span>);
   }
 });
</code></pre>
</li>
<li><p>Define an endpoint to update a webhook using id</p>
<pre><code class="lang-javascript"> <span class="hljs-comment">// Update (PUT) a webhook by ID</span>
 app.put(<span class="hljs-string">'/webhooks/:id'</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
   <span class="hljs-keyword">try</span> {
     <span class="hljs-keyword">const</span> updatedWebhook = <span class="hljs-keyword">await</span> Webhook.findByIdAndUpdate(
       req.params.id,
       req.body,
       { <span class="hljs-attr">new</span>: <span class="hljs-literal">true</span> } <span class="hljs-comment">// Return the updated document</span>
     );
     <span class="hljs-keyword">if</span> (!updatedWebhook) {
       <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">404</span>).send(<span class="hljs-string">'Webhook not found'</span>);
     }
     res.json(updatedWebhook);
   } <span class="hljs-keyword">catch</span> (error) {
     <span class="hljs-built_in">console</span>.error(error);
     res.status(<span class="hljs-number">500</span>).send(<span class="hljs-string">'Server Error'</span>);
   }
 });
</code></pre>
</li>
<li><p>Define an endpoint to delete a webhook using id</p>
<pre><code class="lang-javascript"> <span class="hljs-comment">// Delete (DELETE) a webhook by ID</span>
 app.delete(<span class="hljs-string">'/webhooks/:id'</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
   <span class="hljs-keyword">try</span> {
     <span class="hljs-keyword">const</span> deletedWebhook = <span class="hljs-keyword">await</span> Webhook.findByIdAndDelete(req.params.id);
     <span class="hljs-keyword">if</span> (!deletedWebhook) {
       <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">404</span>).send(<span class="hljs-string">'Webhook not found'</span>);
     }
     res.json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Webhook deleted successfully'</span> });
   } <span class="hljs-keyword">catch</span> (error) {
     <span class="hljs-built_in">console</span>.error(error);
     res.status(<span class="hljs-number">500</span>).send(<span class="hljs-string">'Server Error'</span>);
   }
 });
</code></pre>
</li>
</ol>
<h2 id="heading-creating-webhook-triggering-logic-in-nodejs"><strong>Creating Webhook Triggering Logic in Node.js</strong></h2>
<p>In this section, we will create an endpoint that generates a dummy event and subsequently triggers the webhooks subscribed to this event.<br />The steps included in this logic are:</p>
<ol>
<li><p>Fetch all webhooks that are subscribed to the particular event</p>
</li>
<li><p>Define the payload for each webhook</p>
</li>
<li><p>Send a <code>POST</code> request to each webhook with the respective payload</p>
</li>
</ol>
<pre><code class="lang-javascript">app.post(<span class="hljs-string">'/generate-event'</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> {event, data} = req.body;

        <span class="hljs-comment">// fetch all webhooks that subscribed to this event</span>
        <span class="hljs-keyword">const</span> webhooks = <span class="hljs-keyword">await</span> Webhook.find({
            <span class="hljs-attr">events</span>:event
        });



        <span class="hljs-comment">// Define webhook payload</span>
        <span class="hljs-keyword">const</span> webhookPayload = {
            <span class="hljs-attr">event</span>: event,
            <span class="hljs-attr">data</span>: data,
        };

        <span class="hljs-comment">// Send POST request to each webhook endpoint</span>
        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> webhook <span class="hljs-keyword">of</span> webhooks) {
            <span class="hljs-keyword">await</span> axios.post(webhook?.url, webhookPayload);
        }


        res.status(<span class="hljs-number">200</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Event generated and webhook triggered successfully'</span> });
    } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error generating event and triggering webhook:'</span>, error);
        res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">'Internal server error'</span> });
    }
});
</code></pre>
<h2 id="heading-building-a-webhook-receiver-step-by-step"><strong>Building a Webhook Receiver Step-by-Step</strong></h2>
<p><strong>A webhook receiver is essentially a listening service.</strong> It's like a mailbox where other applications can send you messages. Here in our example, we define an endpoint <code>/user-webhook</code> that will receive data, process it and log it on screen.</p>
<ul>
<li><p><strong>Define a new route:</strong> In your <code>index.js</code> file, create a new route <code>/user-webhook</code> to handle incoming webhook requests.</p>
</li>
<li><p><strong>Access the request body:</strong> Use <code>req.body</code> to access the data sent in the webhook payload.</p>
</li>
<li><p><strong>Log the webhook payload:</strong> It's helpful to log the received webhook data for debugging purposes:</p>
</li>
<li><p><strong>Send a successful response:</strong> After processing the webhook, send a successful HTTP response to the sender. This indicates that our server has received and processed the webhook successfully.</p>
</li>
</ul>
<pre><code class="lang-javascript">app.post(<span class="hljs-string">'/webhook'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Webhook received:'</span>, req.body);
  res.status(<span class="hljs-number">200</span>).send(<span class="hljs-string">'Webhook received'</span>);
});
</code></pre>
<h2 id="heading-testing-your-webhook-service-practical-use-cases"><strong>Testing Your Webhook Service - Practical Use Cases</strong></h2>
<p>To test out our webhook service we have to break the tests into 3 use cases as shown below</p>
<ol>
<li><p><strong>Store the webhook details</strong>: Use a tool like Postman to send a POST request to your endpoint to store webhook details.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722509434066/1208ca7b-e78e-416b-96c6-7ed84edf3683.png" alt class="image--center mx-auto" /></p>
</li>
<li><p><strong>Trigger the event</strong>: Create a dummy event and trigger the webhook.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722510074586/dfe30051-ecfe-4da9-9c06-c8ce457a85c2.png" alt class="image--center mx-auto" /></p>
</li>
<li><p><strong>Check results on our receiver endpoint</strong>: Verify that the payload is received at the specified endpoint.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722510105440/c1309391-53de-4473-ba39-8f2e732d7012.png" alt class="image--center mx-auto" /></p>
</li>
</ol>
<p>This is a basic example. In a production environment, you'll likely need to implement more robust error handling, validation, and asynchronous processing for webhooks.</p>
<h2 id="heading-securing-the-webhook-endpoint">Securing the Webhook Endpoint</h2>
<p>Security is paramount when handling webhooks. Here are some essential measures:</p>
<ul>
<li><p><strong>IP Whitelisting:</strong> Restrict incoming requests to specific IP addresses to prevent unauthorized access.</p>
</li>
<li><p><strong>API Keys or Tokens:</strong> A secret token or API key is required to be included in the request headers for authentication.</p>
</li>
<li><p><strong>HTTPS:</strong> Use HTTPS to encrypt data transmission.</p>
</li>
<li><p><strong>Content Verification:</strong> Verify the integrity of incoming webhook data using checksums or digital signatures.</p>
</li>
<li><p><strong>Rate Limiting:</strong> Implement rate limiting to protect against abuse and denial-of-service attacks.</p>
</li>
</ul>
<h2 id="heading-validating-and-processing-incoming-webhook-data">Validating and Processing Incoming Webhook Data</h2>
<p>Before processing webhook data, it's essential to validate it for correctness and security:</p>
<ul>
<li><p><strong>Data Format Validation:</strong> Ensure the incoming data adheres to the expected format (JSON, XML, etc.).</p>
</li>
<li><p><strong>Data Integrity Verification:</strong> Check for missing or invalid fields.</p>
</li>
<li><p><strong>Data Type Validation:</strong> Verify that data types match the expected schema.</p>
</li>
<li><p><strong>Security Checks:</strong> Look for potential malicious content or attacks.</p>
</li>
</ul>
<p>Once validated, process the webhook data based on its content. This might involve storing the data, triggering other actions, or updating the application state.</p>
<h2 id="heading-best-practices-for-webhook-implementation">Best Practices for Webhook Implementation</h2>
<ul>
<li><p><strong>Reliable Delivery:</strong> Implement retry mechanisms and dead letter queues for failed deliveries.</p>
</li>
<li><p><strong>Error Handling:</strong> Provide informative error messages and log errors for debugging.</p>
</li>
<li><p><strong>Scalability:</strong> Design your webhook service to handle increasing traffic and data volumes.</p>
</li>
<li><p><strong>Security:</strong> Prioritize security measures like authentication, authorization, and data encryption.</p>
</li>
<li><p><strong>Documentation:</strong> Create clear documentation for developers using your webhook service.</p>
</li>
</ul>
<p>In conclusion, building a secure and efficient webhook service in Node.js involves understanding the fundamentals of webhooks, setting up a Node.js project, implementing CRUD operations, and creating robust webhook triggering and receiving mechanisms. By following best practices such as securing endpoints, validating incoming data, and ensuring reliable delivery, you can create a resilient and scalable webhook service. This guide provides a comprehensive roadmap to help you achieve real-time updates and efficient resource utilization, ultimately enhancing the performance and security of your application.</p>
]]></content:encoded></item><item><title><![CDATA[What is Database Sharding and How Does It Work?]]></title><description><![CDATA[In today's data-driven world, modern applications face the ever-growing challenge of managing massive volumes of information. Traditional monolithic databases struggle with bottlenecks, leading to sluggish performance and limited scalability. Enter d...]]></description><link>https://gauravbytes.dev/what-is-database-sharding-and-how-does-it-work</link><guid isPermaLink="true">https://gauravbytes.dev/what-is-database-sharding-and-how-does-it-work</guid><category><![CDATA[Node.js]]></category><category><![CDATA[Databases]]></category><category><![CDATA[sharding]]></category><category><![CDATA[System Architecture]]></category><category><![CDATA[APIs]]></category><category><![CDATA[architecture]]></category><dc:creator><![CDATA[Gaurav Kumar]]></dc:creator><pubDate>Tue, 18 Jun 2024 09:03:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1718436894015/b7a3a4d9-4336-46b8-b71f-155ffc656f58.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In today's data-driven world, modern applications face the ever-growing challenge of managing massive volumes of information. Traditional monolithic databases struggle with bottlenecks, leading to sluggish performance and limited scalability. Enter database sharding—a powerful solution designed to enhance scalability and boost performance by distributing data across multiple shards.</p>
<h2 id="heading-what-is-database-sharding">What is Database sharding?</h2>
<p>Database sharding is a technique for horizontal scaling of databases, where the data is split across multiple database instances, or shards, to improve performance and reduce the impact of large amounts of data on a single database. (ref: <a target="_blank" href="https://www.geeksforgeeks.org/database-sharding-a-system-design-concept/">Database Sharding</a>)</p>
<p>Each shard is essentially a separate database instance or server that contains a subset of the overall data. The goal of sharding is to distribute the data and database load across multiple shards, allowing for improved scalability, performance, and fault tolerance in a distributed system.</p>
<h3 id="heading-key-components-of-sharding">Key components of sharding:</h3>
<ol>
<li><p><strong>Shard Key:</strong> This is the attribute used to distribute data across different shards. It's like a label that determines which shard a particular piece of data belongs to. Choosing the right shard key is crucial for even data distribution and efficient querying. Common shard keys include user ID, product category, or date.</p>
</li>
<li><p><strong>Shards:</strong> These are individual databases that hold a subset of the overall data. Imagine a large library divided into multiple sections - each section (shard) focuses on a specific category of books. The total data is distributed across these shards based on the shard key.</p>
</li>
<li><p><strong>Shard Map (or Catalog):</strong> This acts like a directory, keeping track of which shard stores data for a specific shard key value. When your application needs to access data, it consults the shard map to locate the appropriate shard. Think of it as a library card catalogue that tells you which section (shard) to find a particular book (data) based on its title or author (shard key).</p>
</li>
<li><p><strong>Shard Router:</strong> This is a component within your application or a separate service that interacts with the shard map. It receives the shard key from your application logic and uses the shard map to determine the appropriate shard for the desired operation (read or write). The shard router then directs the request to the specific shard.</p>
</li>
</ol>
<h3 id="heading-benefits-of-sharding"><strong>Benefits of Sharding</strong></h3>
<ul>
<li><p><strong>Enhanced Scalability:</strong> Sharding allows for horizontal scaling by distributing data across multiple databases (shards). As data volume increases, additional shards can be seamlessly integrated, ensuring the application can accommodate growth without performance sacrifices.</p>
</li>
<li><p><strong>Improved Performance:</strong> Sharding optimizes query execution by enabling queries to target specific data subsets within individual shards. This reduces the amount of data scanned, resulting in significantly faster response times and a more responsive user experience.</p>
</li>
<li><p><strong>Increased Availability:</strong> Sharding introduces a degree of fault tolerance. If one shard encounters an issue, other shards remain unaffected, mitigating the impact on overall system availability. This enhances application resilience and ensures service continuity.</p>
</li>
</ul>
<h2 id="heading-implementation-in-nodejs"><strong>Implementation in Node.js</strong></h2>
<p>Now that we have a foundational understanding of database sharding and its benefits, let's dive into a practical implementation example using Node.js and MongoDB.</p>
<h3 id="heading-create-two-shards-database-instances">Create Two Shards (Database Instances)</h3>
<p>In this example, we have created two database instances, both hosted on <a target="_blank" href="https://www.mongodb.com/products/platform/atlas-database">Atlas</a>. You can also use Docker or self-host if you prefer.</p>
<h3 id="heading-define-the-shard-map">Define the shard map</h3>
<p>To define the shard map, create a simple JavaScript object that maps product categories to their respective shard connection strings.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> shardMap = {
    <span class="hljs-string">'Clothing'</span>: <span class="hljs-string">'database url for clothing products'</span>,
    <span class="hljs-string">'Electronics'</span>: <span class="hljs-string">'database url for electronics products'</span>,
    <span class="hljs-string">'default'</span>: <span class="hljs-string">'database url for all other products'</span>

}
</code></pre>
<h3 id="heading-define-the-function-that-returns-the-proper-shard-based-on-the-category">Define the function that returns the proper shard based on the category</h3>
<p>Create a function that takes a product category as input and returns the appropriate shard connection.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Fetch the right shard</span>
<span class="hljs-keyword">const</span> fetchShard = <span class="hljs-function">(<span class="hljs-params">category</span>) =&gt;</span> {
    <span class="hljs-comment">// get shard credentials</span>
    <span class="hljs-keyword">const</span> credentials = shardMap?.[category]
    <span class="hljs-comment">// connect with the server</span>
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> MongoClient(credentials ?? shardMap?.default);
}
</code></pre>
<h3 id="heading-define-the-api-routes-for-uploading-products">Define the API routes for uploading products</h3>
<p>Set up an API route to handle product uploads. This route will use the <code>fetchShard</code> function to determine the correct shard for storing the product.</p>
<pre><code class="lang-javascript">app.post(<span class="hljs-string">'/products'</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {

    <span class="hljs-keyword">const</span> {products} = req.body;
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> product <span class="hljs-keyword">of</span> products) {
        <span class="hljs-comment">// get database connection for each product</span>
        <span class="hljs-keyword">const</span> client = <span class="hljs-keyword">await</span> fetchShard(product?.category)
        <span class="hljs-keyword">const</span> db = client.db(<span class="hljs-string">'database-sharding'</span>)
        <span class="hljs-keyword">const</span> productCollection = db.collection(<span class="hljs-string">"products"</span>);
        <span class="hljs-keyword">await</span> productCollection.insertOne(product)
    }
    res.send(<span class="hljs-string">"ok"</span>)
})
</code></pre>
<h3 id="heading-define-the-api-route-that-returns-products-based-on-category">Define the API route that returns products based on category</h3>
<p>Set up an API route to fetch products by category. This route will also use the <code>fetchShard</code> function to determine the correct shard to query.</p>
<pre><code class="lang-javascript">app.get(<span class="hljs-string">'/products/:category'</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
    <span class="hljs-keyword">const</span> {category} = req.params;
    <span class="hljs-keyword">try</span> {
        <span class="hljs-comment">// find out the correct shard</span>
        <span class="hljs-keyword">const</span> client = <span class="hljs-keyword">await</span> fetchShard(category)
        <span class="hljs-comment">// connect to the database</span>
        <span class="hljs-keyword">const</span> db = client.db(<span class="hljs-string">'database-sharding'</span>)
        <span class="hljs-keyword">const</span> productCollection = db.collection(<span class="hljs-string">"products"</span>);
        <span class="hljs-keyword">const</span> products = <span class="hljs-keyword">await</span> productCollection.find({category}).toArray();
        res.send(products)
    } <span class="hljs-keyword">catch</span> (err) {
        <span class="hljs-built_in">console</span>.log(err)
        res.status(<span class="hljs-number">500</span>).json({<span class="hljs-attr">message</span>: <span class="hljs-string">'Error fetching products'</span>});
    }
});
</code></pre>
<h3 id="heading-test-out-our-application">Test out our application</h3>
<p>To test the application, you can use tools like Postman or Curl to send HTTP requests to the API endpoints. For example:</p>
<ol>
<li><p>To upload products:</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1718438456771/34cfa585-9354-4a61-8533-8eed513a45aa.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>To fetch products by category:</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1718438033494/15ce782d-00d7-41d1-b612-22051e448cd9.png" alt class="image--center mx-auto" /></p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1718438052003/fda41293-8580-49df-a182-c33b91e33625.png" alt class="image--center mx-auto" /></p>
</li>
</ol>
<h2 id="heading-considerations-and-best-practices-for-sharding">Considerations and Best Practices for Sharding</h2>
<p>Database sharding offers significant advantages in terms of scalability and performance, but it's important to be aware of its complexities and best practices for successful implementation:</p>
<ul>
<li><p><strong>Increased Application Complexity:</strong> Sharding introduces an additional layer of abstraction between your application and the database. You'll need to manage shard routing, shard key selection, and potential consistency issues across shards.</p>
</li>
<li><p><strong>Shard Key Selection:</strong> Choosing the right shard key is critical. It should evenly distribute data across shards and align with your most frequent query patterns. A poorly chosen shard key can lead to bottlenecks and negate the benefits of sharding.</p>
</li>
<li><p><strong>Scalability Considerations:</strong> While sharding enables horizontal scaling, it's not a magic bullet. Adding new shards comes with its own management overhead. Evaluate your data access patterns and growth projections to determine if sharding is the most suitable solution for your specific needs.</p>
</li>
</ul>
<p>By understanding the benefits and considerations of sharding, you can make informed decisions to optimize your Node.js applications for scalability and performance as your data demands continue to evolve. Remember, sharding is a powerful tool, but like any powerful tool, it requires careful planning and execution to reap the maximum rewards.</p>
]]></content:encoded></item><item><title><![CDATA[Reliable Background Task Execution using BullMQ and Node.js]]></title><description><![CDATA[In modern Node.js applications, ensuring smooth background processing is crucial for maintaining responsiveness and scalability. Message queues offer an effective solution, decoupling message production from consumption and enabling asynchronous task...]]></description><link>https://gauravbytes.dev/reliable-background-task-execution-using-bullmq-and-nodejs</link><guid isPermaLink="true">https://gauravbytes.dev/reliable-background-task-execution-using-bullmq-and-nodejs</guid><category><![CDATA[Queues]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[bullmq]]></category><category><![CDATA[TypeScript]]></category><dc:creator><![CDATA[Gaurav Kumar]]></dc:creator><pubDate>Mon, 20 May 2024 17:22:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1715843401750/f04f23ab-2685-4bf4-87a3-87bd44f667c4.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In modern Node.js applications, ensuring smooth background processing is crucial for maintaining responsiveness and scalability. Message queues offer an effective solution, decoupling message production from consumption and enabling asynchronous task handling. BullMQ, a popular choice, provides a robust and efficient way to implement message queues in your Node.js projects. This article guides you through integrating BullMQ for reliable background processing</p>
<h3 id="heading-understanding-message-queues"><strong>Understanding Message Queues</strong></h3>
<p>Imagine a conveyor belt, producers place tasks on the belt (queue), and separate worker processes (consumers) pick them up for execution. This asynchronous approach decouples producers and consumers, allowing them to operate independently without hindering each other's performance. Message queues offer significant advantages:</p>
<ul>
<li><p><strong>Scalability:</strong> Effortlessly scale producers and consumers to manage increasing workloads.</p>
</li>
<li><p><strong>Resilience:</strong> Tasks persist in the queue, ensuring delivery even if producers or consumers encounter temporary outages.</p>
</li>
<li><p><strong>Asynchronous Processing:</strong> Producers don't have to wait for tasks to finish processing, leading to a more responsive application.</p>
</li>
<li><p><strong>Fault Tolerance:</strong> Queues automatically retry tasks upon failures, enhancing system reliability</p>
</li>
</ul>
<h3 id="heading-getting-started-with-bullmq"><strong>Getting Started with BullMQ</strong></h3>
<ol>
<li><p><strong>Installation:</strong></p>
<p> Begin by installing the <code>bullmq</code> and <code>ioredis</code> packages using npm:</p>
<pre><code class="lang-bash"> npm install --save bullmq ioredis
</code></pre>
<p> <code>bullmq</code> provides core functionalities for managing queues and jobs, while <code>ioredis</code> acts as the Redis client library.</p>
</li>
<li><p><strong>Connecting to Redis:</strong></p>
<p> We are going to use a self-hosted Redis server, download redis from here and install it in your system <a target="_blank" href="https://redis.io/downloads/">https://redis.io/downloads/</a></p>
<pre><code class="lang-javascript"> <span class="hljs-keyword">const</span> Redis = <span class="hljs-built_in">require</span>(<span class="hljs-string">'ioredis'</span>);

 <span class="hljs-keyword">const</span> redisConfig = 

 <span class="hljs-keyword">const</span> redisConnection = <span class="hljs-keyword">new</span> Redis({
   <span class="hljs-attr">port</span>: <span class="hljs-number">6379</span>, <span class="hljs-comment">// Replace with your Redis port if different</span>
   <span class="hljs-attr">host</span>: <span class="hljs-string">'127.0.0.1'</span>, <span class="hljs-comment">// Replace with your Redis host if different</span>
 });

 <span class="hljs-built_in">module</span>.exports = redisConnection;
</code></pre>
<p> This code defines the Redis configuration and creates a connection object.</p>
</li>
<li><p><strong>Creating a Queue:</strong></p>
<p> Import the <code>Queue</code> class from BullMQ and your Redis connection in your main application file (e.g., <code>index.js</code>)</p>
<pre><code class="lang-javascript"> <span class="hljs-keyword">import</span> {Queue} <span class="hljs-keyword">from</span> <span class="hljs-string">'bullmq'</span>;

 <span class="hljs-keyword">const</span> commentNotificationQueue = <span class="hljs-keyword">new</span> Queue(<span class="hljs-string">'comment-email-notification'</span>, 
 {<span class="hljs-attr">connection</span>: redisConnection});
</code></pre>
<p> Here, you create a new BullMQ queue named <code>comment-email-notification</code>. The <code>connection</code> property specifies the Redis connection established earlier.</p>
</li>
</ol>
<h3 id="heading-adding-jobs-to-the-queue"><strong>Adding Jobs to the Queue</strong></h3>
<ol>
<li><p><strong>Job Data:</strong></p>
<p> Jobs in BullMQ consist of data you want to process asynchronously. This data can be anything relevant to your task, such as file paths, user IDs, or API request parameters.</p>
</li>
<li><p><strong>Adding a Job:</strong></p>
<p> Use the <code>add</code> method on your queue instance to add a new job:</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">addJobs</span>(<span class="hljs-params"></span>) </span>{
     <span class="hljs-keyword">const</span> comments = [
         {
             <span class="hljs-string">"text"</span>: <span class="hljs-string">"I really enjoyed your article on machine learning! The explanation of different algorithms was very clear and concise. Keep up the good work!"</span>,
             <span class="hljs-string">"commenter_name"</span>: <span class="hljs-string">"John Smith"</span>,
             <span class="hljs-string">"commenter_email"</span>: <span class="hljs-string">"john.smith@example.com"</span>, <span class="hljs-string">"author_name"</span>: <span class="hljs-string">"Jane Doe"</span>,
             <span class="hljs-string">"author_email"</span>: <span class="hljs-string">"jane.doe@example.com"</span>
         },
         {
             <span class="hljs-string">"text"</span>: <span class="hljs-string">"This phone is amazing! The battery life is fantastic, and the camera takes stunning photos. I would highly recommend it to anyone looking for a new phone."</span>,
             <span class="hljs-string">"commenter_name"</span>: <span class="hljs-string">"Sarah Lee"</span>,
             <span class="hljs-string">"commenter_email"</span>: <span class="hljs-string">"sarah.lee@example.com"</span>, <span class="hljs-string">"author_name"</span>: <span class="hljs-string">"Stephan Josh"</span>
         },
         {
             <span class="hljs-string">"text"</span>: <span class="hljs-string">"Thanks for your insights on the future of AI. I agree that ethical considerations are crucial as this technology advances."</span>,
             <span class="hljs-string">"commenter_name"</span>: <span class="hljs-string">"David Kim"</span>,
             <span class="hljs-string">"commenter_email"</span>: <span class="hljs-string">"david.kim@example.com"</span>, <span class="hljs-string">"author_name"</span>: <span class="hljs-string">"Michael Brown"</span>,
             <span class="hljs-string">"author_email"</span>: <span class="hljs-string">"michael.brown@example.com"</span>
         }
     ]
     <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> comment <span class="hljs-keyword">of</span> comments) {
         <span class="hljs-keyword">await</span> commentNotificationQueue.add(comment?.commenter_name, comment);
     }
 }

 <span class="hljs-keyword">await</span> addJobs();
</code></pre>
<p> This code adds a job named based on <code>commenter's name</code> with the specified data (<code>jobData</code>) to the <code>comment-email-notification</code> queue.</p>
</li>
<li><p><strong>Job Options (Optional):</strong></p>
<p> BullMQ offers various options for customizing job behaviour:</p>
<ul>
<li><p><code>delay</code>: Schedule the job for execution later (in milliseconds).</p>
</li>
<li><p><code>attempts</code>: Define the number of retries for failed jobs.</p>
</li>
<li><p><code>backoff</code>: Set a backoff strategy for retries after failures (e.g., exponential delay).</p>
</li>
<li><p><code>priority</code>: Assign a priority value to jobs (higher values are processed sooner).</p>
</li>
</ul>
</li>
</ol>
<p>    These options provide granular control over job processing and error handling.</p>
<p>This is how the jobs are stored in the Redis database:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715846189548/a483d648-d9b7-4ba4-9dd0-06cc84eefab6.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-consuming-jobs-workers"><strong>Consuming Jobs (Workers)</strong></h3>
<ol>
<li><p><strong>Worker Creation:</strong></p>
<p> Workers are Node.js processes that continuously listen for new jobs in a queue and execute them. Create a worker using the <code>Worker</code> class:</p>
<pre><code class="lang-javascript"> <span class="hljs-keyword">const</span> commentNotificationWorker = <span class="hljs-keyword">new</span> Worker(<span class="hljs-string">'comment-email-notification'</span>,
     <span class="hljs-keyword">async</span> job =&gt; {
         <span class="hljs-keyword">const</span> {commenter_name, author_name} = job?.data;
         <span class="hljs-comment">// process comment and send email to author's email about the new comment that he/she receives</span>

         <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`<span class="hljs-subst">${commenter_name}</span> commented on <span class="hljs-subst">${author_name}</span> article`</span>)
     }, {<span class="hljs-attr">connection</span>: redisConnection});
</code></pre>
<p> This code defines a worker for the <code>comment-email-notification</code> queue. The provided asynchronous function (<code>async (job) =&gt; {...}</code>) will be executed for each job received by the worker. Remember to replace the placeholder comment with your actual business logic that utilizes the <code>job.data</code> property.</p>
</li>
<li><p><strong>Test the worker:</strong></p>
<p> This is the output of the worker when the job is executed from the queue</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715846244707/cd025544-b708-45bb-b37b-9bae03717123.png" alt class="image--center mx-auto" /></p>
<p> Here is the link to the source code: <a target="_blank" href="https://github.com/icon-gaurav/nodejs-bullmq-implementation.git">https://github.com/icon-gaurav/nodejs-bullmq-implementation.git</a></p>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[Quick and Simple Method for Building a WebSocket Server in Node.js]]></title><description><![CDATA[Imagine working on a document with colleagues simultaneously. You type a sentence, and it instantly appears on everyone else's screens, no refresh button is needed. This seamless collaboration is made possible by a powerful technology called WebSocke...]]></description><link>https://gauravbytes.dev/quick-and-simple-method-for-building-a-websocket-server-in-nodejs</link><guid isPermaLink="true">https://gauravbytes.dev/quick-and-simple-method-for-building-a-websocket-server-in-nodejs</guid><category><![CDATA[websockets]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[socket]]></category><dc:creator><![CDATA[Gaurav Kumar]]></dc:creator><pubDate>Tue, 07 May 2024 08:10:03 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1713511029548/d969f0c0-b657-4bce-af5f-8364d01b0e24.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Imagine working on a document with colleagues simultaneously. You type a sentence, and it instantly appears on everyone else's screens, no refresh button is needed. This seamless collaboration is made possible by a powerful technology called WebSockets.</p>
<h2 id="heading-what-are-websockets"><strong>What are WebSockets?</strong></h2>
<p>WebSockets are communication protocols that establish a persistent, two-way connection between a web browser (client) and a web server. Unlike HTTP, which operates on a request-response basis, WebSockets create an open channel for continuous data exchange in both directions – just like in our Google Docs example.</p>
<p><strong>Think of it like a constant conversation:</strong></p>
<ul>
<li><p>HTTP: Imagine having to raise your hand and wait for your turn to speak every time you want to communicate something in Google Docs. This is similar to HTTP, where the client makes a request, waits for the server's response, and then makes another request if needed.</p>
</li>
<li><p>WebSockets: With WebSockets, it's like having a dedicated open line. You can speak (send data) and hear (receive data) simultaneously, fostering a real-time exchange.</p>
</li>
</ul>
<p>WebSockets are great for building real-time functionality into web applications, such as games and chat apps, or for communicating financial trading data or IoT sensor data.</p>
<p>In this article, let’s create a WebSocket server, and use Postman to send and receive messages across the WebSocket connection.</p>
<h2 id="heading-setup-the-project">Setup the project</h2>
<p>Create a new directory named <code>websockets</code></p>
<pre><code class="lang-bash">mkdir websockets
</code></pre>
<p>Initialize the project using npm</p>
<pre><code class="lang-bash">npm init -y
</code></pre>
<p>Install the WebSocket library in the project</p>
<pre><code class="lang-bash">npm install ws --save
</code></pre>
<p>Create a file name <code>server.js</code> in the root directory</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { WebSocketServer } <span class="hljs-keyword">from</span> <span class="hljs-string">'ws'</span>;

<span class="hljs-keyword">const</span> wss = <span class="hljs-keyword">new</span> WebSocketServer({ <span class="hljs-attr">port</span>: <span class="hljs-number">8080</span> });

wss.on(<span class="hljs-string">'connection'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">connection</span>(<span class="hljs-params">ws</span>) </span>{
    ws.on(<span class="hljs-string">'message'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">message</span>(<span class="hljs-params">data</span>) </span>{
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'received: %s'</span>, data);
    });

    ws.send(<span class="hljs-string">'Hey!, Welcome to the websocket server!'</span>);
});
</code></pre>
<p>Run the server</p>
<pre><code class="lang-bash">node server.js
</code></pre>
<h2 id="heading-test-the-server">Test the server</h2>
<p>To test our WebSocket server we will be using Postman.</p>
<p>In Postman, select <strong>New &gt; WebSocket Request</strong> to open a new tab. Enter the WebSocket server URL. A WebSocket URL begins with <code>ws://</code> or <code>wss://</code> and our server is running on <a target="_blank" href="http://localhost:8080"><code>localhost:8080</code></a>. Click <strong>Connect</strong>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713508462356/263a526c-9b13-48f5-9391-813a076cd42e.png" alt class="image--center mx-auto" /></p>
<p>Once the connection between the server and the postman is established, the Message pane will display the list of incoming and outgoing network messages.</p>
<h3 id="heading-inspect-handshake-details">Inspect handshake details.</h3>
<p>The connection we established between the Postman client and the local server is bidirectional, which means that in addition to receiving messages, we can also send them. Under the <strong>Message</strong> tab, write your message and click <strong>Send</strong>.</p>
<p>We may check the outgoing message in the <strong>Messages</strong> pane.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713508953581/d84c1cef-b3f8-4e4b-ae4a-a09c53ce6252.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-verify-the-outgoing-message-was-received-by-your-local-server">Verify the outgoing message was received by your local server:</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713509029331/147c431c-fe6f-40ba-969d-51d41009ad5d.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-send-system-information"><strong>Send system information</strong></h2>
<p>Let's update our WebSocket server and use it to send computer information in a set of interval</p>
<p>Let's install the system information package</p>
<pre><code class="lang-bash">npm install systeminformation
</code></pre>
<p>Let's add the function that will send a message about the current load on our CPU.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> si <span class="hljs-keyword">from</span> <span class="hljs-string">"systeminformation"</span>;

 <span class="hljs-built_in">setInterval</span>(<span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> cpuTemp = <span class="hljs-built_in">JSON</span>.stringify(<span class="hljs-keyword">await</span> si.currentLoad());
    ws.send(cpuTemp);
  }, <span class="hljs-number">1000</span>);
</code></pre>
<p>Here is the fully updated <code>server.js</code> file</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { WebSocketServer } <span class="hljs-keyword">from</span> <span class="hljs-string">'ws'</span>;
<span class="hljs-keyword">import</span> si <span class="hljs-keyword">from</span> <span class="hljs-string">"systeminformation"</span>;

<span class="hljs-keyword">const</span> wss = <span class="hljs-keyword">new</span> WebSocketServer({ <span class="hljs-attr">port</span>: <span class="hljs-number">8080</span> });

wss.on(<span class="hljs-string">'connection'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">connection</span>(<span class="hljs-params">ws</span>) </span>{
    ws.on(<span class="hljs-string">'message'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">message</span>(<span class="hljs-params">data</span>) </span>{
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'received: %s'</span>, data);
    });

    ws.send(<span class="hljs-string">'Hey!, Welcome to the websocket server!'</span>);
    <span class="hljs-built_in">setInterval</span>(<span class="hljs-keyword">async</span> () =&gt; {
        <span class="hljs-keyword">const</span> cpuTemp = <span class="hljs-built_in">JSON</span>.stringify(<span class="hljs-keyword">await</span> si.currentLoad());
        ws.send(cpuTemp);
    }, <span class="hljs-number">1000</span>);
});
</code></pre>
<p>Save your changes, and restart the local server from the command line:</p>
<pre><code class="lang-bash">node server.js
</code></pre>
<h3 id="heading-test-the-system-information">Test the system information</h3>
<p>Return to Postman and <strong>Connect</strong> to the local server. This time, we receive information about our CPU every second!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1713509474048/78051c88-de66-4f14-b741-c657f50bc949.png" alt class="image--center mx-auto" /></p>
<p>We have explored the core concepts and walked through building a basic server that sends CPU load time to the client. It's just the beginning, we can leverage WebSockets to build applications that unleash the power of real-time communication like chat applications, real-time notifications etc.</p>
<p>Github source code - <a target="_blank" href="https://github.com/icon-gaurav/node-websockets.git">https://github.com/icon-gaurav/node-websockets.git</a></p>
]]></content:encoded></item><item><title><![CDATA[Best Architecture Options for SaaS Platforms]]></title><description><![CDATA[In today's dynamic digital landscape, Software as a Service (SaaS) has emerged as a game-changer for businesses, offering scalable solutions without the hassle of managing complex infrastructure. But what exactly is SaaS, and why do we need it? This ...]]></description><link>https://gauravbytes.dev/best-architecture-options-for-saas-platforms</link><guid isPermaLink="true">https://gauravbytes.dev/best-architecture-options-for-saas-platforms</guid><category><![CDATA[SaaS]]></category><category><![CDATA[architecture]]></category><category><![CDATA[System Design]]></category><category><![CDATA[scalability]]></category><category><![CDATA[Microservices]]></category><category><![CDATA[customization]]></category><category><![CDATA[Security]]></category><dc:creator><![CDATA[Gaurav Kumar]]></dc:creator><pubDate>Mon, 22 Apr 2024 06:29:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1712602368127/974a66e9-2b7d-4b21-a156-f0f08555b01f.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In today's dynamic digital landscape, Software as a Service (SaaS) has emerged as a game-changer for businesses, offering scalable solutions without the hassle of managing complex infrastructure. But what exactly is SaaS, and why do we need it? This comprehensive guide'll delve into the essence of SaaS, explore its architecture, and unravel the key considerations for implementing a robust SaaS solution.</p>
<h2 id="heading-why-saas"><strong>Why SaaS?</strong></h2>
<p>The traditional model of software deployment often comes with hefty upfront costs, maintenance headaches, and scalability limitations. This is where Software as a Service (SaaS) shines. SaaS offers a compelling alternative, delivering software applications over the internet in a subscription-based model. This reduces upfront expenses and provides seamless scalability, automatic updates, and enhanced accessibility.</p>
<p>Here's why SaaS is rapidly becoming the go-to choice for businesses of all sizes:</p>
<ul>
<li><p><strong>Reduced Costs:</strong> SaaS eliminates the upfront investment required for traditional software licenses and eliminates the need for expensive hardware and IT infrastructure. Subscription fees are typically lower, and the provider handles maintenance costs.</p>
</li>
<li><p><strong>Scalability:</strong> SaaS applications can easily scale up or down based on your needs. No more worrying about overspending on unused software or struggling with insufficient capacity during growth spurts.</p>
</li>
<li><p><strong>Automatic Updates:</strong> SaaS providers handle updates and security patches, ensuring you're always using the latest version with improved features and security fixes.</p>
</li>
<li><p><strong>Accessibility:</strong> SaaS applications are accessible from anywhere with an internet connection and a compatible device. This empowers your workforce with remote working capabilities and improved collaboration.</p>
</li>
<li><p><strong>Ease of Use:</strong> SaaS solutions are typically web-based and user-friendly, requiring minimal training for your team. This reduces the burden on your IT department and gets your team up and running quickly.</p>
</li>
</ul>
<h2 id="heading-building-your-saas-empire-architectural-considerations">Building Your SaaS Empire: Architectural Considerations</h2>
<p>Now that we understand the benefits of SaaS, let's delve into the world of building your own SaaS application. The architecture you choose forms the foundation of your application, impacting scalability, security, and performance. Here are the key architectural approaches for SaaS</p>
<h3 id="heading-1-multi-tenant-architecture">1. Multi-tenant Architecture:</h3>
<p>The most common and cost-effective approach. A single codebase and infrastructure serve multiple tenants (customers). Data isolation techniques like schema isolation or database sharding ensure tenant data remains separate and secure.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1712601924699/ae742e4e-5bd7-4b10-adc7-564daef12ea7.jpeg" alt class="image--center mx-auto" /></p>
<p><strong>Benefits:</strong></p>
<ul>
<li><p><strong>Cost efficiency:</strong> Resource sharing reduces infrastructure and maintenance costs.</p>
</li>
<li><p><strong>Scalable:</strong> Easily accommodates a growing user base.</p>
</li>
<li><p><strong>Faster Development and Deployment:</strong> New features are rolled out to all tenants simultaneously.</p>
</li>
</ul>
<p><strong>Challenges:</strong></p>
<ul>
<li><p><strong>Data Isolation Complexity:</strong> Guaranteeing complete data separation requires meticulous design and implementation.</p>
</li>
<li><p><strong>Upgrades and Maintenance:</strong> Rollouts can affect all tenants simultaneously.</p>
</li>
</ul>
<h3 id="heading-2-single-tenant-architecture">2. Single-tenant Architecture:</h3>
<p>Each tenant has its dedicated application instance and infrastructure. This offers the highest level of customization and control but comes at a higher cost.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1712601854748/959c9896-5145-4f2b-9f61-3732a869951f.jpeg" alt class="image--center mx-auto" /></p>
<p><strong>Benefits:</strong></p>
<ul>
<li><p><strong>Unparalleled Data Isolation</strong>: Each tenant has complete control over its data, eliminating data breach concerns.</p>
</li>
<li><p><strong>Customization</strong>: Tenants can tailor the application to their specific needs.</p>
</li>
</ul>
<p><strong>Challenges:</strong></p>
<ul>
<li><p><strong>Increased Cost</strong>: Maintaining separate instances is resource-intensive.</p>
</li>
<li><p><strong>Slower Development and Deployment</strong>: Changes need individual deployment for each tenant instance.</p>
</li>
<li><p><strong>Scalability</strong>: Scaling involves adding resources for each new tenant.</p>
</li>
</ul>
<h3 id="heading-3-hybrid-architecture">3. Hybrid Architecture:</h3>
<p>A blend of multi-tenant and single-tenant approaches. The core application logic might be multi-tenant, while specific components or data can be single-tenant for enhanced customization.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1712601968668/09fef03e-a540-43ad-8632-cf2c9b927f3f.jpeg" alt class="image--center mx-auto" /></p>
<p><strong>Benefits:</strong></p>
<ul>
<li><p><strong>Cost-Customization Balance</strong>: Provides a balance between affordability and tenant-specific needs.</p>
</li>
<li><p><strong>Flexibility</strong>: Allows for tailored solutions based on individual tenant requirements.</p>
</li>
</ul>
<p><strong>Challenges:</strong></p>
<ul>
<li><p><strong>Increased Complexity</strong>: Managing this architecture requires more planning and coordination.</p>
</li>
<li><p><strong>Potential Data Isolation Concerns</strong>: Careful design is crucial for single-tenant components.</p>
</li>
</ul>
<h2 id="heading-choosing-the-right-architectural-fit">Choosing the Right Architectural Fit</h2>
<p>While designing the architecture for a SaaS application, several factors must be carefully considered to meet the unique requirements of the business and its users</p>
<ul>
<li><p><strong>Number of Tenants:</strong> Multi-tenant is generally preferable for a large user base where customization is not of much concern.</p>
</li>
<li><p><strong>Customization Needs:</strong> Single-tenant caters better for highly customized requirements.</p>
</li>
<li><p><strong>Budget and Scalability Goals:</strong> Cost and scalability needs should be carefully considered.</p>
</li>
</ul>
<p><strong>Additional Considerations:</strong></p>
<ul>
<li><p><strong>Microservices Architecture:</strong> Breaking down the application into independent, smaller services improves scalability and maintainability. This approach can be implemented with any of the above architectures.</p>
</li>
<li><p><strong>API-driven Architecture:</strong> Exposing functionalities through APIs allows easier integration with third-party applications and services.</p>
</li>
</ul>
<p>By understanding the "whys" and "hows" of SaaS architecture, we can make informed decisions for building a robust and scalable SaaS application that caters to specific business needs. Remember, the chosen architecture is pivotal to your SaaS application's success. Choose wisely!</p>
]]></content:encoded></item><item><title><![CDATA[Build Real-time notification API with Apollo Server and GraphQL subscriptions]]></title><description><![CDATA[Imagine receiving instant updates about new messages, friend requests, or activity notifications without refreshing the page. Real-time notifications are the key to keeping users engaged and informed in today's fast-paced digital landscape. Real-time...]]></description><link>https://gauravbytes.dev/build-real-time-notification-api-with-apollo-server-and-graphql-subscriptions</link><guid isPermaLink="true">https://gauravbytes.dev/build-real-time-notification-api-with-apollo-server-and-graphql-subscriptions</guid><category><![CDATA[GraphQL]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Apollo GraphQL]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[GraphQL Subscriptions]]></category><category><![CDATA[websockets]]></category><category><![CDATA[notifications]]></category><dc:creator><![CDATA[Gaurav Kumar]]></dc:creator><pubDate>Wed, 03 Apr 2024 07:07:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1711915605495/1715e91c-c06d-4321-a830-5f80e44630a1.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Imagine receiving instant updates about new messages, friend requests, or activity notifications without refreshing the page. Real-time notifications are the key to keeping users engaged and informed in today's fast-paced digital landscape. Real-time notifications enhance engagement, satisfaction, and retention. Keep users hooked with interactive, dynamic experiences that leave a lasting impression.</p>
<p>We can add real-time updates to our graphql API using graphql subscriptions. In this article, we will focus on implementing <strong>Subscriptions</strong> type that helps in building real-time notification.</p>
<h2 id="heading-what-are-graphql-subscriptions">What are graphql subscriptions?</h2>
<p>Subscriptions are a GraphQL feature that allows a server to send data to its clients when a specific <em>event</em> happens. Subscriptions are just part of your GraphQL contract, and they refer to <em>events</em>. To be able to send these <em>events</em> in real-time, you need to choose a transport that has support for that.<br />Because the server usually pushes subscription updates (instead of polling by the client), they generally use the WebSocket protocol instead of HTTP.</p>
<h2 id="heading-setup-graphql-server">Setup graphql server</h2>
<p>To set up a graphql server for subscription we have to use express middleware because it will run a web socket server and a graphql server. We already know how to set up a graphql server using standalone server configuration (here is the link to that article <a target="_blank" href="https://gauravbytes.hashnode.dev/beginners-guide-to-graphql-setting-up-your-nodejs-project-with-ease">setup graphql server</a>). So in this section, we only discuss the changes needed for express compatibility. The only thing that needs to change is our <code>server.ts</code> file.</p>
<p>Here is the GitHub link: <a target="_blank" href="https://github.com/icon-gaurav/mastering-graphql-with-nodejs/tree/graphql-subscriptions">https://github.com/icon-gaurav/mastering-graphql-with-nodejs/tree/graphql-subscriptions</a></p>
<p>You can clone this and follow along with my explanation.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { ApolloServer } <span class="hljs-keyword">from</span> <span class="hljs-string">'@apollo/server'</span>;
<span class="hljs-keyword">import</span> { expressMiddleware } <span class="hljs-keyword">from</span> <span class="hljs-string">'@apollo/server/express4'</span>;
<span class="hljs-keyword">import</span> { ApolloServerPluginDrainHttpServer } <span class="hljs-keyword">from</span> <span class="hljs-string">'@apollo/server/plugin/drainHttpServer'</span>;
<span class="hljs-keyword">import</span> { createServer } <span class="hljs-keyword">from</span> <span class="hljs-string">'http'</span>;
<span class="hljs-keyword">import</span> express <span class="hljs-keyword">from</span> <span class="hljs-string">'express'</span>;
<span class="hljs-keyword">import</span> { makeExecutableSchema } <span class="hljs-keyword">from</span> <span class="hljs-string">'@graphql-tools/schema'</span>;
<span class="hljs-keyword">import</span> cors <span class="hljs-keyword">from</span> <span class="hljs-string">'cors'</span>;
<span class="hljs-keyword">import</span> resolvers <span class="hljs-keyword">from</span> <span class="hljs-string">'./graphql/resolvers'</span>
<span class="hljs-keyword">import</span> typeDefs <span class="hljs-keyword">from</span> <span class="hljs-string">"./graphql/schema"</span>;
<span class="hljs-keyword">import</span> mongoose <span class="hljs-keyword">from</span> <span class="hljs-string">"mongoose"</span>;

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">startServer</span>(<span class="hljs-params"></span>)</span>{
    <span class="hljs-keyword">const</span> app = express();

    <span class="hljs-keyword">const</span> httpServer = createServer(app);

    <span class="hljs-keyword">const</span> schema = makeExecutableSchema({
        resolvers,
        typeDefs,
    });

    <span class="hljs-keyword">const</span> apolloServer = <span class="hljs-keyword">new</span> ApolloServer({
        schema,
        introspection:<span class="hljs-literal">true</span>,
    });

    <span class="hljs-keyword">await</span> apolloServer.start();

    app.use(<span class="hljs-string">'/graphql'</span>, cors&lt;cors.CorsRequest&gt;(), express.json(), expressMiddleware(apolloServer));

    <span class="hljs-keyword">const</span> DATABASE_URL: <span class="hljs-built_in">string</span> = <span class="hljs-string">`mongodb://localhost:27017/test`</span>;
    mongoose.connect(DATABASE_URL)
        .then(<span class="hljs-function">() =&gt;</span> {
            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Database connected successfully'</span>)
        })
        .catch(<span class="hljs-function">(<span class="hljs-params">e: <span class="hljs-built_in">any</span></span>) =&gt;</span> {
            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Error connecting : '</span>, e?.message)
        })

    <span class="hljs-keyword">const</span> PORT = <span class="hljs-number">4000</span>;
<span class="hljs-comment">// Now that our HTTP server is fully set up, we can listen to it.</span>
    httpServer.listen(PORT, <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Server is now running on http://localhost:<span class="hljs-subst">${PORT}</span>/graphql`</span>);
    });
}

startServer();
</code></pre>
<p>To start the server run this command <code>npm run start:dev</code></p>
<p>The server will start at port 4000 using express middleware.</p>
<h2 id="heading-define-subscription-in-the-schema">Define subscription in the schema</h2>
<p>Schema's Subscription type defines the top-level fields that a user/client can subscribe to. Defining a Subscription type is as easy as defining the schema of type Query and Mutation.</p>
<pre><code class="lang-graphql"><span class="hljs-comment"># Subscription type definition in GraphQL Schema</span>
<span class="hljs-keyword">type</span> Subscription {
        <span class="hljs-symbol">postCreated:</span> Post
    }

<span class="hljs-comment"># Return type of the Subscription</span>
<span class="hljs-keyword">type</span> Post {
        <span class="hljs-symbol">_id:</span> ID!
        <span class="hljs-symbol">title:</span> String!
        <span class="hljs-symbol">content:</span> String!
    }
</code></pre>
<p>The <code>postCreated</code> field will get updated whenever a new post is created in the backend and it will notify all clients with the newly created post who are subscribed to this event.</p>
<h2 id="heading-enable-subscriptions">Enable subscriptions</h2>
<p>To enable subscriptions we have to start the WebSocket server, here are the easy steps to start the GraphQL Websocket Server</p>
<ol>
<li><p>Install <code>graphql-ws</code>, <code>ws</code>, and <code>@graphql-tools/schema</code></p>
<pre><code class="lang-bash"> npm install --save graphql-ws ws @graphql-tools/schema
</code></pre>
</li>
<li><p>Create a WebsocketServer instance to use as a subscription server</p>
<pre><code class="lang-typescript"> <span class="hljs-keyword">import</span> { WebSocketServer } <span class="hljs-keyword">from</span> <span class="hljs-string">'ws'</span>;
 <span class="hljs-keyword">import</span> { useServer } <span class="hljs-keyword">from</span> <span class="hljs-string">'graphql-ws/lib/use/ws'</span>;

 <span class="hljs-comment">// Creating the WebSocket server</span>
     <span class="hljs-keyword">const</span> wsServer = <span class="hljs-keyword">new</span> WebSocketServer({
         <span class="hljs-comment">// This is the `httpServer` we created in a previous step.</span>
         server: httpServer,
         <span class="hljs-comment">// Pass a different path here if app.use</span>
         <span class="hljs-comment">// serves expressMiddleware at a different path</span>
         path: <span class="hljs-string">'/subscriptions'</span>,
     });
</code></pre>
</li>
<li><p>Add a plugin to the ApolloServer constructor to clean WebSocketServer and HTTP server, it helps in the proper shutdown of all the servers running on the machine.</p>
<pre><code class="lang-typescript">
     <span class="hljs-keyword">const</span> serverCleanup = useServer({ schema }, wsServer);

     <span class="hljs-keyword">const</span> apolloServer = <span class="hljs-keyword">new</span> ApolloServer({
         schema,
         introspection:<span class="hljs-literal">true</span>,
         plugins: [
             <span class="hljs-comment">// Proper shutdown for the HTTP server.</span>
             ApolloServerPluginDrainHttpServer({ httpServer }),

             <span class="hljs-comment">// Proper shutdown for the WebSocket server.</span>
             {
                 <span class="hljs-keyword">async</span> serverWillStart() {
                     <span class="hljs-keyword">return</span> {
                         <span class="hljs-keyword">async</span> drainServer() {
                             <span class="hljs-keyword">await</span> serverCleanup.dispose();
                         },
                     };
                 },
             },
         ],
     });
</code></pre>
</li>
</ol>
<p>Here is the fully configured <code>server.ts</code> file.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { ApolloServer } <span class="hljs-keyword">from</span> <span class="hljs-string">'@apollo/server'</span>;
<span class="hljs-keyword">import</span> { expressMiddleware } <span class="hljs-keyword">from</span> <span class="hljs-string">'@apollo/server/express4'</span>;
<span class="hljs-keyword">import</span> { ApolloServerPluginDrainHttpServer } <span class="hljs-keyword">from</span> <span class="hljs-string">'@apollo/server/plugin/drainHttpServer'</span>;
<span class="hljs-keyword">import</span> { createServer } <span class="hljs-keyword">from</span> <span class="hljs-string">'http'</span>;
<span class="hljs-keyword">import</span> express <span class="hljs-keyword">from</span> <span class="hljs-string">'express'</span>;
<span class="hljs-keyword">import</span> { makeExecutableSchema } <span class="hljs-keyword">from</span> <span class="hljs-string">'@graphql-tools/schema'</span>;
<span class="hljs-keyword">import</span> { WebSocketServer } <span class="hljs-keyword">from</span> <span class="hljs-string">'ws'</span>;
<span class="hljs-keyword">import</span> cors <span class="hljs-keyword">from</span> <span class="hljs-string">'cors'</span>;
<span class="hljs-keyword">import</span> { useServer } <span class="hljs-keyword">from</span> <span class="hljs-string">'graphql-ws/lib/use/ws'</span>;
<span class="hljs-keyword">import</span> resolvers <span class="hljs-keyword">from</span> <span class="hljs-string">'./graphql/resolvers'</span>
<span class="hljs-keyword">import</span> typeDefs <span class="hljs-keyword">from</span> <span class="hljs-string">"./graphql/schema"</span>;
<span class="hljs-keyword">import</span> mongoose <span class="hljs-keyword">from</span> <span class="hljs-string">"mongoose"</span>;

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">startServer</span>(<span class="hljs-params"></span>)</span>{
    <span class="hljs-keyword">const</span> app = express();

    <span class="hljs-keyword">const</span> httpServer = createServer(app);

    <span class="hljs-keyword">const</span> schema = makeExecutableSchema({
        resolvers,
        typeDefs,
    });

    <span class="hljs-comment">// Creating the WebSocket server</span>
    <span class="hljs-keyword">const</span> wsServer = <span class="hljs-keyword">new</span> WebSocketServer({
        <span class="hljs-comment">// This is the `httpServer` we created in a previous step.</span>
        server: httpServer,
        <span class="hljs-comment">// Pass a different path here if app.use</span>
        <span class="hljs-comment">// serves expressMiddleware at a different path</span>
        path: <span class="hljs-string">'/subscriptions'</span>,
    });

<span class="hljs-comment">// Hand in the schema we just created and have the</span>
<span class="hljs-comment">// WebSocketServer start listening.</span>
    <span class="hljs-keyword">const</span> serverCleanup = useServer({ schema }, wsServer);

    <span class="hljs-keyword">const</span> apolloServer = <span class="hljs-keyword">new</span> ApolloServer({
        schema,
        introspection:<span class="hljs-literal">true</span>,
        plugins: [
            <span class="hljs-comment">// Proper shutdown for the HTTP server.</span>
            ApolloServerPluginDrainHttpServer({ httpServer }),

            <span class="hljs-comment">// Proper shutdown for the WebSocket server.</span>
            {
                <span class="hljs-keyword">async</span> serverWillStart() {
                    <span class="hljs-keyword">return</span> {
                        <span class="hljs-keyword">async</span> drainServer() {
                            <span class="hljs-keyword">await</span> serverCleanup.dispose();
                        },
                    };
                },
            },
        ],
    });

    <span class="hljs-keyword">await</span> apolloServer.start();

    app.use(<span class="hljs-string">'/graphql'</span>, cors&lt;cors.CorsRequest&gt;(), express.json(), expressMiddleware(apolloServer));

    <span class="hljs-keyword">const</span> DATABASE_URL: <span class="hljs-built_in">string</span> = <span class="hljs-string">`mongodb://localhost:27017/test`</span>
    mongoose.connect(DATABASE_URL)
        .then(<span class="hljs-function">() =&gt;</span> {
            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Database connected successfully'</span>)
        })
        .catch(<span class="hljs-function">(<span class="hljs-params">e: <span class="hljs-built_in">any</span></span>) =&gt;</span> {
            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Error connecting : '</span>, e?.message)
        })

    <span class="hljs-keyword">const</span> PORT = <span class="hljs-number">4000</span>;
<span class="hljs-comment">// Now that our HTTP server is fully set up, we can listen to it.</span>
    httpServer.listen(PORT, <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Server is now running on http://localhost:<span class="hljs-subst">${PORT}</span>/graphql`</span>);
    });
}

startServer();
</code></pre>
<h2 id="heading-implement-the-resolvers-for-subscription">Implement the resolvers for subscription</h2>
<p>Resolvers for subscriptions are different from Mutation and Query types, as subscription field resolvers are objects that define a <code>subscribe</code> function. Let's implement a resolver for <code>postCreated</code> subscription. Here is how we define the resolver for the subscription type</p>
<pre><code class="lang-typescript">Subscription:{
        postCreated: {
            subscribe: <span class="hljs-function">() =&gt;</span> pubsub.asyncIterator([<span class="hljs-string">'POST_CREATED'</span>]),
        },
    }
</code></pre>
<ol>
<li><p>The <code>subscribe</code> function <strong>must</strong> return an object of type <code>AsyncIterator</code>, a standard interface for iterating over asynchronous results.</p>
</li>
<li><p><code>AsyncIterator</code> is generated by <code>pubsub.asyncIterator</code></p>
</li>
<li><p><code>pubsub.asyncIterator</code> object listens for events that are associated with a label and adds them to a queue to process them</p>
</li>
</ol>
<h2 id="heading-publish-an-event">Publish an event</h2>
<p>This step involves publishing an event for which we need a real-time notification. This generally happens when there is a new database entry or some updates from the admin side.</p>
<p>In this example, we will publish an <code>"POST_CREATED"</code> event whenever a new post is created using <code>createPost</code> mutation.</p>
<pre><code class="lang-typescript">createPost: <span class="hljs-keyword">async</span> (_parent: <span class="hljs-built_in">any</span>, args: <span class="hljs-built_in">any</span>, _context: <span class="hljs-built_in">any</span>) =&gt; {
            <span class="hljs-comment">// create new post document</span>
            <span class="hljs-keyword">const</span> post = <span class="hljs-keyword">new</span> Post(args);
            <span class="hljs-comment">//save post document and return the saved document</span>
            <span class="hljs-keyword">await</span> post.save();
            <span class="hljs-keyword">await</span> pubsub.publish(<span class="hljs-string">'POST_CREATED'</span>, {postCreated: post});
            <span class="hljs-keyword">return</span> post;
        }
</code></pre>
<p><code>pubsub.publish</code> function do the publishing of the event along with the payload of this event which in this case is post data.</p>
<p>Here is the full example of all the resolvers that is needed</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> {posts, users, users_posts} <span class="hljs-keyword">from</span> <span class="hljs-string">"../utils/data"</span>;
<span class="hljs-keyword">import</span> Post <span class="hljs-keyword">from</span> <span class="hljs-string">"../models/Post"</span>;
<span class="hljs-keyword">import</span> User <span class="hljs-keyword">from</span> <span class="hljs-string">"../models/User"</span>;
<span class="hljs-keyword">import</span> bcrypt <span class="hljs-keyword">from</span> <span class="hljs-string">'bcrypt'</span>;
<span class="hljs-keyword">import</span> jwt <span class="hljs-keyword">from</span> <span class="hljs-string">'jsonwebtoken'</span>;
<span class="hljs-keyword">import</span> { PubSub } <span class="hljs-keyword">from</span> <span class="hljs-string">'graphql-subscriptions'</span>;

<span class="hljs-keyword">const</span> pubsub = <span class="hljs-keyword">new</span> PubSub();


<span class="hljs-keyword">const</span> resolvers = {
    Query: {
        posts: <span class="hljs-keyword">async</span> (_parent: <span class="hljs-built_in">any</span>, _args: <span class="hljs-built_in">any</span>, context: <span class="hljs-built_in">any</span>) =&gt; {
            <span class="hljs-keyword">return</span> Post.find();
        },
    },
    Mutation: {
        <span class="hljs-comment">// Mutation that will create a new post and save in DB</span>
        createPost: <span class="hljs-keyword">async</span> (_parent: <span class="hljs-built_in">any</span>, args: <span class="hljs-built_in">any</span>, _context: <span class="hljs-built_in">any</span>) =&gt; {
            <span class="hljs-comment">// create new post document</span>
            <span class="hljs-keyword">const</span> post = <span class="hljs-keyword">new</span> Post(args);
            <span class="hljs-comment">//save post document and return the saved document</span>
            <span class="hljs-keyword">await</span> post.save();
            <span class="hljs-comment">// publish the "POST_CREATED" event</span>
            <span class="hljs-keyword">await</span> pubsub.publish(<span class="hljs-string">'POST_CREATED'</span>, {postCreated: post});
            <span class="hljs-keyword">return</span> post;
        }
    },
    Subscription:{
        postCreated: {
            <span class="hljs-comment">// resolving subscribe function</span>
            subscribe: <span class="hljs-function">() =&gt;</span> pubsub.asyncIterator([<span class="hljs-string">'POST_CREATED'</span>]),
        },
    }
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> resolvers;
</code></pre>
<p>Now the server is ready, we can test our subscription.</p>
<h2 id="heading-test-our-api">Test our API</h2>
<p>Run the server using <code>npm run start:dev</code> command</p>
<p>Here is the output of this server in which one window defines how we create a post and in another window, we listen to the subscription and see the data updated there. In the same way, we can use this system to build a full-fledged notification system, including authentication, authorization and filtering.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711911887586/35190d3a-9fd3-45fb-9012-eecccd9a8d97.gif" alt class="image--center mx-auto" /></p>
<p>GraphQL subscriptions offer a powerful solution for real-time data updates in web applications. By leveraging subscriptions, developers can enhance user experience by providing instant updates without the need for constant polling. Implementing GraphQL subscriptions is quite fun and I believe we enjoyed it.</p>
<p>Here is the GitHub link: <a target="_blank" href="https://github.com/icon-gaurav/mastering-graphql-with-nodejs/tree/graphql-subscriptions">https://github.com/icon-gaurav/mastering-graphql-with-nodejs/tree/graphql-subscriptions</a></p>
<p><strong><em>Note:</em></strong> <em>It is not recommended to use the PubSub class for production as it uses an in-memory event publishing system, instead we can use an external datastore-based library according to the use cases.</em></p>
<p>You can check the library list and further info here: <a target="_blank" href="https://www.apollographql.com/docs/apollo-server/data/subscriptions/#production-pubsub-libraries">https://www.apollographql.com/docs/apollo-server/data/subscriptions/#production-pubsub-libraries</a></p>
]]></content:encoded></item><item><title><![CDATA[Empower Your GraphQL API Testing: A Step-by-Step Jest Guide]]></title><description><![CDATA[Are you ready to take your GraphQL API testing to the next level? If you've ever wondered how to test your GraphQL endpoints using Jest effectively, you're in the right place. Testing GraphQL APIs can be challenging due to their unique query language...]]></description><link>https://gauravbytes.dev/empower-your-graphql-api-testing-a-step-by-step-jest-guide</link><guid isPermaLink="true">https://gauravbytes.dev/empower-your-graphql-api-testing-a-step-by-step-jest-guide</guid><category><![CDATA[Testing]]></category><category><![CDATA[Jest]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[GraphQL]]></category><category><![CDATA[unit tests]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[Integration Testing]]></category><dc:creator><![CDATA[Gaurav Kumar]]></dc:creator><pubDate>Sat, 16 Mar 2024 07:13:59 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1709374723047/7e62e21f-3689-4d00-ad3a-15cb1863b04c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Are you ready to take your GraphQL API testing to the next level? If you've ever wondered how to test your GraphQL endpoints using Jest effectively, you're in the right place. Testing GraphQL APIs can be challenging due to their unique query language and resolvers, but fear not – Jest is here to simplify the process.</p>
<p>You can fork this GitHub repository (<a target="_blank" href="https://github.com/icon-gaurav/mastering-graphql-with-nodejs/tree/testing-with-jest">Repository Link</a>) and get along with me step-by-step</p>
<p>Let's dive in and unlock the secrets to effective GraphQL API testing together!</p>
<h3 id="heading-installing-jest-and-required-dependencies">Installing Jest and required dependencies</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># for typescript</span>
npm install --save-dev ts-jest

<span class="hljs-comment"># for javascript</span>
npm install --save-dev jest
</code></pre>
<h3 id="heading-configuring-jest-for-testing">Configuring Jest for testing</h3>
<p>Since we are using typescript, we need to configure our application a little bit</p>
<p>Let's initialize the jest using the command</p>
<pre><code class="lang-bash">npx ts-jest config:init
</code></pre>
<p>This will generate a <strong><em><mark>jest.config.js</mark></em></strong> file. This is what makes jest to work with typescript.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709574944290/21268ed1-7e64-4cdd-8095-b394e22222ec.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-writing-test-cases">Writing Test cases</h3>
<p>Let's create a file named <strong><em><mark>createPost.test.ts</mark></em></strong> in the <strong><em><mark>tests</mark></em>*</strong>* directory.</p>
<p>Then import the <strong><em>@jest/globals</em></strong> library, which has the required functions to test.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> {describe, expect, test} <span class="hljs-keyword">from</span> <span class="hljs-string">'@jest/globals'</span>;
<span class="hljs-keyword">import</span> {GraphQLClient} <span class="hljs-keyword">from</span> <span class="hljs-string">'graphql-request'</span>;
</code></pre>
<p>We need a graphql client to execute the graphql query and get the response to compare the expected result. We are going to use the '<strong><em>graphql-request</em></strong>' library for that</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> {GraphQLClient} <span class="hljs-keyword">from</span> <span class="hljs-string">'graphql-request'</span>;

<span class="hljs-comment">// create a new graphql client for requesting to our api</span>
<span class="hljs-keyword">const</span> client = <span class="hljs-keyword">new</span> GraphQLClient(<span class="hljs-string">"http://localhost:4000/"</span>)
</code></pre>
<p>Let's define what we are going to test in this example.</p>
<p>In this example, we are going to test the <strong><em>createPost</em></strong> mutation in which we first create the post using our API and then check whether it has the same title as the post or not</p>
<pre><code class="lang-typescript">describe(<span class="hljs-string">'create post mutation'</span>, <span class="hljs-function">() =&gt;</span> {

    test(<span class="hljs-string">'creates a new post'</span>, <span class="hljs-keyword">async</span> () =&gt; {

        <span class="hljs-comment">// graphql query</span>
        <span class="hljs-keyword">const</span> query = <span class="hljs-string">`#graphql 
        mutation CREATE_POST_TEST($title: String!, $content: String) {
            createPost(title: $title, content: $content) {
                _id
                content
                content
                title
            }
        }
        `</span>;

        <span class="hljs-comment">// query variables</span>
        <span class="hljs-keyword">const</span> variables = {
            <span class="hljs-string">"title"</span>: <span class="hljs-string">"testing post title"</span>,
            <span class="hljs-string">"content"</span>: <span class="hljs-string">"testing post content"</span>
        };

        <span class="hljs-keyword">let</span> data: <span class="hljs-built_in">any</span> = {};

        <span class="hljs-keyword">try</span> {
            <span class="hljs-comment">// execute the query</span>
            data = <span class="hljs-keyword">await</span> client.request(query, variables);

        } <span class="hljs-keyword">catch</span> (e) {
            <span class="hljs-comment">// report the error</span>
            <span class="hljs-built_in">console</span>.log(e)
        }

        expect(data?.createPost).toHaveProperty(<span class="hljs-string">"title"</span>, variables?.title)

    });

});
</code></pre>
<p>In the above code</p>
<ol>
<li><p>We use <strong><em><mark>describe</mark></em></strong> to describe a unit test suite. Even though it's not required, it's helpful to understand what this test suite is all about.</p>
</li>
<li><p>In the <strong><em><mark>test</mark></em></strong>, we write the test code. The first argument of this function tells about what this test case is all about and the second argument is a callback function that contains the test code</p>
</li>
<li><p>In the callback function first, we sent the API request and then compared the actual and expected responses. The test passes if both match otherwise it fails.</p>
</li>
</ol>
<h3 id="heading-execute-the-test-cases">Execute the test cases</h3>
<p>There are two steps in executing the test cases</p>
<ol>
<li>Start the API server</li>
</ol>
<pre><code class="lang-bash">npm run start:dev
</code></pre>
<ol start="2">
<li>Run jest to execute all of the test cases</li>
</ol>
<pre><code class="lang-bash">jest
</code></pre>
<h3 id="heading-output">Output</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709576704056/6ec50537-8fb9-4814-be65-3540f72a8b48.png" alt class="image--center mx-auto" /></p>
<p>With Jest's help, we've learned how to set up a killer testing environment, write top-notch test cases, and integrate testing seamlessly into our workflow. So, let's keep that testing momentum going and deliver some seriously reliable and resilient APIs to our users! Ready to level up? Let's do this!</p>
]]></content:encoded></item></channel></rss>