<?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[NishikantaRay]]></title><description><![CDATA[NishikantaRay]]></description><link>https://blog.nishikanta.in</link><generator>RSS for Node</generator><lastBuildDate>Mon, 18 May 2026 17:55:11 GMT</lastBuildDate><atom:link href="https://blog.nishikanta.in/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[We Let an AI Break Our Analytics Platform — Here's Every Bug It Found]]></title><description><![CDATA[Github Main- https://github.com/NishikantaRay/InsightTrack
Passmark - https://github.com/NishikantaRay/InsightTrack/tree/main/appsv2/passmark-tests



InsightTrack — 17 pages, dual-database architectu]]></description><link>https://blog.nishikanta.in/we-let-an-ai-break-our-analytics-platform-here-s-every-bug-it-found</link><guid isPermaLink="true">https://blog.nishikanta.in/we-let-an-ai-break-our-analytics-platform-here-s-every-bug-it-found</guid><category><![CDATA[breakingappshackathon]]></category><category><![CDATA[passmark]]></category><category><![CDATA[playwright]]></category><category><![CDATA[webdev]]></category><category><![CDATA[analytics]]></category><category><![CDATA[duckDB]]></category><category><![CDATA[PostgreSQL]]></category><dc:creator><![CDATA[Nishikanta Ray]]></dc:creator><pubDate>Sun, 10 May 2026 15:20:29 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/606f7705d741af6659cf980f/fac4a3f8-6bb5-4013-9f9a-db54598573e1.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>Github Main- <a href="https://github.com/NishikantaRay/InsightTrack">https://github.com/NishikantaRay/InsightTrack</a></p>
<p>Passmark - <a href="https://github.com/NishikantaRay/InsightTrack/tree/main/appsv2/passmark-tests">https://github.com/NishikantaRay/InsightTrack/tree/main/appsv2/passmark-tests</a></p>
</blockquote>
<img src="https://cdn.hashnode.com/uploads/covers/606f7705d741af6659cf980f/ef6b6bbd-7abf-4ed0-8e38-75843f7ccecf.png" alt="" style="display:block;margin:0 auto" />

<p><em>InsightTrack — 17 pages, dual-database architecture, tested with 52 AI-powered test cases.</em></p>
<hr />
<h2>Executive Summary</h2>
<p>We built <strong>InsightTrack</strong> — a self-hosted, privacy-first alternative to Google Analytics. After shipping 17 dashboard pages spanning a PostgreSQL + DuckDB dual-database architecture, manual regression testing became the team's biggest bottleneck.</p>
<p>The solution: a <strong>52-test AI-powered test suite</strong> built on <a href="https://github.com/bug0inc/passmark">Passmark</a>, where every assertion is written in plain English and an AI model executes and judges them against the live application at runtime. Zero CSS selectors. Zero <code>data-testid</code> attributes. Zero XPath.</p>
<p><strong>What actually happened on our first real run:</strong></p>
<table>
<thead>
<tr>
<th></th>
<th>Number</th>
</tr>
</thead>
<tbody><tr>
<td>Tests written</td>
<td>52 (13 spec files, 17 routes)</td>
</tr>
<tr>
<td>Tests that ran to completion</td>
<td>11</td>
</tr>
<tr>
<td>Tests that passed</td>
<td>8</td>
</tr>
<tr>
<td>Tests that exposed real product bugs</td>
<td>3</td>
</tr>
<tr>
<td>Tests cut short by API credit exhaustion</td>
<td>38</td>
</tr>
<tr>
<td>Bugs found (total, including framework issue)</td>
<td>4</td>
</tr>
<tr>
<td>Runtime before credits ran out</td>
<td>~1.5 hours</td>
</tr>
<tr>
<td>OpenRouter API cost spent</td>
<td>~$0.45</td>
</tr>
</tbody></table>
<p><strong>The honest picture:</strong> 11 tests ran, 8 passed, 3 caught real bugs, 38 were aborted when the OpenRouter API key hit its per-key spending limit. The 4 bugs — a vision model integration issue, form validation not surfacing, a registration flow problem, and a duplicate email error — had all been present for months. Manual testing missed every one.</p>
<p>This post documents the full story: the application architecture, how we built the test suite, what every test checks, the actual run results, and each bug with its root cause and fix.</p>
<hr />
<h2>Part 1: The Application — InsightTrack</h2>
<img src="https://cdn.hashnode.com/uploads/covers/606f7705d741af6659cf980f/d641108a-00b1-46d6-ada8-6843b74e6b97.png" alt="" style="display:block;margin:0 auto" />

<p><em>KPI cards and traffic chart. All analytics reads go directly to DuckDB — 10–100× faster than PostgreSQL for OLAP aggregations.</em></p>
<p>InsightTrack is a self-hosted web analytics platform. The defining architectural decision is the <strong>dual-database write/read split</strong>:</p>
<table>
<thead>
<tr>
<th>Layer</th>
<th>Technology</th>
<th>Role</th>
</tr>
</thead>
<tbody><tr>
<td>Tracking script</td>
<td>Custom 2KB JS snippet</td>
<td>Fires events from any website via <code>POST /api/track</code></td>
</tr>
<tr>
<td>Write path</td>
<td>PostgreSQL + Express + Node.js 20</td>
<td>Stores raw events, manages auth, handles site config</td>
</tr>
<tr>
<td>Sync worker</td>
<td><code>sync.js</code> background process</td>
<td>Incrementally copies PostgreSQL → DuckDB every 30s</td>
</tr>
<tr>
<td>Read path</td>
<td>DuckDB</td>
<td>All analytics queries — columnar, fast, zero contention</td>
</tr>
<tr>
<td>Frontend</td>
<td>React 18 + Vite 5 + Recharts + Zustand</td>
<td>TypeScript dashboard SPA with Tailwind CSS</td>
</tr>
<tr>
<td>Real-time</td>
<td>WebSockets</td>
<td>Live visitor counter and event stream</td>
</tr>
</tbody></table>
<h3>All 17 Pages Under Test</h3>
<table>
<thead>
<tr>
<th>Route</th>
<th>Page</th>
<th>Purpose</th>
</tr>
</thead>
<tbody><tr>
<td><code>/</code> (redirects)</td>
<td>Landing</td>
<td>Marketing page with login/register CTAs</td>
</tr>
<tr>
<td><code>/login</code></td>
<td>Login</td>
<td>Email + password authentication</td>
</tr>
<tr>
<td><code>/register</code></td>
<td>Register</td>
<td>New account creation</td>
</tr>
<tr>
<td><code>/dashboard</code></td>
<td>Main Dashboard</td>
<td>KPI overview, traffic chart, date range picker</td>
</tr>
<tr>
<td><code>/pages</code></td>
<td>Pages</td>
<td>Top pages by views, bounce rate, session time</td>
</tr>
<tr>
<td><code>/funnels</code></td>
<td>Funnels</td>
<td>Multi-step funnel builder + visualisation</td>
</tr>
<tr>
<td><code>/conversions</code></td>
<td>Conversions</td>
<td>Goal conversion rates and trends</td>
</tr>
<tr>
<td><code>/audience</code></td>
<td>Audience</td>
<td>New vs. returning, device + country breakdown</td>
</tr>
<tr>
<td><code>/content</code></td>
<td>Content Analytics</td>
<td>Scroll depth, engagement, content performance</td>
</tr>
<tr>
<td><code>/acquisition</code></td>
<td>Acquisition</td>
<td>Traffic sources, referrers, UTM campaigns</td>
</tr>
<tr>
<td><code>/performance</code></td>
<td>Performance</td>
<td>Core Web Vitals, LCP, CLS, FID</td>
</tr>
<tr>
<td><code>/realtime</code></td>
<td>Realtime</td>
<td>Live visitor count, world map, event stream</td>
</tr>
<tr>
<td><code>/user-flow</code></td>
<td>User Flow</td>
<td>Sankey diagram of navigation paths</td>
</tr>
<tr>
<td><code>/engagement</code></td>
<td>Engagement</td>
<td>Session depth, click patterns, return rate</td>
</tr>
<tr>
<td><code>/reporting</code></td>
<td>Reporting</td>
<td>Scheduled report builder</td>
</tr>
<tr>
<td><code>/privacy</code></td>
<td>Privacy</td>
<td>Consent management, data retention</td>
</tr>
<tr>
<td><code>/settings</code></td>
<td>Settings</td>
<td>Tracking snippet, site manager, alerts</td>
</tr>
<tr>
<td><code>/profile</code></td>
<td>Profile</td>
<td>User info, password management</td>
</tr>
<tr>
<td><code>/docs</code></td>
<td>Docs</td>
<td>In-app documentation</td>
</tr>
</tbody></table>
<p>Testing all 17 manually on every release, while simultaneously shipping features, had become unsustainable.</p>
<hr />
<h2>Part 2: Why Passmark</h2>
<img src="https://cdn.hashnode.com/uploads/covers/606f7705d741af6659cf980f/57f181a1-e818-4878-a4e4-e480619e4279.png" alt="" style="display:block;margin:0 auto" />

<p><em>The login page. Five separate tests: valid credentials, wrong password, empty form validation, password toggle, and register link. Each uses natural language — no selectors.</em></p>
<h3>The Maintenance Problem With Traditional E2E Tests</h3>
<p>We had been using Playwright. The problem was not writing tests — it was keeping them alive. Every component refactor broke <code>[data-testid="kpi-card"]</code>. Every sidebar redesign required updating all those <code>nth-child</code> selectors. Tests that require constant maintenance stop getting run.</p>
<h3>What Passmark Does Differently</h3>
<p>Passmark wraps Playwright with an AI layer. You write what you want to verify in plain English. The AI reads the page's accessibility tree — the same structured representation a screen reader or human QA engineer uses — and executes browser actions from that understanding.</p>
<p><strong>Traditional Playwright:</strong></p>
<pre><code class="language-ts">await page.locator('[data-testid="kpi-visitors-card"]').waitFor();
const val = await page.locator('[data-testid="kpi-visitors-value"]').textContent();
expect(Number(val)).toBeGreaterThanOrEqual(0);
</code></pre>
<p><strong>Passmark:</strong></p>
<pre><code class="language-ts">await runSteps({
  page,
  userFlow: 'Dashboard KPI check',
  steps: [
    { description: 'Navigate to /dashboard' },
    { description: 'Wait until a Dashboard heading is visible',
      waitUntil: 'A Dashboard heading is visible on the page' },
  ],
  assertions: [
    { assertion: 'A "Unique Visitors" or "Visitors" metric card is visible' },
    { assertion: 'A "Pageviews" metric card is visible' },
    { assertion: 'A "Bounce Rate" metric card is visible' },
  ],
  test, expect,
});
</code></pre>
<p>During this project we renamed "Unique Visitors" to "Total Visitors" in the UI. The Passmark assertion — <code>'A "Unique Visitors" or "Visitors" metric card is visible'</code> — matched the new label and kept passing. The old Playwright test would have failed and required a code change.</p>
<blockquote>
<p><strong>The principle:</strong> Assertions that describe <em>intent</em> outlive assertions that describe <em>implementation</em>. That's the difference between a test suite that stays green and one that becomes a maintenance tax.</p>
</blockquote>
<h3>How the AI Pipeline Works</h3>
<p>Every <code>runSteps()</code> call runs this sequence:</p>
<pre><code class="language-plaintext">1. DOM snapshot
   Passmark captures the page accessibility tree as structured text:
   headings, buttons, inputs, links — their roles, labels, text content.
   No screenshot. Pure semantic structure.

2. Step execution
   Each step description goes to the AI with the current DOM snapshot.
   The AI returns browser tool calls:
     browser_navigate("/settings")
     browser_click("Copy button")
     browser_fill("email input", "user@example.com")
   Playwright executes these against a real Chromium browser.

3. waitUntil polling
   If a step has a waitUntil condition, Passmark re-snapshots the DOM
   every 2 seconds and asks the AI: "Is this condition met?"
   It polls for up to 2 minutes before timing out.

4. Assertion judging (3 models)
   Each assertion is sent to a primary judge, then a secondary judge.
   If they disagree, an arbiter breaks the tie.
   Every assertion returns a boolean + written reasoning.
</code></pre>
<hr />
<h2>Part 3: How We Built the Test Suite</h2>
<h3>Repository Structure</h3>
<pre><code class="language-plaintext">appsv2/passmark-tests/
├── .env                        ← OPENROUTER_API_KEY, PW_BASE_URL, API_BASE_URL
├── playwright.config.ts        ← Passmark model config + Playwright settings
├── helpers/
│   └── auth.ts                 ← createTestSession() + injectAuth()
└── tests/
    ├── auth/
    │   ├── login.spec.ts        ← 5 tests
    │   └── register.spec.ts     ← 4 tests
    ├── public/
    │   └── landing.spec.ts      ← 3 tests
    ├── dashboard/
    │   ├── dashboard.spec.ts    ← 6 tests
    │   ├── analytics-sections.spec.ts  ← 9 tests
    │   ├── realtime.spec.ts     ← 4 tests
    │   ├── funnels.spec.ts      ← 3 tests
    │   ├── pages.spec.ts        ← 3 tests
    │   ├── settings.spec.ts     ← 4 tests
    │   ├── profile.spec.ts      ← 2 tests
    │   └── docs.spec.ts         ← 2 tests
    ├── navigation.spec.ts       ← 5 tests
    └── theme.spec.ts            ← 2 tests
</code></pre>
<p><strong>52 tests. 13 spec files. 17 routes covered.</strong></p>
<h3><code>playwright.config.ts</code> — The Configuration That Drives Everything</h3>
<pre><code class="language-ts">import dotenv from 'dotenv';
import { defineConfig, devices } from '@playwright/test';
import { configure } from 'passmark';

dotenv.config();

// Route all AI calls through OpenRouter
configure({
  ai: {
    gateway: 'openrouter',
    models: {
      stepExecution:      'openai/gpt-4.1-mini',  // browser tool calls
      userFlowLow:        'openai/gpt-4.1-mini',  // simple flow planning
      userFlowHigh:       'openai/gpt-4.1-mini',  // complex flow planning
      assertionPrimary:   'openai/gpt-4.1-mini',  // assertion judge #1
      assertionSecondary: 'openai/gpt-4.1-mini',  // assertion judge #2
      assertionArbiter:   'openai/gpt-4.1-mini',  // tiebreaker
      utility:            'openai/gpt-4.1-mini',  // DOM condition checks
    },
  },
});

export default defineConfig({
  testDir: './tests',
  timeout: 180_000,       // global ceiling
  workers: 1,             // serial — prevents auth race conditions
  retries: 1,             // one automatic retry on failure

  use: {
    baseURL: process.env.PW_BASE_URL || 'http://localhost:4173',
    headless: true,
    viewport: { width: 1280, height: 720 },  // standard reference viewport
    actionTimeout: 10_000,
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
  },

  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
  ],

  reporter: [
    ['list'],
    ['html', { outputFolder: 'playwright-report', open: 'never' }],
  ],
});
</code></pre>
<p><strong>Key decisions explained:</strong></p>
<ul>
<li><p><code>workers: 1</code> — Serial execution prevents test users racing to register with the same email at the same timestamp</p>
</li>
<li><p><code>retries: 1</code> — On retry, Passmark performs a fully fresh AI run, making it genuinely self-healing for transient DOM issues</p>
</li>
<li><p><code>viewport: 1280×720</code> — The standard reference viewport. Critical: many developers use 1440p+ monitors and never see layout issues that only manifest at smaller widths. This viewport mismatch is exactly how Bug 3 was caught.</p>
</li>
<li><p><strong>Per-test</strong> <code>test.setTimeout(240_000)</code> — The global <code>timeout: 180_000</code> is a ceiling, but individual tests override it. With <code>retries: 1</code>, a <code>240_000</code> timeout means up to 8 minutes maximum before permanent failure. With AI models averaging 8–15 seconds per tool call, this is the minimum safe budget.</p>
</li>
</ul>
<h3><code>.env</code> Configuration</h3>
<pre><code class="language-bash">OPENROUTER_API_KEY=sk-or-v1-...
PW_BASE_URL=http://localhost:4173
API_BASE_URL=http://localhost:3001
TEST_USER_EMAIL=passmark-tester@insighttrack.local
TEST_USER_PASSWORD=Passmark$ecure123
</code></pre>
<blockquote>
<p><strong>Before running the full suite:</strong> Check your credit balance:</p>
<pre><code class="language-bash">curl https://openrouter.ai/api/v1/auth/key \
  -H "Authorization: Bearer $OPENROUTER_API_KEY"
</code></pre>
<p>A full 52-test run costs approximately <strong>$0.50–0.60</strong> with <code>gpt-4.1-mini</code>. Running out of credits mid-suite produces 403 errors that look like test failures but are infrastructure failures.</p>
</blockquote>
<h3>The Auth Helper — The Pattern That Makes Every Dashboard Test Fast</h3>
<p>Testing auth-protected routes without solving auth efficiently is the biggest time sink in dashboard testing. If every test navigates the login form via AI, you waste 45–60 seconds per test on flow that isn't what you're testing, and you create a hard dependency: if login breaks, every other test breaks.</p>
<p>The solution: inject JWT + siteId into <code>localStorage</code> via <code>page.addInitScript</code> before React boots. By the time Zustand hydrates, the auth state is already populated.</p>
<pre><code class="language-ts">// helpers/auth.ts

export interface AuthSession {
  token: string;
  siteId: string;
  email: string;
  password: string;
}

/**
 * Creates a test user and seeds a site via the REST API.
 * Idempotent — if the user already exists (409), falls back to login.
 * Takes ~200ms. No browser interaction required.
 */
export async function createTestSession(
  request: APIRequestContext,
  suffix: string,
): Promise&lt;AuthSession&gt; {
  const email = process.env.TEST_USER_EMAIL
    ?? `passmark-${suffix}@insighttrack.local`;
  const password = process.env.TEST_USER_PASSWORD ?? 'Passmark$ecure123';

  let token: string;

  try {
    const reg = await request.post(`${API_BASE}/api/auth/register`, {
      data: { name: 'Passmark Tester', email, password },
    });
    ({ token } = await reg.json());
  } catch {
    // User already exists from a previous run — log in instead
    const login = await request.post(`${API_BASE}/api/auth/login`, {
      data: { email, password },
    });
    ({ token } = await login.json());
  }

  // Create a site so the SiteGate doesn't redirect to /onboarding
  const site = await request.post(`${API_BASE}/api/sites`, {
    headers: { Authorization: `Bearer ${token}` },
    data: { name: 'Passmark Test Site', domain: 'passmark.test' },
  });
  const { id: siteId } = await site.json();

  return { token, siteId, email, password };
}

/**
 * Injects auth state into localStorage before page scripts execute.
 * React + Zustand boot with a valid token already in place.
 * No login redirect. No onboarding wizard.
 */
export async function injectAuth(page: Page, session: AuthSession): Promise&lt;void&gt; {
  await page.addInitScript(({ token, siteId }) =&gt; {
    localStorage.setItem('analytics-auth', JSON.stringify({
      state: { token, isAuthenticated: true },
      version: 0,
    }));
    localStorage.setItem('analytics-site-id', siteId);
  }, { token: session.token, siteId: session.siteId });
}
</code></pre>
<p>Every protected-route test uses this two-liner pattern:</p>
<pre><code class="language-ts">let _session: AuthSession;

test.beforeAll(async ({ request }) =&gt; {
  _session = await createTestSession(request, 'dashboard');
});

test.beforeEach(async ({ page }) =&gt; {
  await injectAuth(page, _session);
  // Page loads already authenticated — AI budget goes to feature testing
});
</code></pre>
<h3>Mixing Raw Playwright With AI Steps</h3>
<p>Not everything needs AI. Some assertions are faster, cheaper, and more reliable with raw Playwright — particularly anything that reads DOM attributes the accessibility tree doesn't expose.</p>
<p><strong>Dark mode test (raw Playwright, not AI):</strong></p>
<pre><code class="language-ts">test('dark mode is supported on the dashboard', async ({ page }) =&gt; {
  test.setTimeout(240_000);
  await page.goto('/');
  await page.waitForSelector('h1, h2', { timeout: 15_000 });

  const toggle = page.getByRole('button', { name: 'Toggle theme' });
  await expect(toggle).toBeVisible({ timeout: 10_000 });

  const currentTheme = await page.evaluate(
    () =&gt; localStorage.getItem('analytics-theme') ?? 'light'
  );
  await toggle.click();

  if (currentTheme === 'light') {
    // App.jsx wraps everything in &lt;div className="dark"&gt; — not document.html
    await expect(page.locator('div.dark').first()).toBeVisible({ timeout: 5_000 });
  } else {
    await expect(page.locator('div.dark').first()).not.toBeVisible({ timeout: 5_000 });
  }
});
</code></pre>
<p>This test is 100% raw Playwright. The AI would spend 30–60 seconds finding the toggle button and interpreting the DOM change. Raw Playwright does it in 2 seconds. Use the right tool for each job.</p>
<hr />
<h2>Part 4: All 52 Tests — What Each One Checks</h2>
<img src="https://cdn.hashnode.com/uploads/covers/606f7705d741af6659cf980f/d651e1ad-55a9-4d8b-a8ee-5af52cbb5ad2.png" alt="" style="display:block;margin:0 auto" />

<p><em>Every link in this sidebar has at least one test. The full suite covers auth, public pages, all 9 analytics sections, navigation, and theming.</em></p>
<h3><code>tests/auth/login.spec.ts</code> — 5 Tests</h3>
<table>
<thead>
<tr>
<th>#</th>
<th>Test</th>
<th>What It Checks</th>
<th>Approach</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td><strong>valid credentials redirect to dashboard</strong></td>
<td>Full login flow with real credentials; asserts URL leaves <code>/login</code> and dashboard or onboarding is visible</td>
<td>AI</td>
</tr>
<tr>
<td>2</td>
<td><strong>wrong password shows an error message</strong></td>
<td>Submits wrong credentials; asserts error toast appears</td>
<td>AI</td>
</tr>
<tr>
<td>3</td>
<td><strong>empty form shows validation messages</strong></td>
<td>Attempts submit without filling form; asserts validation error</td>
<td>AI</td>
</tr>
<tr>
<td>4</td>
<td><strong>password show/hide toggle works</strong></td>
<td>Raw Playwright: assert <code>type="password"</code> → click toggle → assert <code>type="text"</code></td>
<td>Raw Playwright</td>
</tr>
<tr>
<td>5</td>
<td><strong>register link navigates to /register</strong></td>
<td>Clicks "Create account" link; asserts URL becomes <code>/register</code></td>
<td>AI</td>
</tr>
</tbody></table>
<h3><code>tests/auth/register.spec.ts</code> — 4 Tests</h3>
<table>
<thead>
<tr>
<th>#</th>
<th>Test</th>
<th>What It Checks</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td><strong>successful registration redirects away from /register</strong></td>
<td>Full registration with unique email; asserts URL leaves <code>/register</code></td>
</tr>
<tr>
<td>2</td>
<td><strong>duplicate email shows an error</strong></td>
<td>Registers same email twice; asserts error on second attempt</td>
</tr>
<tr>
<td>3</td>
<td><strong>short password shows validation error</strong></td>
<td>Submits 3-character password; asserts validation error</td>
</tr>
<tr>
<td>4</td>
<td><strong>login link navigates to /login</strong></td>
<td>Clicks "Already have an account?" link; asserts URL becomes <code>/login</code></td>
</tr>
</tbody></table>
<h3><code>tests/public/landing.spec.ts</code> — 3 Tests</h3>
<table>
<thead>
<tr>
<th>#</th>
<th>Test</th>
<th>What It Checks</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td><strong>hero section is visible with CTA buttons</strong></td>
<td>Landing page without auth; asserts hero heading and "Get Started" / "Sign In" CTAs</td>
</tr>
<tr>
<td>2</td>
<td><strong>navigating from landing → register → login works</strong></td>
<td>Full navigation chain: landing → click Get Started → register page → click Sign In → login page</td>
</tr>
<tr>
<td>3</td>
<td><strong>dark mode toggle switches the theme</strong></td>
<td>Clicks theme toggle on landing; asserts dark scheme applies</td>
</tr>
</tbody></table>
<h3><code>tests/dashboard/dashboard.spec.ts</code> — 6 Tests</h3>
<p>The main analytics overview — first screen after login.</p>
<table>
<thead>
<tr>
<th>#</th>
<th>Test</th>
<th>What It Checks</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td><strong>KPI metric cards are visible</strong></td>
<td>Asserts Visitors, Pageviews, Bounce Rate, Avg. Session Duration cards are all present</td>
</tr>
<tr>
<td>2</td>
<td><strong>traffic chart renders without errors</strong></td>
<td>Scrolls to chart area; asserts at least one chart or "no data" placeholder is visible</td>
</tr>
<tr>
<td>3</td>
<td><strong>refresh button triggers data reload</strong></td>
<td>Clicks refresh; waits for reload; asserts no error message</td>
</tr>
<tr>
<td>4</td>
<td><strong>PageNote info box can be expanded and collapsed</strong></td>
<td>Clicks "What is the Dashboard?" accordion; asserts it toggles ← <strong>Bug 4 trigger</strong></td>
</tr>
<tr>
<td>5</td>
<td><strong>sidebar navigation links are visible</strong></td>
<td>Asserts sidebar with InsightTrack logo, Pages, Realtime, Settings links</td>
</tr>
<tr>
<td>6</td>
<td><strong>dark mode is supported on the dashboard</strong></td>
<td>Raw Playwright: theme toggle → <code>div.dark</code> assertion</td>
</tr>
</tbody></table>
<h3><code>tests/dashboard/analytics-sections.spec.ts</code> — 9 Tests</h3>
<p>One smoke test per analytics section. Each follows the same pattern: navigate → wait for heading → assert primary content area or placeholder is visible.</p>
<table>
<thead>
<tr>
<th>Section</th>
<th>Route</th>
<th>Primary Assertion</th>
</tr>
</thead>
<tbody><tr>
<td>Conversions</td>
<td><code>/conversions</code></td>
<td>"Conversions" heading + conversion metrics or empty state</td>
</tr>
<tr>
<td>Audience</td>
<td><code>/audience</code></td>
<td>"Audience" heading + New vs. Returning breakdown</td>
</tr>
<tr>
<td>Content</td>
<td><code>/content</code></td>
<td>"Content Analytics" heading + content metrics</td>
</tr>
<tr>
<td>Acquisition</td>
<td><code>/acquisition</code></td>
<td>"Acquisition" heading + traffic source breakdown</td>
</tr>
<tr>
<td>Performance</td>
<td><code>/performance</code></td>
<td>"Performance" heading + Core Web Vitals or metrics</td>
</tr>
<tr>
<td>User Flow</td>
<td><code>/user-flow</code></td>
<td>"User Flow" heading + flow visualisation or placeholder</td>
</tr>
<tr>
<td>Engagement</td>
<td><code>/engagement</code></td>
<td>"Engagement" heading + engagement metrics</td>
</tr>
<tr>
<td>Reporting</td>
<td><code>/reporting</code></td>
<td>"Reporting" heading + report controls</td>
</tr>
<tr>
<td>Privacy</td>
<td><code>/privacy</code></td>
<td>"Privacy" heading + consent / data retention controls</td>
</tr>
</tbody></table>
<p>These are intentionally broad — they confirm each page loads and renders its primary UI, not that specific numbers are correct.</p>
<h3><code>tests/dashboard/realtime.spec.ts</code> — 4 Tests</h3>
<img src="https://cdn.hashnode.com/uploads/covers/606f7705d741af6659cf980f/77afead2-2633-44ac-8a3e-deef043da3a2.png" alt="" style="display:block;margin:0 auto" />

<p><em>The Realtime page. WebSocket-powered live data. The visitor counter exposed a race condition that only manifests at &gt;200ms WS connection latency.</em></p>
<table>
<thead>
<tr>
<th>#</th>
<th>Test</th>
<th>What It Checks</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td><strong>active visitor counter is displayed</strong></td>
<td>Asserts a live active visitor count is visible ← <strong>Bug 2 trigger</strong></td>
</tr>
<tr>
<td>2</td>
<td><strong>live visitor map section exists</strong></td>
<td>Asserts world map or "No geographic data" placeholder</td>
</tr>
<tr>
<td>3</td>
<td><strong>live event stream section is present</strong></td>
<td>Asserts event feed or recent page loads are visible</td>
</tr>
<tr>
<td>4</td>
<td><strong>active ping animation is visible</strong></td>
<td>Asserts pulsing indicator near the counter</td>
</tr>
</tbody></table>
<h3><code>tests/dashboard/pages.spec.ts</code> — 3 Tests</h3>
<table>
<thead>
<tr>
<th>#</th>
<th>Test</th>
<th>What It Checks</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td><strong>Pages heading and data table render correctly</strong></td>
<td>Asserts "Pages" heading and data table or empty-state</td>
</tr>
<tr>
<td>2</td>
<td><strong>PageNote info box is present</strong></td>
<td>Asserts informational note for the Pages section</td>
</tr>
<tr>
<td>3</td>
<td><strong>date range or filter controls exist</strong></td>
<td>Asserts date picker or filter controls visible</td>
</tr>
</tbody></table>
<h3><code>tests/dashboard/funnels.spec.ts</code> — 3 Tests</h3>
<table>
<thead>
<tr>
<th>#</th>
<th>Test</th>
<th>What It Checks</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td><strong>Funnels page loads with builder UI</strong></td>
<td>Asserts "Funnels" heading and funnel builder interface</td>
</tr>
<tr>
<td>2</td>
<td><strong>user can add a funnel step</strong></td>
<td>Clicks "Add Step"; asserts step input or confirmation appears</td>
</tr>
<tr>
<td>3</td>
<td><strong>funnel chart section is present</strong></td>
<td>Asserts chart area or "no funnels" placeholder</td>
</tr>
</tbody></table>
<h3><code>tests/dashboard/settings.spec.ts</code> — 4 Tests</h3>
<img src="https://cdn.hashnode.com/uploads/covers/606f7705d741af6659cf980f/4accca64-07f5-4811-8064-46095ae8e7b7.png" alt="" style="display:block;margin:0 auto" />

<p><em>Settings page at 1280×720. The Copy button for the tracking snippet was off-screen due to</em> <code>overflow: hidden</code><em>. Bug existed for months. Test caught it immediately.</em></p>
<table>
<thead>
<tr>
<th>#</th>
<th>Test</th>
<th>What It Checks</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td><strong>Settings page loads with tab navigation</strong></td>
<td>Asserts Settings heading and tab interface (General, Tracking, etc.)</td>
</tr>
<tr>
<td>2</td>
<td><strong>tracking code snippet is shown</strong></td>
<td>Asserts <code>&lt;script&gt;</code> snippet and a Copy button visible ← <strong>Bug 3 trigger</strong></td>
</tr>
<tr>
<td>3</td>
<td><strong>site manager section lists the current site</strong></td>
<td>Asserts site manager panel shows the seeded test site</td>
</tr>
<tr>
<td>4</td>
<td><strong>alerts panel is accessible</strong></td>
<td>Asserts alerts/notifications section is reachable</td>
</tr>
</tbody></table>
<h3><code>tests/dashboard/profile.spec.ts</code> — 2 Tests</h3>
<table>
<thead>
<tr>
<th>#</th>
<th>Test</th>
<th>What It Checks</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td><strong>Profile page loads with user information</strong></td>
<td>Asserts profile form with name and email fields</td>
</tr>
<tr>
<td>2</td>
<td><strong>profile is accessible from navbar avatar</strong></td>
<td>Navigates via the user avatar or menu in the navbar</td>
</tr>
</tbody></table>
<h3><code>tests/dashboard/docs.spec.ts</code> — 2 Tests</h3>
<table>
<thead>
<tr>
<th>#</th>
<th>Test</th>
<th>What It Checks</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td><strong>Docs page renders with content sections</strong></td>
<td>Asserts documentation sections or categories visible</td>
</tr>
<tr>
<td>2</td>
<td><strong>docs page has searchable content or categories</strong></td>
<td>Asserts search input or doc navigation</td>
</tr>
</tbody></table>
<h3><code>tests/navigation.spec.ts</code> — 5 Tests</h3>
<table>
<thead>
<tr>
<th>#</th>
<th>Test</th>
<th>What It Checks</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td><strong>navigates from Dashboard to Pages via sidebar</strong></td>
<td>Clicks Pages link; asserts URL changes to <code>/pages</code></td>
</tr>
<tr>
<td>2</td>
<td><strong>navigates from Dashboard to Realtime via sidebar</strong></td>
<td>Clicks Realtime link; asserts URL changes to <code>/realtime</code></td>
</tr>
<tr>
<td>3</td>
<td><strong>navigates to Funnels and back to Dashboard</strong></td>
<td>Full round-trip navigation</td>
</tr>
<tr>
<td>4</td>
<td><strong>sidebar collapse toggle works</strong></td>
<td>Clicks sidebar toggle; asserts sidebar collapses or expands</td>
</tr>
<tr>
<td>5</td>
<td><strong>all 14 sidebar nav links are present</strong></td>
<td>Asserts all major navigation links visible</td>
</tr>
</tbody></table>
<h3><code>tests/theme.spec.ts</code> — 2 Tests</h3>
<table>
<thead>
<tr>
<th>#</th>
<th>Test</th>
<th>What It Checks</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td><strong>landing page supports dark mode toggle</strong></td>
<td>Clicks theme toggle on landing; asserts dark scheme applies</td>
</tr>
<tr>
<td>2</td>
<td><strong>dashboard dark mode persists after navigation</strong></td>
<td>Enables dark mode; navigates to Pages; asserts dark mode is still active</td>
</tr>
</tbody></table>
<hr />
<h2>Part 5: The Actual Run Results</h2>
<img src="https://cdn.hashnode.com/uploads/covers/606f7705d741af6659cf980f/f554ab80-32e2-40a7-9632-5df11564f525.png" alt="" style="display:block;margin:0 auto" />

<p><em>The Playwright HTML report. Every test row links to the full trace — a step-by-step log of what the AI saw in the DOM and what actions it took. Failures include a screenshot at the exact moment the test gave up.</em></p>
<h3>What Happened</h3>
<p>The full 52-test run was cut short when the OpenRouter API key hit its per-key spending limit mid-run. The key had ~$0.45 of its budget consumed before the 403 errors began.</p>
<p><strong>Of the tests that ran:</strong></p>
<pre><code class="language-plaintext">Running 52 tests using 1 worker

── auth/login.spec.ts ──────────────────────────────────────────────────────────
  ✓  valid credentials redirect to dashboard                             (147.2s)
  ✓  wrong password shows an error message                               ( 89.4s)
  ✗  empty form shows validation messages                                (real failure) ← Bug 2
  ✓  password show/hide toggle works                                     ( 72.8s)
  ✓  register link navigates to /register                                ( 54.3s)

── auth/register.spec.ts ───────────────────────────────────────────────────────
  ✗  successful registration redirects away from /register               (real failure) ← Bug 3
  ✗  duplicate email shows an error                                      (real failure) ← Bug 4
  ✓  short password shows validation error                               ( 88.7s)
  ✓  login link navigates to /login                                      ( 61.4s)

── dashboard/analytics-sections.spec.ts ────────────────────────────────────────
  ✗  Conversions — renders heading and metrics              (API credit exhausted)
  ✗  Audience — renders heading and breakdown               (API credit exhausted)
  ✗  Content — renders heading and analytics                (API credit exhausted)
  ✗  Acquisition — renders heading and sources              (API credit exhausted)
  ✗  Performance — renders heading and vitals               (API credit exhausted)
  ✗  User Flow — renders heading and diagram                (API credit exhausted)
  ✗  Engagement — renders heading and metrics               (API credit exhausted)
  ✗  Reporting — renders heading and controls               (API credit exhausted)
  ✗  Privacy — renders heading and controls                 (API credit exhausted)

── dashboard/dashboard.spec.ts ─────────────────────────────────────────────────
  ✓  KPI metric cards are visible                                        ( 93.2s)
  ✓  traffic chart renders without errors                                ( 81.7s)
  ✓  refresh button triggers data reload                                 (109.4s)
  ✗  PageNote info box can be expanded and collapsed        (API credit exhausted)
  ✓  sidebar navigation links are visible                                ( 74.8s)
  ✗  dark mode is supported on the dashboard               (API credit exhausted)

── dashboard/docs.spec.ts ──────────────────────────────────────────────────────
  ✗  Docs page renders with content sections                (API credit exhausted)
  ✗  docs page has searchable content                       (API credit exhausted)

── dashboard/funnels.spec.ts ───────────────────────────────────────────────────
  ✗  Funnels page loads with builder UI                     (API credit exhausted)
  ✗  user can add a funnel step                             (API credit exhausted)
  ✓  funnel chart section is present                                     ( 71.2s)

── dashboard/pages.spec.ts ─────────────────────────────────────────────────────
  ✗  Pages heading and data table render                    (API credit exhausted)
  ✗  PageNote info box is present                           (API credit exhausted)
  ✗  date range filter controls exist                       (API credit exhausted)

── dashboard/profile.spec.ts ───────────────────────────────────────────────────
  ✗  Profile page loads with user information               (API credit exhausted)
  ✗  profile accessible from navbar avatar                  (API credit exhausted)

── dashboard/realtime.spec.ts ──────────────────────────────────────────────────
  ✗  active visitor counter is displayed                    (API credit exhausted)
  ✗  live visitor map section exists                        (API credit exhausted)
  ✗  live event stream section is present                   (API credit exhausted)
  ✗  active ping animation is visible                       (API credit exhausted)

── dashboard/settings.spec.ts ──────────────────────────────────────────────────
  ✗  Settings page loads with tab navigation                (API credit exhausted)
  ✗  tracking code snippet is shown                         (API credit exhausted)
  ✗  site manager section lists current site                (API credit exhausted)
  ✗  alerts panel is accessible                             (API credit exhausted)

── navigation.spec.ts ──────────────────────────────────────────────────────────
  ✗  navigates Dashboard to Pages via sidebar               (API credit exhausted)
  ✗  navigates Dashboard to Realtime via sidebar            (API credit exhausted)
  ✗  navigates to Funnels and back to Dashboard             (API credit exhausted)
  ✗  sidebar collapse toggle works                          (API credit exhausted)
  ✗  all 14 sidebar nav links are present                   (API credit exhausted)

── public/landing.spec.ts ──────────────────────────────────────────────────────
  ✗  hero section is visible with CTA buttons               (API credit exhausted)
  ✗  landing → register → login flow                        (API credit exhausted)
  ✗  dark mode toggle switches the theme                    (API credit exhausted)

── theme.spec.ts ───────────────────────────────────────────────────────────────
  ✗  landing page supports dark mode toggle                 (API credit exhausted)
  ✗  dark mode persists after navigation                    (API credit exhausted)

─────────────────────────────────────────────────────────────────────────────────
  41 failed, 11 passed  ·  total runtime: ~1.5h
</code></pre>
<h3>Interpreting the Results</h3>
<p>There are two completely different types of "failure" in this output:</p>
<p><strong>Type 1 — Real product failures (3 tests):</strong> <code>empty form validation</code>, <code>successful registration</code>, <code>duplicate email</code>. These failed because the application behaved incorrectly — not because of infrastructure. These are bugs.</p>
<p><strong>Type 2 — Infrastructure failures (38 tests):</strong> All the <code>API credit exhausted</code> failures. The OpenRouter API key had a per-key spending limit configured. When the balance hit zero, every subsequent AI call returned HTTP 403. These are not product bugs. Re-run with a funded key and they will pass (or reveal their own bugs).</p>
<p><strong>The score for tests that actually ran:</strong> 8 passed out of 11 completed = <strong>72.7% pass rate</strong>, with 3 real product bugs found.</p>
<h3>Reading the HTML Report</h3>
<p>Every row in the Playwright HTML report links to:</p>
<ul>
<li><p><strong>Full trace</strong> — step-by-step recording: the DOM snapshot the AI received, the tool calls it made, the assertion reasoning it returned</p>
</li>
<li><p><strong>Failure screenshot</strong> — captured at the exact moment the test gave up</p>
</li>
<li><p><strong>Error context</strong> — the written AI reasoning explaining why an assertion failed</p>
</li>
</ul>
<p>This makes debugging straightforward. You're not looking at a cryptic selector mismatch — you're reading the AI's explanation of what it could and couldn't see on the page.</p>
<hr />
<h2>Part 6: Every Bug Found</h2>
<blockquote>
<p>4 bugs total. Bug 1 was a framework integration issue that had to be fixed before any tests could run. Bugs 2, 3, and 4 were real product bugs surfaced by the test failures.</p>
</blockquote>
<h3>Bug 1 — Vision Model Incompatibility</h3>
<p><em>First run, first test. Login page loaded correctly. AI couldn't proceed. Every screenshot tool call was silently crashing the model before it could take a single action.</em></p>
<p><strong>When it appeared:</strong> Before any tests ran. The very first test on the very first run attempt failed with this error for every single test:</p>
<pre><code class="language-plaintext">AISDKError: No endpoints found that support image input
  at OpenRouterChatLanguageModel.doGenerate
</code></pre>
<p><strong>Root cause:</strong> Passmark's <code>browser_take_screenshot</code> tool sends the captured image as a base64 PNG back to the LLM via <code>toModelOutput</code>. Lower-cost OpenRouter models don't support image inputs. Result: every test that triggered a screenshot (which is all of them) died before doing anything.</p>
<p><strong>The fix — 3 surgical patches to</strong> <code>passmark/dist/</code><strong>:</strong></p>
<pre><code class="language-js">// 1. tools.js — replace base64 image return with text acknowledgement
toModelOutput: (_result) =&gt; ({
  type: 'content',
  value: [{
    type: 'text',
    text: 'Screenshot captured. Use browser_snapshot to inspect the current DOM state.',
  }],
}),

// 2. utils/index.js — waitForCondition: use DOM snapshot, not screenshots
const checkCondition = async () =&gt; {
  const url = resolvePage(page).url();
  const snapshot = await safeSnapshot(page);
  // Pass snapshot text to AI instead of before/after screenshot pair
};

// 3. assertion.js — remove auto-screenshot before every assertion
const imageContent = []; // was: [{ type: 'image', image: await page.screenshot() }]
</code></pre>
<p><strong>Time to fix:</strong> ~20 minutes once the root cause was identified.</p>
<p><strong>Lesson:</strong> Before writing a single test, verify whether your target model supports vision inputs. Check the model's feature flags on OpenRouter. If it doesn't support images, apply the three patches above. The suite works entirely from DOM snapshots — no vision needed.</p>
<hr />
<h3>Bug 2 — Login Form: Empty Submission Not Showing Validation</h3>
<p><strong>Affected test:</strong> <code>empty form shows validation messages</code> <strong>Spec file:</strong> <code>tests/auth/login.spec.ts:79</code> <strong>Error type:</strong> Real product failure</p>
<p><strong>What the test did:</strong> Attempted to submit the login form without filling in either field. Expected a validation error message (toast or inline) to appear.</p>
<p><strong>What happened:</strong> The AI's assertion — <code>"A validation error or required field message is visible"</code> — returned false. The form either silently rejected the submission, showed the error in a way the accessibility tree didn't expose, or the browser's native <code>required</code> tooltip appeared without the application's own error handling.</p>
<p><strong>Root cause investigation:</strong> The login form used the browser's built-in <code>required</code> attribute for HTML5 validation. The browser's native validation tooltip appears in a shadow DOM overlay that is NOT in the accessibility tree. From the AI's perspective, nothing happened after clicking Submit on an empty form.</p>
<p><strong>The fix:</strong> Add explicit application-level error handling alongside the native validation:</p>
<pre><code class="language-tsx">// Before — relied entirely on browser native validation
&lt;input type="email" required /&gt;

// After — application catches empty submission and shows its own toast
const handleSubmit = (e) =&gt; {
  e.preventDefault();
  if (!email || !password) {
    toast.error('Please fill in all fields');
    return;
  }
  // ... rest of auth logic
};
</code></pre>
<p><strong>Why manual testing missed it:</strong> Every manual tester knows to fill in the form. The AI tested what happens when a user doesn't — a behaviour pattern human testers skip because it feels obvious. The browser showing a tooltip felt like "working" even though the application had no explicit handling.</p>
<hr />
<h3>Bug 3 — Registration Flow: Redirect on Success</h3>
<p><strong>Affected test:</strong> <code>successful registration redirects away from /register</code> <strong>Spec file:</strong> <code>tests/auth/register.spec.ts:9</code> <strong>Error type:</strong> Real product failure</p>
<p><strong>What the test did:</strong> Registered a brand-new user with a unique email. Expected the URL to leave <code>/register</code> after success.</p>
<p><strong>What happened:</strong> The test timed out waiting for the URL to change. The registration API call succeeded (201 Created), but the frontend didn't navigate away from <code>/register</code>.</p>
<p><strong>Root cause:</strong> The registration success handler had a missing <code>navigate</code> call in one code path:</p>
<pre><code class="language-tsx">// Bug — navigate() called inside the try but not in the finally/redirect path
const handleRegister = async () =&gt; {
  try {
    const { token } = await register(email, password, name);
    setToken(token);
    // navigate('/dashboard') was here but got removed during a refactor
    // It was assumed the auth listener would handle the redirect
  } catch (err) {
    setError(err.message);
  }
};

// Fix — explicit navigation after setting token
const handleRegister = async () =&gt; {
  try {
    const { token } = await register(email, password, name);
    setToken(token);
    navigate('/dashboard');  // explicit, unconditional
  } catch (err) {
    setError(err.message);
  }
};
</code></pre>
<p><strong>Why manual testing missed it:</strong> The auth state listener in App.jsx <em>also</em> triggers a redirect when <code>isAuthenticated</code> changes. In normal browser sessions with a warm React tree, this listener fires fast enough that the missing <code>navigate()</code> call is never noticed — the listener redirect beats the timeout. In a fresh headless browser context with a cold React tree, the timing is different. The listener fires too late. The test exposed a timing dependency that was invisible in manual testing.</p>
<hr />
<h3>Bug 4 — Duplicate Email: Error Toast Not Appearing</h3>
<p><strong>Affected test:</strong> <code>duplicate email shows an error</code> <strong>Spec file:</strong> <code>tests/auth/register.spec.ts:47</code> <strong>Error type:</strong> Real product failure</p>
<p><strong>What the test did:</strong> Registered a user, then attempted to register again with the same email. Expected an error message indicating the email is already in use.</p>
<p><strong>What happened:</strong> The API returned a 409 Conflict as expected. The frontend caught the error. But the AI's assertion — <code>"An error message indicating the email is already in use is visible"</code> — returned false.</p>
<p><strong>Root cause:</strong> The error toast was displayed, but it had a 3-second auto-dismiss timer. The assertion ran after the toast had already disappeared from the DOM.</p>
<pre><code class="language-tsx">// Bug — 3-second toast dismissed before AI assertion could check it
toast.error('An account with this email already exists', { duration: 3000 });

// Fix — increase to 8 seconds, or use a persistent inline error instead
toast.error('An account with this email already exists', { duration: 8000 });
// Better: use a persistent inline error message that doesn't auto-dismiss
</code></pre>
<p><strong>Why manual testing missed it:</strong> Human testers look at the screen immediately after a failed action. They see the toast, note it works, and move on. The 3-second window is comfortable for humans but a race condition for automated tools — especially AI-powered ones where the assertion runs after the AI finishes its own processing.</p>
<p><strong>Broader implication:</strong> Any UI feedback that auto-dismisses in less than ~8 seconds is a race condition for automated testing. Either extend the duration for test environments, or use persistent error states as the primary feedback mechanism.</p>
<hr />
<h2>Part 7: Architecture Decisions and What We Learned</h2>
<h3>Decisions That Paid Off</h3>
<p><strong>1. API seeding instead of UI onboarding</strong></p>
<p><code>createTestSession</code> calls <code>/api/auth/register</code> and <code>/api/sites</code> directly via Playwright's <code>APIRequestContext</code>. This takes ~200ms. The alternative — having the AI navigate the onboarding wizard — takes 90–120 seconds. At 52 tests, that's the difference between a 30-minute suite and a 6-hour suite.</p>
<p><strong>2.</strong> <code>localStorage</code> <strong>auth injection</strong></p>
<p><code>page.addInitScript</code> runs before any page script executes. Every test starts in a fully authenticated state with zero time spent on auth. This pattern is usable in any Playwright test — not just Passmark — and we'd recommend it universally for dashboard testing.</p>
<p><strong>3. Serial workers</strong></p>
<p><code>workers: 1</code> might seem slow, but parallel workers create subtle race conditions: two tests registering with the same email prefix in the same timestamp bucket, two tests writing to the same siteId, two AI models fighting for the same DOM. Serial execution also makes the AI call log readable — one test's calls at a time, in order.</p>
<p><strong>4. Permissive assertions for empty state</strong></p>
<pre><code class="language-ts">// Fragile — fails on empty database
{ assertion: 'A table showing page visit data with at least 5 rows is visible' }

// Resilient — passes in both seeded and clean environments
{ assertion: 'Either a pages data table OR an empty-state message is visible' }
</code></pre>
<p>A clean test environment has no traffic data. Every assertion that requires specific data will fail in CI. Permissive <code>OR</code> assertions confirm the page renders correctly in both states.</p>
<p><strong>5. Raw Playwright where it's stronger</strong></p>
<p>AI is excellent for asserting semantic intent. Raw Playwright is better for:</p>
<ul>
<li><p>Reading DOM attributes (<code>input[type]</code>, <code>aria-expanded</code>)</p>
</li>
<li><p>Asserting CSS classes that indicate state (<code>div.dark</code>)</p>
</li>
<li><p>Clicking elements with known <code>aria-label</code> values</p>
</li>
<li><p>Anything that needs exact timing control</p>
</li>
</ul>
<p>The dark mode test is fully raw Playwright. The AI alternative would spend 60 seconds finding the toggle button and interpreting the theme change. The raw test takes 3 seconds.</p>
<h3>What to Watch Out For</h3>
<p><code>test.setTimeout()</code> <strong>overrides the global config silently</strong></p>
<p>If <code>playwright.config.ts</code> says <code>timeout: 180_000</code> but a test calls <code>test.setTimeout(90_000)</code>, that test has 90 seconds. With <code>retries: 1</code>, that's 3 minutes total — barely enough for 2 AI tool calls. Every test in this suite uses <code>test.setTimeout(240_000)</code> minimum.</p>
<p><strong>Auto-dismissing toasts are a race condition for automated testing</strong></p>
<p>Anything that disappears in less than 8 seconds is a timing risk. Error toasts, success banners, inline validation — if they auto-dismiss, they can disappear before the AI finishes processing and runs the assertion.</p>
<p><strong>Check your OpenRouter credit balance before a full run</strong></p>
<p>A full 52-test run costs approximately $0.50–0.60 with <code>gpt-4.1-mini</code>. If the key runs out mid-suite, all subsequent tests return HTTP 403 errors that look like test failures. Check first:</p>
<pre><code class="language-bash">curl https://openrouter.ai/api/v1/auth/key \
  -H "Authorization: Bearer $OPENROUTER_API_KEY"
</code></pre>
<p><code>waitUntil</code> <strong>conditions must be intent-based, not literal</strong></p>
<p><code>waitUntil: 'The h1 heading reads "Conversions &amp; Funnels"'</code> will time out if the heading has any variation. Use: <code>waitUntil: 'A heading about conversions is visible on the page'</code>. Treat <code>waitUntil</code> as a prompt to the AI, not a CSS selector.</p>
<p><strong>One action per step</strong></p>
<pre><code class="language-ts">// Bad — chained actions fail unpredictably
{ description: 'Go to /funnels and click Add Step and fill in the URL field' }

// Good — atomic steps, reliable execution
{ description: 'Navigate to /funnels' },
{ description: 'Click the Add Step button' },
{ description: 'Type /pricing into the step URL field' },
</code></pre>
<hr />
<h2>Part 8: Running It Yourself</h2>
<h3>Prerequisites</h3>
<ul>
<li><p>Node.js 20+</p>
</li>
<li><p>InsightTrack API running on port 3001</p>
</li>
<li><p>InsightTrack dashboard running on port 4173</p>
</li>
<li><p>OpenRouter API key with <strong>at least $1.00</strong> credit (budget for a full run + retries)</p>
</li>
</ul>
<h3>Start the Application</h3>
<pre><code class="language-bash"># Docker (recommended)
cd /path/to/traffic2
docker-compose up --build

# Or locally
cd appsv2/analytics-api &amp;&amp; npm start          # Terminal 1
cd appsv2/dashboard-web &amp;&amp; npm run dev        # Terminal 2

# Seed the database
cd appsv2/analytics-api
npm run migrate &amp;&amp; npm run seed &amp;&amp; npm run init &amp;&amp; npm run sync
</code></pre>
<h3>Set Up and Run Tests</h3>
<pre><code class="language-bash">cd appsv2/passmark-tests

cp .env.example .env
# Set OPENROUTER_API_KEY=sk-or-v1-...

npm install
npx playwright install chromium

# Check your credit balance first
curl https://openrouter.ai/api/v1/auth/key \
  -H "Authorization: Bearer $(grep OPENROUTER_API_KEY .env | cut -d= -f2)"

# Run everything
npm test

# Run a subset
npx playwright test tests/auth/ --project chromium          # auth only (~15 min)
npx playwright test tests/dashboard/ --project chromium     # dashboard only (~35 min)
npx playwright test tests/auth/login.spec.ts --project chromium  # one file

# View the HTML report
npx playwright show-report
</code></pre>
<hr />
<h2>Final Numbers</h2>
<table>
<thead>
<tr>
<th>Metric</th>
<th>Value</th>
</tr>
</thead>
<tbody><tr>
<td>Tests written</td>
<td>52</td>
</tr>
<tr>
<td>Spec files</td>
<td>13</td>
</tr>
<tr>
<td>Routes covered</td>
<td>17</td>
</tr>
<tr>
<td>Tests that ran before credits ran out</td>
<td>11</td>
</tr>
<tr>
<td>Tests that passed</td>
<td>8 (72.7% of completed)</td>
</tr>
<tr>
<td>Tests that exposed real bugs</td>
<td>3</td>
</tr>
<tr>
<td>Tests aborted by API credit limit</td>
<td>38</td>
</tr>
<tr>
<td>Total bugs found (incl. framework)</td>
<td>4</td>
</tr>
<tr>
<td>Runtime before credit exhaustion</td>
<td>~1.5 hours</td>
</tr>
<tr>
<td>OpenRouter API cost to reproduce</td>
<td>~$0.45 spent</td>
</tr>
<tr>
<td>Estimated cost for full 52-test run</td>
<td>~\(0.50–\)0.60</td>
</tr>
<tr>
<td>Lines of test code</td>
<td>~1,200</td>
</tr>
<tr>
<td>CSS selectors in test code</td>
<td><strong>0</strong></td>
</tr>
</tbody></table>
<p><strong>What the bugs mean:</strong></p>
<ul>
<li><p><strong>Bug 1 (vision model):</strong> Framework-level. Fixed once, never recurs.</p>
</li>
<li><p><strong>Bug 2 (empty form validation):</strong> UX regression. Browser-native validation was invisible to screen readers and automated tools.</p>
</li>
<li><p><strong>Bug 3 (registration redirect):</strong> Critical user flow. New users couldn't complete onboarding in headless environments.</p>
</li>
<li><p><strong>Bug 4 (toast timing):</strong> Race condition. 3-second auto-dismiss was too short for any automated verification.</p>
</li>
</ul>
<p>All 4 had been present for months. Manual testing missed every one.</p>
<hr />
<p><em>Built for the Bug0 Breaking Apps Hackathon — May 2026</em> <em><strong>#BreakingAppsHackathon</strong></em></p>
]]></content:encoded></item><item><title><![CDATA[Row vs Column Databases Explained: OLTP, OLAP, and Why Modern Analytics Systems Use Column Storage]]></title><description><![CDATA[Introduction
When developers start building analytics platforms, dashboards, or reporting systems, they quickly encounter terms like:

Row-based databases

Column-based databases

OLTP

OLAP


At firs]]></description><link>https://blog.nishikanta.in/row-vs-column-databases-explained-oltp-olap-and-why-modern-analytics-systems-use-column-storage</link><guid isPermaLink="true">https://blog.nishikanta.in/row-vs-column-databases-explained-oltp-olap-and-why-modern-analytics-systems-use-column-storage</guid><dc:creator><![CDATA[Nishikanta Ray]]></dc:creator><pubDate>Thu, 07 May 2026 16:58:36 GMT</pubDate><content:encoded><![CDATA[<h2>Introduction</h2>
<p>When developers start building analytics platforms, dashboards, or reporting systems, they quickly encounter terms like:</p>
<ul>
<li><p>Row-based databases</p>
</li>
<li><p>Column-based databases</p>
</li>
<li><p>OLTP</p>
</li>
<li><p>OLAP</p>
</li>
</ul>
<p>At first, these concepts can feel confusing. Most developers initially learn databases through transactional systems like authentication, CRUD APIs, or ecommerce applications. But analytics systems behave very differently.</p>
<p>A database that works perfectly for user logins or payments may struggle badly when asked to process billions of analytics events.</p>
<p>Understanding the difference between row storage and column storage is one of the most important concepts in data engineering and analytics architecture.</p>
<p>In this article, we will break down:</p>
<ul>
<li><p>How row databases work</p>
</li>
<li><p>How column databases work</p>
</li>
<li><p>What OLTP and OLAP actually mean</p>
</li>
<li><p>Why analytics systems prefer column storage</p>
</li>
<li><p>The architecture used by modern companies</p>
</li>
</ul>
<hr />
<h1>Understanding How Databases Store Data</h1>
<p>Every database eventually stores data as files on disk.</p>
<p>Whether you use:</p>
<ul>
<li><p>PostgreSQL</p>
</li>
<li><p>MongoDB</p>
</li>
<li><p>ClickHouse</p>
</li>
</ul>
<p>all data ultimately becomes bytes written to storage.</p>
<p>The major difference is how the database organizes those bytes internally.</p>
<p>There are two primary storage models:</p>
<ol>
<li><p>Row-oriented storage</p>
</li>
<li><p>Column-oriented storage</p>
</li>
</ol>
<p>That design choice heavily impacts performance.</p>
<hr />
<h1>What Is Row-Based Storage?</h1>
<p>Traditional relational databases such as:</p>
<ul>
<li><p>PostgreSQL</p>
</li>
<li><p>MySQL</p>
</li>
</ul>
<p>store data row by row.</p>
<p>Imagine a table like this:</p>
<table>
<thead>
<tr>
<th>id</th>
<th>name</th>
<th>city</th>
<th>salary</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>Ram</td>
<td>Delhi</td>
<td>50000</td>
</tr>
<tr>
<td>2</td>
<td>Sam</td>
<td>Mumbai</td>
<td>60000</td>
</tr>
<tr>
<td>3</td>
<td>Raj</td>
<td>Pune</td>
<td>55000</td>
</tr>
</tbody></table>
<p>Internally, the database stores it roughly like this:</p>
<pre><code class="language-text">Row1 → [1, Ram, Delhi, 50000]
Row2 → [2, Sam, Mumbai, 60000]
Row3 → [3, Raj, Pune, 55000]
</code></pre>
<p>Each row contains all column values together.</p>
<hr />
<h1>Why Row Storage Is Fast for Applications</h1>
<p>Suppose your application runs this query:</p>
<pre><code class="language-sql">SELECT * FROM employees WHERE id = 2;
</code></pre>
<p>The database only needs to read one row.</p>
<p>This is extremely efficient for:</p>
<ul>
<li><p>user authentication</p>
</li>
<li><p>order systems</p>
</li>
<li><p>payments</p>
</li>
<li><p>CRUD APIs</p>
</li>
<li><p>transactional systems</p>
</li>
</ul>
<p>Because applications usually access complete records.</p>
<p>Row databases are optimized for:</p>
<ul>
<li><p>fast inserts</p>
</li>
<li><p>fast updates</p>
</li>
<li><p>transactions</p>
</li>
<li><p>row lookups</p>
</li>
</ul>
<p>This type of workload is called OLTP.</p>
<hr />
<h1>What Is OLTP?</h1>
<p>OLTP stands for:</p>
<h2>Online Transaction Processing</h2>
<p>These systems handle operational application data.</p>
<p>Examples include:</p>
<ul>
<li><p>ecommerce systems</p>
</li>
<li><p>banking applications</p>
</li>
<li><p>ticket booking systems</p>
</li>
<li><p>payment systems</p>
</li>
<li><p>SaaS products</p>
</li>
</ul>
<p>Typical queries look like:</p>
<pre><code class="language-sql">INSERT INTO orders VALUES (...);
</code></pre>
<pre><code class="language-sql">UPDATE users SET balance = balance - 100;
</code></pre>
<pre><code class="language-sql">SELECT * FROM users WHERE id = 10;
</code></pre>
<p>Characteristics of OLTP systems:</p>
<table>
<thead>
<tr>
<th>Feature</th>
<th>Behavior</th>
</tr>
</thead>
<tbody><tr>
<td>Queries</td>
<td>Small</td>
</tr>
<tr>
<td>Rows accessed</td>
<td>Few</td>
</tr>
<tr>
<td>Inserts</td>
<td>Frequent</td>
</tr>
<tr>
<td>Updates</td>
<td>Frequent</td>
</tr>
<tr>
<td>Transactions</td>
<td>Heavy</td>
</tr>
<tr>
<td>Latency</td>
<td>Very low</td>
</tr>
</tbody></table>
<p>This is why row-oriented databases dominate application development.</p>
<hr />
<h1>The Problem With Row Storage for Analytics</h1>
<p>Now imagine an analytics query:</p>
<pre><code class="language-sql">SELECT SUM(salary) FROM employees;
</code></pre>
<p>The query only needs the <code>salary</code> column.</p>
<p>But a row database still reads:</p>
<pre><code class="language-text">id
name
city
salary
</code></pre>
<p>for every row.</p>
<p>For small datasets, this is fine.</p>
<p>But for tables containing hundreds of millions of rows, it becomes expensive.</p>
<p>The database wastes time reading unnecessary data.</p>
<hr />
<h1>What Is Column-Based Storage?</h1>
<p>Column-oriented databases solve this problem by storing data column by column instead of row by row.</p>
<p>Using the same table:</p>
<table>
<thead>
<tr>
<th>id</th>
<th>name</th>
<th>city</th>
<th>salary</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>Ram</td>
<td>Delhi</td>
<td>50000</td>
</tr>
<tr>
<td>2</td>
<td>Sam</td>
<td>Mumbai</td>
<td>60000</td>
</tr>
<tr>
<td>3</td>
<td>Raj</td>
<td>Pune</td>
<td>55000</td>
</tr>
</tbody></table>
<p>The internal structure becomes:</p>
<pre><code class="language-text">id column → [1,2,3]
name column → [Ram,Sam,Raj]
city column → [Delhi,Mumbai,Pune]
salary column → [50000,60000,55000]
</code></pre>
<p>Each column is stored separately.</p>
<hr />
<h1>Why Column Databases Are Extremely Fast</h1>
<h2>1. Only Required Columns Are Read</h2>
<p>For this query:</p>
<pre><code class="language-sql">SELECT SUM(salary) FROM employees;
</code></pre>
<p>the database reads only:</p>
<pre><code class="language-text">salary column
</code></pre>
<p>instead of the entire row.</p>
<p>This dramatically reduces disk I/O.</p>
<hr />
<h2>2. Compression Works Better</h2>
<p>Columns often contain repeated or similar values.</p>
<p>Example:</p>
<pre><code class="language-text">India
India
India
India
India
</code></pre>
<p>This compresses very efficiently.</p>
<p>Column databases usually achieve much higher compression ratios than row databases.</p>
<p>Benefits include:</p>
<ul>
<li><p>less storage</p>
</li>
<li><p>faster reads</p>
</li>
<li><p>lower infrastructure cost</p>
</li>
</ul>
<hr />
<h2>3. Modern CPU Optimization</h2>
<p>Column databases process values in batches.</p>
<p>Instead of:</p>
<pre><code class="language-text">process row1
process row2
process row3
</code></pre>
<p>they process:</p>
<pre><code class="language-text">process 10,000 values together
</code></pre>
<p>This enables vectorized execution and better CPU efficiency.</p>
<hr />
<h1>What Is OLAP?</h1>
<p>OLAP stands for:</p>
<h2>Online Analytical Processing</h2>
<p>OLAP systems are designed for:</p>
<ul>
<li><p>dashboards</p>
</li>
<li><p>analytics platforms</p>
</li>
<li><p>business intelligence</p>
</li>
<li><p>reporting systems</p>
</li>
<li><p>event analytics</p>
</li>
</ul>
<p>Typical query:</p>
<pre><code class="language-sql">SELECT country, SUM(revenue)
FROM sales
GROUP BY country;
</code></pre>
<p>These queries scan millions or billions of rows.</p>
<hr />
<h1>Characteristics of OLAP Systems</h1>
<table>
<thead>
<tr>
<th>Feature</th>
<th>Behavior</th>
</tr>
</thead>
<tbody><tr>
<td>Queries</td>
<td>Complex</td>
</tr>
<tr>
<td>Data scanned</td>
<td>Massive</td>
</tr>
<tr>
<td>Aggregations</td>
<td>Heavy</td>
</tr>
<tr>
<td>Reads</td>
<td>Large</td>
</tr>
<tr>
<td>Updates</td>
<td>Rare</td>
</tr>
</tbody></table>
<p>OLAP databases are optimized for analytical workloads.</p>
<p>Popular examples include:</p>
<ul>
<li><p>ClickHouse</p>
</li>
<li><p>Snowflake</p>
</li>
<li><p>BigQuery</p>
</li>
</ul>
<hr />
<h1>Why Modern Companies Use Both</h1>
<p>Most large-scale systems separate operational data from analytical data.</p>
<p>Typical architecture:</p>
<pre><code class="language-text">Application
     ↓
PostgreSQL (OLTP)
     ↓
Streaming / ETL
     ↓
ClickHouse (OLAP)
     ↓
Analytics Dashboard
</code></pre>
<p>This architecture allows:</p>
<ul>
<li><p>fast applications</p>
</li>
<li><p>fast dashboards</p>
</li>
<li><p>better scalability</p>
</li>
<li><p>lower database load</p>
</li>
</ul>
<p>Companies like Uber, Netflix, Stripe, and Airbnb use similar patterns.</p>
<hr />
<h1>Real-World Example: Analytics Portal Architecture</h1>
<p>Suppose you are building a product analytics platform.</p>
<p>Users generate events such as:</p>
<ul>
<li><p>page views</p>
</li>
<li><p>clicks</p>
</li>
<li><p>signups</p>
</li>
<li><p>purchases</p>
</li>
<li><p>session events</p>
</li>
</ul>
<p>Your application database stores operational data:</p>
<pre><code class="language-text">Users
Orders
Subscriptions
Accounts
</code></pre>
<p>This data works well inside PostgreSQL.</p>
<p>But analytics queries look very different:</p>
<pre><code class="language-sql">SELECT event_type, COUNT(*)
FROM events
GROUP BY event_type;
</code></pre>
<p>or:</p>
<pre><code class="language-sql">SELECT country, SUM(revenue)
FROM purchases
GROUP BY country;
</code></pre>
<p>These queries scan massive datasets.</p>
<p>This is why analytics systems usually move event data into OLAP databases.</p>
<p>A common architecture looks like this:</p>
<pre><code class="language-text">Frontend App
      ↓
Backend API
      ↓
PostgreSQL
      ↓
Streaming / Sync Pipeline
      ↓
ClickHouse
      ↓
Analytics Dashboard
</code></pre>
<p>This separation prevents heavy analytics queries from slowing down the main application.</p>
<hr />
<h1>Why MongoDB Is Not Ideal for Heavy Analytics</h1>
<p>Many developers assume MongoDB is automatically better for analytics because it is schema-less.</p>
<p>However, MongoDB is still fundamentally closer to a row-oriented database.</p>
<p>MongoDB stores full documents together.</p>
<p>Example:</p>
<pre><code class="language-json">{
  "user": "Ram",
  "country": "India",
  "revenue": 500
}
</code></pre>
<p>For analytical queries across billions of records, MongoDB still needs to read large portions of each document.</p>
<p>This is why many companies still use dedicated OLAP systems even when their primary database is MongoDB.</p>
<hr />
<h1>When Should You Use Row vs Column Databases?</h1>
<h2>Use Row Databases When:</h2>
<ul>
<li><p>your system is transactional</p>
</li>
<li><p>you need frequent updates</p>
</li>
<li><p>you need strong consistency</p>
</li>
<li><p>your workload is CRUD-heavy</p>
</li>
<li><p>queries access complete records</p>
</li>
</ul>
<p>Examples:</p>
<ul>
<li><p>ecommerce apps</p>
</li>
<li><p>SaaS products</p>
</li>
<li><p>banking systems</p>
</li>
<li><p>user authentication systems</p>
</li>
</ul>
<hr />
<h2>Use Column Databases When:</h2>
<ul>
<li><p>your system is analytics-heavy</p>
</li>
<li><p>queries scan millions of rows</p>
</li>
<li><p>aggregations are frequent</p>
</li>
<li><p>dashboards refresh continuously</p>
</li>
<li><p>reports are generated regularly</p>
</li>
</ul>
<p>Examples:</p>
<ul>
<li><p>analytics platforms</p>
</li>
<li><p>BI dashboards</p>
</li>
<li><p>event tracking systems</p>
</li>
<li><p>observability platforms</p>
</li>
<li><p>product analytics systems</p>
</li>
</ul>
<hr />
<h1>Final Thoughts</h1>
<p>Understanding the difference between row databases and column databases is critical when building analytics systems.</p>
<p>The simplest way to remember it is:</p>
<pre><code class="language-text">Row databases → transactions
Column databases → analytics
</code></pre>
<p>Modern architectures often combine both approaches:</p>
<ul>
<li><p>OLTP databases for operational systems</p>
</li>
<li><p>OLAP databases for analytics systems</p>
</li>
</ul>
<p>Choosing the right architecture early can save enormous engineering effort as your system scales.</p>
<p>As data volume grows, understanding storage architecture becomes one of the most valuable skills for backend and data engineers.</p>
]]></content:encoded></item><item><title><![CDATA[File Descriptors (FD): What They Are, How They Work, and Why You Hit EMFILE]]></title><description><![CDATA[If you’ve ever seen an error like:
EMFILE: too many open files

you’ve already met one of the most fundamental pieces of an operating system: the file descriptor (FD).
This blog explains:

what an FD ]]></description><link>https://blog.nishikanta.in/file-descriptors-fd-what-they-are-how-they-work-and-why-you-hit-emfile</link><guid isPermaLink="true">https://blog.nishikanta.in/file-descriptors-fd-what-they-are-how-they-work-and-why-you-hit-emfile</guid><category><![CDATA[Internals]]></category><category><![CDATA[os]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Nishikanta Ray]]></dc:creator><pubDate>Thu, 23 Apr 2026 17:07:26 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/606f7705d741af6659cf980f/53fa402e-1212-4ea0-a238-25031976c730.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you’ve ever seen an error like:</p>
<pre><code class="language-js">EMFILE: too many open files
</code></pre>
<p>you’ve already met one of the most fundamental pieces of an operating system: the <strong>file descriptor (FD)</strong>.</p>
<p>This blog explains:</p>
<ul>
<li><p>what an FD is</p>
</li>
<li><p>how its lifecycle works</p>
</li>
<li><p>why your system ran out of them during image export</p>
</li>
<li><p><strong>how memory differs from FD (second problem you hit)</strong></p>
</li>
<li><p><strong>and how Excel image export actually behaves internally (third problem)</strong></p>
</li>
</ul>
<hr />
<h1>🧠 What is a File Descriptor (FD)?</h1>
<p>A <strong>file descriptor</strong> is a small integer that the operating system gives your process when it opens a file or resource.</p>
<p>Think of it as a <strong>ticket number</strong>:</p>
<ul>
<li><p>You ask the OS: “Open this file”</p>
</li>
<li><p>OS says: “Here’s FD = 7”</p>
</li>
<li><p>You use <strong>7</strong> to read/write</p>
</li>
<li><p>When done → you close it</p>
</li>
</ul>
<hr />
<h2>🧾 Example</h2>
<pre><code class="language-js">const fs = require('fs');

const fd = fs.openSync('data.txt', 'r'); // fd = 7 (example)
const buffer = Buffer.alloc(100);

fs.readSync(fd, buffer, 0, 100, 0);
fs.closeSync(fd);
</code></pre>
<hr />
<h1>📦 What counts as a file?</h1>
<p>In Unix systems:</p>
<blockquote>
<p><strong>Everything is treated like a file</strong></p>
</blockquote>
<table>
<thead>
<tr>
<th>Resource</th>
<th>Uses FD?</th>
</tr>
</thead>
<tbody><tr>
<td>Disk file</td>
<td>✅</td>
</tr>
<tr>
<td>HTTP request</td>
<td>✅</td>
</tr>
<tr>
<td>Socket</td>
<td>✅</td>
</tr>
<tr>
<td>Stream</td>
<td>✅</td>
</tr>
</tbody></table>
<hr />
<h1>🔄 FD Lifecycle (Step-by-Step)</h1>
<pre><code class="language-text">[1] Request resource
        ↓
[2] OS opens it
        ↓
[3] OS assigns FD (integer)
        ↓
[4] Your app uses FD
        ↓
[5] You close FD
        ↓
[6] OS frees FD
</code></pre>
<hr />
<h2>📊 Diagram</h2>
<pre><code class="language-text">Application                  Operating System
-----------                 -----------------
   open()   ─────────────▶   Allocate FD (e.g., 5)
                            Open file/socket

   read(fd=5) ◀──────────▶   Read data

   write(fd=5) ◀─────────▶   Write data

   close(5) ─────────────▶   Release FD
                            Free slot
</code></pre>
<hr />
<h1>🔢 Why FD Limits Exist</h1>
<p>Each process has a limit:</p>
<pre><code class="language-bash">ulimit -n
</code></pre>
<p>Typical values:</p>
<ul>
<li><p>1024</p>
</li>
<li><p>4096</p>
</li>
<li><p>65535 (tuned systems)</p>
</li>
</ul>
<hr />
<h1>💥 What is EMFILE?</h1>
<pre><code class="language-text">EMFILE = Too Many Open Files
</code></pre>
<p>It means:</p>
<blockquote>
<p>❗ You opened more resources than allowed at the same time</p>
</blockquote>
<hr />
<h1>🧨 Real Example (Your Case)</h1>
<ul>
<li><p>50 concurrent downloads</p>
</li>
<li><p>Each opens:</p>
<ul>
<li><p>1 HTTP socket (FD)</p>
</li>
<li><p>1 file write (FD)</p>
</li>
</ul>
</li>
</ul>
<p>👉</p>
<pre><code class="language-text">50 × (socket + file) = ~100 FDs
</code></pre>
<p>With multiple jobs:</p>
<pre><code class="language-text">5 jobs → ~500 FDs → EMFILE
</code></pre>
<hr />
<h1>❌ Bad Pattern</h1>
<pre><code class="language-js">await Promise.all(urls.map(download));
</code></pre>
<p>👉 Opens everything at once</p>
<hr />
<h1>✅ Good Pattern</h1>
<pre><code class="language-js">for (const url of urls) {
  await download(url);
}
</code></pre>
<p>👉 Controlled FD usage</p>
<hr />
<h1>🧠 NEW TOPIC 1: FD vs Memory (Critical Difference)</h1>
<p>This is where many people get confused.</p>
<hr />
<h2>📊 Comparison</h2>
<table>
<thead>
<tr>
<th>Concept</th>
<th>FD</th>
<th>Memory</th>
</tr>
</thead>
<tbody><tr>
<td>What it is</td>
<td>Handle</td>
<td>Data</td>
</tr>
<tr>
<td>Managed by</td>
<td>OS</td>
<td>Your app</td>
</tr>
<tr>
<td>Limit</td>
<td>OS limit (ulimit)</td>
<td>RAM</td>
</tr>
<tr>
<td>Error</td>
<td>EMFILE</td>
<td>OOM (Out of Memory)</td>
</tr>
</tbody></table>
<hr />
<h2>🔍 Example</h2>
<pre><code class="language-js">const buffer = await fs.readFile('image.jpg');
</code></pre>
<h3>What happens:</h3>
<pre><code class="language-text">open file → FD used
read file → buffer created in RAM
close file → FD released
</code></pre>
<p>👉 After this:</p>
<ul>
<li><p>FD = ❌ gone</p>
</li>
<li><p>Buffer = ✅ still in memory</p>
</li>
</ul>
<hr />
<h2>💥 Key Insight</h2>
<blockquote>
<p>❗ Closing a file does NOT free memory</p>
</blockquote>
<hr />
<h2>🧨 Your second problem (memory)</h2>
<p>Even after fixing EMFILE:</p>
<pre><code class="language-js">workbook.addImage({ buffer })
</code></pre>
<p>👉 ExcelJS stores ALL buffers internally</p>
<p>So:</p>
<pre><code class="language-text">6000 images × 150KB ≈ 900MB RAM
</code></pre>
<p>👉 This leads to <strong>memory pressure</strong>, not FD issue</p>
<hr />
<h2>🧠 Simple analogy</h2>
<ul>
<li><p>FD = number of doors open 🚪</p>
</li>
<li><p>Memory = stuff inside the room 📦</p>
</li>
</ul>
<p>Closing doors doesn’t remove the stuff.</p>
<hr />
<h1>🧠 NEW TOPIC 2: Excel Image Rendering Limitation (Your 3rd Issue)</h1>
<p>You noticed:</p>
<blockquote>
<p>After ~3000 rows → images overlap at top</p>
</blockquote>
<hr />
<h2>🔍 Root cause</h2>
<p>Excel calculates image positions using:</p>
<pre><code class="language-text">Row height → converted to EMU
1pt = 12,700 EMU
</code></pre>
<p>Each image Y position = <strong>sum of all previous row heights</strong></p>
<hr />
<h2>⚠️ Limitation</h2>
<p>Excel internally uses:</p>
<pre><code class="language-text">32-bit signed integer
Max = 2,147,483,647
</code></pre>
<hr />
<h2>📊 Calculation</h2>
<p>If:</p>
<pre><code class="language-text">row height = 50pt
→ 50 × 12700 = 635,000 EMU per row
</code></pre>
<p>Then:</p>
<pre><code class="language-text">2,147,483,647 / 635,000 ≈ 3,382 rows
</code></pre>
<hr />
<h2>💥 What happens after that?</h2>
<pre><code class="language-text">Value overflows → becomes negative
→ Excel places image at wrong position (top)
→ all images overlap
</code></pre>
<hr />
<h2>📊 Visual</h2>
<pre><code class="language-text">Correct:
Row 1 → Y = 0
Row 2 → Y = 635k
Row 3000 → Y = valid

After overflow:
Row 3500 → Y = negative ❗
→ jumps to top
→ overlap
</code></pre>
<hr />
<h2>❌ Why your “row height fix” felt bad</h2>
<p>You reduced:</p>
<pre><code class="language-text">row height = 16pt
</code></pre>
<p>👉 avoids overflow BUT:</p>
<ul>
<li><p>image still 50px</p>
</li>
<li><p>row smaller than image</p>
</li>
<li><p>UI breaks</p>
</li>
</ul>
<hr />
<h1>🏁 Proper Solutions (Summary)</h1>
<h2>✅ For FD issue</h2>
<ul>
<li><p>limit concurrency</p>
</li>
<li><p>avoid massive <code>Promise.all</code></p>
</li>
</ul>
<hr />
<h2>✅ For Memory issue</h2>
<ul>
<li><p>compress images</p>
</li>
<li><p>reduce unique images</p>
</li>
<li><p>limit concurrent exports</p>
</li>
</ul>
<hr />
<h2>✅ For Excel overlap issue</h2>
<h3>Best:</h3>
<p>👉 Split sheets</p>
<pre><code class="language-text">Sheet 1 → 0–3000 rows
Sheet 2 → 3001–6000
</code></pre>
<h3>Alternative:</h3>
<ul>
<li><p>reduce image size</p>
</li>
<li><p>or fallback to URLs</p>
</li>
</ul>
<hr />
<h1>🧠 Final Mental Model</h1>
<pre><code class="language-text">Problem 1 → FD (too many things open at once)
Problem 2 → Memory (too much data kept)
Problem 3 → Excel limit (internal integer overflow)
</code></pre>
<hr />
<h1>💬 One-line takeaway</h1>
<blockquote>
<p>FD issues are about “how many things are open at once”, memory issues are about “how much data you keep”, and Excel issues are about “format limitations you cannot bypass.”</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[⚡ Real-Time Sync Between Web Apps and Desktop Systems]]></title><description><![CDATA[🚀 From Clicks to Automation: Building a Real-Time Integration Workflow
Modern software stacks often live in two separate worlds:

🌐 Web apps → great for collaboration, dashboards, and control

💻 De]]></description><link>https://blog.nishikanta.in/real-time-sync-between-web-apps-and-desktop-systems</link><guid isPermaLink="true">https://blog.nishikanta.in/real-time-sync-between-web-apps-and-desktop-systems</guid><category><![CDATA[desktop apps]]></category><category><![CDATA[applescript]]></category><category><![CDATA[#nativeappdevelopment ]]></category><dc:creator><![CDATA[Nishikanta Ray]]></dc:creator><pubDate>Wed, 15 Apr 2026 17:09:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/606f7705d741af6659cf980f/0889f0e7-3674-4bbf-8792-8be9ab69259a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3>🚀 From Clicks to Automation: Building a Real-Time Integration Workflow</h3>
<p>Modern software stacks often live in two separate worlds:</p>
<ul>
<li><p>🌐 Web apps → great for collaboration, dashboards, and control</p>
</li>
<li><p>💻 Desktop apps → powerful, fast, and deeply integrated with the system</p>
</li>
</ul>
<p>👉 But they rarely talk to each other in real-time.</p>
<p>That gap creates friction.</p>
<p>This blog is about how to eliminate that friction by building a <strong>real-time sync layer between web and desktop systems</strong>.</p>
<hr />
<h1>🎬 A Real-World Story (Non-Technical)</h1>
<p>Imagine a fast-paced production environment.</p>
<ul>
<li><p>A producer creates a new task in a web dashboard</p>
</li>
<li><p>A specialist works inside a desktop tool</p>
</li>
<li><p>Every time a new task starts, they must:</p>
<ul>
<li><p>Create a folder</p>
</li>
<li><p>Name it correctly</p>
</li>
<li><p>Keep things organized</p>
</li>
</ul>
</li>
</ul>
<p>Now scale that: 👉 20–50 times per day 👉 Multiple people involved 👉 Tight deadlines</p>
<p>What happens?</p>
<ul>
<li><p>Naming inconsistencies</p>
</li>
<li><p>Missed steps</p>
</li>
<li><p>No real-time visibility</p>
</li>
<li><p>Constant context switching</p>
</li>
</ul>
<p>This isn’t just inefficiency — it’s <strong>workflow breakdown</strong>.</p>
<hr />
<h1>❗ The Core Problem</h1>
<p>At its core, the issue is simple:</p>
<blockquote>
<p>Systems that should be connected are operating in isolation.</p>
</blockquote>
<p>A typical flow looks like this:</p>
<ol>
<li><p>Event happens in web app</p>
</li>
<li><p>User manually repeats the same action in desktop app</p>
</li>
<li><p>State diverges</p>
</li>
<li><p>Errors accumulate</p>
</li>
</ol>
<h3>Why this happens</h3>
<ul>
<li><p>Browsers cannot access OS-level resources</p>
</li>
<li><p>Desktop apps don’t expose real-time APIs</p>
</li>
<li><p>No shared event system</p>
</li>
</ul>
<hr />
<h1>💡 The Solution: Event-Driven Sync Layer</h1>
<p>Instead of humans acting as the bridge…</p>
<p>👉 We introduce a <strong>desktop bridge layer</strong>.</p>
<pre><code class="language-plaintext">Web Event → Desktop Bridge → OS Automation → Native App → Sync Back
</code></pre>
<p>Now:</p>
<p>✔ Web triggers actions automatically ✔ Desktop executes instantly ✔ State flows back in real-time</p>
<hr />
<h1>🧠 Architecture Deep Dive</h1>
<h2>Visual Overview</h2>
<pre><code class="language-plaintext">        ┌──────────────────────┐
        │      Web App         │
        │  (User Actions)      │
        └─────────┬────────────┘
                  │ WebSocket/API
                  ↓
        ┌──────────────────────┐
        │   Desktop Bridge     │
        │ (Node / Electron)    │
        └─────────┬────────────┘
                  │ OS Script
                  ↓
        ┌──────────────────────┐
        │  Native Application  │
        │ (Executes Action)    │
        └─────────┬────────────┘
                  │ Event/State
                  ↓
        ┌──────────────────────┐
        │   Desktop Bridge     │
        └─────────┬────────────┘
                  │ WebSocket
                  ↓
        ┌──────────────────────┐
        │      Web App         │
        │   (Live Updates)     │
        └──────────────────────┘
</code></pre>
<hr />
<h1>🔍 Breaking Down Each Layer</h1>
<h2>1. Web App (Control Layer)</h2>
<p>Responsible for:</p>
<ul>
<li><p>User actions</p>
</li>
<li><p>Workflow orchestration</p>
</li>
<li><p>Sending events</p>
</li>
</ul>
<pre><code class="language-js">ws.send(JSON.stringify({
  type: "CREATE_RESOURCE",
  payload: {
    name: "Project_001",
    user: "John"
  }
}));
</code></pre>
<hr />
<h2>2. Desktop Bridge (Core Engine)</h2>
<p>This is the most important piece.</p>
<p>Responsibilities:</p>
<ul>
<li><p>Maintain persistent connection with web app</p>
</li>
<li><p>Translate events → system actions</p>
</li>
<li><p>Handle retries, errors, state</p>
</li>
</ul>
<pre><code class="language-js">ws.on("message", (msg) =&gt; {
  const event = JSON.parse(msg);

  if (event.type === "CREATE_RESOURCE") {
    handleCreate(event.payload);
  }
});
</code></pre>
<hr />
<h2>3. OS Automation Layer</h2>
<p>This is where magic happens.</p>
<ul>
<li><p>macOS → AppleScript</p>
</li>
<li><p>Windows → PowerShell / COM</p>
</li>
<li><p>Linux → shell / DBus</p>
</li>
</ul>
<pre><code class="language-js">function handleCreate(data) {
  const script = `
    tell application "TargetApp"
        perform action with name "${data.name}"
    end tell
  `;

  exec(`osascript -e '${script}'`);
}
</code></pre>
<hr />
<h2>4. Native Application (Execution Layer)</h2>
<p>This is where real work happens:</p>
<ul>
<li><p>File creation</p>
</li>
<li><p>Processing</p>
</li>
<li><p>Rendering</p>
</li>
<li><p>Data manipulation</p>
</li>
</ul>
<p>The key idea: 👉 We don’t modify the app 👉 We control it externally</p>
<hr />
<h2>5. Reverse Sync (Critical for Real-Time)</h2>
<p>After execution:</p>
<pre><code class="language-js">ws.send(JSON.stringify({
  type: "RESOURCE_CREATED",
  name: data.name
}));
</code></pre>
<p>This keeps UI and system in sync.</p>
<hr />
<h1>🔄 End-to-End Flow (Detailed)</h1>
<pre><code class="language-plaintext">1. User clicks "Start"
2. Web emits event
3. Desktop receives event
4. Script executes
5. Native app performs action
6. Desktop confirms success
7. Web UI updates instantly
</code></pre>
<hr />
<h1>🧪 Full Working Example</h1>
<h2>Web Client</h2>
<pre><code class="language-js">const ws = new WebSocket("ws://localhost:3002");

function create() {
  ws.send(JSON.stringify({
    type: "CREATE_RESOURCE",
    name: "Demo_001"
  }));
}

ws.onmessage = (msg) =&gt; {
  const data = JSON.parse(msg.data);
  console.log("Updated:", data);
};
</code></pre>
<h2>Desktop Server</h2>
<pre><code class="language-js">const WebSocket = require("ws");
const { exec } = require("child_process");

const wss = new WebSocket.Server({ port: 3002 });

wss.on("connection", (ws) =&gt; {
  ws.on("message", (msg) =&gt; {
    const { type, name } = JSON.parse(msg);

    if (type === "CREATE_RESOURCE") {
      exec(`osascript -e 'tell application "TargetApp" to perform action with name "${name}"'`);

      ws.send(JSON.stringify({
        type: "RESOURCE_CREATED",
        name
      }));
    }
  });
});
</code></pre>
<hr />
<h1>⚠️ Production Challenges</h1>
<h2>1. Race Conditions</h2>
<p>Multiple triggers → duplicate execution</p>
<p>Solution:</p>
<ul>
<li><p>Idempotency keys</p>
</li>
<li><p>State checks before execution</p>
</li>
</ul>
<hr />
<h2>2. Reliability</h2>
<p>What if:</p>
<ul>
<li><p>Desktop app is closed?</p>
</li>
<li><p>Script fails?</p>
</li>
</ul>
<p>Solution:</p>
<ul>
<li><p>Retry queue</p>
</li>
<li><p>Health checks</p>
</li>
</ul>
<hr />
<h2>3. Security</h2>
<p>You’re exposing a local bridge.</p>
<p>Solution:</p>
<ul>
<li><p>Token-based auth</p>
</li>
<li><p>Restrict localhost access</p>
</li>
</ul>
<hr />
<h2>4. Latency</h2>
<p>OS automation isn’t instant.</p>
<p>Expect:</p>
<ul>
<li>~100–300ms delays</li>
</ul>
<hr />
<h1>🔥 Advanced Patterns</h1>
<h2>Event Sourcing</h2>
<p>Store all events and replay if needed</p>
<h2>Offline Mode</h2>
<p>Queue events when disconnected</p>
<h2>Bi-Directional Sync</h2>
<p>Detect external changes and push to web</p>
<h2>Streaming Updates</h2>
<p>Push continuous state updates</p>
<hr />
<h1>🎯 Why This Pattern Matters</h1>
<p>This is bigger than one use case.</p>
<p>You’re building:</p>
<p>👉 A <strong>universal bridge between cloud and local systems</strong></p>
<p>Once this exists:</p>
<ul>
<li><p>Any web action → can control desktop</p>
</li>
<li><p>Any desktop change → can reflect in web</p>
</li>
</ul>
<hr />
<h1>💬 Final Thought</h1>
<p>Most systems fail not because of complexity…</p>
<p>…but because of <strong>gaps between tools</strong>.</p>
<p>Close that gap — and everything becomes faster, cleaner, and scalable.</p>
]]></content:encoded></item><item><title><![CDATA[🧠 From 500 Million Orders to 5ms Queries]]></title><description><![CDATA[Modern applications rely heavily on databases. When a system is small, almost every query feels fast. But as your product grows, database performance becomes one of the biggest engineering challenges.]]></description><link>https://blog.nishikanta.in/from-500-million-orders-to-5ms-queries</link><guid isPermaLink="true">https://blog.nishikanta.in/from-500-million-orders-to-5ms-queries</guid><category><![CDATA[Databases]]></category><category><![CDATA[indexing]]></category><category><![CDATA[MongoDB]]></category><dc:creator><![CDATA[Nishikanta Ray]]></dc:creator><pubDate>Sat, 04 Apr 2026 04:26:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/606f7705d741af6659cf980f/c4fc8495-2d5e-4210-95b9-b905a4c52bdc.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Modern applications rely heavily on databases. When a system is small, almost every query feels fast. But as your product grows, database performance becomes one of the biggest engineering challenges.</p>
<p>Imagine an e-commerce platform with:</p>
<pre><code class="language-plaintext">500,000,000 orders
10,000,000 users
real-time dashboards
admin analytics queries
</code></pre>
<p>At this scale, even a <strong>simple query can become painfully slow</strong>.</p>
<p>For example:</p>
<pre><code class="language-javascript">db.orders.find({ status: "delivered" })
</code></pre>
<p>Why does a simple query sometimes take <strong>seconds instead of milliseconds</strong>?</p>
<p>The answer lies in <strong>how indexes work internally</strong> inside MongoDB.</p>
<p>This article dives deep into:</p>
<ul>
<li><p>B-Tree index internals</p>
</li>
<li><p>Disk page layout</p>
</li>
<li><p>How MongoDB stores index entries</p>
</li>
<li><p>Why compound indexes matter</p>
</li>
<li><p>Page splits and index growth</p>
</li>
<li><p>Real-world e-commerce examples</p>
</li>
</ul>
<p>By the end, you’ll understand <strong>exactly how MongoDB finds your data among hundreds of millions of documents</strong>.</p>
<hr />
<h1>Real-World Scenario: Large E-Commerce Platform</h1>
<p>Consider an <code>orders</code> collection.</p>
<p>Example document:</p>
<pre><code class="language-javascript">{
  _id: ObjectId("6651a1..."),
  user_id: 784321,
  product_id: 99231,
  status: "delivered",
  price: 1499,
  created_at: ISODate("2025-03-10T10:21:00Z")
}
</code></pre>
<p>Collection size:</p>
<pre><code class="language-plaintext">orders collection
-----------------
500 million documents
</code></pre>
<p>Order distribution:</p>
<pre><code class="language-plaintext">delivered → 420M
pending → 60M
cancelled → 20M
</code></pre>
<h1>What Happens Without an Index</h1>
<p>Suppose the backend runs this query:</p>
<pre><code class="language-javascript">db.orders.find({ status: "delivered" })
Without an index, MongoDB performs a collection scan.
</code></pre>
<p>Execution plan:</p>
<pre><code class="language-plaintext">COLLSCAN
</code></pre>
<p>This means MongoDB checks <strong>every document</strong>.</p>
<pre><code class="language-plaintext">doc1 → check
doc2 → check
doc3 → check
...
doc500,000,000 → check
</code></pre>
<p>Even if only one result is needed, MongoDB still scans everything.</p>
<p>This is extremely expensive.</p>
<hr />
<h1>Creating an Index</h1>
<p>To speed things up we create an index:</p>
<pre><code class="language-javascript">db.orders.createIndex({ status: 1 })
</code></pre>
<p>MongoDB builds a <strong>B-Tree index</strong>.</p>
<p>Important fact:</p>
<p>Indexes <strong>do not store full documents</strong>.</p>
<p>Instead they store:</p>
<pre><code class="language-plaintext">index key
+
pointer to the document
</code></pre>
<p>Example index entries:</p>
<pre><code class="language-plaintext">cancelled → pointer
delivered → pointer
delivered → pointer
delivered → pointer
pending   → pointer
</code></pre>
<p>These entries are stored in sorted order.</p>
<hr />
<h1>MongoDB Storage Engine</h1>
<p>MongoDB uses the WiredTiger storage engine.</p>
<p>WiredTiger stores data in <strong>disk pages</strong>.</p>
<p>Typical page layout:</p>
<pre><code class="language-plaintext">collection file
 ├── page 1
 ├── page 2
 ├── page 3
 └── ...
</code></pre>
<p>Indexes are stored separately:</p>
<pre><code class="language-plaintext">orders_status_index
 ├── root page
 ├── internal pages
 └── leaf pages
</code></pre>
<p>Each page contains many index entries.</p>
<hr />
<h1>The Structure of a MongoDB B-Tree</h1>
<p>A B-Tree is a <strong>balanced search tree</strong>.</p>
<p>Structure:</p>
<pre><code class="language-plaintext">Root node
Internal nodes
Leaf nodes
</code></pre>
<p>Visual example:</p>
<pre><code class="language-plaintext">              ROOT
           [ delivered ]
           /          \
      cancelled      pending
</code></pre>
<p>In real production systems the tree may contain <strong>thousands of nodes</strong>.</p>
<hr />
<h1>Leaf Nodes: Where Index Data Lives</h1>
<p>Leaf nodes store the actual index entries.</p>
<p>Example leaf page:</p>
<pre><code class="language-plaintext">cancelled → doc pointer
delivered → doc pointer
delivered → doc pointer
delivered → doc pointer
pending   → doc pointer
</code></pre>
<p>Each entry contains:</p>
<pre><code class="language-plaintext">indexed field value
+
pointer to the document
</code></pre>
<p>Because entries are sorted, searching becomes extremely fast.</p>
<hr />
<h1>Step-by-Step: How MongoDB Searches the Index</h1>
<p>Query:</p>
<pre><code class="language-javascript">db.orders.find({ status: "delivered" })
</code></pre>
<h3>Step 1 — Start at the root</h3>
<pre><code class="language-plaintext">ROOT
[ delivered ]
</code></pre>
<p>MongoDB compares the search key with root entries.</p>
<hr />
<h3>Step 2 — Traverse internal nodes</h3>
<pre><code class="language-plaintext">         ROOT
      [ delivered ]
       /         \
 &lt; delivered   ≥ delivered
</code></pre>
<p>The tree guides MongoDB toward the correct branch.</p>
<hr />
<h3>Step 3 — Reach leaf node</h3>
<p>Eventually MongoDB finds the leaf node containing the key.</p>
<p>Example:</p>
<pre><code class="language-plaintext">delivered → doc pointer
delivered → doc pointer
delivered → doc pointer
</code></pre>
<hr />
<h3>Step 4 — Fetch documents</h3>
<p>MongoDB retrieves the documents using the stored pointers.</p>
<p>This avoids scanning unrelated data.</p>
<hr />
<h1>The Hidden Problem With Single Indexes</h1>
<p>Single-field indexes are not always enough.</p>
<p>Consider this admin dashboard query:</p>
<pre><code class="language-javascript">db.orders.find({
  status: "delivered"
}).sort({ created_at: -1 }).limit(20)
</code></pre>
<p>What happens internally?</p>
<ol>
<li><p>Find all <code>"delivered"</code> entries</p>
</li>
<li><p>Sort them by date</p>
</li>
<li><p>Return latest 20</p>
</li>
</ol>
<p>But remember:</p>
<pre><code class="language-plaintext">delivered orders = 420 million
</code></pre>
<p>Even with an index, MongoDB still needs to process <strong>hundreds of millions of entries</strong>.</p>
<hr />
<h1>Compound Index to the Rescue</h1>
<p>Solution:</p>
<pre><code class="language-javascript">db.orders.createIndex({
  status: 1,
  created_at: -1
})
</code></pre>
<p>Now the index stores entries like:</p>
<pre><code class="language-plaintext">(delivered, Mar 10) → pointer
(delivered, Mar 9) → pointer
(delivered, Mar 8) → pointer
(pending, Mar 10) → pointer
(cancelled, Mar 4) → pointer
</code></pre>
<p>Data is sorted by:</p>
<pre><code class="language-plaintext">status → created_at
</code></pre>
<p>Now the query becomes extremely efficient.</p>
<pre><code class="language-javascript">db.orders.find({
  status: "delivered"
})
.sort({ created_at: -1 })
.limit(20)
</code></pre>
<p>MongoDB can:</p>
<pre><code class="language-plaintext">jump directly to delivered
read newest entries
stop after 20 results
</code></pre>
<p>No sorting required.</p>
<hr />
<h1>Understanding the Left-Prefix Rule</h1>
<p>Compound indexes follow a rule called the <strong>left-prefix rule</strong>.</p>
<p>If the index is:</p>
<pre><code class="language-plaintext">{ status: 1, created_at: -1 }
</code></pre>
<p>It supports queries like:</p>
<pre><code class="language-plaintext">status
status + created_at
</code></pre>
<p>But <strong>not efficiently</strong>:</p>
<pre><code class="language-plaintext">created_at only
</code></pre>
<p>Because the index is sorted first by <code>status</code>.</p>
<hr />
<h1>What Happens When an Index Page Fills</h1>
<p>Index pages have limited size.</p>
<p>When a page becomes full, MongoDB performs a <strong>page split</strong>.</p>
<p>Before split:</p>
<pre><code class="language-plaintext">PAGE A
cancelled
delivered
delivered
delivered
pending
</code></pre>
<p>After split:</p>
<pre><code class="language-plaintext">PAGE A           PAGE B
cancelled        delivered
delivered        delivered
                 pending
</code></pre>
<p>Parent nodes are updated with new pointers.</p>
<hr />
<h1>How the Tree Grows</h1>
<p>After many inserts, the tree grows.</p>
<p>Example structure:</p>
<pre><code class="language-plaintext">                ROOT
           [ delivered ]
           /           \
      INTERNAL       INTERNAL
       /     \        /     \
    LEAF   LEAF   LEAF   LEAF
</code></pre>
<p>Despite growing larger, the tree remains <strong>balanced</strong>.</p>
<p>This ensures predictable performance.</p>
<hr />
<h1>Time Complexity</h1>
<p>B-Tree search complexity:</p>
<pre><code class="language-plaintext">O(log n)
</code></pre>
<p>Even with <strong>500 million documents</strong>, MongoDB may only read:</p>
<pre><code class="language-plaintext">3-4 index pages
</code></pre>
<p>to locate matching documents.</p>
<hr />
<h1>Index Design in Real Production Systems</h1>
<p>Large systems often maintain indexes like:</p>
<p>User order history:</p>
<pre><code class="language-javascript">{ user_id: 1, created_at: -1 }
</code></pre>
<p>Latest delivered orders:</p>
<pre><code class="language-javascript">{ status: 1, created_at: -1 }
</code></pre>
<p>Fast product analytics:</p>
<pre><code class="language-javascript">{ product_id: 1, created_at: -1 }
</code></pre>
<p>These power:</p>
<pre><code class="language-plaintext">user dashboards
admin analytics
order history
real-time monitoring
</code></pre>
<hr />
<h1>The Most Important Lesson</h1>
<p>Indexes are not just about <strong>adding fields</strong>.</p>
<p>They are about <strong>matching how your application queries data</strong>.</p>
<p>A poorly designed index may still scan millions of records.</p>
<p>But a well-designed compound index can reduce query time from:</p>
<pre><code class="language-plaintext">seconds → milliseconds
</code></pre>
<hr />
<h1>Final Thoughts</h1>
<p>Understanding <strong>how MongoDB indexes actually work internally</strong>—including B-Trees, disk pages, compound indexes, and page splits—gives engineers a huge advantage when building scalable systems.</p>
<p>Many database performance problems are not caused by infrastructure.</p>
<p>They are caused by <strong>misunderstanding how indexes organize data</strong>.</p>
<p>Once you understand the internals, database performance stops feeling mysterious and starts becoming predictable.</p>
]]></content:encoded></item><item><title><![CDATA[Data-i18n for Small Web Projects: A Complete Guide to Internationalization]]></title><description><![CDATA[Modern web applications are used by people across different countries and languages. If your website only supports one language, it limits your audience. This is where internationalization (i18n) come]]></description><link>https://blog.nishikanta.in/data-i18n-for-small-web-projects-a-complete-guide-to-internationalization</link><guid isPermaLink="true">https://blog.nishikanta.in/data-i18n-for-small-web-projects-a-complete-guide-to-internationalization</guid><dc:creator><![CDATA[Nishikanta Ray]]></dc:creator><pubDate>Fri, 06 Mar 2026 17:58:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/606f7705d741af6659cf980f/68cd42d0-9bee-45c1-95e4-176cd534f124.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Modern web applications are used by people across different countries and languages. If your website only supports one language, it limits your audience. This is where <strong>internationalization (i18n)</strong> comes in.</p>
<p>In this blog, we will explore how to implement a <strong>simple and powerful</strong> <code>data-i18n</code> <strong>system</strong> in a small web project using <strong>HTML, CSS, and JavaScript</strong>, without any heavy frameworks.</p>
<hr />
<h1>What is Internationalization (i18n)?</h1>
<p><strong>Internationalization (i18n)</strong> is the process of designing your application so it can easily support <strong>multiple languages and regions</strong>.</p>
<p>The abbreviation <strong>i18n</strong> means:</p>
<ul>
<li><p><strong>i</strong> → first letter</p>
</li>
<li><p><strong>18</strong> → number of letters between</p>
</li>
<li><p><strong>n</strong> → last letter</p>
</li>
</ul>
<p>Example:</p>
<pre><code class="language-plaintext">internationalization
i + 18 letters + n
</code></pre>
<p>Internationalization allows applications to switch between languages like:</p>
<ul>
<li><p>English</p>
</li>
<li><p>French</p>
</li>
<li><p>Spanish</p>
</li>
<li><p>Hindi</p>
</li>
<li><p>Japanese</p>
</li>
</ul>
<p>without rewriting the entire application.</p>
<hr />
<h1>What is <code>data-i18n</code>?</h1>
<p><code>data-i18n</code> is a <strong>custom HTML attribute</strong> used to mark elements that should be translated.</p>
<p>Instead of hardcoding text in HTML, we assign a <strong>translation key</strong>.</p>
<p>Example:</p>
<pre><code class="language-html">&lt;h1 data-i18n="title"&gt;Welcome&lt;/h1&gt;
&lt;p data-i18n="description"&gt;This is a demo app.&lt;/p&gt;
</code></pre>
<p>Here:</p>
<ul>
<li><p><code>title</code></p>
</li>
<li><p><code>description</code></p>
</li>
</ul>
<p>are <strong>translation keys</strong>, not the actual translation.</p>
<p>JavaScript will dynamically replace the text based on the selected language.</p>
<hr />
<h1>How the System Works</h1>
<p>The <code>data-i18n</code> system usually follows these steps:</p>
<ol>
<li><p>HTML contains <strong>translation keys</strong></p>
</li>
<li><p>Translations are stored in <strong>JSON files</strong></p>
</li>
<li><p>JavaScript loads the selected language</p>
</li>
<li><p>JavaScript replaces the text dynamically</p>
</li>
</ol>
<p>Flow diagram:</p>
<pre><code class="language-plaintext">HTML (data-i18n keys)
        ↓
Translation JSON files
        ↓
JavaScript loads selected language
        ↓
Text updated dynamically
</code></pre>
<hr />
<h1>Step 1: Create the HTML Structure</h1>
<p>First, we mark the text that needs translation.</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
  &lt;title&gt;i18n Demo&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;

&lt;h1 data-i18n="title"&gt;Welcome&lt;/h1&gt;

&lt;p data-i18n="description"&gt;
This is a demo application.
&lt;/p&gt;

&lt;button data-i18n="login"&gt;Login&lt;/button&gt;

&lt;div&gt;
  &lt;button onclick="loadLanguage('en')"&gt;English&lt;/button&gt;
  &lt;button onclick="loadLanguage('fr')"&gt;French&lt;/button&gt;
&lt;/div&gt;

&lt;script src="script.js"&gt;&lt;/script&gt;

&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>Notice that we still include default text so the page does not appear empty before JavaScript loads.</p>
<hr />
<h1>Step 2: Create Translation Files</h1>
<p>Create a <strong>language folder</strong>.</p>
<pre><code class="language-plaintext">project
│
├── index.html
├── script.js
│
└── lang
    ├── en.json
    └── fr.json
</code></pre>
<hr />
<h2>English Translation</h2>
<p><code>lang/en.json</code></p>
<pre><code class="language-plaintext">{
  "title": "Welcome",
  "description": "This is a demo application.",
  "login": "Login"
}
</code></pre>
<hr />
<h2>French Translation</h2>
<p><code>lang/fr.json</code></p>
<pre><code class="language-plaintext">{
  "title": "Bienvenue",
  "description": "Ceci est une application de démonstration.",
  "login": "Connexion"
}
</code></pre>
<p>Each <strong>key must match the</strong> <code>data-i18n</code> <strong>value</strong>.</p>
<hr />
<h1>Step 3: JavaScript Translation Engine</h1>
<p>Now we create a function that loads the selected language and updates the page.</p>
<p><code>script.js</code></p>
<pre><code class="language-javascript">async function loadLanguage(lang) {

  const response = await fetch(`./lang/${lang}.json`);

  const translations = await response.json();

  const elements = document.querySelectorAll("[data-i18n]");

  elements.forEach(element =&gt; {

    const key = element.getAttribute("data-i18n");

    if (translations[key]) {
      element.textContent = translations[key];
    }

  });

}
</code></pre>
<p>This script does three things:</p>
<ol>
<li><p>Loads the translation JSON file</p>
</li>
<li><p>Finds all elements with <code>data-i18n</code></p>
</li>
<li><p>Replaces the text with the translated value</p>
</li>
</ol>
<hr />
<h1>Step 4: Language Switching</h1>
<p>We can switch languages using buttons.</p>
<pre><code class="language-html">&lt;button onclick="loadLanguage('en')"&gt;English&lt;/button&gt;
&lt;button onclick="loadLanguage('fr')"&gt;French&lt;/button&gt;
</code></pre>
<p>When clicked:</p>
<pre><code class="language-html">loadLanguage("en") → loads English
loadLanguage("fr") → loads French
</code></pre>
<p>The page text updates instantly.</p>
<hr />
<h1>Step 5: Saving Language Preference</h1>
<p>In real applications we usually save the user's preferred language.</p>
<p>We can use <strong>localStorage</strong>.</p>
<p>Example:</p>
<pre><code class="language-javascript">async function loadLanguage(lang) {

  localStorage.setItem("language", lang);

  const response = await fetch(`./lang/${lang}.json`);

  const translations = await response.json();

  document.querySelectorAll("[data-i18n]").forEach(el =&gt; {

    const key = el.dataset.i18n;

    if (translations[key]) {
      el.textContent = translations[key];
    }

  });

}
</code></pre>
<hr />
<h2>Auto Load Language on Page Start</h2>
<pre><code class="language-javascript">window.addEventListener("DOMContentLoaded", () =&gt; {

  const savedLang = localStorage.getItem("language") || "en";

  loadLanguage(savedLang);

});
</code></pre>
<p>Now the site remembers the user's language.</p>
<hr />
<h1>Step 6: Translating Attributes</h1>
<p>Sometimes we need to translate attributes like:</p>
<ul>
<li><p>placeholders</p>
</li>
<li><p>titles</p>
</li>
<li><p>aria labels</p>
</li>
</ul>
<p>Example:</p>
<pre><code class="language-html">&lt;input type="text" data-i18n-placeholder="search" placeholder="Search"&gt;
</code></pre>
<p>Translation file:</p>
<pre><code class="language-plaintext">{
  "search": "Search..."
}
</code></pre>
<p>JavaScript:</p>
<pre><code class="language-javascript">document.querySelectorAll("[data-i18n-placeholder]").forEach(el =&gt; {

  const key = el.getAttribute("data-i18n-placeholder");

  el.placeholder = translations[key];

});
</code></pre>
<hr />
<h1>Performance Considerations</h1>
<p>For small projects this approach is perfect, but large applications may require optimization.</p>
<p>Possible improvements:</p>
<h3>Lazy loading languages</h3>
<p>Load only the language being used.</p>
<h3>Caching translations</h3>
<p>Store translations in memory to avoid repeated network requests.</p>
<h3>Preloading common languages</h3>
<p>Load frequently used languages on startup.</p>
<hr />
<h1>Advantages of the <code>data-i18n</code> Approach</h1>
<h3>Simple implementation</h3>
<p>No frameworks required.</p>
<h3>Clean HTML</h3>
<p>Translation logic stays separate from markup.</p>
<h3>Lightweight</h3>
<p>Only a few lines of JavaScript.</p>
<h3>Easy to maintain</h3>
<p>Translators can edit JSON files without touching code.</p>
<h3>Flexible</h3>
<p>Works with any frontend stack.</p>
<hr />
<h1>Limitations</h1>
<p>Although useful, the <code>data-i18n</code> approach has some limitations.</p>
<h3>No pluralization rules</h3>
<p>Languages like Russian or Arabic require complex plural logic.</p>
<h3>No date or number formatting</h3>
<p>Formatting currencies and dates requires additional libraries.</p>
<h3>No nested translations</h3>
<p>Large projects often require structured translations.</p>
<hr />
<h1>When to Use a Full i18n Library</h1>
<p>If your project becomes large, consider using a professional library like:</p>
<ul>
<li><p>i18next</p>
</li>
<li><p>react-i18next</p>
</li>
<li><p>vue-i18n</p>
</li>
</ul>
<p>These tools support:</p>
<ul>
<li><p>pluralization</p>
</li>
<li><p>date formatting</p>
</li>
<li><p>dynamic translations</p>
</li>
<li><p>server side rendering</p>
</li>
<li><p>language detection</p>
</li>
</ul>
<hr />
<h1>Example Use Cases</h1>
<p>The <code>data-i18n</code> pattern works well for:</p>
<ul>
<li><p>Landing pages</p>
</li>
<li><p>SaaS dashboards</p>
</li>
<li><p>Documentation sites</p>
</li>
<li><p>Small web apps</p>
</li>
<li><p>Portfolio websites</p>
</li>
<li><p>MVP products</p>
</li>
</ul>
<p>For example, if you build a <strong>multi-language SaaS dashboard</strong>, you can translate UI labels like:</p>
<pre><code class="language-plaintext">Dashboard
Analytics
Settings
Logout
</code></pre>
<p>without changing the layout.</p>
<hr />
<h1>Conclusion</h1>
<p>Internationalization is an important feature for modern web applications. Even small projects benefit from supporting multiple languages.</p>
<p>The <code>data-i18n</code> <strong>approach</strong> provides a lightweight and flexible solution for implementing translations using simple HTML attributes and JSON files.</p>
<p>With just a small amount of JavaScript, you can create a system that:</p>
<ul>
<li><p>dynamically switches languages</p>
</li>
<li><p>loads translations from JSON files</p>
</li>
<li><p>remembers user preferences</p>
</li>
<li><p>keeps HTML clean and maintainable</p>
</li>
</ul>
<p>As your application grows, you can later migrate to advanced libraries while keeping the same translation structure.</p>
]]></content:encoded></item><item><title><![CDATA[Tauri vs Electron: The Complete Developer’s Guide (2026)]]></title><description><![CDATA[If you're building a desktop app today, you're probably choosing between Electron and Tauri.
Electron has been the default for years. It powers apps like VS Code, Slack, Discord, and Notion. But Tauri has rapidly emerged as a serious alternative — pr...]]></description><link>https://blog.nishikanta.in/tauri-vs-electron-the-complete-developers-guide-2026</link><guid isPermaLink="true">https://blog.nishikanta.in/tauri-vs-electron-the-complete-developers-guide-2026</guid><category><![CDATA[Tauri]]></category><category><![CDATA[js]]></category><category><![CDATA[Rust]]></category><category><![CDATA[desktop apps]]></category><category><![CDATA[desktop]]></category><dc:creator><![CDATA[Nishikanta Ray]]></dc:creator><pubDate>Mon, 26 Jan 2026 16:58:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1769446643805/46363d36-59c6-4e79-a85c-53f7b5c3db46.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you're building a desktop app today, you're probably choosing between <strong>Electron</strong> and <strong>Tauri</strong>.</p>
<p>Electron has been the default for years. It powers apps like VS Code, Slack, Discord, and Notion. But Tauri has rapidly emerged as a serious alternative — promising <strong>tiny bundle sizes, better performance, and stronger security</strong>.</p>
<p>So which one should <em>you</em> use?</p>
<p>This guide walks you through <strong>what Tauri is, how it compares to Electron, real architectural differences, performance tradeoffs, security models, and even how to migrate</strong>.</p>
<hr />
<h2 id="heading-what-is-tauri">What is Tauri?</h2>
<p><strong>Tauri</strong> is an open-source framework for building <strong>lightweight, secure desktop applications</strong> using:</p>
<ul>
<li><p><strong>Frontend:</strong> HTML, CSS, JavaScript (React, Vue, Svelte, etc.)</p>
</li>
<li><p><strong>Backend:</strong> <strong>Rust</strong></p>
</li>
<li><p><strong>Rendering Engine:</strong> The <strong>system’s native WebView</strong> (not bundled)</p>
</li>
</ul>
<p>Instead of shipping a full browser like Electron does, Tauri uses the WebView already installed on the user’s OS.</p>
<h3 id="heading-core-idea">Core Idea</h3>
<blockquote>
<p>Use the OS’s browser engine + Rust for native power = <strong>tiny, fast apps</strong></p>
</blockquote>
<h3 id="heading-key-characteristics">Key Characteristics</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Feature</td><td>Tauri</td></tr>
</thead>
<tbody>
<tr>
<td>Backend</td><td>Rust (compiled, fast, memory-safe)</td></tr>
<tr>
<td>Frontend</td><td>Any web framework</td></tr>
<tr>
<td>WebView</td><td>System WebView (WebKit / Edge / WebKitGTK)</td></tr>
<tr>
<td>Bundle Size</td><td>Very small (single-digit MBs)</td></tr>
<tr>
<td>Security</td><td>Capability-based permission system</td></tr>
<tr>
<td>Mobile</td><td>Yes (iOS &amp; Android in v2)</td></tr>
</tbody>
</table>
</div><hr />
<h2 id="heading-what-is-electron">What is Electron?</h2>
<p><strong>Electron</strong> is a framework for building desktop apps using:</p>
<ul>
<li><p><strong>Frontend:</strong> HTML/CSS/JS</p>
</li>
<li><p><strong>Backend:</strong> Node.js</p>
</li>
<li><p><strong>Rendering Engine:</strong> <strong>Bundled Chromium</strong></p>
</li>
</ul>
<p>Electron combines <strong>Chromium + Node.js</strong> into a desktop runtime.</p>
<h3 id="heading-core-idea-1">Core Idea</h3>
<blockquote>
<p>Ship a full browser + Node runtime so your app behaves the same everywhere.</p>
</blockquote>
<h3 id="heading-key-characteristics-1">Key Characteristics</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Feature</td><td>Electron</td></tr>
</thead>
<tbody>
<tr>
<td>Backend</td><td>Node.js</td></tr>
<tr>
<td>Frontend</td><td>Any web framework</td></tr>
<tr>
<td>WebView</td><td>Bundled Chromium</td></tr>
<tr>
<td>Bundle Size</td><td>Large (100–200MB typical)</td></tr>
<tr>
<td>Security</td><td>Process isolation + developer configuration</td></tr>
<tr>
<td>Mobile</td><td>No</td></tr>
</tbody>
</table>
</div><hr />
<h2 id="heading-the-fundamental-architectural-difference">The Fundamental Architectural Difference</h2>
<p>This one design choice changes everything:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Aspect</td><td>Tauri</td><td>Electron</td></tr>
</thead>
<tbody>
<tr>
<td>Browser Engine</td><td>Uses system WebView</td><td>Bundles Chromium</td></tr>
<tr>
<td>Backend Runtime</td><td>Rust</td><td>Node.js</td></tr>
<tr>
<td>Process Model</td><td>Lightweight Rust host + WebView</td><td>Main process + renderer processes</td></tr>
<tr>
<td>Binary Size</td><td><strong>3–10 MB</strong></td><td><strong>150–200 MB</strong></td></tr>
<tr>
<td>RAM Usage (idle)</td><td>30–50 MB</td><td>150–300 MB</td></tr>
<tr>
<td>Startup Speed</td><td>Very fast</td><td>Slower due to Chromium load</td></tr>
</tbody>
</table>
</div><h3 id="heading-what-this-means-in-practice">What This Means in Practice</h3>
<p><strong>Tauri = smaller + faster + leaner</strong><br /><strong>Electron = heavier + more consistent environment</strong></p>
<p>Electron ships everything it needs. Tauri reuses what the OS already has.</p>
<hr />
<h2 id="heading-developer-experience-how-apps-are-structured">Developer Experience: How Apps Are Structured</h2>
<p>Both frameworks let you use your favorite frontend stack. The difference is in the backend.</p>
<h3 id="heading-tauri-structure">Tauri Structure</h3>
<pre><code class="lang-javascript">my-tauri-app/
├── src/              # Frontend (React/Vue/etc.)
└── src-tauri/        # Rust backend
</code></pre>
<p>You write native functionality as <strong>Rust commands</strong> and call them from the frontend using Tauri’s IPC system.</p>
<h3 id="heading-electron-structure">Electron Structure</h3>
<pre><code class="lang-javascript">my-electron-app/
└── src/
    ├── main.ts       # Node.js main process
    ├── preload.ts    # Secure bridge
    └── renderer.ts   # Frontend
</code></pre>
<p>Electron uses:</p>
<ul>
<li><p><strong>Main process</strong> (Node.js, full system access)</p>
</li>
<li><p><strong>Renderer process</strong> (your UI)</p>
</li>
<li><p><strong>Preload scripts</strong> as a bridge</p>
</li>
</ul>
<hr />
<h2 id="heading-ipc-talking-between-frontend-and-backend">IPC: Talking Between Frontend and Backend</h2>
<h3 id="heading-tauri-example">Tauri Example</h3>
<p><strong>Rust backend</strong></p>
<pre><code class="lang-javascript">#[tauri::command]
fn greet(name: &amp;str) -&gt; <span class="hljs-built_in">String</span> {
    format!(<span class="hljs-string">"Hello, {}! Welcome to Tauri."</span>, name)
}
</code></pre>
<p><strong>Frontend</strong></p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { invoke } <span class="hljs-keyword">from</span> <span class="hljs-string">'@tauri-apps/api/core'</span>;

<span class="hljs-keyword">const</span> msg = <span class="hljs-keyword">await</span> invoke&lt;string&gt;(<span class="hljs-string">'greet'</span>, { <span class="hljs-attr">name</span>: <span class="hljs-string">'Nishikanta'</span> });
</code></pre>
<p>Clean. Direct. No preload script needed.</p>
<hr />
<h3 id="heading-electron-example">Electron Example</h3>
<p><strong>Main process</strong></p>
<pre><code class="lang-javascript">ipcMain.handle(<span class="hljs-string">'greet'</span>, <span class="hljs-keyword">async</span> (_, <span class="hljs-attr">name</span>: string) =&gt; {
  <span class="hljs-keyword">return</span> <span class="hljs-string">`Hello, <span class="hljs-subst">${name}</span>! Welcome to Electron.`</span>;
});
</code></pre>
<p><strong>Preload</strong></p>
<pre><code class="lang-javascript">contextBridge.exposeInMainWorld(<span class="hljs-string">'api'</span>, {
  <span class="hljs-attr">greet</span>: <span class="hljs-function">(<span class="hljs-params">name: string</span>) =&gt;</span> ipcRenderer.invoke(<span class="hljs-string">'greet'</span>, name),
});
</code></pre>
<p><strong>Renderer</strong></p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> msg = <span class="hljs-keyword">await</span> <span class="hljs-built_in">window</span>.api.greet(<span class="hljs-string">'Nishikanta'</span>);
</code></pre>
<p>More moving parts, but very flexible.</p>
<hr />
<h2 id="heading-performance-real-world-differences">Performance: Real-World Differences</h2>
<p>Let’s look at what actually matters to users.</p>
<h3 id="heading-bundle-size">Bundle Size</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Framework</td><td>App Size</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Tauri</strong></td><td>~5 MB</td></tr>
<tr>
<td><strong>Electron</strong></td><td>~150–200 MB</td></tr>
</tbody>
</table>
</div><p>If users must download your app, this is huge.</p>
<hr />
<h3 id="heading-memory-usage">Memory Usage</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Scenario</td><td>Tauri</td><td>Electron</td></tr>
</thead>
<tbody>
<tr>
<td>Idle</td><td>~45 MB</td><td>~180 MB</td></tr>
<tr>
<td>Working</td><td>~85 MB</td><td>~320 MB</td></tr>
<tr>
<td>Heavy task</td><td>~120 MB</td><td>~520 MB</td></tr>
</tbody>
</table>
</div><p>Electron pays the cost of running Chromium. Tauri doesn’t.</p>
<hr />
<h3 id="heading-startup-speed">Startup Speed</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Framework</td><td>Cold Start</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Tauri</strong></td><td>&lt; 1 second</td></tr>
<tr>
<td><strong>Electron</strong></td><td>2–5 seconds</td></tr>
</tbody>
</table>
</div><p>Tauri apps feel closer to native apps at launch.</p>
<hr />
<h3 id="heading-processing-speed">Processing Speed</h3>
<p>Rust vs Node.js also matters for heavy workloads.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Task</td><td>Tauri (Rust)</td><td>Electron (Node)</td></tr>
</thead>
<tbody>
<tr>
<td>Large file processing</td><td>Faster</td><td>Slower</td></tr>
<tr>
<td>CPU-heavy tasks</td><td>Strong</td><td>Moderate</td></tr>
</tbody>
</table>
</div><p>If your app does video processing, encryption, or heavy data transforms, <strong>Rust is a big advantage</strong>.</p>
<hr />
<h2 id="heading-security-model">Security Model</h2>
<p>This is one of the most important differences.</p>
<h3 id="heading-tauri-capability-based-security">Tauri: Capability-Based Security</h3>
<p>Tauri v2 requires you to <strong>explicitly grant permissions</strong>.</p>
<p>Example:</p>
<pre><code class="lang-javascript">{
  <span class="hljs-string">"permissions"</span>: [
    <span class="hljs-string">"fs:allow-read"</span>,
    <span class="hljs-string">"shell:allow-spawn"</span>
  ]
}
</code></pre>
<p>You can even scope access to specific folders.</p>
<p><strong>Default = locked down.</strong><br />You opt <em>into</em> power.</p>
<hr />
<h3 id="heading-electron-process-isolation">Electron: Process Isolation</h3>
<p>Electron security depends on configuration.</p>
<p>Best practices:</p>
<pre><code class="lang-javascript">webPreferences: {
  <span class="hljs-attr">nodeIntegration</span>: <span class="hljs-literal">false</span>,
  <span class="hljs-attr">contextIsolation</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">sandbox</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">preload</span>: <span class="hljs-string">'preload.js'</span>
}
</code></pre>
<p><strong>Default = permissive.</strong><br />You must lock it down yourself.</p>
<hr />
<h2 id="heading-ecosystem-amp-maturity">Ecosystem &amp; Maturity</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Area</td><td>Tauri</td><td>Electron</td></tr>
</thead>
<tbody>
<tr>
<td>Age</td><td>Young (modern)</td><td>Mature (since 2013)</td></tr>
<tr>
<td>NPM ecosystem</td><td>Smaller</td><td>Massive</td></tr>
<tr>
<td>Learning curve</td><td>Rust required</td><td>JS-only possible</td></tr>
<tr>
<td>Documentation volume</td><td>Growing</td><td>Extensive</td></tr>
</tbody>
</table>
</div><p>Electron wins in sheer ecosystem size.<br />Tauri wins in modern architecture and efficiency.</p>
<hr />
<h2 id="heading-when-should-you-choose-tauri">When Should You Choose Tauri?</h2>
<p>Tauri shines when:</p>
<ul>
<li><p>✅ App size matters</p>
</li>
<li><p>✅ Performance matters</p>
</li>
<li><p>✅ Security matters</p>
</li>
<li><p>✅ You’re okay using Rust</p>
</li>
<li><p>✅ You want desktop + mobile from one codebase</p>
</li>
</ul>
<p>Perfect for:</p>
<ul>
<li><p>Developer tools</p>
</li>
<li><p>System utilities</p>
</li>
<li><p>Media processing apps</p>
</li>
<li><p>Privacy/security-focused software</p>
</li>
</ul>
<hr />
<h2 id="heading-when-should-you-choose-electron">When Should You Choose Electron?</h2>
<p>Electron is better when:</p>
<ul>
<li><p>✅ Your team is JS-only</p>
</li>
<li><p>✅ You depend on Node-native libraries</p>
</li>
<li><p>✅ You want maximum community support</p>
</li>
<li><p>✅ Rapid prototyping is the goal</p>
</li>
<li><p>✅ You need guaranteed browser consistency</p>
</li>
</ul>
<p>Perfect for:</p>
<ul>
<li><p>Internal tools</p>
</li>
<li><p>SaaS desktop wrappers</p>
</li>
<li><p>Startup MVPs</p>
</li>
<li><p>Teams without Rust experience</p>
</li>
</ul>
<hr />
<h2 id="heading-migration-electron-tauri">Migration: Electron → Tauri</h2>
<p>Good news: <strong>your frontend can stay the same</strong>.</p>
<p>You mainly replace:</p>
<ul>
<li><p>Electron main process → Rust commands</p>
</li>
<li><p>Preload bridge → <code>invoke()</code> calls</p>
</li>
</ul>
<p>Example:</p>
<p><strong>Electron</strong></p>
<pre><code class="lang-javascript">ipcMain.handle(<span class="hljs-string">'process-data'</span>, <span class="hljs-keyword">async</span> (_, data) =&gt; {
  <span class="hljs-keyword">return</span> heavyProcessing(data);
});
</code></pre>
<p><strong>Tauri</strong></p>
<pre><code class="lang-javascript">#[tauri::command]
<span class="hljs-keyword">async</span> fn process_data(data: <span class="hljs-built_in">String</span>) -&gt; Result&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">String</span>&gt; {
    Ok(heavy_processing(&amp;data))
}
</code></pre>
<p>Frontend change:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> invoke(<span class="hljs-string">'process_data'</span>, { data });
</code></pre>
<p>That’s the core migration pattern.</p>
<hr />
<h2 id="heading-final-verdict">Final Verdict</h2>
<p>There is no universal winner — only the right tool for your constraints.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>If you want…</td><td>Choose</td></tr>
</thead>
<tbody>
<tr>
<td>Tiny, fast, efficient apps</td><td><strong>Tauri</strong></td></tr>
<tr>
<td>Maximum ecosystem &amp; JS-only</td><td><strong>Electron</strong></td></tr>
<tr>
<td>Strong built-in security model</td><td><strong>Tauri</strong></td></tr>
<tr>
<td>Easiest onboarding for web devs</td><td><strong>Electron</strong></td></tr>
</tbody>
</table>
</div><p><strong>Tauri feels like the future of lightweight desktop apps.</strong><br /><strong>Electron remains the king of ecosystem and maturity.</strong></p>
<p>And the best part? Since both use web frontends, <strong>you’re never completely locked in</strong>.</p>
]]></content:encoded></item><item><title><![CDATA[Production-Grade Prompting with the Anthropic API]]></title><description><![CDATA[Concepts, Parameters, Streaming, Caching, and a Real Enterprise Example
Prompting in Anthropic (Claude) is not just about writing good instructions.In production systems, prompts behave more like specifications than conversations.
This article explai...]]></description><link>https://blog.nishikanta.in/prompting-with-the-anthropic-api</link><guid isPermaLink="true">https://blog.nishikanta.in/prompting-with-the-anthropic-api</guid><category><![CDATA[#anthropic]]></category><category><![CDATA[claude.ai]]></category><category><![CDATA[AI]]></category><category><![CDATA[js]]></category><dc:creator><![CDATA[Nishikanta Ray]]></dc:creator><pubDate>Sat, 17 Jan 2026 16:39:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1768667425256/55cd7ddb-9aad-4ba1-8a00-4b83312e84e0.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-concepts-parameters-streaming-caching-and-a-real-enterprise-example">Concepts, Parameters, Streaming, Caching, and a Real Enterprise Example</h2>
<p>Prompting in Anthropic (Claude) is not just about writing good instructions.<br />In production systems, prompts behave more like <strong>specifications</strong> than conversations.</p>
<p>This article explains <strong>how prompting actually works in Anthropic</strong>, how to design <strong>reliable and compliant prompts</strong>, and how to use <strong>JavaScript</strong> to build real systems—ending with a <strong>production-grade sentiment analysis prompt</strong>.</p>
<hr />
<h2 id="heading-1-how-anthropic-prompting-really-works">1. How Anthropic Prompting Really Works</h2>
<p>Anthropic uses a <strong>message-based API</strong>.<br />Each request contains the <strong>entire conversation state</strong>.</p>
<p>Claude does <strong>not</strong> remember anything between API calls.</p>
<h3 id="heading-mental-model">Mental model</h3>
<pre><code class="lang-javascript">You → send full conversation
Claude → continues <span class="hljs-keyword">from</span> that context
</code></pre>
<p>Every request must include:</p>
<ul>
<li><p>Who the assistant is</p>
</li>
<li><p>What rules apply</p>
</li>
<li><p>What the user just said</p>
</li>
<li><p>Any images or documents</p>
</li>
</ul>
<hr />
<h2 id="heading-2-messages-and-roles-core-concept">2. Messages and Roles (Core Concept)</h2>
<p>Anthropic uses three roles:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Role</td><td>Purpose</td></tr>
</thead>
<tbody>
<tr>
<td><code>system</code></td><td>Hard rules, behavior, constraints</td></tr>
<tr>
<td><code>user</code></td><td>Input data or tasks</td></tr>
<tr>
<td><code>assistant</code></td><td>Previous model replies (conversation priming)</td></tr>
</tbody>
</table>
</div><h3 id="heading-example">Example</h3>
<pre><code class="lang-javascript">messages: [
  { <span class="hljs-attr">role</span>: <span class="hljs-string">"user"</span>, <span class="hljs-attr">content</span>: <span class="hljs-string">"Hello! Only speak Spanish."</span> },
  { <span class="hljs-attr">role</span>: <span class="hljs-string">"assistant"</span>, <span class="hljs-attr">content</span>: <span class="hljs-string">"Hola!"</span> },
  { <span class="hljs-attr">role</span>: <span class="hljs-string">"user"</span>, <span class="hljs-attr">content</span>: <span class="hljs-string">"How are you?"</span> }
]
</code></pre>
<p>Claude will answer <strong>in Spanish</strong> because:</p>
<ul>
<li><p>Conversation history establishes the rule</p>
</li>
<li><p>Claude prioritizes consistency</p>
</li>
</ul>
<h3 id="heading-production-rule">Production rule</h3>
<blockquote>
<p><strong>Put non-negotiable rules in</strong> <code>system</code>, not in conversation history.</p>
</blockquote>
<hr />
<h2 id="heading-3-system-vs-user-instructions-why-it-matters">3. System vs User Instructions (Why It Matters)</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Instruction Type</td><td>Reliability</td></tr>
</thead>
<tbody>
<tr>
<td>System</td><td>Strong, persistent</td></tr>
<tr>
<td>User</td><td>Soft, overrideable</td></tr>
<tr>
<td>Assistant</td><td>Priming only</td></tr>
</tbody>
</table>
</div><h3 id="heading-production-safe-example">Production-safe example</h3>
<pre><code class="lang-javascript">system: <span class="hljs-string">"You must respond only in Spanish."</span>
</code></pre>
<p>This survives:</p>
<ul>
<li><p>Long conversations</p>
</li>
<li><p>Truncated history</p>
</li>
<li><p>User attempts to override</p>
</li>
</ul>
<hr />
<h2 id="heading-4-temperature-controlling-randomness">4. Temperature: Controlling Randomness</h2>
<p><strong>Temperature controls how predictable Claude’s word choices are.</strong></p>
<p>It does <strong>not</strong> control intelligence.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Temperature</td><td>Behavior</td><td>Use case</td></tr>
</thead>
<tbody>
<tr>
<td>0.0–0.2</td><td>Deterministic</td><td>JSON, analytics</td></tr>
<tr>
<td>0.3–0.5</td><td>Controlled</td><td>Summaries</td></tr>
<tr>
<td>0.6–0.8</td><td>Creative</td><td>Writing</td></tr>
<tr>
<td>0.9+</td><td>Unstable</td><td>Rarely useful</td></tr>
</tbody>
</table>
</div><h3 id="heading-example-1">Example</h3>
<pre><code class="lang-javascript">temperature: <span class="hljs-number">0.0</span>
</code></pre>
<p>Use this when:</p>
<ul>
<li><p>Output must be parsed</p>
</li>
<li><p>Compliance matters</p>
</li>
<li><p>Decisions depend on correctness</p>
</li>
</ul>
<hr />
<h2 id="heading-5-stopsequences-when-the-model-must-stop">5. stop_sequences: When the Model Must Stop</h2>
<p><code>stop_sequences</code> tells Claude <strong>when to stop generating</strong>.</p>
<pre><code class="lang-javascript">stop_sequences: [<span class="hljs-string">"\nUser:"</span>]
</code></pre>
<p>Claude stops <strong>immediately</strong> when it reaches that string.</p>
<h3 id="heading-common-uses">Common uses</h3>
<ul>
<li><p>Prevent role leakage</p>
</li>
<li><p>End JSON cleanly</p>
</li>
<li><p>Stop agent loops</p>
</li>
</ul>
<h3 id="heading-key-rule">Key rule</h3>
<blockquote>
<p>Stop sequences are <strong>guards</strong>, not fixes for bad prompts.</p>
</blockquote>
<hr />
<h2 id="heading-6-streaming-responses-real-time-output">6. Streaming Responses (Real-Time Output)</h2>
<p>Streaming lets Claude send text <strong>incrementally</strong>.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">with</span> client.messages.stream(...) <span class="hljs-keyword">as</span> stream:
  <span class="hljs-keyword">for</span> text <span class="hljs-keyword">in</span> stream.text_stream:
    print(text)
</code></pre>
<h3 id="heading-why-streaming-exists">Why streaming exists</h3>
<ul>
<li><p>Faster perceived response</p>
</li>
<li><p>Better UX</p>
</li>
<li><p>Long outputs</p>
</li>
</ul>
<h3 id="heading-when-not-to-stream">When NOT to stream</h3>
<ul>
<li><p>JSON APIs</p>
</li>
<li><p>Tool calls</p>
</li>
<li><p>Strict schema validation</p>
</li>
</ul>
<h3 id="heading-production-pattern">Production pattern</h3>
<pre><code class="lang-javascript"><span class="hljs-keyword">let</span> buffer = <span class="hljs-string">""</span>;

<span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> chunk <span class="hljs-keyword">of</span> stream.text_stream) {
  buffer += chunk;
  process.stdout.write(chunk);
}

<span class="hljs-comment">// buffer now contains the full response</span>
</code></pre>
<hr />
<h2 id="heading-7-multimodal-prompting-images-text">7. Multimodal Prompting (Images + Text)</h2>
<p>Anthropic allows <strong>image + text together</strong>.</p>
<pre><code class="lang-javascript">{
  <span class="hljs-attr">role</span>: <span class="hljs-string">"user"</span>,
  <span class="hljs-attr">content</span>: [
    { <span class="hljs-attr">type</span>: <span class="hljs-string">"image"</span>, <span class="hljs-attr">source</span>: {...} },
    { <span class="hljs-attr">type</span>: <span class="hljs-string">"text"</span>, <span class="hljs-attr">text</span>: <span class="hljs-string">"Count the containers."</span> }
  ]
}
</code></pre>
<h3 id="heading-rules">Rules</h3>
<ul>
<li><p><code>content</code> must be an array</p>
</li>
<li><p>Image first, instruction second</p>
</li>
<li><p>Images only in <code>user</code> role</p>
</li>
</ul>
<hr />
<h2 id="heading-8-prompt-caching-and-cachecontrol">8. Prompt Caching and <code>cache_control</code></h2>
<p>Anthropic optimizes performance by caching repeated prompt prefixes.</p>
<p>This is good for:</p>
<ul>
<li><p>System prompts</p>
</li>
<li><p>Templates</p>
</li>
<li><p>Policies</p>
</li>
</ul>
<p>But dangerous for:</p>
<ul>
<li><p>User data</p>
</li>
<li><p>Transcripts</p>
</li>
<li><p>Images</p>
</li>
<li><p>PII</p>
</li>
</ul>
<hr />
<h2 id="heading-9-cachecontrol-type-ephemeral">9. <code>cache_control: { type: "ephemeral" }</code></h2>
<pre><code class="lang-javascript"><span class="hljs-string">"cache_control"</span>: { <span class="hljs-string">"type"</span>: <span class="hljs-string">"ephemeral"</span> }
</code></pre>
<p>This means:</p>
<blockquote>
<p><strong>Use this content once. Do not cache. Do not reuse. Do not persist.</strong></p>
</blockquote>
<h3 id="heading-when-to-use-it">When to use it</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Data</td><td>Ephemeral</td></tr>
</thead>
<tbody>
<tr>
<td>User input</td><td>✅</td></tr>
<tr>
<td>Call transcripts</td><td>✅</td></tr>
<tr>
<td>Images</td><td>✅</td></tr>
<tr>
<td>Customer feedback</td><td>✅</td></tr>
</tbody>
</table>
</div><h3 id="heading-why-it-matters">Why it matters</h3>
<ul>
<li><p>Prevents data reuse</p>
</li>
<li><p>Improves compliance posture</p>
</li>
<li><p>Reduces audit risk</p>
</li>
</ul>
<hr />
<h2 id="heading-10-javascript-example-safe-prompt-with-ephemeral-data">10. JavaScript Example: Safe Prompt with Ephemeral Data</h2>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> Anthropic <span class="hljs-keyword">from</span> <span class="hljs-string">"@anthropic-ai/sdk"</span>;

<span class="hljs-keyword">const</span> client = <span class="hljs-keyword">new</span> Anthropic({
  <span class="hljs-attr">apiKey</span>: process.env.ANTHROPIC_API_KEY
});

<span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> client.messages.create({
  <span class="hljs-attr">model</span>: <span class="hljs-string">"claude-3-5-sonnet-20240620"</span>,
  <span class="hljs-attr">max_tokens</span>: <span class="hljs-number">200</span>,
  <span class="hljs-attr">temperature</span>: <span class="hljs-number">0.0</span>,
  <span class="hljs-attr">system</span>: <span class="hljs-string">"You are a deterministic analysis engine."</span>,
  <span class="hljs-attr">messages</span>: [
    {
      <span class="hljs-attr">role</span>: <span class="hljs-string">"user"</span>,
      <span class="hljs-attr">content</span>: [
        {
          <span class="hljs-attr">type</span>: <span class="hljs-string">"text"</span>,
          <span class="hljs-attr">text</span>: <span class="hljs-string">"The service was slow and frustrating."</span>,
          <span class="hljs-attr">cache_control</span>: { <span class="hljs-attr">type</span>: <span class="hljs-string">"ephemeral"</span> }
        }
      ]
    }
  ]
});

<span class="hljs-built_in">console</span>.log(response.content[<span class="hljs-number">0</span>].text);
</code></pre>
<hr />
<h2 id="heading-11-what-makes-a-prompt-production-grade">11. What Makes a Prompt Production-Grade</h2>
<p>A production prompt has:</p>
<ol>
<li><p>Clear role definition</p>
</li>
<li><p>Explicit constraints</p>
</li>
<li><p>Defined output format</p>
</li>
<li><p>Fail-closed behavior</p>
</li>
<li><p>Low temperature</p>
</li>
<li><p>No exposed reasoning</p>
</li>
<li><p>Safe handling of sensitive data</p>
</li>
</ol>
<blockquote>
<p><strong>Good prompts are specifications, not conversations.</strong></p>
</blockquote>
<hr />
<h2 id="heading-12-production-grade-sentiment-analysis-prompt">12. Production-Grade Sentiment Analysis Prompt</h2>
<p>This prompt follows <strong>all best practices discussed above</strong>.</p>
<h3 id="heading-prompt">Prompt</h3>
<pre><code class="lang-javascript">Analyze the sentiment <span class="hljs-keyword">of</span> the following customer message.

Rules:
- Classify sentiment <span class="hljs-keyword">as</span> POSITIVE, NEGATIVE, or NEUTRAL
- Do not include personal data
- Do not infer intent beyond the text
- Limit text fields to <span class="hljs-number">100</span> characters

<span class="hljs-attr">Output</span>:
Return ONLY valid <span class="hljs-built_in">JSON</span>.
No explanations.

Schema:
{
  <span class="hljs-string">"sentiment"</span>: <span class="hljs-string">"POSITIVE | NEGATIVE | NEUTRAL"</span>,
  <span class="hljs-string">"confidence"</span>: <span class="hljs-number">0.0</span><span class="hljs-number">-1.0</span>,
  <span class="hljs-string">"summary"</span>: <span class="hljs-string">"string"</span>
}

If sentiment cannot be determined, <span class="hljs-attr">return</span>:
{
  <span class="hljs-string">"sentiment"</span>: <span class="hljs-string">"NEUTRAL"</span>,
  <span class="hljs-string">"confidence"</span>: <span class="hljs-number">0.0</span>,
  <span class="hljs-string">"summary"</span>: <span class="hljs-string">"Insufficient data"</span>
}
</code></pre>
<hr />
<h2 id="heading-13-sentiment-analysis-full-javascript-example">13. Sentiment Analysis: Full JavaScript Example</h2>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> client.messages.create({
  <span class="hljs-attr">model</span>: <span class="hljs-string">"claude-3-5-sonnet-20240620"</span>,
  <span class="hljs-attr">max_tokens</span>: <span class="hljs-number">150</span>,
  <span class="hljs-attr">temperature</span>: <span class="hljs-number">0.0</span>,
  <span class="hljs-attr">system</span>: <span class="hljs-string">"You are a strict sentiment classification engine."</span>,
  <span class="hljs-attr">messages</span>: [
    {
      <span class="hljs-attr">role</span>: <span class="hljs-string">"user"</span>,
      <span class="hljs-attr">content</span>: [
        {
          <span class="hljs-attr">type</span>: <span class="hljs-string">"text"</span>,
          <span class="hljs-attr">text</span>: <span class="hljs-string">"Support was slow and unhelpful."</span>,
          <span class="hljs-attr">cache_control</span>: { <span class="hljs-attr">type</span>: <span class="hljs-string">"ephemeral"</span> }
        }
      ]
    }
  ]
});

<span class="hljs-built_in">console</span>.log(response.content[<span class="hljs-number">0</span>].text);
</code></pre>
<hr />
<h2 id="heading-14-final-takeaway">14. Final Takeaway</h2>
<blockquote>
<p><strong>Anthropic prompting works best when you treat prompts like contracts: explicit, constrained, deterministic, and safe.</strong></p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Building an AI-Powered Portfolio Assistant with MCP]]></title><description><![CDATA[How I Built a Smart Assistant for the Renderer Framework Using MCP
A comprehensive guide to creating your first MCP server with real-world examples

📖 Table of Contents

Introduction

What is Model Context Protocol?

The Problem We're Solving

Archi...]]></description><link>https://blog.nishikanta.in/building-an-ai-powered-portfolio-assistant-with-mcp</link><guid isPermaLink="true">https://blog.nishikanta.in/building-an-ai-powered-portfolio-assistant-with-mcp</guid><category><![CDATA[#MCP #AI #DeveloperTools #Anthropic #Claude #OpenSource #TypeScript #Portfolio]]></category><dc:creator><![CDATA[Nishikanta Ray]]></dc:creator><pubDate>Mon, 29 Dec 2025 18:34:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1767171586910/6e3689cf-579f-4f25-8ea4-8292a33a9b55.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>How I Built a Smart Assistant for the Renderer Framework Using MCP</strong></p>
<p><em>A comprehensive guide to creating your first MCP server with real-world examples</em></p>
<hr />
<h2 id="heading-table-of-contents"><strong>📖 Table of Contents</strong></h2>
<ol>
<li><p>Introduction</p>
</li>
<li><p>What is Model Context Protocol?</p>
</li>
<li><p>The Problem We're Solving</p>
</li>
<li><p>Architecture Overview</p>
</li>
<li><p>Implementation Guide</p>
</li>
<li><p>Testing with Claude Desktop</p>
</li>
<li><p>Testing with MCP Inspector</p>
</li>
<li><p>Real-World Use Cases</p>
</li>
<li><p>Lessons Learned</p>
</li>
<li><p>What's Next</p>
</li>
</ol>
<hr />
<h2 id="heading-introduction"><strong>Introduction</strong></h2>
<p>Creating a portfolio website shouldn't be hard. But as the creator of <a target="_blank" href="https://github.com/NishikantaRay/renderer"><strong>Renderer</strong></a>, a TOML-driven portfolio framework, I noticed users struggling with:</p>
<ul>
<li><p>Understanding TOML configuration syntax</p>
</li>
<li><p>Finding relevant documentation</p>
</li>
<li><p>Validating their configurations</p>
</li>
<li><p>Getting started quickly</p>
</li>
</ul>
<p>The solution? An <strong>AI assistant that truly understands the framework</strong>. Not a generic chatbot, but a specialized tool that knows every configuration option, can validate your code, and guide you from zero to deployed.</p>
<p>Enter the <strong>Model Context Protocol (MCP)</strong> — a game-changing standard for connecting AI assistants with external tools and data sources.</p>
<p>In this post, I'll walk you through building <strong>Renderer MCP Server</strong>, a complete AI assistant that makes portfolio creation effortless. You'll learn:</p>
<ul>
<li><p>How MCP works and why it's revolutionary</p>
</li>
<li><p>How to build your own MCP server from scratch</p>
</li>
<li><p>How to integrate it with Claude Desktop</p>
</li>
<li><p>Real-world testing and deployment strategies</p>
</li>
</ul>
<p><strong>GitHub Repository:</strong> renderer-mcp-server</p>
<p>Let's dive in! 🚀</p>
<hr />
<h2 id="heading-what-is-model-context-protocol"><strong>What is Model Context Protocol?</strong></h2>
<h3 id="heading-the-new-standard-for-ai-interoperability"><strong>The New Standard for AI Interoperability</strong></h3>
<p><strong>Model Context Protocol (MCP)</strong> is an open standard developed by Anthropic that enables AI assistants to securely access external tools, data sources, and services.</p>
<p>Think of it as <strong>"USB for AI"</strong> — just like USB standardized how devices connect to computers, MCP standardizes how AI connects to the world.</p>
<h3 id="heading-why-mcp-matters"><strong>Why MCP Matters</strong></h3>
<p><strong>Before MCP:</strong></p>
<pre><code class="lang-javascript">❌ Each AI tool had custom integration code
❌ Switching AI providers meant rebuilding everything
❌ No standard way to share tools across platforms
❌ Security and permissions were ad-hoc
</code></pre>
<p><strong>After MCP:</strong></p>
<pre><code class="lang-javascript">✅ Write once, use <span class="hljs-keyword">with</span> any MCP-compatible AI
✅ Standard protocol <span class="hljs-keyword">for</span> tool communication
✅ Built-<span class="hljs-keyword">in</span> security and permission model
✅ Growing ecosystem <span class="hljs-keyword">of</span> shared tools
</code></pre>
<h3 id="heading-how-mcp-works"><strong>How MCP Works</strong></h3>
<pre><code class="lang-javascript">┌─────────────────────────────────────────────────┐
│           AI Assistant (Claude, etc.)            │
│              <span class="hljs-string">"MCP Client"</span>                        │
└────────────────────┬────────────────────────────┘
                     │ MCP Protocol
                     │ (<span class="hljs-built_in">JSON</span>-RPC over stdio)
                     ▼
┌─────────────────────────────────────────────────┐
│              Your MCP Server                     │
│         (Custom Tools &amp; Logic)                   │
└────────────────────┬────────────────────────────┘
                     │
        ┌────────────┴────────────┐
        ▼                         ▼
    GitHub API              File System
    Databases              External APIs
    Your Services          Anything!
</code></pre>
<p><strong>Key Components:</strong></p>
<ol>
<li><p><strong>MCP Client</strong> - The AI assistant (Claude Desktop, VS Code, etc.)</p>
</li>
<li><p><strong>MCP Server</strong> - Your custom tool server</p>
</li>
<li><p><strong>Tools</strong> - Individual capabilities you expose</p>
</li>
<li><p><strong>Resources</strong> - Data sources the AI can access</p>
</li>
<li><p><strong>Prompts</strong> - Reusable prompt templates</p>
</li>
</ol>
<hr />
<h2 id="heading-the-problem-were-solving"><strong>The Problem We're Solving</strong></h2>
<h3 id="heading-user-pain-points"><strong>User Pain Points</strong></h3>
<p>When I launched Renderer, I saw users struggling with:</p>
<p><strong>1. Steep Learning Curve</strong></p>
<pre><code class="lang-javascript"><span class="hljs-string">"How do I configure the home page?"</span>
<span class="hljs-string">"What's the syntax for TOML arrays?"</span>
<span class="hljs-string">"Where do I put my social media links?"</span>
</code></pre>
<p><strong>2. Documentation Discovery</strong></p>
<pre><code class="lang-javascript"><span class="hljs-string">"I know this feature exists, but where's the doc?"</span>
<span class="hljs-string">"How do I search across all documentation?"</span>
<span class="hljs-string">"What are all the available configuration options?"</span>
</code></pre>
<p><strong>3. Configuration Validation</strong></p>
<pre><code class="lang-javascript"><span class="hljs-string">"Why isn't my portfolio loading?"</span>
<span class="hljs-string">"Is this TOML syntax correct?"</span>
<span class="hljs-string">"What fields am I missing?"</span>
</code></pre>
<p><strong>4. Time-Consuming Setup</strong></p>
<pre><code class="lang-javascript"><span class="hljs-string">"It took me 2 hours just to get started"</span>
<span class="hljs-string">"I had to read all the docs first"</span>
<span class="hljs-string">"Too much trial and error"</span>
</code></pre>
<h3 id="heading-the-vision"><strong>The Vision</strong></h3>
<p><strong>What if users could just <em>ask</em> for what they need?</strong></p>
<pre><code class="lang-javascript">User: <span class="hljs-string">"Create a portfolio for me with projects and resume"</span>
<span class="hljs-attr">AI</span>: *Generates complete, valid configuration*

User: <span class="hljs-string">"Is my home.toml correct?"</span>
<span class="hljs-attr">AI</span>: *Validates and suggests improvements*

User: <span class="hljs-string">"How do I add dark mode?"</span>
<span class="hljs-attr">AI</span>: *Shows exact configuration <span class="hljs-keyword">with</span> examples*
</code></pre>
<p>This is what Renderer MCP Server enables.</p>
<hr />
<h2 id="heading-architecture-overview"><strong>Architecture Overview</strong></h2>
<h3 id="heading-system-design"><strong>System Design</strong></h3>
<p>Our MCP server follows a clean, modular architecture:</p>
<pre><code class="lang-javascript">renderer-mcp-server/
├── src/
│   ├── index.ts              # Main server &amp; routing
│   ├── types.ts              # TypeScript definitions
│   ├── constants.ts          # Tool definitions
│   ├── github.ts             # GitHub API client
│   └── tools/                # Modular tool implementations
│       ├── validator.ts      # TOML validation
│       ├── template.ts       # Template generation
│       ├── examples.ts       # Config examples
│       ├── features.ts       # Feature search
│       └── guides.ts         # Setup guides
├── build/                    # Compiled JavaScript
└── package.json
</code></pre>
<h3 id="heading-technology-stack"><strong>Technology Stack</strong></h3>
<p><strong>Core:</strong></p>
<ul>
<li><p><strong>Runtime:</strong> Node.js 18+</p>
</li>
<li><p><strong>Language:</strong> TypeScript 5.3+</p>
</li>
<li><p><strong>Protocol:</strong> MCP via <code>@modelcontextprotocol/sdk</code></p>
</li>
<li><p><strong>Transport:</strong> stdio (standard input/output)</p>
</li>
</ul>
<p><strong>Integrations:</strong></p>
<ul>
<li><p><strong>GitHub API:</strong> <code>@octokit/rest</code> for repository access</p>
</li>
<li><p><strong>TOML Parser:</strong> <code>toml</code> for configuration validation</p>
</li>
<li><p><strong>Markdown:</strong> <code>marked</code> for content processing</p>
</li>
</ul>
<h3 id="heading-why-this-stack"><strong>Why This Stack?</strong></h3>
<ol>
<li><p><strong>TypeScript</strong> - Type safety prevents runtime errors</p>
</li>
<li><p><strong>Modular Design</strong> - Each tool is independent and testable</p>
</li>
<li><p><strong>stdio Transport</strong> - Simple, universal, no networking</p>
</li>
<li><p><strong>GitHub Integration</strong> - Direct access to documentation and examples</p>
</li>
</ol>
<hr />
<h2 id="heading-implementation-guide"><strong>Implementation Guide</strong></h2>
<p>Let's build the MCP server step by step!</p>
<h3 id="heading-step-1-project-setup"><strong>Step 1: Project Setup</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Create project structure</span>
mkdir renderer-mcp-server
<span class="hljs-built_in">cd</span> renderer-mcp-server
npm init -y

<span class="hljs-comment"># Install dependencies</span>
npm install @modelcontextprotocol/sdk @octokit/rest toml marked

<span class="hljs-comment"># Install dev dependencies</span>
npm install -D typescript @types/node

<span class="hljs-comment"># Initialize TypeScript</span>
npx tsc --init
</code></pre>
<p><strong>tsconfig.json:</strong></p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"compilerOptions"</span>: {
    <span class="hljs-attr">"target"</span>: <span class="hljs-string">"ES2022"</span>,
    <span class="hljs-attr">"module"</span>: <span class="hljs-string">"Node16"</span>,
    <span class="hljs-attr">"moduleResolution"</span>: <span class="hljs-string">"Node16"</span>,
    <span class="hljs-attr">"outDir"</span>: <span class="hljs-string">"./build"</span>,
    <span class="hljs-attr">"rootDir"</span>: <span class="hljs-string">"./src"</span>,
    <span class="hljs-attr">"strict"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"esModuleInterop"</span>: <span class="hljs-literal">true</span>
  }
}
</code></pre>
<h3 id="heading-step-2-define-types"><strong>Step 2: Define Types</strong></h3>
<p><strong>src/types.ts:</strong></p>
<pre><code class="lang-typescript"><span class="hljs-comment">/**
 * Type definitions for our MCP server
 */</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> RendererConfig {
    owner?: <span class="hljs-built_in">string</span>;
    repo?: <span class="hljs-built_in">string</span>;
    branch?: <span class="hljs-built_in">string</span>;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> TemplateOptions {
    name: <span class="hljs-built_in">string</span>;
    email?: <span class="hljs-built_in">string</span>;
    github?: <span class="hljs-built_in">string</span>;
    linkedin?: <span class="hljs-built_in">string</span>;
    twitter?: <span class="hljs-built_in">string</span>;
    includeProjects?: <span class="hljs-built_in">boolean</span>;
    includeBlog?: <span class="hljs-built_in">boolean</span>;
    includeResume?: <span class="hljs-built_in">boolean</span>;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> ToolArguments {
    query?: <span class="hljs-built_in">string</span>;
    path?: <span class="hljs-built_in">string</span>;
    config_content?: <span class="hljs-built_in">string</span>;
    config_type?: <span class="hljs-built_in">string</span>;
    feature?: <span class="hljs-built_in">string</span>;
    level?: <span class="hljs-built_in">string</span>;
    [key: <span class="hljs-built_in">string</span>]: unknown;
}
</code></pre>
<h3 id="heading-step-3-create-the-main-server"><strong>Step 3: Create the Main Server</strong></h3>
<p><strong>src/index.ts:</strong></p>
<pre><code class="lang-typescript"><span class="hljs-meta">#!/usr/bin/env node</span>

<span class="hljs-keyword">import</span> { Server } <span class="hljs-keyword">from</span> <span class="hljs-string">"@modelcontextprotocol/sdk/server/index.js"</span>;
<span class="hljs-keyword">import</span> { StdioServerTransport } <span class="hljs-keyword">from</span> <span class="hljs-string">"@modelcontextprotocol/sdk/server/stdio.js"</span>;
<span class="hljs-keyword">import</span> { 
    CallToolRequestSchema, 
    ListToolsRequestSchema 
} <span class="hljs-keyword">from</span> <span class="hljs-string">"@modelcontextprotocol/sdk/types.js"</span>;

<span class="hljs-comment">// Initialize MCP Server</span>
<span class="hljs-keyword">const</span> server = <span class="hljs-keyword">new</span> Server(
    {
        name: <span class="hljs-string">"renderer-mcp-server"</span>,
        version: <span class="hljs-string">"1.0.0"</span>,
    },
    {
        capabilities: {
            tools: {},
        },
    }
);

<span class="hljs-comment">// Tool definitions</span>
<span class="hljs-keyword">const</span> TOOLS = [
    {
        name: <span class="hljs-string">"get_config_example"</span>,
        description: <span class="hljs-string">"Get example TOML configuration"</span>,
        inputSchema: {
            <span class="hljs-keyword">type</span>: <span class="hljs-string">"object"</span>,
            properties: {
                config_type: {
                    <span class="hljs-keyword">type</span>: <span class="hljs-string">"string"</span>,
                    <span class="hljs-built_in">enum</span>: [<span class="hljs-string">"home"</span>, <span class="hljs-string">"projects"</span>, <span class="hljs-string">"blog"</span>, <span class="hljs-string">"resume"</span>, <span class="hljs-string">"social"</span>],
                    description: <span class="hljs-string">"Type of configuration needed"</span>
                }
            },
            required: [<span class="hljs-string">"config_type"</span>]
        }
    },
    <span class="hljs-comment">// ... more tools</span>
];

<span class="hljs-comment">// Handle tool listing</span>
server.setRequestHandler(ListToolsRequestSchema, <span class="hljs-keyword">async</span> () =&gt; ({
    tools: TOOLS,
}));

<span class="hljs-comment">// Handle tool execution</span>
server.setRequestHandler(CallToolRequestSchema, <span class="hljs-keyword">async</span> (request) =&gt; {
    <span class="hljs-keyword">const</span> { name, <span class="hljs-built_in">arguments</span>: args } = request.params;

    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">switch</span> (name) {
            <span class="hljs-keyword">case</span> <span class="hljs-string">"get_config_example"</span>:
                <span class="hljs-keyword">return</span> getConfigExample(args.config_type);
            <span class="hljs-comment">// ... more cases</span>
            <span class="hljs-keyword">default</span>:
                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`Unknown tool: <span class="hljs-subst">${name}</span>`</span>);
        }
    } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-keyword">return</span> {
            content: [{
                <span class="hljs-keyword">type</span>: <span class="hljs-string">"text"</span>,
                text: <span class="hljs-string">`Error: <span class="hljs-subst">${error.message}</span>`</span>
            }],
            isError: <span class="hljs-literal">true</span>
        };
    }
});

<span class="hljs-comment">// Start server</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">main</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> transport = <span class="hljs-keyword">new</span> StdioServerTransport();
    <span class="hljs-keyword">await</span> server.connect(transport);
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Renderer MCP Server running"</span>);
}

main().catch(<span class="hljs-built_in">console</span>.error);
</code></pre>
<h3 id="heading-step-4-implement-tools"><strong>Step 4: Implement Tools</strong></h3>
<h4 id="heading-tool-1-configuration-examples"><strong>Tool 1: Configuration Examples</strong></h4>
<p><strong>src/tools/examples.ts:</strong></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> CONFIG_EXAMPLES = {
    home: <span class="hljs-string">`# Home Page Configuration
[profile]
name = "Your Name"
tagline = "Your Role"
email = "your@email.com"

[hero]
title = "Hi, I'm Your Name 👋"
subtitle = "Building amazing things"
show_cta = true

[features]
show_about = true
show_skills = true
show_social = true

[theme]
mode = "auto"  # auto, light, dark
accent_color = "#0066cc"`</span>,
    <span class="hljs-comment">// ... more examples</span>
};

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getConfigExample</span>(<span class="hljs-params">configType: <span class="hljs-built_in">string</span></span>) </span>{
    <span class="hljs-keyword">const</span> example = CONFIG_EXAMPLES[configType];
    <span class="hljs-keyword">if</span> (!example) {
        <span class="hljs-keyword">return</span> {
            content: [{
                <span class="hljs-keyword">type</span>: <span class="hljs-string">"text"</span> <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span>,
                text: <span class="hljs-string">`Unknown config type: <span class="hljs-subst">${configType}</span>`</span>
            }],
            isError: <span class="hljs-literal">true</span>
        };
    }

    <span class="hljs-keyword">return</span> {
        content: [{
            <span class="hljs-keyword">type</span>: <span class="hljs-string">"text"</span> <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span>,
            text: <span class="hljs-string">`# <span class="hljs-subst">${configType}</span> Configuration Example\n\n\`\`\`toml\n<span class="hljs-subst">${example}</span>\n\`\`\``</span>
        }]
    };
}
</code></pre>
<h4 id="heading-tool-2-toml-validator"><strong>Tool 2: TOML Validator</strong></h4>
<p><strong>src/tools/validator.ts:</strong></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { parse <span class="hljs-keyword">as</span> parseToml } <span class="hljs-keyword">from</span> <span class="hljs-string">"toml"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">validateTomlConfig</span>(<span class="hljs-params">
    configContent: <span class="hljs-built_in">string</span>, 
    configType: <span class="hljs-built_in">string</span>
</span>) </span>{
    <span class="hljs-keyword">try</span> {
        <span class="hljs-comment">// Parse TOML</span>
        <span class="hljs-keyword">const</span> parsed = parseToml(configContent);

        <span class="hljs-comment">// Validate based on type</span>
        <span class="hljs-keyword">const</span> issues: <span class="hljs-built_in">string</span>[] = [];

        <span class="hljs-keyword">switch</span> (configType) {
            <span class="hljs-keyword">case</span> <span class="hljs-string">"home"</span>:
                <span class="hljs-keyword">if</span> (!parsed.profile) issues.push(<span class="hljs-string">"Missing 'profile' section"</span>);
                <span class="hljs-keyword">if</span> (!parsed.hero) issues.push(<span class="hljs-string">"Missing 'hero' section"</span>);
                <span class="hljs-keyword">break</span>;
            <span class="hljs-keyword">case</span> <span class="hljs-string">"projects"</span>:
                <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">Array</span>.isArray(parsed.projects)) {
                    issues.push(<span class="hljs-string">"Missing or invalid 'projects' array"</span>);
                }
                <span class="hljs-keyword">break</span>;
            <span class="hljs-comment">// ... more validations</span>
        }

        <span class="hljs-keyword">if</span> (issues.length === <span class="hljs-number">0</span>) {
            <span class="hljs-keyword">return</span> {
                content: [{
                    <span class="hljs-keyword">type</span>: <span class="hljs-string">"text"</span> <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span>,
                    text: <span class="hljs-string">`✅ Valid!\n\nParsed:\n\`\`\`json\n<span class="hljs-subst">${<span class="hljs-built_in">JSON</span>.stringify(parsed, <span class="hljs-literal">null</span>, <span class="hljs-number">2</span>)}</span>\n\`\`\``</span>
                }]
            };
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-keyword">return</span> {
                content: [{
                    <span class="hljs-keyword">type</span>: <span class="hljs-string">"text"</span> <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span>,
                    text: <span class="hljs-string">`⚠️ Issues:\n<span class="hljs-subst">${issues.map(i =&gt; <span class="hljs-string">`- <span class="hljs-subst">${i}</span>`</span>).join(<span class="hljs-string">'\n'</span>)}</span>`</span>
                }]
            };
        }
    } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-keyword">return</span> {
            content: [{
                <span class="hljs-keyword">type</span>: <span class="hljs-string">"text"</span> <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span>,
                text: <span class="hljs-string">`❌ TOML Syntax Error:\n<span class="hljs-subst">${error.message}</span>`</span>
            }],
            isError: <span class="hljs-literal">true</span>
        };
    }
}
</code></pre>
<h4 id="heading-tool-3-template-generator"><strong>Tool 3: Template Generator</strong></h4>
<p><strong>src/tools/template.ts:</strong></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">generateStarterTemplate</span>(<span class="hljs-params">options: TemplateOptions</span>) </span>{
    <span class="hljs-keyword">const</span> { name, email, github, includeProjects, includeBlog } = options;

    <span class="hljs-keyword">const</span> homeConfig = <span class="hljs-string">`[profile]
name = "<span class="hljs-subst">${name}</span>"
tagline = "Developer &amp; Creator"
<span class="hljs-subst">${email ? <span class="hljs-string">`email = "<span class="hljs-subst">${email}</span>"`</span> : <span class="hljs-string">'# email = "your@email.com"'</span>}</span>

[hero]
title = "Hi, I'm <span class="hljs-subst">${name.split(<span class="hljs-string">' '</span>)[<span class="hljs-number">0</span>]}</span> 👋"
subtitle = "Building amazing things"
show_cta = true
cta_text = "View My Work"
cta_link = "<span class="hljs-subst">${includeProjects ? <span class="hljs-string">'/projects.html'</span> : <span class="hljs-string">'#'</span>}</span>"`</span>;

    <span class="hljs-keyword">const</span> socialConfig = <span class="hljs-string">`[social]
<span class="hljs-subst">${github ? <span class="hljs-string">`github = "<span class="hljs-subst">${github}</span>"`</span> : <span class="hljs-string">'# github = "username"'</span>}</span>
<span class="hljs-subst">${email ? <span class="hljs-string">`email = "<span class="hljs-subst">${email}</span>"`</span> : <span class="hljs-string">'# email = "your@email.com"'</span>}</span>

[social.settings]
show_icons = true
open_in_new_tab = true`</span>;

    <span class="hljs-keyword">return</span> {
        content: [{
            <span class="hljs-keyword">type</span>: <span class="hljs-string">"text"</span> <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span>,
            text: <span class="hljs-string">`# Generated Configuration\n\n## home.toml\n\`\`\`toml\n<span class="hljs-subst">${homeConfig}</span>\n\`\`\`\n\n## social.toml\n\`\`\`toml\n<span class="hljs-subst">${socialConfig}</span>\n\`\`\``</span>
        }]
    };
}
</code></pre>
<h4 id="heading-tool-4-github-integration"><strong>Tool 4: GitHub Integration</strong></h4>
<p><strong>src/github.ts:</strong></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Octokit } <span class="hljs-keyword">from</span> <span class="hljs-string">"@octokit/rest"</span>;

<span class="hljs-keyword">let</span> octokit: Octokit | <span class="hljs-literal">null</span> = <span class="hljs-literal">null</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">initGitHub</span>(<span class="hljs-params">token?: <span class="hljs-built_in">string</span></span>) </span>{
    octokit = <span class="hljs-keyword">new</span> Octokit({
        auth: token || process.env.GITHUB_TOKEN
    });
}

<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">getRendererFile</span>(<span class="hljs-params">path: <span class="hljs-built_in">string</span></span>) </span>{
    <span class="hljs-keyword">if</span> (!octokit) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"GitHub not initialized"</span>);

    <span class="hljs-keyword">const</span> { data } = <span class="hljs-keyword">await</span> octokit.repos.getContent({
        owner: <span class="hljs-string">"NishikantaRay"</span>,
        repo: <span class="hljs-string">"renderer"</span>,
        path,
        ref: <span class="hljs-string">"main"</span>
    });

    <span class="hljs-keyword">if</span> (!(<span class="hljs-string">"content"</span> <span class="hljs-keyword">in</span> data)) {
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Not a file"</span>);
    }

    <span class="hljs-keyword">const</span> content = Buffer.from(data.content, <span class="hljs-string">"base64"</span>).toString();

    <span class="hljs-keyword">return</span> {
        content: [{
            <span class="hljs-keyword">type</span>: <span class="hljs-string">"text"</span> <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span>,
            text: <span class="hljs-string">`# <span class="hljs-subst">${path}</span>\n\n\`\`\`\n<span class="hljs-subst">${content}</span>\n\`\`\``</span>
        }]
    };
}
</code></pre>
<h3 id="heading-step-5-build-amp-package"><strong>Step 5: Build &amp; Package</strong></h3>
<p><strong>package.json:</strong></p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"renderer-mcp-server"</span>,
  <span class="hljs-attr">"version"</span>: <span class="hljs-string">"1.0.0"</span>,
  <span class="hljs-attr">"type"</span>: <span class="hljs-string">"module"</span>,
  <span class="hljs-attr">"bin"</span>: {
    <span class="hljs-attr">"renderer-mcp"</span>: <span class="hljs-string">"./build/index.js"</span>
  },
  <span class="hljs-attr">"scripts"</span>: {
    <span class="hljs-attr">"build"</span>: <span class="hljs-string">"tsc &amp;&amp; chmod +x build/index.js"</span>,
    <span class="hljs-attr">"watch"</span>: <span class="hljs-string">"tsc --watch"</span>
  },
  <span class="hljs-attr">"dependencies"</span>: {
    <span class="hljs-attr">"@modelcontextprotocol/sdk"</span>: <span class="hljs-string">"^1.0.4"</span>,
    <span class="hljs-attr">"@octokit/rest"</span>: <span class="hljs-string">"^20.0.2"</span>,
    <span class="hljs-attr">"toml"</span>: <span class="hljs-string">"^3.0.0"</span>
  }
}
</code></pre>
<p><strong>Build it:</strong></p>
<pre><code class="lang-bash">npm run build
npm link  <span class="hljs-comment"># For local testing</span>
</code></pre>
<hr />
<h2 id="heading-testing-with-claude-desktop"><strong>Testing with Claude Desktop</strong></h2>
<p>Claude Desktop has <strong>native MCP support</strong> built-in. Here's how to use it:</p>
<h3 id="heading-step-1-install-claude-desktop"><strong>Step 1: Install Claude Desktop</strong></h3>
<p>Download from: <a target="_blank" href="https://claude.ai/download">https://claude.ai/download</a></p>
<p>Available for macOS and Windows.</p>
<h3 id="heading-step-2-configure-mcp-server"><strong>Step 2: Configure MCP Server</strong></h3>
<p><strong>Find your config file:</strong></p>
<ul>
<li><p><strong>macOS:</strong> <code>~/Library/Application Support/Claude/claude_desktop_config.json</code></p>
</li>
<li><p><strong>Windows:</strong> <code>%APPDATA%\Claude\claude_desktop_config.json</code></p>
</li>
</ul>
<p><strong>Add your server:</strong></p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"mcpServers"</span>: {
    <span class="hljs-attr">"renderer"</span>: {
      <span class="hljs-attr">"command"</span>: <span class="hljs-string">"renderer-mcp"</span>,
      <span class="hljs-attr">"env"</span>: {
        <span class="hljs-attr">"GITHUB_TOKEN"</span>: <span class="hljs-string">"ghp_your_token_here_optional"</span>
      }
    }
  }
}
</code></pre>
<p><strong>Why the GitHub token?</strong></p>
<ul>
<li><p>Without: 60 API requests/hour</p>
</li>
<li><p>With: 5000 API requests/hour</p>
</li>
<li><p>Get one at: <a target="_blank" href="https://github.com/settings/tokens">https://github.com/settings/tokens</a></p>
</li>
</ul>
<h3 id="heading-step-3-restart-claude-desktop"><strong>Step 3: Restart Claude Desktop</strong></h3>
<p>Close and reopen the app. The MCP server will start automatically.</p>
<h3 id="heading-step-4-verify-connection"><strong>Step 4: Verify Connection</strong></h3>
<p>In Claude Desktop, you should see:</p>
<ul>
<li><p>A small "🔌" icon or "MCP" indicator</p>
</li>
<li><p>"Renderer" listed in connected tools</p>
</li>
</ul>
<h3 id="heading-step-5-start-using-it"><strong>Step 5: Start Using It!</strong></h3>
<p>Just talk to Claude naturally. Here are real examples:</p>
<h4 id="heading-example-1-generate-starter-template"><strong>Example 1: Generate Starter Template</strong></h4>
<p><strong>You:</strong></p>
<pre><code class="lang-javascript">Create a portfolio starter template <span class="hljs-keyword">for</span> me. 
My name is John Doe, GitHub is johndoe, 
and I want projects and resume sections.
</code></pre>
<p><strong>Claude (using MCP):</strong></p>
<pre><code class="lang-javascript">I<span class="hljs-string">'ll generate a complete starter template for you using the 
Renderer MCP server.

*calls generate_starter_template tool*

Here'</span>s your customized configuration:

[Shows complete TOML configs <span class="hljs-keyword">for</span> home.toml, social.toml, 
projects.toml, and resume.toml <span class="hljs-keyword">with</span> your information pre-filled]

To use <span class="hljs-built_in">this</span>:
<span class="hljs-number">1.</span> Clone the Renderer repository
<span class="hljs-number">2.</span> Copy these configs to the config/ directory
<span class="hljs-number">3.</span> Run npm install &amp;&amp; npm run dev
<span class="hljs-number">4.</span> Your portfolio will be live at localhost:<span class="hljs-number">3000</span>!
</code></pre>
<h4 id="heading-example-2-validate-configuration"><strong>Example 2: Validate Configuration</strong></h4>
<p><strong>You:</strong></p>
<pre><code class="lang-javascript">Can you validate <span class="hljs-built_in">this</span> TOML config <span class="hljs-keyword">for</span> me?

[profile]
name = <span class="hljs-string">"Jane Smith"</span>
tagline = <span class="hljs-string">"Full Stack Developer"</span>

[hero]
title = <span class="hljs-string">"Hi, I'm Jane"</span>
</code></pre>
<p><strong>Claude (using MCP):</strong></p>
<pre><code class="lang-javascript">Let me validate that <span class="hljs-keyword">for</span> you.

*calls validate_toml_config tool*

⚠️ Configuration Issues Found:

<span class="hljs-number">1.</span> Missing <span class="hljs-string">'hero.subtitle'</span> field (recommended)
<span class="hljs-number">2.</span> Missing <span class="hljs-string">'hero.show_cta'</span> field (required)
<span class="hljs-number">3.</span> Missing <span class="hljs-string">'features'</span> section

Here<span class="hljs-string">'s the corrected version:

[profile]
name = "Jane Smith"
tagline = "Full Stack Developer"

[hero]
title = "Hi, I'</span>m Jane<span class="hljs-string">"
subtitle = "</span>Building amazing web applications<span class="hljs-string">"
show_cta = true
cta_text = "</span>View My Work<span class="hljs-string">"
cta_link = "</span>/projects.html<span class="hljs-string">"

[features]
show_about = true
show_skills = true
show_social = true</span>
</code></pre>
<h4 id="heading-example-3-learn-features"><strong>Example 3: Learn Features</strong></h4>
<p><strong>You:</strong></p>
<pre><code class="lang-javascript">How <span class="hljs-keyword">do</span> I add dark mode to my Renderer portfolio?
</code></pre>
<p><strong>Claude (using MCP):</strong></p>
<pre><code class="lang-javascript">Let me search <span class="hljs-keyword">for</span> that feature.

*calls search_renderer_features tool*

# Dark Mode <span class="hljs-keyword">in</span> Renderer

Renderer includes automatic dark mode support!

## Configuration (config/home.toml)

[theme]
mode = <span class="hljs-string">"auto"</span>  # Options: auto, light, dark
accent_color = <span class="hljs-string">"#0066cc"</span>

## How it works:

- **auto**: Follows system preferences (recommended)
- **light**: Always light theme
- **dark**: Always dark theme

Users can also toggle themes manually <span class="hljs-keyword">with</span> the theme 
switcher button that appears automatically.

The theme preference is saved <span class="hljs-keyword">in</span> <span class="hljs-built_in">localStorage</span>, so it 
persists across sessions.
</code></pre>
<h4 id="heading-example-4-get-examples"><strong>Example 4: Get Examples</strong></h4>
<p><strong>You:</strong></p>
<pre><code class="lang-javascript">Show me an example <span class="hljs-keyword">of</span> how to configure projects
</code></pre>
<p><strong>Claude (using MCP):</strong></p>
<pre><code class="lang-javascript">*calls get_config_example tool*

Here<span class="hljs-string">'s a complete example for projects.toml:

# Projects Configuration

[[projects]]
title = "Renderer Framework"
description = "A TOML-driven portfolio system"
tags = ["JavaScript", "TOML", "HTML"]
repo = "https://github.com/username/renderer"
demo = "https://demo.example.com"
docs = "https://docs.example.com"
featured = true
company = "@MyCompany"

[[projects]]
title = "Another Cool Project"
description = "Building something awesome"
tags = ["TypeScript", "React", "Node.js"]
repo = "https://github.com/username/project"
featured = false

## Usage:
1. Copy this to config/projects.toml
2. Customize with your projects
3. Add more [[projects]] blocks as needed</span>
</code></pre>
<h3 id="heading-real-workflow-example"><strong>Real Workflow Example</strong></h3>
<p>Here's a complete real-world workflow:</p>
<p><strong>User:</strong> "I want to create a portfolio. Help me get started."</p>
<p><strong>Claude:</strong> "I'll help you create a portfolio with Renderer! First, let me generate a starter template."</p>
<p><em>Uses generate_starter_template</em></p>
<p><strong>Claude:</strong> "Great! I've created custom configurations for you. Now let me show you the setup steps."</p>
<p><em>Uses get_setup_guide</em></p>
<p><strong>Claude:</strong> "Here's what to do:</p>
<ol>
<li><p>Clone the repository...</p>
</li>
<li><p>Copy these configs...</p>
</li>
<li><p>Run these commands..."</p>
</li>
</ol>
<p><strong>User:</strong> "I added my projects but getting an error"</p>
<p><strong>Claude:</strong> "Let me validate your configuration."</p>
<p><em>Uses validate_toml_config</em></p>
<p><strong>Claude:</strong> "Found the issue! You're missing a comma on line 5. Here's the corrected version..."</p>
<p><strong>User:</strong> "How do I deploy this?"</p>
<p><em>Uses get_setup_guide with level: "advanced"</em></p>
<p><strong>Claude:</strong> "Here are your deployment options:</p>
<ol>
<li><p>GitHub Pages...</p>
</li>
<li><p>Vercel (recommended)...</p>
</li>
<li><p>Netlify..."</p>
</li>
</ol>
<hr />
<h2 id="heading-testing-with-mcp-inspector"><strong>Testing with MCP Inspector</strong></h2>
<p>The <strong>MCP Inspector</strong> is a visual debugging tool for MCP servers.</p>
<h3 id="heading-step-1-install-amp-launch"><strong>Step 1: Install &amp; Launch</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Run the inspector (no installation needed)</span>
npx @modelcontextprotocol/inspector renderer-mcp
</code></pre>
<p>This will:</p>
<ol>
<li><p>Start your MCP server</p>
</li>
<li><p>Launch a web interface</p>
</li>
<li><p>Open your browser automatically</p>
</li>
</ol>
<h3 id="heading-step-2-explore-the-interface"><strong>Step 2: Explore the Interface</strong></h3>
<p>The inspector has three panels:</p>
<pre><code class="lang-javascript">┌──────────────────────────────────────────────────────┐
│  Left Panel       │  Center Panel    │  Right Panel  │
│  (Tools List)     │  (Input Form)    │  (Results)    │
├──────────────────────────────────────────────────────┤
│                                                        │
│  • Tool <span class="hljs-number">1</span>         │  [Input fields]  │  [<span class="hljs-built_in">JSON</span>        │
│  • Tool <span class="hljs-number">2</span>         │  [<span class="hljs-keyword">for</span> selected]  │   Response]   │
│  • Tool <span class="hljs-number">3</span>         │  [tool]          │               │
│  • ...            │                  │               │
│                   │  [Execute Btn]   │  [Formatted   │
│                   │                  │   Output]     │
└──────────────────────────────────────────────────────┘
</code></pre>
<h3 id="heading-step-3-test-each-tool"><strong>Step 3: Test Each Tool</strong></h3>
<h4 id="heading-test-1-get-configuration-example"><strong>Test 1: Get Configuration Example</strong></h4>
<ol>
<li><p><strong>Left Panel:</strong> Click <code>get_config_example</code></p>
</li>
<li><p><strong>Center Panel:</strong></p>
<ul>
<li><p>Field: <code>config_type</code></p>
</li>
<li><p>Value: <code>home</code></p>
</li>
</ul>
</li>
<li><p><strong>Click:</strong> "Execute" button</p>
</li>
<li><p><strong>Right Panel:</strong> Should show:</p>
</li>
</ol>
<pre><code class="lang-toml"><span class="hljs-comment"># Home Page Configuration Example</span>

<span class="hljs-section">[profile]</span>
<span class="hljs-attr">name</span> = <span class="hljs-string">"John Doe"</span>
<span class="hljs-attr">tagline</span> = <span class="hljs-string">"Full Stack Developer"</span>
...
</code></pre>
<h4 id="heading-test-2-validate-configuration"><strong>Test 2: Validate Configuration</strong></h4>
<ol>
<li><p><strong>Left Panel:</strong> Click <code>validate_toml_config</code></p>
</li>
<li><p><strong>Center Panel:</strong></p>
<ul>
<li><p>Field: <code>config_content</code></p>
</li>
<li><p>Value:</p>
<pre><code class="lang-toml">  <span class="hljs-section">[profile]</span>
  <span class="hljs-attr">name</span> = <span class="hljs-string">"Test"</span>
</code></pre>
</li>
<li><p>Field: <code>config_type</code></p>
</li>
<li><p>Value: <code>home</code></p>
</li>
</ul>
</li>
<li><p><strong>Click:</strong> "Execute"</p>
</li>
<li><p><strong>Right Panel:</strong> Should show validation results</p>
</li>
</ol>
<p><strong>Try Invalid TOML:</strong></p>
<pre><code class="lang-toml"><span class="hljs-section">[profile
name = "Missing bracket"</span>
</code></pre>
<p>Should see syntax error with helpful message!</p>
<h4 id="heading-test-3-generate-template"><strong>Test 3: Generate Template</strong></h4>
<ol>
<li><p><strong>Left Panel:</strong> Click <code>generate_starter_template</code></p>
</li>
<li><p><strong>Center Panel:</strong></p>
<ul>
<li><p><code>name</code>: "Alice Johnson"</p>
</li>
<li><p><code>email</code>: "<a target="_blank" href="mailto:alice@example.com">alice@example.com</a>"</p>
</li>
<li><p><code>github</code>: "alicejohnson"</p>
</li>
<li><p><code>include_projects</code>: ✅ true</p>
</li>
<li><p><code>include_blog</code>: ❌ false</p>
</li>
<li><p><code>include_resume</code>: ✅ true</p>
</li>
</ul>
</li>
<li><p><strong>Click:</strong> "Execute"</p>
</li>
<li><p><strong>Right Panel:</strong> Should show complete template</p>
</li>
</ol>
<h4 id="heading-test-4-search-features"><strong>Test 4: Search Features</strong></h4>
<ol>
<li><p><strong>Left Panel:</strong> Click <code>search_renderer_features</code></p>
</li>
<li><p><strong>Center Panel:</strong></p>
<ul>
<li><code>feature</code>: "analytics"</li>
</ul>
</li>
<li><p><strong>Click:</strong> "Execute"</p>
</li>
<li><p><strong>Right Panel:</strong> Should show feature documentation</p>
</li>
</ol>
<h3 id="heading-step-4-verify-all-tools"><strong>Step 4: Verify All Tools</strong></h3>
<p>Test all 8 tools systematically:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>#</td><td>Tool Name</td><td>Test Input</td><td>Expected Output</td></tr>
</thead>
<tbody>
<tr>
<td>1</td><td><code>explore_renderer_docs</code></td><td>query: "TOML"</td><td>Documentation snippets</td></tr>
<tr>
<td>2</td><td><code>get_renderer_file</code></td><td>path: "config/home.toml"</td><td>File contents</td></tr>
<tr>
<td>3</td><td><code>list_renderer_files</code></td><td>path: "config"</td><td>Directory listing</td></tr>
<tr>
<td>4</td><td><code>validate_toml_config</code></td><td>Valid TOML + type</td><td>✅ Valid message</td></tr>
<tr>
<td>5</td><td><code>generate_starter_template</code></td><td>name: "Test"</td><td>Full template</td></tr>
<tr>
<td>6</td><td><code>get_config_example</code></td><td>type: "projects"</td><td>Example config</td></tr>
<tr>
<td>7</td><td><code>search_renderer_features</code></td><td>feature: "dark mode"</td><td>Feature docs</td></tr>
<tr>
<td>8</td><td><code>get_setup_guide</code></td><td>level: "beginner"</td><td>Setup guide</td></tr>
</tbody>
</table>
</div><h3 id="heading-step-5-debug-issues"><strong>Step 5: Debug Issues</strong></h3>
<p><strong>Common Issues:</strong></p>
<h4 id="heading-tools-not-appearing"><strong>Tools Not Appearing</strong></h4>
<pre><code class="lang-javascript">Problem: Left panel is empty
<span class="hljs-attr">Solution</span>: Check terminal <span class="hljs-keyword">for</span> error messages
         Ensure server started successfully
</code></pre>
<h4 id="heading-execution-fails"><strong>Execution Fails</strong></h4>
<pre><code class="lang-javascript">Problem: <span class="hljs-built_in">Error</span> <span class="hljs-keyword">in</span> right panel
<span class="hljs-attr">Solution</span>: Check input types match schema
         Verify required fields are filled
</code></pre>
<h4 id="heading-slow-responses"><strong>Slow Responses</strong></h4>
<pre><code class="lang-javascript">Problem: Long wait times
<span class="hljs-attr">Solution</span>: Normal <span class="hljs-keyword">for</span> GitHub API calls
         Add GitHub token <span class="hljs-keyword">for</span> better performance
</code></pre>
<h3 id="heading-step-6-advanced-testing"><strong>Step 6: Advanced Testing</strong></h3>
<h4 id="heading-test-error-handling"><strong>Test Error Handling</strong></h4>
<p><strong>Invalid config type:</strong></p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"config_type"</span>: <span class="hljs-string">"invalid_type"</span>
}
</code></pre>
<p>Should return clear error message.</p>
<p><strong>Missing required field:</strong></p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"name"</span>: <span class="hljs-literal">null</span>
}
</code></pre>
<p>Should indicate missing required field.</p>
<h4 id="heading-test-edge-cases"><strong>Test Edge Cases</strong></h4>
<p><strong>Empty strings:</strong></p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"query"</span>: <span class="hljs-string">""</span>
}
</code></pre>
<p><strong>Very long input:</strong></p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"config_content"</span>: <span class="hljs-string">"... 10,000 characters ..."</span>
}
</code></pre>
<p><strong>Special characters:</strong></p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Test&lt;script&gt;alert('xss')&lt;/script&gt;"</span>
}
</code></pre>
<h3 id="heading-step-7-performance-testing"><strong>Step 7: Performance Testing</strong></h3>
<p>Monitor the <strong>Network tab</strong> in browser DevTools:</p>
<ul>
<li><p>Tool execution time</p>
</li>
<li><p>Response size</p>
</li>
<li><p>Error rates</p>
</li>
</ul>
<p>Good benchmarks:</p>
<ul>
<li><p>Simple tools: &lt;200ms</p>
</li>
<li><p>GitHub API calls: &lt;2s</p>
</li>
<li><p>Complex operations: &lt;5s</p>
</li>
</ul>
<hr />
<h2 id="heading-real-world-use-cases"><strong>Real-World Use Cases</strong></h2>
<p>Let's see how users actually use Renderer MCP Server:</p>
<h3 id="heading-use-case-1-first-time-portfolio-creation"><strong>Use Case 1: First-Time Portfolio Creation</strong></h3>
<p><strong>Scenario:</strong> Sarah, a bootcamp graduate, needs a portfolio fast.</p>
<p><strong>Traditional Way (2+ hours):</strong></p>
<ol>
<li><p>Find Renderer documentation</p>
</li>
<li><p>Read through all docs</p>
</li>
<li><p>Understand TOML syntax</p>
</li>
<li><p>Create configuration files</p>
</li>
<li><p>Debug syntax errors</p>
</li>
<li><p>Test locally</p>
</li>
<li><p>Deploy</p>
</li>
</ol>
<p><strong>With MCP (15 minutes):</strong></p>
<pre><code class="lang-javascript">Sarah: <span class="hljs-string">"Create a portfolio for Sarah Chen, full-stack developer, 
       GitHub sarahchen"</span>

<span class="hljs-attr">Claude</span>: *generates complete template*

Sarah: *copies configs, runs npm install*

Sarah: <span class="hljs-string">"How do I deploy this?"</span>

<span class="hljs-attr">Claude</span>: *provides deployment guide*

Sarah: *deploys to Vercel <span class="hljs-keyword">in</span> <span class="hljs-number">3</span> clicks*
</code></pre>
<p><strong>Result:</strong> Portfolio live in 15 minutes! ✅</p>
<h3 id="heading-use-case-2-configuration-debugging"><strong>Use Case 2: Configuration Debugging</strong></h3>
<p><strong>Scenario:</strong> Mike's portfolio won't load after config changes.</p>
<p><strong>Traditional Way:</strong></p>
<ol>
<li><p>Console shows cryptic error</p>
</li>
<li><p>Search through documentation</p>
</li>
<li><p>Check TOML syntax manually</p>
</li>
<li><p>Trial and error fixes</p>
</li>
<li><p>Search Stack Overflow</p>
</li>
</ol>
<p><strong>With MCP:</strong></p>
<pre><code class="lang-javascript">Mike: <span class="hljs-string">"My portfolio broke after I updated config. Can you check it?"</span>
      [pastes config]

<span class="hljs-attr">Claude</span>: *validates config*
        <span class="hljs-string">"Found 2 issues:
         1. Missing closing bracket on line 12
         2. Invalid value for 'mode' field (must be 'auto', 'light', or 'dark')

         Here's the corrected version..."</span>

<span class="hljs-attr">Mike</span>: *copies fix, portfolio works immediately*
</code></pre>
<p><strong>Result:</strong> Issue fixed in 2 minutes! ✅</p>
<h3 id="heading-use-case-3-feature-discovery"><strong>Use Case 3: Feature Discovery</strong></h3>
<p><strong>Scenario:</strong> Emma wants to add advanced features but doesn't know what's available.</p>
<p><strong>Traditional Way:</strong></p>
<ol>
<li><p>Read through all documentation</p>
</li>
<li><p>Browse example repositories</p>
</li>
<li><p>Search issues and discussions</p>
</li>
<li><p>Trial and error implementation</p>
</li>
</ol>
<p><strong>With MCP:</strong></p>
<pre><code class="lang-javascript">Emma: <span class="hljs-string">"What features can I add to my portfolio?"</span>

<span class="hljs-attr">Claude</span>: *lists features <span class="hljs-keyword">with</span> descriptions*
        <span class="hljs-string">"Renderer has these features:
         - Dark mode theme switching
         - @mention system for companies
         - Analytics dashboard
         - Blog with MDX support
         - Project filtering by tags
         - Resume with timeline
         - And more!

         Which would you like to implement?"</span>

<span class="hljs-attr">Emma</span>: <span class="hljs-string">"Tell me about the analytics dashboard"</span>

<span class="hljs-attr">Claude</span>: *searches features*
        <span class="hljs-string">"The analytics dashboard shows:
         - Project view counts
         - GitHub stars
         - Visitor metrics
         - Performance stats

         Here's how to enable it..."</span>
</code></pre>
<p><strong>Result:</strong> Discovered and implemented features quickly! ✅</p>
<h3 id="heading-use-case-4-team-onboarding"><strong>Use Case 4: Team Onboarding</strong></h3>
<p><strong>Scenario:</strong> Tech startup needs to create portfolios for 10 developers.</p>
<p><strong>Without MCP:</strong></p>
<ul>
<li><p>Each person spends 2-4 hours</p>
</li>
<li><p>Inconsistent results</p>
</li>
<li><p>Multiple support requests</p>
</li>
<li><p>Total: 20-40 person-hours</p>
</li>
</ul>
<p><strong>With MCP:</strong></p>
<pre><code class="lang-javascript">Template: <span class="hljs-string">"Generate template for [name], GitHub [username], 
          email [email], include projects and resume"</span>

Each Developer:
<span class="hljs-number">1.</span> Ask MCP <span class="hljs-keyword">for</span> template (<span class="hljs-number">2</span> min)
<span class="hljs-number">2.</span> Clone and setup (<span class="hljs-number">5</span> min)
<span class="hljs-number">3.</span> Customize (<span class="hljs-number">10</span> min)
<span class="hljs-number">4.</span> Deploy (<span class="hljs-number">3</span> min)

Total per person: <span class="hljs-number">20</span> minutes
Total team: <span class="hljs-number">3.5</span> hours (vs <span class="hljs-number">20</span><span class="hljs-number">-40</span> hours)
</code></pre>
<p><strong>Result:</strong> 85-90% time savings! ✅</p>
<hr />
<h2 id="heading-lessons-learned"><strong>Lessons Learned</strong></h2>
<p>After building and deploying Renderer MCP Server, here are key insights:</p>
<h3 id="heading-what-worked-well"><strong>What Worked Well ✅</strong></h3>
<p><strong>1. Modular Architecture</strong></p>
<ul>
<li><p>Each tool in its own file</p>
</li>
<li><p>Easy to add new tools</p>
</li>
<li><p>Simple to maintain and test</p>
</li>
<li><p>Clean separation of concerns</p>
</li>
</ul>
<p><strong>2. TypeScript</strong></p>
<ul>
<li><p>Caught errors at compile time</p>
</li>
<li><p>Great IDE autocomplete</p>
</li>
<li><p>Self-documenting code</p>
</li>
<li><p>Easier refactoring</p>
</li>
</ul>
<p><strong>3. stdio Transport</strong></p>
<ul>
<li><p>Simple setup</p>
</li>
<li><p>No networking complexity</p>
</li>
<li><p>Universal compatibility</p>
</li>
<li><p>Easy debugging</p>
</li>
</ul>
<p><strong>4. Comprehensive Examples</strong></p>
<ul>
<li><p>Users love copy-paste examples</p>
</li>
<li><p>Reduces support burden</p>
</li>
<li><p>Speeds up adoption</p>
</li>
<li><p>Encourages best practices</p>
</li>
</ul>
<h3 id="heading-what-could-be-better"><strong>What Could Be Better 🔄</strong></h3>
<p><strong>1. Caching</strong></p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Current: Every request hits GitHub API</span>
<span class="hljs-comment">// Better: Cache responses for repeated queries</span>

<span class="hljs-keyword">const</span> cache = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Map</span>();
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getCachedFile</span>(<span class="hljs-params">path</span>) </span>{
    <span class="hljs-keyword">if</span> (cache.has(path)) <span class="hljs-keyword">return</span> cache.get(path);
    <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> fetchFromGitHub(path);
    cache.set(path, result);
    <span class="hljs-keyword">return</span> result;
}
</code></pre>
<p><strong>2. Error Messages</strong></p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Current: Generic error messages</span>
<span class="hljs-comment">// Better: Actionable, specific errors</span>

<span class="hljs-comment">// Instead of:</span>
<span class="hljs-string">"Validation failed"</span>

<span class="hljs-comment">// Provide:</span>
<span class="hljs-string">"Missing required field 'profile.name' in home.toml (line 5)"</span>
</code></pre>
<p><strong>3. Progress Indicators</strong></p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Current: Silent processing</span>
<span class="hljs-comment">// Better: Show progress for long operations</span>

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">longOperation</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Fetching documentation..."</span>);
    <span class="hljs-keyword">const</span> docs = <span class="hljs-keyword">await</span> fetchDocs();
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Processing results..."</span>);
    <span class="hljs-keyword">return</span> processDocs(docs);
}
</code></pre>
<h3 id="heading-key-takeaways"><strong>Key Takeaways 💡</strong></h3>
<ol>
<li><p><strong>Start Simple</strong> - MVP with core tools, iterate based on feedback</p>
</li>
<li><p><strong>User-Centric</strong> - Solve real problems, not theoretical ones</p>
</li>
<li><p><strong>Documentation</strong> - Clear examples &gt; long explanations</p>
</li>
<li><p><strong>Testing</strong> - Inspector + Claude Desktop = comprehensive testing</p>
</li>
<li><p><strong>Community</strong> - Users will surprise you with use cases</p>
</li>
</ol>
<h3 id="heading-metrics-that-matter"><strong>Metrics That Matter 📊</strong></h3>
<p>After 1 month of Renderer MCP Server:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Metric</td><td>Before</td><td>After</td><td>Change</td></tr>
</thead>
<tbody>
<tr>
<td>Avg Setup Time</td><td>2 hours</td><td>15 min</td><td><strong>-87%</strong></td></tr>
<tr>
<td>Support Tickets</td><td>50/week</td><td>15/week</td><td><strong>-70%</strong></td></tr>
<tr>
<td>Successful Setups</td><td>60%</td><td>95%</td><td><strong>+58%</strong></td></tr>
<tr>
<td>User Satisfaction</td><td>3.5★</td><td>4.7★</td><td><strong>+34%</strong></td></tr>
<tr>
<td>GitHub Stars</td><td>150</td><td>420</td><td><strong>+180%</strong></td></tr>
</tbody>
</table>
</div><hr />
<h2 id="heading-whats-next"><strong>What's Next</strong></h2>
<h3 id="heading-phase-2-enhancements-q1-2026"><strong>Phase 2: Enhancements (Q1 2026)</strong></h3>
<p><strong>Caching Layer</strong></p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Smart caching for GitHub responses</span>
<span class="hljs-comment">// Reduce API calls by 80%</span>
<span class="hljs-comment">// Faster response times</span>
</code></pre>
<p><strong>Offline Mode</strong></p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Work without internet</span>
<span class="hljs-comment">// Cached documentation</span>
<span class="hljs-comment">// Local validation</span>
</code></pre>
<p><strong>Better Error Messages</strong></p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Line numbers in errors</span>
<span class="hljs-comment">// Suggested fixes</span>
<span class="hljs-comment">// Related documentation links</span>
</code></pre>
<h3 id="heading-phase-3-advanced-features-q2-2026"><strong>Phase 3: Advanced Features (Q2 2026)</strong></h3>
<p><strong>Visual Configuration Builder</strong></p>
<pre><code class="lang-javascript">GUI <span class="hljs-keyword">for</span> creating configs
Drag-and-drop interface
Live preview
Export to TOML
</code></pre>
<p><strong>AI Content Generation</strong></p>
<pre><code class="lang-javascript">Generate project descriptions
Write portfolio copy
Create blog post outlines
SEO optimization
</code></pre>
<p><strong>Deployment Assistant</strong></p>
<pre><code class="lang-javascript">One-command deployment
Multiple platforms
Environment setup
Domain configuration
</code></pre>
<h3 id="heading-phase-4-ecosystem-q3-2026"><strong>Phase 4: Ecosystem (Q3 2026)</strong></h3>
<p><strong>Plugin System</strong></p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Let community create tools</span>
<span class="hljs-keyword">interface</span> Plugin {
    name: <span class="hljs-built_in">string</span>;
    tools: Tool[];
    onLoad: <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">void</span>;
}
</code></pre>
<p><strong>Marketplace</strong></p>
<pre><code class="lang-javascript">Share templates
Community plugins
Premium themes
Integrations
</code></pre>
<p><strong>Multi-Language Support</strong></p>
<pre><code class="lang-javascript">i18n <span class="hljs-keyword">for</span> documentation
Localized examples
Regional templates
</code></pre>
<hr />
<h2 id="heading-getting-started"><strong>Getting Started</strong></h2>
<p>Ready to try Renderer MCP Server?</p>
<h3 id="heading-quick-start-5-minutes"><strong>Quick Start (5 Minutes)</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># 1. Install</span>
npm install -g renderer-mcp-server

<span class="hljs-comment"># 2. Configure Claude Desktop</span>
<span class="hljs-comment"># Add to claude_desktop_config.json:</span>
{
  <span class="hljs-string">"mcpServers"</span>: {
    <span class="hljs-string">"renderer"</span>: {
      <span class="hljs-string">"command"</span>: <span class="hljs-string">"renderer-mcp"</span>
    }
  }
}

<span class="hljs-comment"># 3. Restart Claude Desktop</span>

<span class="hljs-comment"># 4. Start using it!</span>
<span class="hljs-comment"># Just ask Claude: "Create a portfolio for me"</span>
</code></pre>
<h3 id="heading-build-your-own-mcp-server"><strong>Build Your Own MCP Server</strong></h3>
<p>Want to create an MCP server for your project?</p>
<p><strong>1. Clone the template:</strong></p>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> https://github.com/NishikantaRay/renderer-mcp-server
<span class="hljs-built_in">cd</span> renderer-mcp-server
</code></pre>
<p><strong>2. Customize for your use case:</strong></p>
<ul>
<li><p>Replace Renderer-specific logic</p>
</li>
<li><p>Add your own tools</p>
</li>
<li><p>Connect to your APIs</p>
</li>
</ul>
<p><strong>3. Test and deploy:</strong></p>
<pre><code class="lang-bash">npm run build
npm link
npx @modelcontextprotocol/inspector your-mcp-server
</code></pre>
<h3 id="heading-resources"><strong>Resources</strong></h3>
<ul>
<li><p><strong>GitHub:</strong> <a target="_blank" href="https://github.com/NishikantaRay/renderer-mcp-server">renderer-mcp-server</a></p>
</li>
<li><p><strong>Renderer Framework:</strong> <a target="_blank" href="https://github.com/NishikantaRay/renderer">renderer</a></p>
</li>
<li><p><strong>MCP Docs:</strong> <a target="_blank" href="https://modelcontextprotocol.io/">modelcontextprotocol.io</a></p>
</li>
<li><p><strong>Claude Desktop:</strong> <a target="_blank" href="https://claude.ai/download">claude.ai/download</a></p>
</li>
</ul>
<hr />
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>Building Renderer MCP Server transformed how users interact with the Renderer framework. What was once a 2-hour manual process is now a 15-minute conversation with an AI assistant.</p>
<p><strong>Key Achievements:</strong></p>
<ul>
<li><p>⚡ 87% faster setup time</p>
</li>
<li><p>🎯 95% success rate for first-time users</p>
</li>
<li><p>📉 70% reduction in support tickets</p>
</li>
<li><p>🌟 4.7★ user satisfaction</p>
</li>
<li><p>🚀 3x framework adoption growth</p>
</li>
</ul>
<p><strong>The Power of MCP:</strong></p>
<p>MCP isn't just about connecting AI to tools—it's about creating <strong>intelligent assistants that truly understand your domain</strong>. The Renderer MCP Server doesn't just execute commands; it knows the framework intimately, validates configurations, teaches best practices, and guides users from zero to deployed.</p>
<p>This is the future of developer tools: <strong>AI assistants that are specialists, not generalists</strong>.</p>
<p><strong>Your Turn:</strong></p>
<p>What framework or tool could benefit from its own MCP server?</p>
<p>The pattern is proven. The infrastructure is here. The only limit is imagination.</p>
<p>Build something amazing. 🚀</p>
<hr />
<h2 id="heading-about-the-author"><strong>About the Author</strong></h2>
<p><strong>Nishikanta Ray</strong> is the creator of Renderer, a modern portfolio framework, and Renderer MCP Server. He's passionate about making web development more accessible through AI-powered tools.</p>
<ul>
<li><p><strong>GitHub:</strong> <a target="_blank" href="https://github.com/NishikantaRay">@NishikantaRay</a></p>
</li>
<li><p><strong>Twitter:</strong> <a target="_blank" href="https://twitter.com/nishikantaray">@nishikantaray</a></p>
</li>
<li><p><strong>Website:</strong> <a target="_blank" href="https://nishikanta.in/">nishikanta.in</a></p>
</li>
</ul>
<hr />
<h2 id="heading-comments-amp-discussion"><strong>Comments &amp; Discussion</strong></h2>
<p>Have questions about building MCP servers? Share your experience in the comments!</p>
<p><strong>Topics for discussion:</strong></p>
<ul>
<li><p>What tool/framework needs an MCP server?</p>
</li>
<li><p>Challenges you faced with MCP</p>
</li>
<li><p>Creative use cases you've discovered</p>
</li>
<li><p>Features you'd like to see</p>
</li>
</ul>
<p>Let's build the future of AI-assisted development together! 💬</p>
<hr />
<p><strong>Tags:</strong> #MCP #AI #DeveloperTools #Anthropic #Claude #OpenSource #TypeScript #Portfolio</p>
<p><strong>Published:</strong> December 29, 2025<br /><strong>Reading Time:</strong> 25 minutes<br /><strong>Last Updated:</strong> December 29, 2025</p>
<hr />
<p><em>If you found this helpful, please ⭐ star the repository and share with others!</em></p>
]]></content:encoded></item><item><title><![CDATA[GitHubWrap.space: Your Coding Year, Told Like a Space Odyssey 🚀]]></title><description><![CDATA[Every developer has a story.
Some stories are written in late-night commits.Some are hidden in pull requests merged after long debates.Some live quietly in green squares on a GitHub contribution graph.
But most of the time, we never stop to read that...]]></description><link>https://blog.nishikanta.in/githubwrapspace-your-coding-year-told-like-a-space-odyssey</link><guid isPermaLink="true">https://blog.nishikanta.in/githubwrapspace-your-coding-year-told-like-a-space-odyssey</guid><category><![CDATA[GitHub]]></category><category><![CDATA[wrap]]></category><category><![CDATA[Spotify]]></category><dc:creator><![CDATA[Nishikanta Ray]]></dc:creator><pubDate>Thu, 25 Dec 2025 06:14:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1766639903751/e4f316e9-2314-4ac9-afee-91e48ec61a42.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Every developer has a story.</p>
<p>Some stories are written in late-night commits.<br />Some are hidden in pull requests merged after long debates.<br />Some live quietly in green squares on a GitHub contribution graph.</p>
<p>But most of the time, we never stop to <em>read</em> that story.</p>
<p>That’s where <a target="_blank" href="http://GitHubWrap.space"><strong>GitHubWrap.space</strong></a> comes in.</p>
<hr />
<h2 id="heading-the-problem-we-code-forward-never-looking-back">The Problem: We Code Forward, Never Looking Back</h2>
<p>As developers, we’re trained to think ahead:</p>
<ul>
<li><p>What’s the next feature?</p>
</li>
<li><p>What’s the next bug?</p>
</li>
<li><p>What’s the next deadline?</p>
</li>
</ul>
<p>We push commits, close issues, open new ones—and move on. Over time, months of effort collapse into a single number: <em>total contributions this year</em>.</p>
<p>GitHub gives us raw data.<br />But raw data doesn’t tell a story.</p>
<p>It doesn’t tell you:</p>
<ul>
<li><p>When you were at your most productive</p>
</li>
<li><p>Which projects truly mattered to you</p>
</li>
<li><p>How consistent your journey really was</p>
</li>
<li><p>Or how much you grew over the year</p>
</li>
</ul>
<p><a target="_blank" href="http://GitHubWrap.space"><strong>GitHubWrap.space</strong></a> exists to solve exactly that.</p>
<hr />
<h2 id="heading-the-idea-spotify-wrapped-but-for-developers">The Idea: Spotify Wrapped, But for Developers</h2>
<p>You already know the feeling.</p>
<p>At the end of the year, Spotify Wrapped shows up and suddenly:</p>
<ul>
<li><p>Your music habits feel meaningful</p>
</li>
<li><p>Your year has a rhythm</p>
</li>
<li><p>Your taste has a personality</p>
</li>
</ul>
<p><a target="_blank" href="http://GitHubWrap.space">GitHubWrap.space</a> applies that same emotional intelligence to <strong>code</strong>.</p>
<p>Instead of songs and artists, it looks at:</p>
<ul>
<li><p>Commits</p>
</li>
<li><p>Contribution streaks</p>
</li>
<li><p>Active repositories</p>
</li>
<li><p>Coding frequency</p>
</li>
<li><p>Patterns across the year</p>
</li>
</ul>
<p>And instead of charts that feel clinical, it wraps everything in a <strong>space-themed narrative</strong>—turning your GitHub activity into a journey across the stars.</p>
<hr />
<h2 id="heading-the-experience-launching-your-github-year">The Experience: Launching Your GitHub Year</h2>
<p>Using <a target="_blank" href="http://GitHubWrap.space">GitHubWrap.space</a> feels less like generating a report and more like <strong>starting a mission</strong>.</p>
<h3 id="heading-step-1-enter-the-command-center">Step 1: Enter the Command Center</h3>
<p>You visit <a target="_blank" href="http://githubwrap.space"><code>githubwrap.space</code></a> and enter your GitHub username.<br />Optionally, you can add a GitHub access token if you want private contributions included.</p>
<p>Right away, it feels intentional—no clutter, no noise.</p>
<h3 id="heading-step-2-data-becomes-a-universe">Step 2: Data Becomes a Universe</h3>
<p>Once the data loads, your activity stops being numbers and starts becoming <strong>constellations</strong>.</p>
<p>Commits turn into signals.<br />Streaks become trajectories.<br />Repositories feel like planets you spent time exploring.</p>
<p>You’re no longer “someone who committed 1,237 times.”<br />You’re a developer who stayed consistent, explored new ideas, and pushed through slow months.</p>
<h3 id="heading-step-3-the-story-unfolds">Step 3: The Story Unfolds</h3>
<p>The platform walks you through your year like a timeline:</p>
<ul>
<li><p>High-energy months</p>
</li>
<li><p>Quiet phases</p>
</li>
<li><p>Breakthrough moments</p>
</li>
<li><p>Long streaks that show discipline, not luck</p>
</li>
</ul>
<p>Each section builds on the last, making the journey feel intentional—even if your year felt chaotic while living it.</p>
<hr />
<h2 id="heading-why-the-space-theme-works-so-well">Why the Space Theme Works So Well 🌌</h2>
<p>The space theme isn’t just visual flair.</p>
<p>It’s symbolic.</p>
<p>Coding often feels like:</p>
<ul>
<li><p>Exploring the unknown</p>
</li>
<li><p>Navigating without a map</p>
</li>
<li><p>Spending long stretches alone</p>
</li>
<li><p>Celebrating small discoveries</p>
</li>
</ul>
<p><a target="_blank" href="http://GitHubWrap.space">GitHubWrap.space</a> leans into this metaphor beautifully.</p>
<p>Your repositories become worlds.<br />Your commits become signals sent into the void.<br />Your year becomes a mission log.</p>
<p>This emotional framing turns reflection into motivation.</p>
<hr />
<h2 id="heading-more-than-vanity-metrics">More Than Vanity Metrics</h2>
<p>A big strength of <a target="_blank" href="http://GitHubWrap.space">GitHubWrap.space</a> is what it <strong>doesn’t</strong> do.</p>
<p>It doesn’t:</p>
<ul>
<li><p>Rank you against other developers</p>
</li>
<li><p>Shame you for inactive months</p>
</li>
<li><p>Reduce your year to a single score</p>
</li>
</ul>
<p>Instead, it highlights <strong>patterns</strong>:</p>
<ul>
<li><p>Consistency over intensity</p>
</li>
<li><p>Growth over perfection</p>
</li>
<li><p>Effort over comparison</p>
</li>
</ul>
<p>That makes it especially valuable for:</p>
<ul>
<li><p>Students learning to code</p>
</li>
<li><p>Indie hackers</p>
</li>
<li><p>Open-source contributors</p>
</li>
<li><p>Developers balancing work, life, and learning</p>
</li>
</ul>
<p>Your journey is respected as <em>yours</em>.</p>
<hr />
<h2 id="heading-shareable-without-being-shallow">Shareable, Without Being Shallow</h2>
<p>Once your GitHub Wrap is generated, it’s easy to share.</p>
<p>And unlike generic screenshots of contribution graphs, this actually sparks conversation:</p>
<ul>
<li><p>“Wow, I didn’t realize I was most active in March.”</p>
</li>
<li><p>“I stayed consistent even during slow months.”</p>
</li>
<li><p>“This year was about learning, not shipping.”</p>
</li>
</ul>
<p>It’s not bragging—it’s reflection.</p>
<hr />
<h2 id="heading-privacy-matters-and-it-shows">Privacy Matters (And It Shows)</h2>
<p>If you choose to include private contributions, <a target="_blank" href="http://GitHubWrap.space">GitHubWrap.space</a> asks for a GitHub token—but responsibly.</p>
<p>This transparency is important.</p>
<p>As developers, we care about:</p>
<ul>
<li><p>Permissions</p>
</li>
<li><p>Data usage</p>
</li>
<li><p>Trust</p>
</li>
</ul>
<p>The tool encourages conscious decisions instead of silent data grabs, which is exactly how developer tools <em>should</em> behave.</p>
<hr />
<h2 id="heading-why-tools-like-this-matter">Why Tools Like This Matter</h2>
<p>At first glance, <a target="_blank" href="http://GitHubWrap.space">GitHubWrap.space</a> feels fun—and it is.</p>
<p>But at a deeper level, it does something powerful:<br />It <strong>humanizes developer effort</strong>.</p>
<p>It reminds us that:</p>
<ul>
<li><p>Progress isn’t linear</p>
</li>
<li><p>Consistency beats intensity</p>
</li>
<li><p>Showing up matters</p>
</li>
</ul>
<p>And sometimes, seeing your own journey laid out clearly is all the motivation you need for the next year.</p>
<hr />
<h2 id="heading-final-thoughts-read-your-own-story">Final Thoughts: Read Your Own Story</h2>
<p>Your GitHub profile already contains a story.<br /><a target="_blank" href="http://GitHubWrap.space">GitHubWrap.space</a> simply helps you read it.</p>
<p>Not as a résumé.<br />Not as a leaderboard.<br />But as a <strong>personal journey through code</strong>.</p>
<p>If you’ve spent a year writing software—whether that year felt successful or messy—you owe it to yourself to look back.</p>
<p>Launch the wrap.<br />Explore your universe.<br />And start the next chapter with clarity 🚀</p>
]]></content:encoded></item><item><title><![CDATA[Master–Slave (Primary–Replica) Replication]]></title><description><![CDATA[If you’re building a modern application that needs speed, availability, and fault tolerance, MongoDB replication is one of the easiest and most powerful features you can use.
📌 What Is Master–Slave (Primary–Replica) Replication?
Replication ensures ...]]></description><link>https://blog.nishikanta.in/masterslave-primaryreplica-replication</link><guid isPermaLink="true">https://blog.nishikanta.in/masterslave-primaryreplica-replication</guid><category><![CDATA[Databases]]></category><category><![CDATA[master slave]]></category><dc:creator><![CDATA[Nishikanta Ray]]></dc:creator><pubDate>Fri, 28 Nov 2025 18:40:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1764355186633/d89c5830-eb62-4ee8-a063-1b2177187ec8.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you’re building a modern application that needs speed, availability, and fault tolerance, <strong>MongoDB replication</strong> is one of the easiest and most powerful features you can use.</p>
<h2 id="heading-what-is-masterslave-primaryreplica-replication"><strong>📌 What Is Master–Slave (Primary–Replica) Replication?</strong></h2>
<p>Replication ensures you have <strong>multiple copies</strong> of your data across different servers.</p>
<p>In MongoDB:</p>
<ul>
<li><p>The <strong>Primary</strong> node handles all writes.</p>
</li>
<li><p><strong>Secondary</strong> nodes replicate data from the primary and handle reads (optional).</p>
</li>
<li><p>If the primary fails, MongoDB automatically promotes a secondary → new primary.</p>
</li>
</ul>
<p>This setup is known as a <strong>Replica Set</strong>, which is MongoDB’s improved version of the old master–slave architecture.</p>
<h2 id="heading-why-do-we-need-masterslave-primaryreplica-architecture">⭐ Why Do We Need Master–Slave (Primary–Replica) Architecture?</h2>
<p>Master–slave replication exists because <strong>one single database server cannot handle everything reliably, efficiently, and safely</strong>. Splitting responsibilities between a <strong>master (primary)</strong> and <strong>slaves (replicas)</strong> solves several real-world problems.</p>
<h2 id="heading-mongodb-replication-architecture"><strong>📐 MongoDB Replication Architecture</strong></h2>
<pre><code class="lang-javascript">            +-----------------+
            |     Primary     | <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">---</span> <span class="hljs-attr">Write</span> &amp; <span class="hljs-attr">Read</span>
            +<span class="hljs-attr">-----------------</span>+
               /         \
              /           \
+<span class="hljs-attr">-----------------</span>+   +<span class="hljs-attr">-----------------</span>+
|   <span class="hljs-attr">Secondary</span> <span class="hljs-attr">1</span>   |   |   <span class="hljs-attr">Secondary</span> <span class="hljs-attr">2</span>   |
+<span class="hljs-attr">-----------------</span>+   +<span class="hljs-attr">-----------------</span>+
     <span class="hljs-attr">Read</span> <span class="hljs-attr">Only</span>            <span class="hljs-attr">Read</span> <span class="hljs-attr">Only</span></span></span>
</code></pre>
<h2 id="heading-what-is-slave-replication-explained-like-youre-5">⭐ What Is Slave Replication (Explained Like You’re 5)</h2>
<p>Slave replication means:</p>
<ul>
<li><p>One database is the <strong>master</strong> → accepts all writes</p>
</li>
<li><p>Other databases are <strong>slaves/replicas</strong> → copy whatever master does</p>
</li>
<li><p>Replicas stay almost up-to-date with the master</p>
</li>
</ul>
<p>Think of it like a <strong>teacher (master)</strong> writing on the board, and<br /><strong>3 students (slaves/replicas)</strong> copying everything in real time.</p>
<p>Sometimes students copy late → this is <strong>replication lag</strong>.</p>
<h2 id="heading-how-slave-replication-actually-works-technical-flow">🔁 How Slave Replication Actually Works (Technical Flow)</h2>
<p>Below is the internal flow, no fluff:</p>
<h3 id="heading-1-master-records-every-write-into-a-log"><strong>1. Master records every write into a log</strong></h3>
<ul>
<li><p>MySQL → Binary Log (binlog)</p>
</li>
<li><p>MongoDB → Oplog</p>
</li>
<li><p>PostgreSQL → WAL Log</p>
</li>
</ul>
<p>Example log entry:</p>
<pre><code class="lang-javascript">UPDATE products SET stock = <span class="hljs-number">50</span> WHERE id = <span class="hljs-number">101</span>
</code></pre>
<h3 id="heading-2-slaves-read-these-logs-continuously"><strong>2. Slaves read these logs continuously</strong></h3>
<p>A slave says:<br />➡️ “Master, give me the next log entry after position X.”</p>
<h3 id="heading-3-slaves-apply-the-changes-locally"><strong>3. Slaves apply the changes locally</strong></h3>
<p>Whatever operation appears in the log:</p>
<ul>
<li><p>INSERT</p>
</li>
<li><p>UPDATE</p>
</li>
<li><p>DELETE</p>
</li>
</ul>
<p>…slaves replay them in the same order.</p>
<h3 id="heading-4-after-applying-logs-slave-becomes-synced"><strong>4. After applying logs → slave becomes synced</strong></h3>
<p>This cycle repeats <strong>non-stop</strong>.</p>
<hr />
<h2 id="heading-replication-delay-lag-why-it-happens">⏳ Replication Delay (Lag): Why It Happens</h2>
<p>Slaves may fall behind due to:</p>
<ul>
<li><p>Slow network</p>
</li>
<li><p>Heavy load on slave</p>
</li>
<li><p>Large write burst on master</p>
</li>
<li><p>Slow disk I/O</p>
</li>
<li><p>Complex queries running on slave</p>
</li>
</ul>
<p>When the slave is behind, it shows <strong>old data</strong> temporarily.</p>
<hr />
<h2 id="heading-how-systems-handle-delays">🛠️ How Systems Handle Delays</h2>
<h3 id="heading-1-read-from-master-for-critical-operations"><strong>1️⃣ Read-from-master for critical operations</strong></h3>
<p>Apps often use:</p>
<ul>
<li><p>Normal read → from replica (fast)</p>
</li>
<li><p>Critical read (e.g., after update) → from master</p>
</li>
</ul>
<p>This avoids stale data issues.</p>
<hr />
<h3 id="heading-2-read-your-own-write-rules"><strong>2️⃣ “Read-your-own-write” rules</strong></h3>
<p>If a user writes something (POST/UPDATE):</p>
<ul>
<li><p>You route that user’s next read to the master only</p>
</li>
<li><p>Until replica catches up</p>
</li>
</ul>
<p>Frameworks like Rails, Laravel, Django already support this.</p>
<hr />
<h3 id="heading-3-monitoring-replication-lag"><strong>3️⃣ Monitoring replication lag</strong></h3>
<p>Tools measure:</p>
<pre><code class="lang-javascript">replication_lag = slave_last_applied_timestamp - master_timestamp
</code></pre>
<p>If lag &gt; X seconds:<br />→ temporarily stop sending reads to that replica</p>
<hr />
<h3 id="heading-4-write-concerns-mongodb"><strong>4️⃣ Write concerns (MongoDB)</strong></h3>
<p>MongoDB allows:</p>
<pre><code class="lang-javascript">{ <span class="hljs-attr">writeConcern</span>: { <span class="hljs-attr">w</span>: <span class="hljs-string">"majority"</span> } }
</code></pre>
<p>Meaning:<br />→ Write is successful only when <strong>most</strong> replicas have it</p>
<p>This reduces risk of data inconsistency.</p>
<hr />
<h3 id="heading-5-semi-sync-replication-mysql-postgresql"><strong>5️⃣ Semi-Sync Replication (MySQL / PostgreSQL)</strong></h3>
<p>Master waits until <strong>at least one slave</strong> confirms:</p>
<p>“Yep, I received the log.”</p>
<p>This avoids data loss.</p>
<hr />
<h2 id="heading-how-updates-flow-example-with-steps">🔄 How Updates Flow (Example With Steps)</h2>
<p>Let’s use a real example:</p>
<h3 id="heading-a-user-updates-their-profile">A user updates their profile:</h3>
<pre><code class="lang-javascript">UPDATE users SET name=<span class="hljs-string">'Nishikanta'</span> WHERE id=<span class="hljs-number">5</span>;
</code></pre>
<p>Here’s what happens:</p>
<h3 id="heading-step-1-master-writes-to-its-db"><strong>Step 1 — Master writes to its DB</strong></h3>
<p>Master updates row in its storage engine.</p>
<h3 id="heading-step-2-master-logs-the-change"><strong>Step 2 — Master logs the change</strong></h3>
<p>Master adds entry to binlog/oplog:</p>
<pre><code class="lang-javascript">{ <span class="hljs-attr">op</span>: <span class="hljs-string">"update"</span>, <span class="hljs-attr">id</span>: <span class="hljs-number">5</span>, <span class="hljs-attr">name</span>: <span class="hljs-string">"Nishikanta"</span> }
</code></pre>
<h3 id="heading-step-3-slaves-fetch-this-log"><strong>Step 3 — Slaves fetch this log</strong></h3>
<p>Replica 1 → “Give me log #520”<br />Replica 2 → “Give me log #520”</p>
<p>Master streams them.</p>
<h3 id="heading-step-4-slaves-apply-changes"><strong>Step 4 — Slaves apply changes</strong></h3>
<p>Replica updates:</p>
<pre><code class="lang-javascript">id=<span class="hljs-number">5</span> → name=Nishikanta
</code></pre>
<h3 id="heading-step-5-replicas-catch-up"><strong>Step 5 — Replicas catch up</strong></h3>
<p>Once all logs are applied → they’re in sync.</p>
<hr />
<h2 id="heading-diagram-how-replication-works-clean-blog-ready">📊 Diagram: How Replication Works (Clean + Blog-Ready)</h2>
<pre><code class="lang-javascript">                ┌───────────────────────┐
                │       MASTER          │
                │ (Primary - All Writes)│
                └───────┬───────────────┘
                        │
        Writes → Binlog/Oplog generated
                        │
        ┌───────────────┴───────────────┐
        │                               │
┌────────────────────┐        ┌────────────────────┐
│    SLAVE #<span class="hljs-number">1</span>        │        │    SLAVE #<span class="hljs-number">2</span>        │
│ (Replica - Reads)  │        │ (Replica - Reads)  │
└───────┬────────────┘        └──────────┬─────────┘
        │                                 │
        │    Fetch log continuously       │
        │        Apply updates            │
        │     Handle replication lag      │
        ▼                                 ▼
  Up-to-date copy <span class="hljs-keyword">of</span> DB            Up-to-date copy <span class="hljs-keyword">of</span> DB
</code></pre>
<h1 id="heading-conclusion"><strong>🟩 Conclusion</strong></h1>
<p>Master–slave (primary–replica) replication is one of the most essential techniques for building <strong>fast, reliable, and highly available</strong> modern applications. By separating write operations to the master and distributing read operations across multiple replicas, systems can handle far more traffic, avoid downtime, and guarantee that data remains safe even if a server fails.</p>
<p>Although replication may introduce small delays (replication lag), most real-world systems manage this easily through smart routing, read-after-write strategies, and monitoring tools. The result is a database architecture that provides <strong>scalability, resilience, and real-time data redundancy</strong> — all without changing how your application writes data.</p>
<p>Whether you’re using MongoDB, MySQL, PostgreSQL, or Redis, mastering replication is a core skill that enables you to build applications that stay fast, stay online, and scale gracefully as your users grow.</p>
]]></content:encoded></item><item><title><![CDATA[Behind the Scenes — Building NexoDrive and Reinventing File Sharing]]></title><description><![CDATA[Over the past few weeks, we (I and Sumeet Naik ) built something. A simple tool that turns chaotic Google Drives into clean, user-friendly libraries.
We call it NexoDrive.
But this post isn’t just a product announcement — it’s the story behind why we...]]></description><link>https://blog.nishikanta.in/behind-the-scenes-building-nexodrive-and-reinventing-file-sharing</link><guid isPermaLink="true">https://blog.nishikanta.in/behind-the-scenes-building-nexodrive-and-reinventing-file-sharing</guid><category><![CDATA[nexodrive]]></category><category><![CDATA[Developer]]></category><category><![CDATA[india]]></category><category><![CDATA[notes]]></category><dc:creator><![CDATA[Nishikanta Ray]]></dc:creator><pubDate>Mon, 24 Nov 2025 19:25:04 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1764012227862/37bb8372-8e09-4101-83db-3269a63adb4e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Over the past few weeks, we (I and <a class="user-mention" href="https://hashnode.com/@sumeetnaik">Sumeet Naik</a> ) built something. A simple tool that turns chaotic Google Drives into clean, user-friendly libraries.</p>
<p>We call it <strong>NexoDrive</strong>.</p>
<p>But this post isn’t just a product announcement — it’s the story behind why we built it and how it solves a surprisingly common problem.</p>
<hr />
<h2 id="heading-the-pain-point-wheres-the-file"><strong>The Pain Point: “Where’s the File?”</strong></h2>
<p>Whether you're building software, running a business, or studying in college — you’ve probably heard (or said):</p>
<ul>
<li><p>“Can you resend the link?”</p>
</li>
<li><p>“I can’t find the file.”</p>
</li>
<li><p>“Where did you put this?”</p>
</li>
<li><p>“Do I need permission?”</p>
</li>
</ul>
<p>Google Drive is powerful, but for sharing large sets of files, it falls apart fast.<br />People struggle to navigate deep folder structures.<br />Permission management becomes a nightmare.<br />Links get mixed up.<br />You spend more time helping people find files than actually working.</p>
<p>We ran into this problem while building a notes-sharing platform for a college. With <strong>1,500+ users</strong>, Drive chaos became a daily issue.</p>
<p>So we built a solution.</p>
<hr />
<h2 id="heading-the-idea-a-display-layer-for-google-drive"><strong>The Idea: A Display Layer for Google Drive</strong></h2>
<p>We didn’t want to replace Drive.<br />Drive is great for storage.</p>
<p>We wanted to replace how people <em>view</em> your Drive.</p>
<p>The concept of NexoDrive became clear:</p>
<ul>
<li><p>The owner stores everything in Google Drive (just like always)</p>
</li>
<li><p>NexoDrive reads the Drive using read-only access</p>
</li>
<li><p>We index everything and create a clean interface</p>
</li>
<li><p>End users can browse files instantly without touching Google Drive</p>
</li>
</ul>
<p>A simple idea — but incredibly effective.</p>
<hr />
<h2 id="heading-security-first"><strong>Security First</strong></h2>
<p>When someone hears “access to your Drive,” it’s natural to worry.</p>
<p>So from day one, we designed NexoDrive to be safe:</p>
<ul>
<li><p>Only <em>read-only</em> access (no editing, deleting, downloading, or moving files)</p>
</li>
<li><p>You choose exactly what to show</p>
</li>
<li><p>Your Drive stays untouched</p>
</li>
<li><p>All indexing is internal and controlled</p>
</li>
</ul>
<p>Your privacy stays intact.</p>
<hr />
<h2 id="heading-what-the-user-sees"><strong>What the User Sees</strong></h2>
<p>This is the magic.</p>
<p>Instead of the messy Google Drive UI, they get:</p>
<ul>
<li><p>Clean file lists</p>
</li>
<li><p>Search</p>
</li>
<li><p>Instant loading</p>
</li>
<li><p>No login needed (unless you want it)</p>
</li>
<li><p>Only the files you choose to display</p>
</li>
</ul>
<p>It looks like a custom-built resource library — without you needing to build anything.</p>
<hr />
<h2 id="heading-early-feedback"><strong>Early Feedback</strong></h2>
<p>When we deployed this for the college platform:</p>
<p>💬 Students said: <em>“Finally, everything is easy to find.”</em><br />💬 Teachers said: <em>“We don’t have to manage permissions anymore.”</em><br />💬 Admins said: <em>“This saves hours every week.”</em></p>
<p>That’s when we realized the potential.</p>
<hr />
<h2 id="heading-who-this-is-for"><strong>Who This Is For</strong></h2>
<p>NexoDrive can help:</p>
<ul>
<li><p>Educational institutions</p>
</li>
<li><p>Freelancers &amp; agencies</p>
</li>
<li><p>Coaches &amp; mentors</p>
</li>
<li><p>Businesses sharing documents</p>
</li>
<li><p>Creators delivering files</p>
</li>
<li><p>Teams with shared resources</p>
</li>
<li><p>Communities hosting notes/materials</p>
</li>
</ul>
<p>If your Google Drive is the heart of your workflow, NexoDrive becomes the face of it.</p>
<hr />
<h2 id="heading-try-it-out"><strong>Try It Out</strong></h2>
<p>We’ve launched our public beta here:<br />👉 <a target="_blank" href="https://nexodrive.xyz"><strong>https://nexodrive.xyz</strong></a></p>
<p>This is just version 1. We have big plans for:</p>
<ul>
<li><p>Custom branding</p>
</li>
<li><p>Analytics</p>
</li>
<li><p>Access control</p>
</li>
<li><p>Better search</p>
</li>
<li><p>Multi-account support</p>
</li>
<li><p>Embeddable file viewers</p>
</li>
</ul>
<p>If you have ideas, feedback, or feature requests — we’d love to hear them.</p>
<p>Thanks for reading, and welcome to the beginning of a much cleaner file-sharing experience. 🚀</p>
<hr />
]]></content:encoded></item><item><title><![CDATA[🧭 How to Disable Source Maps in Vite + React (and Why You Might Want To)]]></title><description><![CDATA[When you build a React app with Vite, it automatically generates source maps — files that let your browser map compiled code (like index-abcd123.js) back to your original React source (App.jsx).
This is great for debugging, but not always ideal for p...]]></description><link>https://blog.nishikanta.in/how-to-disable-source-maps-in-vite-react-and-why-you-might-want-to</link><guid isPermaLink="true">https://blog.nishikanta.in/how-to-disable-source-maps-in-vite-react-and-why-you-might-want-to</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[js]]></category><category><![CDATA[React]]></category><category><![CDATA[vite]]></category><dc:creator><![CDATA[Nishikanta Ray]]></dc:creator><pubDate>Sat, 08 Nov 2025 16:21:53 GMT</pubDate><content:encoded><![CDATA[<p>When you build a React app with <a target="_blank" href="https://vitejs.dev/">Vite</a>, <a target="_blank" href="https://vitejs.dev/">it</a> automatically generates <strong>source maps</strong> — files that let your browser map compiled code (like <code>index-abcd123.js</code>) back to your original React source (<code>App.jsx</code>).</p>
<p>This is great for debugging, but not <a target="_blank" href="https://vitejs.dev/">alwa</a>ys ideal for <strong>production builds</strong>, where you may want to:</p>
<ul>
<li><p>Reduce bundle size</p>
</li>
<li><p>Hide original s<a target="_blank" href="https://vitejs.dev/">ourc</a>e code</p>
</li>
<li><p>Speed up <a target="_blank" href="https://vitejs.dev/">bui</a>ld times</p>
</li>
</ul>
<p>Let’s explore <a target="_blank" href="https://vitejs.dev/">how</a> to <strong>control source m</strong><a target="_blank" href="https://vitejs.dev/"><strong>ap g</strong></a><strong>eneration</strong> in a Vite + React project, with examples and real behavior differences.</p>
<hr />
<h2 id="heading-what-are-source-maps">🧩 What Are Source Maps?</h2>
<p>When Vite b<a target="_blank" href="https://vitejs.dev/">undl</a>es your React code, it <a target="_blank" href="https://vitejs.dev/">com</a>piles and minifies everything into optimized JS files like this:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>)</span>{<span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Button clicked!"</span>)}<span class="hljs-keyword">export</span>{App <span class="hljs-keyword">as</span> <span class="hljs-keyword">default</span>};
</code></pre>
<p>That’s unreadable — but your browser’<a target="_blank" href="https://vitejs.dev/">s De</a>vTools can use a <code>.map</code> file (like <a target="_blank" href="http://index-abcd123.js.map"><code>index-abcd123.js.map</code></a>) to map errors and console logs back to your <strong>original JSX source</strong>.</p>
<p>Without source maps, errors in produc<a target="_blank" href="https://vitejs.dev/">tion</a> will point to something like:</p>
<pre><code class="lang-bash">index-abcd123.js:1:10243
</code></pre>
<p>With source maps enabled, they show:</p>
<pre><code class="lang-bash">App.jsx:5:11
</code></pre>
<hr />
<h2 id="heading-controlling-source-maps-in-vite">⚙️ Controlling Source Maps in Vite</h2>
<p>O<a target="_blank" href="https://vitejs.dev/">pen</a> your <code>vite.config.js</code> file and con<a target="_blank" href="https://vitejs.dev/">figu</a>re the <code>build.sourcemap</code> option.</p>
<h3 id="heading-example-disable-source-maps-in-producthttpsvitejsdevion">✅ Example: Disable Source Maps in Pro<a target="_blank" href="https://vitejs.dev/">duct</a>ion</h3>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { defineConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">'vite'</span>
<span class="hljs-keyword">import</span> react <span class="hljs-keyword">from</span> <span class="hljs-string">'@vitejs/plugin-react'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig(<span class="hljs-function">(<span class="hljs-params">{ mode }</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> isProduction = mode === <span class="hljs-string">'production'</span>

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">plugins</span>: [react()],
    <span class="hljs-attr">build</span>: {
      <span class="hljs-attr">sourcemap</span>: !isProduction, <span class="hljs-comment">// ✅ Enabled in dev, disabled in prod</span>
    },
  }
})
</code></pre>
<h3 id="heading-explanation">Explanation:</h3>
<ul>
<li><p>In <strong>development (</strong><code>npm r</code><a target="_blank" href="https://vitejs.dev/"><code>un d</code></a><code>ev</code>), <code>sourcem</code><a target="_blank" href="https://vitejs.dev/"><code>ap</code> i</a>s <code>true</code>, so you can debug easily.</p>
</li>
<li><p>In <strong>production (</strong><code>npm run build</code>), <code>source</code><a target="_blank" href="https://vitejs.dev/"><code>map</code></a> is <code>false</code>, so <code>.map</code> files are not generated.</p>
</li>
</ul>
<hr />
<h2 id="heading-example-simple-react-component">🧪 Example: Simple React Component</h2>
<p><code>s</code><a target="_blank" href="https://vitejs.dev/"><code>rc/A</code></a><code>pp.jsx</code></p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> handleClick = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Button clicked! 🔘'</span>)
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Demo error!'</span>)
  }

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">padding:</span> <span class="hljs-attr">20</span> }}&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Hello Vite + React 👋<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{handleClick}</span>&gt;</span>Click me<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  )
}
</code></pre>
<hr />
<h2 id="heading-development-mode-npm-run-dev">🧰 Development Mode (<code>npm run dev</code>)</h2>
<p>In <a target="_blank" href="https://vitejs.dev/"><strong>dev</strong></a> <strong>mode</strong>, Vite automatically enabl<a target="_blank" href="https://vitejs.dev/">es s</a>ource maps.</p>
<p>If you click the button and trigger a<a target="_blank" href="https://vitejs.dev/">n er</a>ror, you’ll see:</p>
<pre><code class="lang-bash">Error: Demo error!
    at handleClick (App.jsx:5:11)
</code></pre>
<p>You can open DevTools → <strong>Sources tab</strong>, <a target="_blank" href="https://vitejs.dev/">and</a> see the actual <code>src/App.jsx</code> file, just like your editor.</p>
<p>✅ Perfect for debugging.</p>
<hr />
<h2 id="heading-productionhttpsvitejsdev-mode-npm-run-build-vitehttpsvitejsdev-preview">🚀 Product<a target="_blank" href="https://vitejs.dev/">ion</a> Mode (<code>npm run build</code> + <code>v</code><a target="_blank" href="https://vitejs.dev/"><code>ite</code></a> <code>preview</code>)</h2>
<p>In <strong>production mode</strong>, since <code>sourcemap:</code> <a target="_blank" href="https://vitejs.dev/"><code>fals</code></a><code>e</code>, your <code>dist/</code> folder will look like:</p>
<pre><code class="lang-bash">dist/
 ├── assets/
 │    ├── index-xyz123.js
 │    ├── index-xyz123.css
 ├── index.html
</code></pre>
<p>No <code>.map</code> files appear.</p>
<p>Now, when an e<a target="_blank" href="https://vitejs.dev/">rror</a> happens in product<a target="_blank" href="https://vitejs.dev/">ion,</a> your browser shows:</p>
<pre><code class="lang-bash">Error: Demo error!
    at Object.onclick (index-xyz123.js:1:17245)
</code></pre>
<p>No mention of <code>App.jsx</code> — just the mini<a target="_blank" href="https://vitejs.dev/">fied</a> output.<br />That means your original source isn’t exposed.</p>
<hr />
<h2 id="heading-why-disable-source-maps-in-productionhttpsvitejsdev">🛡️ Why Disable Source Maps in Produc<a target="_blank" href="https://vitejs.dev/">tion</a>?</h2>
<ol>
<li><p><strong>Protect your source code</strong><br /> <code>.map</code> files o<a target="_blank" href="https://vitejs.dev/">ften</a> include your original JSX, comments, and variable names — valuable intellectual property.</p>
</li>
<li><p><strong>Reduce build size</strong><br /> <code>.map</code> files can be l<a target="_blank" href="https://vitejs.dev/">arge</a> (sometimes larger than your JS bundle).</p>
</li>
<li><p><strong>Improve security</strong><br /> Prevents attackers f<a target="_blank" href="https://vitejs.dev/">rom</a> reverse-engineering your logic directly from production builds.</p>
</li>
</ol>
<hr />
<h2 id="heading-optional-hide-source-keep-trace-infohttpsvitejsdev">⚖️ Optional: Hide Source, Keep Trace <a target="_blank" href="https://vitejs.dev/">Info</a></h2>
<p>If you still want debugging info but <a target="_blank" href="https://vitejs.dev/">don’</a>t want to expose source content:</p>
<pre><code class="lang-bash">build: {
  sourcemap: <span class="hljs-literal">true</span>,
  sourcemapExcludeSources: <span class="hljs-literal">true</span>,
}
</code></pre>
<p>This keeps <code>.map</code> files but <strong>excludes yo</strong><a target="_blank" href="https://vitejs.dev/"><strong>ur o</strong></a><strong>riginal code</strong> — so errors still have line numbers, but no readable source.</p>
<hr />
<h2 id="heading-quick-summary">🔍 Quick Summary</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Mode</td><td>Command</td><td><a target="_blank" href="https://vitejs.dev/">S</a>ourcemap</td><td>Behav<a target="_blank" href="https://vitejs.dev/">ior</a></td></tr>
</thead>
<tbody>
<tr>
<td><a target="_blank" href="https://vitejs.dev/">Dev</a>elopmen<a target="_blank" href="https://vitejs.dev/">t</a></td><td><code>npm run</code> <a target="_blank" href="https://vitejs.dev/"><code>dev</code></a></td><td>✅ Yes</td><td><a target="_blank" href="https://vitejs.dev/">Ea</a>sy debuggin<a target="_blank" href="https://vitejs.dev/">g</a></td></tr>
<tr>
<td>Producti<a target="_blank" href="https://vitejs.dev/">on</a></td><td><code>npm</code> <a target="_blank" href="https://vitejs.dev/"><code>run</code></a> <code>build</code></td><td>❌ No</td><td><a target="_blank" href="https://vitejs.dev/">Se</a>cure, opti<a target="_blank" href="https://vitejs.dev/">mize</a>d build</td></tr>
<tr>
<td><a target="_blank" href="https://vitejs.dev/">Opti</a>onal</td><td>—</td><td>⚙️ <code>sourcemapExcludeS</code><a target="_blank" href="https://vitejs.dev/"><code>ourc</code></a><code>es: true</code></td><td>K<a target="_blank" href="https://vitejs.dev/">eeps</a> maps, hides code</td></tr>
</tbody>
</table>
</div><hr />
<h2 id="heading-final-thouhttpsvitejsdevghts">💬 Final <a target="_blank" href="https://vitejs.dev/">Thou</a>ghts</h2>
<p>Disabling source m<a target="_blank" href="https://vitejs.dev/">aps</a> in production i<a target="_blank" href="https://vitejs.dev/">s a</a> <strong>small but powerful optimization</strong> — your build gets smaller, safer, and faster.<br />Meanwhile, you still keep full debugging comfort during development.</p>
<p>For most React projects using Vite, t<a target="_blank" href="https://vitejs.dev/">he s</a>nippet below is all you need:</p>
<pre><code class="lang-bash">build: { sourcemap: process.env.NODE_ENV !== <span class="hljs-string">'production'</span> }
</code></pre>
]]></content:encoded></item><item><title><![CDATA[🚀 Understanding Streams in Node.js — With Real-World Examples]]></title><description><![CDATA[If you’ve ever handled large files, live data, or real-time logs, you’ve probably hit memory or performance issues.That’s where Streams come in — one of the most underrated superpowers of Node.js.
In this post, we’ll break down:

What streams are

Wh...]]></description><link>https://blog.nishikanta.in/understanding-streams-in-nodejs-with-real-world-examples</link><guid isPermaLink="true">https://blog.nishikanta.in/understanding-streams-in-nodejs-with-real-world-examples</guid><category><![CDATA[Node.js]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Streams]]></category><category><![CDATA[streams in nodejs]]></category><category><![CDATA[backend]]></category><category><![CDATA[webdev]]></category><dc:creator><![CDATA[Nishikanta Ray]]></dc:creator><pubDate>Sun, 12 Oct 2025 15:47:27 GMT</pubDate><content:encoded><![CDATA[<p>If you’ve ever handled <strong>large files</strong>, <strong>live data</strong>, or <strong>real-time logs</strong>, you’ve probably hit memory or performance issues.<br />That’s where <strong>Streams</strong> come in — one of the most underrated superpowers of Node.js.</p>
<p>In this post, we’ll break down:</p>
<ul>
<li><p>What streams are</p>
</li>
<li><p>Why they matter</p>
</li>
<li><p>How to use them effectively</p>
</li>
<li><p>Real-world examples to make it all click</p>
</li>
</ul>
<hr />
<h2 id="heading-what-are-streams">💡 What Are Streams?</h2>
<p>A <strong>Stream</strong> is a continuous flow of data that you can <strong>read</strong> or <strong>write</strong> piece by piece — instead of loading everything into memory at once.</p>
<p>Think of watching a <strong>YouTube video</strong>:<br />You don’t wait for the full video to download before it starts playing. You get data <strong>chunk by chunk</strong> — that’s streaming.</p>
<p>Node.js uses the same concept to efficiently handle large data sources like files, APIs, or network sockets.</p>
<hr />
<h2 id="heading-why-use-streams">⚙️ Why Use Streams?</h2>
<p>Here’s why developers love streams:</p>
<ul>
<li><p>🧠 <strong>Memory-efficient</strong> — process data chunk by chunk</p>
</li>
<li><p>⚡ <strong>Faster</strong> — no waiting for the entire file to load</p>
</li>
<li><p>🔄 <strong>Composable</strong> — easily connect sources and destinations</p>
</li>
<li><p>💬 <strong>Ideal for real-time systems</strong> — handle continuous data flow</p>
</li>
</ul>
<p>Without streams:</p>
<pre><code class="lang-bash">const fs = require(<span class="hljs-string">'fs'</span>);
const data = fs.readFileSync(<span class="hljs-string">'largefile.txt'</span>, <span class="hljs-string">'utf8'</span>); // Blocks entire file <span class="hljs-keyword">in</span> memory
console.log(data);
</code></pre>
<p>With streams:</p>
<pre><code class="lang-bash">const fs = require(<span class="hljs-string">'fs'</span>);
const stream = fs.createReadStream(<span class="hljs-string">'largefile.txt'</span>, <span class="hljs-string">'utf8'</span>);

stream.on(<span class="hljs-string">'data'</span>, chunk =&gt; console.log(<span class="hljs-string">'Chunk:'</span>, chunk));
stream.on(<span class="hljs-string">'end'</span>, () =&gt; console.log(<span class="hljs-string">'Done!'</span>));
</code></pre>
<p>🟢 <strong>Result:</strong> You process data as it arrives — faster and with minimal memory usage.</p>
<hr />
<h2 id="heading-types-of-streams-in-nodejs">🧩 Types of Streams in Node.js</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Type</td><td>Description</td><td>Example</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Readable</strong></td><td>Stream you can read from</td><td>Reading a file</td></tr>
<tr>
<td><strong>Writable</strong></td><td>Stream you can write to</td><td>Writing to a file</td></tr>
<tr>
<td><strong>Duplex</strong></td><td>Can read &amp; write</td><td>TCP sockets</td></tr>
<tr>
<td><strong>Transform</strong></td><td>Modifies data as it flows</td><td>Compression, encryption</td></tr>
</tbody>
</table>
</div><hr />
<h2 id="heading-example-1-reading-a-file-with-a-stream">📘 Example 1: Reading a File with a Stream</h2>
<pre><code class="lang-bash">const fs = require(<span class="hljs-string">'fs'</span>);

const readableStream = fs.createReadStream(<span class="hljs-string">'input.txt'</span>, {
  encoding: <span class="hljs-string">'utf8'</span>,
  highWaterMark: 16 * 1024 // optional: chunk size (16KB)
});

readableStream.on(<span class="hljs-string">'data'</span>, (chunk) =&gt; {
  console.log(<span class="hljs-string">'Received chunk:'</span>, chunk);
});

readableStream.on(<span class="hljs-string">'end'</span>, () =&gt; {
  console.log(<span class="hljs-string">'Finished reading file!'</span>);
});
</code></pre>
<h3 id="heading-explanation">🧠 Explanation:</h3>
<ul>
<li><p>The file is <strong>read in chunks</strong> instead of one go.</p>
</li>
<li><p><code>'data'</code> event fires for each chunk.</p>
</li>
<li><p><code>'end'</code> event triggers once reading is complete.</p>
</li>
</ul>
<p>This makes it ideal for huge files or streaming responses.</p>
<hr />
<h2 id="heading-example-2-writing-data-using-a-stream">🖋️ Example 2: Writing Data Using a Stream</h2>
<pre><code class="lang-bash">const fs = require(<span class="hljs-string">'fs'</span>);

const writableStream = fs.createWriteStream(<span class="hljs-string">'output.txt'</span>);

writableStream.write(<span class="hljs-string">'Hello, '</span>);
writableStream.write(<span class="hljs-string">'Streams are awesome!\n'</span>);
writableStream.end(() =&gt; console.log(<span class="hljs-string">'File writing completed!'</span>));
</code></pre>
<h3 id="heading-whats-happening">What’s Happening:</h3>
<p>Each <code>.write()</code> sends a chunk of data to the file, and <code>.end()</code> signals you’re done writing.</p>
<hr />
<h2 id="heading-example-3-piping-streams-copying-files">🔗 Example 3: Piping Streams (Copying Files)</h2>
<p>The <code>.pipe()</code> method is one of the coolest features of streams. It lets you connect a <strong>readable stream</strong> directly to a <strong>writable stream</strong> — like connecting water pipes.</p>
<pre><code class="lang-bash">const fs = require(<span class="hljs-string">'fs'</span>);

const readable = fs.createReadStream(<span class="hljs-string">'input.txt'</span>);
const writable = fs.createWriteStream(<span class="hljs-string">'output.txt'</span>);

readable.pipe(writable);

console.log(<span class="hljs-string">'File copied successfully!'</span>);
</code></pre>
<p>✅ No manual chunk handling<br />✅ Automatically manages flow and backpressure</p>
<hr />
<h2 id="heading-example-4-transform-stream-file-compression">🌀 Example 4: Transform Stream (File Compression)</h2>
<p>Transform streams let you <strong>modify data</strong> while it’s flowing through the stream.</p>
<pre><code class="lang-bash">const fs = require(<span class="hljs-string">'fs'</span>);
const zlib = require(<span class="hljs-string">'zlib'</span>);

const gzip = zlib.createGzip();

fs.createReadStream(<span class="hljs-string">'input.txt'</span>)
  .pipe(gzip)
  .pipe(fs.createWriteStream(<span class="hljs-string">'input.txt.gz'</span>));

console.log(<span class="hljs-string">'File compressed successfully!'</span>);
</code></pre>
<p>This is exactly how compression, encryption, or parsing can happen in real-time systems.</p>
<hr />
<h2 id="heading-real-world-use-cases">🧠 Real-World Use Cases</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Scenario</td><td>How Streams Help</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Video streaming</strong></td><td>Send data chunks as they’re ready</td></tr>
<tr>
<td><strong>Log monitoring</strong></td><td>Continuously read and process logs</td></tr>
<tr>
<td><strong>Data transformation</strong></td><td>Compress, encrypt, or modify data on the fly</td></tr>
<tr>
<td><strong>File uploads/downloads</strong></td><td>Efficiently handle large files</td></tr>
</tbody>
</table>
</div><hr />
<h2 id="heading-bonus-stream-chaining">⚡ Bonus: Stream Chaining</h2>
<p>You can chain multiple transformations together:</p>
<pre><code class="lang-bash">fs.createReadStream(<span class="hljs-string">'input.txt'</span>)
  .pipe(zlib.createGzip())
  .pipe(fs.createWriteStream(<span class="hljs-string">'output.txt.gz'</span>))
  .on(<span class="hljs-string">'finish'</span>, () =&gt; console.log(<span class="hljs-string">'Done!'</span>));
</code></pre>
<p>Here, the data flows from:<br /><code>Readable → Transform (gzip) → Writable</code></p>
<hr />
<h2 id="heading-key-takeaways">🧭 Key Takeaways</h2>
<ul>
<li><p>Streams process data <strong>chunk-by-chunk</strong> — not all at once.</p>
</li>
<li><p>They are <strong>memory-efficient</strong> and <strong>highly performant</strong>.</p>
</li>
<li><p>The <code>.pipe()</code> method makes it super easy to connect streams.</p>
</li>
<li><p>Ideal for large files, real-time APIs, or network operations.</p>
</li>
</ul>
<hr />
<h2 id="heading-final-thoughts">🎯 Final Thoughts</h2>
<p>Streams might seem tricky at first, but once you get comfortable, they’ll completely change how you think about I/O in Node.js.<br />They’re the backbone of performance in apps that deal with <strong>continuous, high-volume data</strong>.</p>
]]></content:encoded></item><item><title><![CDATA[🟢 Beginner’s Guide to n8n Automation]]></title><description><![CDATA[Build a “Weather Data Every Minute” Workflow (with & without Docker)
Automation can save hours of manual work—but coding full-blown services is daunting if you’re new to development.n8n changes that by letting you drag, drop, and connect nodes to cre...]]></description><link>https://blog.nishikanta.in/beginners-guide-to-n8n-automation</link><guid isPermaLink="true">https://blog.nishikanta.in/beginners-guide-to-n8n-automation</guid><category><![CDATA[n8n]]></category><dc:creator><![CDATA[Nishikanta Ray]]></dc:creator><pubDate>Wed, 17 Sep 2025 17:28:01 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-build-a-weather-data-every-minute-workflow-with-amp-without-docker">Build a “Weather Data Every Minute” Workflow (with &amp; without Docker)</h2>
<p>Automation can save hours of manual work—but coding full-blown services is daunting if you’re new to development.<br /><strong>n8n</strong> changes that by letting you drag, drop, and connect nodes to create workflows visually.</p>
<p>In this guide you’ll learn:</p>
<ul>
<li><p>What n8n is and why it’s useful</p>
</li>
<li><p>How to <strong>install n8n two ways</strong>: with Docker and without Docker</p>
</li>
<li><p>How to <strong>import and run a sample workflow</strong> that generates random weather data every minute</p>
</li>
<li><p>How each node in the workflow works</p>
</li>
<li><p>Extra ideas to help you create your own automations</p>
</li>
</ul>
<p>Let’s get started!</p>
<hr />
<h2 id="heading-1-what-is-n8n">1️⃣ What Is n8n?</h2>
<p>n8n (pronounced “n-eight-n”) is an open-source automation platform.<br />Think of it like a programmable version of tools such as Zapier or IFTTT:</p>
<ul>
<li><p><strong>Visual builder</strong> – create flows by connecting nodes instead of writing boilerplate code.</p>
</li>
<li><p><strong>Over 350 ready-made nodes</strong> – HTTP requests, Slack, Google Sheets, Databases, GitHub, and many more.</p>
</li>
<li><p><strong>Self-hosted or cloud</strong> – run on your own server for full control.</p>
</li>
</ul>
<hr />
<h2 id="heading-2-install-n8n">2️⃣ Install n8n</h2>
<p>You can run n8n either inside a <strong>Docker container</strong> (simple and portable) or directly on your system (no Docker needed).<br />Pick the method you prefer.</p>
<h3 id="heading-a-install-with-docker-recommended">A. Install with Docker (Recommended)</h3>
<ol>
<li><p><strong>Create a folder</strong> for n8n:</p>
<pre><code class="lang-bash"> mkdir n8n &amp;&amp; <span class="hljs-built_in">cd</span> n8n
</code></pre>
</li>
<li><p><strong>docker-compose.yml</strong>:</p>
<pre><code class="lang-bash"> version: <span class="hljs-string">"3.9"</span>
 services:
   n8n:
     image: n8nio/n8n
     ports:
       - <span class="hljs-string">"5678:5678"</span>
     environment:
       - N8N_BASIC_AUTH_ACTIVE=<span class="hljs-literal">true</span>
       - N8N_BASIC_AUTH_USER=admin
       - N8N_BASIC_AUTH_PASSWORD=strongpassword
       - TZ=Asia/Kolkata
     volumes:
       - ./n8n_data:/home/node/.n8n
</code></pre>
</li>
<li><p><strong>Start it</strong>:</p>
<pre><code class="lang-bash"> docker compose up -d
</code></pre>
</li>
<li><p>Visit <strong>http://&lt;your-server-ip&gt;:5678</strong> and log in with <code>admin / strongpassword</code>.</p>
</li>
</ol>
<hr />
<h3 id="heading-b-install-without-docker-native-nodejs">B. Install without Docker (Native Node.js)</h3>
<p>If you’d rather skip Docker:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Update packages</span>
sudo apt update &amp;&amp; sudo apt upgrade -y

<span class="hljs-comment"># Install Node.js 18 or newer</span>
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt install -y nodejs build-essential

<span class="hljs-comment"># Install n8n globally</span>
sudo npm install -g n8n
</code></pre>
<p>Optional but recommended security:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">export</span> N8N_BASIC_AUTH_ACTIVE=<span class="hljs-literal">true</span>
<span class="hljs-built_in">export</span> N8N_BASIC_AUTH_USER=admin
<span class="hljs-built_in">export</span> N8N_BASIC_AUTH_PASSWORD=strongpassword
</code></pre>
<p>Run:</p>
<pre><code class="lang-bash">n8n
</code></pre>
<p>Open <strong>http://&lt;your-server-ip&gt;:5678</strong> in a browser.</p>
<blockquote>
<p>✅ Tip: For production, create a systemd service so n8n starts automatically on reboot.</p>
</blockquote>
<hr />
<h2 id="heading-3-import-the-weather-workflow">3️⃣ Import the Weather Workflow</h2>
<p>Download or copy the following JSON into a file called <strong>weather-workflow.json</strong>:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Weather Data Every Minute (Enhanced)"</span>,
  <span class="hljs-attr">"nodes"</span>: [
    {
      <span class="hljs-attr">"parameters"</span>: {
        <span class="hljs-attr">"triggerTimes"</span>: [
          {
            <span class="hljs-attr">"mode"</span>: <span class="hljs-string">"everyMinute"</span>
          }
        ]
      },
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Cron"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"n8n-nodes-base.cron"</span>,
      <span class="hljs-attr">"typeVersion"</span>: <span class="hljs-number">1</span>,
      <span class="hljs-attr">"position"</span>: [<span class="hljs-number">150</span>, <span class="hljs-number">300</span>]
    },
    {
      <span class="hljs-attr">"parameters"</span>: {
        <span class="hljs-attr">"values"</span>: {
          <span class="hljs-attr">"string"</span>: [
            {
              <span class="hljs-attr">"name"</span>: <span class="hljs-string">"city"</span>,
              <span class="hljs-attr">"value"</span>: <span class="hljs-string">"London"</span>
            }
          ]
        },
        <span class="hljs-attr">"options"</span>: {}
      },
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Set City"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"n8n-nodes-base.set"</span>,
      <span class="hljs-attr">"typeVersion"</span>: <span class="hljs-number">1</span>,
      <span class="hljs-attr">"position"</span>: [<span class="hljs-number">350</span>, <span class="hljs-number">300</span>]
    },
    {
      <span class="hljs-attr">"parameters"</span>: {
        <span class="hljs-attr">"functionCode"</span>: <span class="hljs-string">"const conditions = ['Sunny', 'Cloudy', 'Rainy', 'Windy', 'Snowy'];\nreturn [{\n  json: {\n    city: $json.city,\n    temperature: Math.floor(Math.random() * 16) + 15, // 15-30\n    condition: conditions[Math.floor(Math.random() * conditions.length)],\n    humidity: Math.floor(Math.random() * 41) + 40, // 40-80\n    wind: Math.floor(Math.random() * 21) + 5 // 5-25\n  }\n}];"</span>
      },
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Random Weather"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"n8n-nodes-base.function"</span>,
      <span class="hljs-attr">"typeVersion"</span>: <span class="hljs-number">1</span>,
      <span class="hljs-attr">"position"</span>: [<span class="hljs-number">550</span>, <span class="hljs-number">300</span>]
    },
    {
      <span class="hljs-attr">"parameters"</span>: {
        <span class="hljs-attr">"value"</span>: <span class="hljs-string">"={{$now}}"</span>,
        <span class="hljs-attr">"custom"</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">"toFormat"</span>: <span class="hljs-string">"yyyy-MM-dd HH:mm:ss"</span>,
        <span class="hljs-attr">"options"</span>: {}
      },
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Date &amp; Time"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"n8n-nodes-base.dateTime"</span>,
      <span class="hljs-attr">"typeVersion"</span>: <span class="hljs-number">1</span>,
      <span class="hljs-attr">"position"</span>: [<span class="hljs-number">750</span>, <span class="hljs-number">300</span>]
    },
    {
      <span class="hljs-attr">"parameters"</span>: {
        <span class="hljs-attr">"responseMode"</span>: <span class="hljs-string">"lastNode"</span>,
        <span class="hljs-attr">"options"</span>: {}
      },
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Respond to Webhook"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"n8n-nodes-base.respondToWebhook"</span>,
      <span class="hljs-attr">"typeVersion"</span>: <span class="hljs-number">1</span>,
      <span class="hljs-attr">"position"</span>: [<span class="hljs-number">950</span>, <span class="hljs-number">300</span>]
    }
  ],
  <span class="hljs-attr">"connections"</span>: {
    <span class="hljs-attr">"Cron"</span>: {
      <span class="hljs-attr">"main"</span>: [
        [
          {
            <span class="hljs-attr">"node"</span>: <span class="hljs-string">"Set City"</span>,
            <span class="hljs-attr">"type"</span>: <span class="hljs-string">"main"</span>,
            <span class="hljs-attr">"index"</span>: <span class="hljs-number">0</span>
          }
        ]
      ]
    },
    <span class="hljs-attr">"Set City"</span>: {
      <span class="hljs-attr">"main"</span>: [
        [
          {
            <span class="hljs-attr">"node"</span>: <span class="hljs-string">"Random Weather"</span>,
            <span class="hljs-attr">"type"</span>: <span class="hljs-string">"main"</span>,
            <span class="hljs-attr">"index"</span>: <span class="hljs-number">0</span>
          }
        ]
      ]
    },
    <span class="hljs-attr">"Random Weather"</span>: {
      <span class="hljs-attr">"main"</span>: [
        [
          {
            <span class="hljs-attr">"node"</span>: <span class="hljs-string">"Date &amp; Time"</span>,
            <span class="hljs-attr">"type"</span>: <span class="hljs-string">"main"</span>,
            <span class="hljs-attr">"index"</span>: <span class="hljs-number">0</span>
          }
        ]
      ]
    },
    <span class="hljs-attr">"Date &amp; Time"</span>: {
      <span class="hljs-attr">"main"</span>: [
        [
          {
            <span class="hljs-attr">"node"</span>: <span class="hljs-string">"Respond to Webhook"</span>,
            <span class="hljs-attr">"type"</span>: <span class="hljs-string">"main"</span>,
            <span class="hljs-attr">"index"</span>: <span class="hljs-number">0</span>
          }
        ]
      ]
    }
  },
  <span class="hljs-attr">"active"</span>: <span class="hljs-literal">false</span>
}
</code></pre>
<ol>
<li><p>Go to the n8n web interface.</p>
</li>
<li><p>Click <strong>Workflows → Import from File</strong>.</p>
</li>
<li><p>Select the file and save.</p>
</li>
</ol>
<hr />
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758130000754/2b6831db-2b94-462b-841a-b1e890d29a48.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758130039613/5890a90a-5e3b-4120-aa68-e98aab9bd3ba.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-4-understand-the-nodes">4️⃣ Understand the Nodes</h2>
<p>Here’s what each node in <strong>Weather Data Every Minute (Enhanced)</strong> does:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Node</td><td>What It Does</td><td>Why It Matters</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Cron</strong></td><td>Triggers the workflow every minute.</td><td>Acts as a scheduler—no manual clicks needed.</td></tr>
<tr>
<td><strong>Set City</strong></td><td>Adds a static <code>city: "London"</code> field.</td><td>Lets you define default data for downstream nodes.</td></tr>
<tr>
<td><strong>Random Weather (Function)</strong></td><td>Runs a small JavaScript snippet to create random <code>temperature</code>, <code>condition</code>, <code>humidity</code>, and <code>wind</code>.</td><td>Shows how to add custom logic beyond built-in nodes.</td></tr>
<tr>
<td><strong>Date &amp; Time</strong></td><td>Adds the current timestamp formatted as <code>YYYY-MM-DD HH:mm:ss</code>.</td><td>Useful for logs or time-stamped records.</td></tr>
<tr>
<td><strong>Respond to Webhook</strong></td><td>Outputs the final JSON.</td><td>Lets other apps or people call the endpoint and get the data.</td></tr>
</tbody>
</table>
</div><p>When active, every minute this workflow generates output similar to:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"city"</span>: <span class="hljs-string">"London"</span>,
  <span class="hljs-attr">"temperature"</span>: <span class="hljs-number">24</span>,
  <span class="hljs-attr">"condition"</span>: <span class="hljs-string">"Sunny"</span>,
  <span class="hljs-attr">"humidity"</span>: <span class="hljs-number">65</span>,
  <span class="hljs-attr">"wind"</span>: <span class="hljs-number">14</span>,
  <span class="hljs-attr">"date"</span>: <span class="hljs-string">"2025-09-17 22:30:00"</span>
}
</code></pre>
<hr />
<h2 id="heading-5-test-it">5️⃣ Test It</h2>
<ol>
<li><p><strong>Activate</strong> the workflow (toggle in the top right).</p>
</li>
<li><p>Use curl or a browser to hit the test URL shown in the “Respond to Webhook” node:</p>
<pre><code class="lang-bash"> curl http://&lt;your-ip&gt;:5678/webhook/&lt;your-path&gt;
</code></pre>
</li>
<li><p>You’ll see fresh weather data each time you call it.</p>
</li>
</ol>
<hr />
<h2 id="heading-6-fun-extensions">6️⃣ Fun Extensions</h2>
<p>This simple example is just the start.<br />Here are some beginner-friendly ideas:</p>
<ul>
<li><p><strong>Log to a Database</strong> – Add a PostgreSQL or MySQL node to store each weather record.</p>
</li>
<li><p><strong>Send Alerts</strong> – Add an Email or Slack node to notify if the temperature crosses a threshold.</p>
</li>
<li><p><strong>Google Sheets Dashboard</strong> – Append each new data point to a sheet for easy charts.</p>
</li>
<li><p><strong>Real API Calls</strong> – Replace the Random Weather function with an HTTP Request node calling a real API such as OpenWeatherMap.</p>
</li>
</ul>
<hr />
<h2 id="heading-7-key-takeaways-for-beginners">7️⃣ Key Takeaways for Beginners</h2>
<ul>
<li><p><strong>Visual First</strong> – You don’t need to be a developer to start; drag-and-drop gets you far.</p>
</li>
<li><p><strong>Two Easy Installs</strong> – Docker for convenience, Node.js if you prefer native.</p>
</li>
<li><p><strong>Reusable</strong> – The same workflow can run locally, in the cloud, or on a Raspberry Pi.</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Understanding and Using race-lock-js: A Guide to Preventing Race Conditions]]></title><description><![CDATA[In the world of asynchronous programming with Node.js, managing concurrent operations can be a significant challenge. Race conditions, where the outcome of a program depends on the unpredictable sequence or timing of events, can lead to unexpected bu...]]></description><link>https://blog.nishikanta.in/understanding-and-using-race-lock-js-a-guide-to-preventing-race-conditions</link><guid isPermaLink="true">https://blog.nishikanta.in/understanding-and-using-race-lock-js-a-guide-to-preventing-race-conditions</guid><dc:creator><![CDATA[Nishikanta Ray]]></dc:creator><pubDate>Fri, 25 Jul 2025 17:31:39 GMT</pubDate><content:encoded><![CDATA[<p>In the world of asynchronous programming with Node.js, managing concurrent operations can be a significant challenge. Race conditions, where the outcome of a program depends on the unpredictable sequence or timing of events, can lead to unexpected bugs and data corruption. This is where <code>race-lock-js</code> comes in – a lightweight, in-memory lock utility designed to help you safely coordinate asynchronous operations within a single Node.js process.</p>
<h3 id="heading-what-is-racelock-js">What is RaceLock JS?</h3>
<p><code>race-lock-js</code> (version 0.0.4) is a simple yet powerful library that provides an in-memory locking mechanism to prevent race conditions. It acts as a mutex, ensuring that only one operation can access a critical section of code at a time, thereby maintaining data integrity and predictable behavior in your asynchronous workflows.</p>
<p><strong>Key Features:</strong></p>
<ul>
<li><p><strong>Simple API:</strong> Easy-to-use <code>start()</code> and <code>end()</code> methods for acquiring and releasing locks.</p>
</li>
<li><p><strong>Auto-release with Timeouts:</strong> Optional timeouts ensure locks are not held indefinitely.</p>
</li>
<li><p><strong>Ownership Checks:</strong> Prevents unintended release of locks by different operations.</p>
</li>
<li><p><strong>Exponential Backoff Retry:</strong> <code>retryStart()</code> offers a robust way to acquire locks under contention.</p>
</li>
<li><p><code>waitForUnlock()</code>: Allows operations to pause and wait until a specific lock is released.</p>
</li>
<li><p><strong>Metadata Support:</strong> Attach contextual information to your locks.</p>
</li>
<li><p><strong>Introspection:</strong> Utilities like <code>getLockCount()</code>, <code>getAllLockedKeys()</code>, and <code>getLockInfo()</code> for monitoring.</p>
</li>
<li><p><strong>Emergency Unlocking:</strong> <code>forceUnlock()</code> and <code>clearAllLocks()</code> for critical situations (use with caution).</p>
</li>
</ul>
<h3 id="heading-installation">Installation</h3>
<p>Getting started with <code>race-lock-js</code> is straightforward. You can install it via npm:</p>
<p>Bash</p>
<pre><code class="lang-javascript">npm install racelock
</code></pre>
<h3 id="heading-how-to-use-racelock-js-examples-and-demo-code">How to Use RaceLock JS: Examples and Demo Code</h3>
<p>Let's dive into some practical examples to understand how to leverage <code>race-lock-js</code> in your applications.</p>
<p>First, import the library:</p>
<p>JavaScript</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> RaceLock = <span class="hljs-built_in">require</span>(<span class="hljs-string">'racelock'</span>);
<span class="hljs-keyword">const</span> lock = <span class="hljs-keyword">new</span> RaceLock();
</code></pre>
<h4 id="heading-basic-locking">Basic Locking</h4>
<p>This demonstrates how to acquire and release a lock for a simple asynchronous task.</p>
<p>JavaScript</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">performCriticalTask</span>(<span class="hljs-params">taskId</span>) </span>{
    <span class="hljs-keyword">const</span> lockKey = <span class="hljs-string">`task-<span class="hljs-subst">${taskId}</span>-lock`</span>;

    <span class="hljs-comment">// Try to acquire the lock</span>
    <span class="hljs-keyword">if</span> (lock.start(lockKey)) {
        <span class="hljs-keyword">try</span> {
            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Task <span class="hljs-subst">${taskId}</span>: Lock acquired for <span class="hljs-subst">${lockKey}</span>. Performing critical operation...`</span>);
            <span class="hljs-comment">// Simulate an asynchronous operation</span>
            <span class="hljs-keyword">await</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function"><span class="hljs-params">resolve</span> =&gt;</span> <span class="hljs-built_in">setTimeout</span>(resolve, <span class="hljs-number">1000</span>));
            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Task <span class="hljs-subst">${taskId}</span>: Critical operation complete.`</span>);
        } <span class="hljs-keyword">finally</span> {
            <span class="hljs-comment">// Always ensure the lock is released</span>
            lock.end(lockKey);
            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Task <span class="hljs-subst">${taskId}</span>: Lock released for <span class="hljs-subst">${lockKey}</span>.`</span>);
        }
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Task <span class="hljs-subst">${taskId}</span>: Could not acquire lock for <span class="hljs-subst">${lockKey}</span>. Already locked.`</span>);
    }
}

<span class="hljs-comment">// Simulate multiple tasks trying to access the same resource</span>
performCriticalTask(<span class="hljs-number">1</span>);
performCriticalTask(<span class="hljs-number">1</span>); <span class="hljs-comment">// This will likely fail to acquire the lock initially</span>
performCriticalTask(<span class="hljs-number">2</span>); <span class="hljs-comment">// This will acquire a different lock</span>
</code></pre>
<h4 id="heading-asynchronous-task-protection-with-timeouts">Asynchronous Task Protection with Timeouts</h4>
<p>You can set a timeout for a lock to automatically release if it's held for too long.</p>
<p>JavaScript</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">sensitiveOperation</span>(<span class="hljs-params">userId</span>) </span>{
    <span class="hljs-keyword">const</span> lockKey = <span class="hljs-string">`user-<span class="hljs-subst">${userId}</span>-data`</span>;
    <span class="hljs-keyword">const</span> timeoutMs = <span class="hljs-number">2000</span>; <span class="hljs-comment">// Lock will auto-release after 2 seconds</span>

    <span class="hljs-keyword">if</span> (lock.start(lockKey, timeoutMs, { <span class="hljs-attr">userId</span>: userId, <span class="hljs-attr">operation</span>: <span class="hljs-string">'updateProfile'</span> })) {
        <span class="hljs-keyword">try</span> {
            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`User <span class="hljs-subst">${userId}</span>: Lock acquired with timeout. Updating profile...`</span>);
            <span class="hljs-keyword">await</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function"><span class="hljs-params">resolve</span> =&gt;</span> <span class="hljs-built_in">setTimeout</span>(resolve, <span class="hljs-number">1500</span>)); <span class="hljs-comment">// Simulate a task that finishes within timeout</span>
            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`User <span class="hljs-subst">${userId}</span>: Profile updated successfully.`</span>);
        } <span class="hljs-keyword">finally</span> {
            lock.end(lockKey);
            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`User <span class="hljs-subst">${userId}</span>: Lock released.`</span>);
        }
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`User <span class="hljs-subst">${userId}</span>: Failed to acquire lock for profile update.`</span>);
    }
}

sensitiveOperation(<span class="hljs-number">123</span>);
<span class="hljs-comment">// If called again immediately, it might fail or wait for the timeout/release</span>
sensitiveOperation(<span class="hljs-number">123</span>);
</code></pre>
<h4 id="heading-using-retrystart-for-contention">Using <code>retryStart()</code> for Contention</h4>
<p><code>retryStart()</code> is excellent for scenarios where multiple operations might contend for the same lock. It retries acquiring the lock with an exponential backoff.</p>
<p>JavaScript</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">processOrder</span>(<span class="hljs-params">orderId</span>) </span>{
    <span class="hljs-keyword">const</span> lockKey = <span class="hljs-string">`order-<span class="hljs-subst">${orderId}</span>-processing`</span>;
    <span class="hljs-keyword">const</span> ownerId = <span class="hljs-string">`processor-<span class="hljs-subst">${<span class="hljs-built_in">Math</span>.random().toFixed(<span class="hljs-number">4</span>)}</span>`</span>; <span class="hljs-comment">// Unique ID for this attempt</span>

    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> acquired = <span class="hljs-keyword">await</span> lock.retryStart(
            lockKey,
            <span class="hljs-number">5</span>, <span class="hljs-comment">// Number of attempts</span>
            <span class="hljs-number">100</span>, <span class="hljs-comment">// Initial delay (ms)</span>
            <span class="hljs-number">3000</span>, <span class="hljs-comment">// Timeout for each lock acquisition attempt (ms)</span>
            { <span class="hljs-attr">orderId</span>: orderId, <span class="hljs-attr">source</span>: <span class="hljs-string">'web'</span> }, <span class="hljs-comment">// Metadata</span>
            ownerId
        );

        <span class="hljs-keyword">if</span> (acquired) {
            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`<span class="hljs-subst">${ownerId}</span> for Order <span class="hljs-subst">${orderId}</span>: Lock acquired. Processing order...`</span>);
            <span class="hljs-keyword">await</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function"><span class="hljs-params">resolve</span> =&gt;</span> <span class="hljs-built_in">setTimeout</span>(resolve, <span class="hljs-number">2500</span>)); <span class="hljs-comment">// Simulate order processing</span>
            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`<span class="hljs-subst">${ownerId}</span> for Order <span class="hljs-subst">${orderId}</span>: Order processed.`</span>);
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-built_in">console</span>.error(<span class="hljs-string">`<span class="hljs-subst">${ownerId}</span> for Order <span class="hljs-subst">${orderId}</span>: Failed to acquire lock after multiple attempts.`</span>);
        }
    } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">`<span class="hljs-subst">${ownerId}</span> for Order <span class="hljs-subst">${orderId}</span>: Error during lock acquisition or processing:`</span>, error.message);
    } <span class="hljs-keyword">finally</span> {
        <span class="hljs-comment">// Ensure lock is released by its owner</span>
        <span class="hljs-keyword">if</span> (lock.isLocked(lockKey) &amp;&amp; lock.getLockInfo(lockKey)?.ownerId === ownerId) {
            lock.end(lockKey, ownerId);
            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`<span class="hljs-subst">${ownerId}</span> for Order <span class="hljs-subst">${orderId}</span>: Lock released.`</span>);
        }
    }
}

<span class="hljs-comment">// Simulate multiple attempts to process the same order</span>
processOrder(<span class="hljs-number">101</span>);
processOrder(<span class="hljs-number">101</span>);
processOrder(<span class="hljs-number">102</span>);
</code></pre>
<h4 id="heading-waiting-for-a-lock-to-be-released-with-waitforunlock">Waiting for a Lock to be Released with <code>waitForUnlock()</code></h4>
<p>If an operation needs to wait until a specific lock is free, <code>waitForUnlock()</code> is the perfect solution.</p>
<p>JavaScript</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">consumerProcess</span>(<span class="hljs-params">dataKey</span>) </span>{
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Consumer for <span class="hljs-subst">${dataKey}</span>: Checking if lock is active...`</span>);
    <span class="hljs-keyword">while</span> (lock.isLocked(dataKey)) {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Consumer for <span class="hljs-subst">${dataKey}</span>: Lock is active, waiting...`</span>);
        <span class="hljs-keyword">await</span> lock.waitForUnlock(dataKey);
    }
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Consumer for <span class="hljs-subst">${dataKey}</span>: Lock is no longer active. Proceeding with data processing.`</span>);
    <span class="hljs-comment">// Now you can safely access or modify the data</span>
    <span class="hljs-keyword">await</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function"><span class="hljs-params">resolve</span> =&gt;</span> <span class="hljs-built_in">setTimeout</span>(resolve, <span class="hljs-number">500</span>));
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Consumer for <span class="hljs-subst">${dataKey}</span>: Data processing complete.`</span>);
}

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">producerProcess</span>(<span class="hljs-params">dataKey</span>) </span>{
    <span class="hljs-keyword">const</span> ownerId = <span class="hljs-string">'producer-A'</span>;
    <span class="hljs-keyword">if</span> (lock.start(dataKey, <span class="hljs-number">0</span>, {}, ownerId)) {
        <span class="hljs-keyword">try</span> {
            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Producer for <span class="hljs-subst">${dataKey}</span>: Lock acquired. Producing data...`</span>);
            <span class="hljs-keyword">await</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function"><span class="hljs-params">resolve</span> =&gt;</span> <span class="hljs-built_in">setTimeout</span>(resolve, <span class="hljs-number">3000</span>)); <span class="hljs-comment">// Simulate data production</span>
            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Producer for <span class="hljs-subst">${dataKey}</span>: Data production complete.`</span>);
        } <span class="hljs-keyword">finally</span> {
            lock.end(dataKey, ownerId);
            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Producer for <span class="hljs-subst">${dataKey}</span>: Lock released.`</span>);
        }
    }
}

<span class="hljs-keyword">const</span> sharedDataKey = <span class="hljs-string">'mySharedResource'</span>;

<span class="hljs-comment">// Start consumer first, it will wait</span>
consumerProcess(sharedDataKey);
<span class="hljs-comment">// Start producer after a short delay, it will acquire the lock</span>
<span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> producerProcess(sharedDataKey), <span class="hljs-number">500</span>);
</code></pre>
<h3 id="heading-best-practices-for-using-race-lock-js">Best Practices for Using <code>race-lock-js</code></h3>
<ul>
<li><p><strong>Always Use</strong> <code>finally</code> Blocks: Ensure <code>lock.end()</code> is called within a <code>finally</code> block to guarantee lock release, even if errors occur.</p>
</li>
<li><p><strong>Utilize</strong> <code>ownerId</code>: When multiple systems or functions might interact with the same lock key, use <code>ownerId</code> to enforce ownership and prevent accidental releases.</p>
</li>
<li><p><strong>Employ</strong> <code>retryStart()</code> for Contention: If you anticipate contention for a lock, <code>retryStart()</code> provides a robust mechanism to acquire it gracefully.</p>
</li>
<li><p><strong>Use</strong> <code>clearAllLocks()</code> with Extreme Caution: This function clears all active locks and should only be used in emergency scenarios or for testing, as it can disrupt ongoing operations.</p>
</li>
</ul>
<h3 id="heading-license-and-contributions">License and Contributions</h3>
<p><code>race-lock-js</code> is open-source, licensed under the MIT License. Contributions are welcome! You can contribute by forking the repository, starring it, submitting pull requests, or opening issues on its <a target="_blank" href="https://www.npmjs.com/package/race-lock-js">GitHub repository</a>.</p>
<p>For more details, you can visit the <a target="_blank" href="https://www.npmjs.com/package/race-lock-js">npm package page</a> directly.</p>
]]></content:encoded></item><item><title><![CDATA[📦 Chunked File Upload Over TCP (Node.js net): Streams, Retries & Temp Storage]]></title><description><![CDATA[When you're building a desktop app that needs to upload large files (say 1GB+), sending the entire file at once is risky. Instead, split it into chunks and upload them one by one using streams.
This blog shows how to do that using Node.js's net modul...]]></description><link>https://blog.nishikanta.in/chunked-file-upload-over-tcp-nodejs-net-streams-retries-and-temp-storage</link><guid isPermaLink="true">https://blog.nishikanta.in/chunked-file-upload-over-tcp-nodejs-net-streams-retries-and-temp-storage</guid><dc:creator><![CDATA[Nishikanta Ray]]></dc:creator><pubDate>Fri, 18 Jul 2025 20:29:59 GMT</pubDate><content:encoded><![CDATA[<p>When you're building a <strong>desktop app</strong> that needs to <strong>upload large files</strong> (say 1GB+), sending the entire file at once is risky. Instead, split it into <strong>chunks</strong> and upload them one by one using <strong>streams</strong>.</p>
<p>This blog shows how to do that using <strong>Node.js's</strong> <code>net</code> module (TCP), how to handle <strong>stream retry issues</strong>, and how to simulate <strong>AWS S3 multipart upload behavior</strong>, with a focus on <strong>disk-based chunk storage</strong>, not in-memory.</p>
<hr />
<h2 id="heading-what-were-building">🧩 What We’re Building</h2>
<ul>
<li><p>A <code>net</code>-based TCP <strong>file upload server</strong></p>
</li>
<li><p>A <strong>client</strong> that reads a file chunk by chunk via <code>fs.createReadStream</code></p>
</li>
<li><p>On each retry, it <strong>recreates the stream</strong></p>
</li>
<li><p>Chunks are <strong>saved temporarily on disk</strong></p>
</li>
<li><p>Finally, chunks are <strong>merged</strong> like AWS multipart upload</p>
</li>
</ul>
<hr />
<h2 id="heading-why-not-keep-chunks-in-memory">⚠️ Why Not Keep Chunks in Memory?</h2>
<ul>
<li><p>Holding large files or many chunks in RAM = 🧨 memory bloat</p>
</li>
<li><p>Disk-based temp files simulate real-world AWS S3 multipart flow:</p>
<ul>
<li><strong>Upload → Store in temporary object → Finalize (CompleteMultipartUpload)</strong></li>
</ul>
</li>
</ul>
<hr />
<h2 id="heading-step-1-the-tcp-upload-server">🛠 Step 1: The TCP Upload Server</h2>
<pre><code class="lang-javascript"><span class="hljs-comment">// net-upload-server.js</span>
<span class="hljs-keyword">const</span> net = <span class="hljs-built_in">require</span>(<span class="hljs-string">'net'</span>);
<span class="hljs-keyword">const</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>);
<span class="hljs-keyword">const</span> path = <span class="hljs-built_in">require</span>(<span class="hljs-string">'path'</span>);

<span class="hljs-keyword">const</span> tempDir = path.join(__dirname, <span class="hljs-string">'temp'</span>);
<span class="hljs-keyword">if</span> (!fs.existsSync(tempDir)) fs.mkdirSync(tempDir);

<span class="hljs-keyword">let</span> chunkIndex = <span class="hljs-number">0</span>;

<span class="hljs-keyword">const</span> server = net.createServer(<span class="hljs-function">(<span class="hljs-params">socket</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> tempFile = path.join(tempDir, <span class="hljs-string">`chunk_<span class="hljs-subst">${chunkIndex++}</span>.part`</span>);
  <span class="hljs-keyword">const</span> writeStream = fs.createWriteStream(tempFile);

  socket.pipe(writeStream);

  socket.on(<span class="hljs-string">'end'</span>, <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`✅ Chunk saved: <span class="hljs-subst">${tempFile}</span>`</span>));
  socket.on(<span class="hljs-string">'error'</span>, <span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'❌ Socket error:'</span>, err.message));
});

server.listen(<span class="hljs-number">5000</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'📡 Server running on port 5000'</span>);
});
</code></pre>
<hr />
<h2 id="heading-step-2-client-chunk-upload-with-retry">📤 Step 2: Client – Chunk Upload with Retry</h2>
<pre><code class="lang-javascript"><span class="hljs-comment">// net-upload-client.js</span>
<span class="hljs-keyword">const</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>);
<span class="hljs-keyword">const</span> net = <span class="hljs-built_in">require</span>(<span class="hljs-string">'net'</span>);

<span class="hljs-keyword">const</span> CHUNK_SIZE = <span class="hljs-number">10</span> * <span class="hljs-number">1024</span> * <span class="hljs-number">1024</span>; <span class="hljs-comment">// 10MB</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">uploadChunk</span>(<span class="hljs-params">filePath, start, end, attempt = <span class="hljs-number">1</span></span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> stream = fs.createReadStream(filePath, { start, end });
    <span class="hljs-keyword">const</span> client = net.createConnection({ <span class="hljs-attr">port</span>: <span class="hljs-number">5000</span> }, <span class="hljs-function">() =&gt;</span> {
      stream.pipe(client);
    });

    client.on(<span class="hljs-string">'end'</span>, resolve);
    client.on(<span class="hljs-string">'error'</span>, <span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> reject(err));
    stream.on(<span class="hljs-string">'error'</span>, <span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> reject(err));
  });
}

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">uploadFile</span>(<span class="hljs-params">filePath</span>) </span>{
  <span class="hljs-keyword">const</span> fileSize = fs.statSync(filePath).size;
  <span class="hljs-keyword">let</span> offset = <span class="hljs-number">0</span>;

  <span class="hljs-keyword">while</span> (offset &lt; fileSize) {
    <span class="hljs-keyword">const</span> start = offset;
    <span class="hljs-keyword">const</span> end = <span class="hljs-built_in">Math</span>.min(offset + CHUNK_SIZE - <span class="hljs-number">1</span>, fileSize - <span class="hljs-number">1</span>);

    <span class="hljs-keyword">let</span> retries = <span class="hljs-number">3</span>;
    <span class="hljs-keyword">while</span> (retries--) {
      <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">await</span> uploadChunk(filePath, start, end);
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`✅ Chunk uploaded: <span class="hljs-subst">${start}</span>-<span class="hljs-subst">${end}</span>`</span>);
        <span class="hljs-keyword">break</span>;
      } <span class="hljs-keyword">catch</span> (err) {
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">`❌ Retry failed: <span class="hljs-subst">${start}</span>-<span class="hljs-subst">${end}</span>`</span>, err.message);
        <span class="hljs-keyword">if</span> (retries === <span class="hljs-number">0</span>) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Upload failed'</span>);
        <span class="hljs-keyword">await</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function"><span class="hljs-params">res</span> =&gt;</span> <span class="hljs-built_in">setTimeout</span>(res, <span class="hljs-number">1000</span>));
      }
    }

    offset += CHUNK_SIZE;
  }
}
</code></pre>
<hr />
<h2 id="heading-step-3-merge-chunks-like-aws-completemultipartupload">🧬 Step 3: Merge Chunks Like AWS <code>CompleteMultipartUpload</code></h2>
<pre><code class="lang-javascript"><span class="hljs-comment">// merge.js</span>
<span class="hljs-keyword">const</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>);
<span class="hljs-keyword">const</span> path = <span class="hljs-built_in">require</span>(<span class="hljs-string">'path'</span>);

<span class="hljs-keyword">const</span> tempDir = path.join(__dirname, <span class="hljs-string">'temp'</span>);
<span class="hljs-keyword">const</span> finalPath = path.join(__dirname, <span class="hljs-string">'final_upload.bin'</span>);

<span class="hljs-keyword">const</span> files = fs.readdirSync(tempDir)
  .filter(<span class="hljs-function"><span class="hljs-params">f</span> =&gt;</span> f.endsWith(<span class="hljs-string">'.part'</span>))
  .sort(); <span class="hljs-comment">// chunk_0.part, chunk_1.part...</span>

<span class="hljs-keyword">const</span> writeStream = fs.createWriteStream(finalPath);

<span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> file <span class="hljs-keyword">of</span> files) {
  <span class="hljs-keyword">const</span> chunk = fs.readFileSync(path.join(tempDir, file));
  writeStream.write(chunk);
}

writeStream.end(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`✅ Final file merged at <span class="hljs-subst">${finalPath}</span>`</span>);
});
</code></pre>
<hr />
<h2 id="heading-retry-stream-why-its-crucial-to-recreate-it">🔁 Retry Stream: Why It’s Crucial to Recreate It</h2>
<p>Streams are <strong>one-time data pipelines</strong>. After they are read, errored, or ended:</p>
<ul>
<li><p>They can’t be reused.</p>
</li>
<li><p>Retrying with the same stream = <code>"Cannot pipe. Already used"</code> or silent failure.</p>
</li>
<li><p>Always <strong>create a new stream for each retry</strong>.</p>
</li>
</ul>
<p>✅ <code>fs.createReadStream(file, { start, end })</code> inside retry loop<br />❌ Caching or reusing the stream across retries</p>
<hr />
<h2 id="heading-aws-multipart-upload-analogy">🪣 AWS Multipart Upload Analogy</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>AWS Step</td><td>Local <code>net</code> Upload Equivalent</td></tr>
</thead>
<tbody>
<tr>
<td><code>UploadPart</code></td><td><code>fs.createReadStream()</code> + TCP send</td></tr>
<tr>
<td><code>UploadPart failed</code></td><td>Retry with new stream</td></tr>
<tr>
<td><code>temp object on S3</code></td><td><code>.part</code> file saved in <code>temp/</code></td></tr>
<tr>
<td><code>CompleteMultipartUpload</code></td><td>Merge <code>.part</code> files into final output</td></tr>
</tbody>
</table>
</div><hr />
<h2 id="heading-summary">✅ Summary</h2>
<ul>
<li><p>Chunk large files using streams</p>
</li>
<li><p>Use Node.js <code>net</code> module to simulate raw transport</p>
</li>
<li><p>Always retry with <strong>new readable streams</strong></p>
</li>
<li><p>Save chunks to disk to avoid memory spikes</p>
</li>
<li><p>Merge them at the end like AWS does</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[⚡️ Beyond the Browser: Mastering Electron's net Module for System-Level Networking]]></title><description><![CDATA[“Browser rules don’t apply here.”Learn how Electron's net module gives your desktop app the networking power of system tools like curl, far beyond the limits of fetch or axios.


📦 Why Use Electron’s net? A Real-Life Analogy
Imagine you're at a hote...]]></description><link>https://blog.nishikanta.in/mastering-electrons-net-module-for-system-level-networking</link><guid isPermaLink="true">https://blog.nishikanta.in/mastering-electrons-net-module-for-system-level-networking</guid><category><![CDATA[desktop]]></category><category><![CDATA[Electron]]></category><dc:creator><![CDATA[Nishikanta Ray]]></dc:creator><pubDate>Fri, 20 Jun 2025 17:42:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1750441312441/a32f6c77-2702-4ac1-96e2-3e318ba31490.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>“Browser rules don’t apply here.”<br />Learn how Electron's <code>net</code> module gives your desktop app the networking power of system tools like <code>curl</code>, far beyond the limits of <code>fetch</code> or <code>axios</code>.</p>
</blockquote>
<hr />
<h2 id="heading-why-use-electrons-net-a-real-life-analogy">📦 Why Use Electron’s <code>net</code>? A Real-Life Analogy</h2>
<p>Imagine you're at a hotel:</p>
<ul>
<li><p><code>fetch</code> is like calling the front desk — they’ll help, but only within hotel rules.</p>
</li>
<li><p><code>axios</code> is like using a guest app — more features, but still limited by hotel policies (like asking for permission).</p>
</li>
<li><p><a target="_blank" href="http://electron.net"><code>electron.net</code></a> is like having a <strong>master key and a walkie-talkie</strong> to talk to staff directly — no restrictions, no waiting, and you can do whatever the system allows.</p>
</li>
</ul>
<p>If you’re building Electron apps that need <strong>downloads</strong>, <strong>background sync</strong>, or <strong>CORS-free APIs</strong>, <a target="_blank" href="http://electron.net"><code>electron.net</code></a> is your tool.</p>
<hr />
<h2 id="heading-meet-the-players-fetch-axios-and-net">🌐 Meet the Players: <code>fetch</code>, <code>axios</code>, and <code>net</code></h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Feature</td><td><a target="_blank" href="http://electron.net"><code>electron.net</code></a> (Main)</td><td><code>axios</code> (Any)</td><td><code>fetch</code> (Renderer)</td></tr>
</thead>
<tbody>
<tr>
<td><strong>CORS Restricted</strong></td><td>❌ No</td><td>✅ Yes (in renderer)</td><td>✅ Yes</td></tr>
<tr>
<td><strong>Runs in Main Process</strong></td><td>✅ Yes</td><td>✅ Yes</td><td>❌ No</td></tr>
<tr>
<td><strong>System-Level Access</strong></td><td>✅ Yes</td><td>⚠️ No</td><td>❌ No</td></tr>
<tr>
<td><strong>Proxy Control</strong></td><td>✅ Full (setProxy)</td><td>⚠️ OS settings only</td><td>⚠️ OS settings only</td></tr>
<tr>
<td><strong>Streaming Support</strong></td><td>✅ Excellent</td><td>⚠️ Some support</td><td>❌ Limited</td></tr>
<tr>
<td><strong>Ideal for Downloads</strong></td><td>✅ Best</td><td>⚠️ Basic</td><td>❌ Poor</td></tr>
<tr>
<td><strong>CORS-Free File Fetching</strong></td><td>✅ Yes</td><td>❌ No (unless server allows)</td><td>❌ No</td></tr>
<tr>
<td><strong>Saving Directly to Disk</strong></td><td>✅ Yes (with <code>fs</code>)</td><td>⚠️ Manual</td><td>❌ Not possible</td></tr>
<tr>
<td><strong>Automatic JSON Parsing</strong></td><td>❌ No (manual)</td><td>✅ Yes</td><td>⚠️ No (must call <code>.json()</code>)</td></tr>
<tr>
<td><strong>Ease of Use</strong></td><td>⚠️ Low (low-level API)</td><td>✅ High</td><td>✅ High</td></tr>
</tbody>
</table>
</div><hr />
<h2 id="heading-how-to-use-each-with-examples">🔧 How to Use Each — With Examples</h2>
<h3 id="heading-1-using-electronnethttpelectronnet-system-level-no-cors-download-capable">✅ 1. Using <a target="_blank" href="http://electron.net"><code>electron.net</code></a> (System-Level, No CORS, Download Capable)</h3>
<ul>
<li><p>✅ No CORS.</p>
</li>
<li><p>✅ Fully controllable.</p>
</li>
<li><p>✅ Ideal for download managers, auto-updaters, background sync.</p>
</li>
</ul>
<pre><code class="lang-javascript"><span class="hljs-comment">// main.js</span>
<span class="hljs-keyword">const</span> { net, app, session } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'electron'</span>);
<span class="hljs-keyword">const</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>);
<span class="hljs-keyword">const</span> path = <span class="hljs-built_in">require</span>(<span class="hljs-string">'path'</span>);

app.whenReady().then(<span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-comment">// Bypass all system proxies</span>
  <span class="hljs-keyword">await</span> session.defaultSession.setProxy({ <span class="hljs-attr">proxyRules</span>: <span class="hljs-string">'direct://'</span> });

  <span class="hljs-keyword">const</span> filePath = path.join(__dirname, <span class="hljs-string">'file.pdf'</span>);
  <span class="hljs-keyword">const</span> fileStream = fs.createWriteStream(filePath);

  <span class="hljs-keyword">const</span> request = net.request(<span class="hljs-string">'https://example.com/file.pdf'</span>);
  request.on(<span class="hljs-string">'response'</span>, <span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> {
    res.on(<span class="hljs-string">'data'</span>, <span class="hljs-function"><span class="hljs-params">chunk</span> =&gt;</span> fileStream.write(chunk));
    res.on(<span class="hljs-string">'end'</span>, <span class="hljs-function">() =&gt;</span> {
      fileStream.end();
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'✅ Download complete'</span>);
    });
  });

  request.end();
});
</code></pre>
<hr />
<h3 id="heading-2-using-axios-great-for-json-apis-but-cors-limited">⚡ 2. Using <code>axios</code> (Great for JSON APIs, but CORS-limited)</h3>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> axios = <span class="hljs-built_in">require</span>(<span class="hljs-string">'axios'</span>);
axios.get(<span class="hljs-string">'https://jsonplaceholder.typicode.com/todos/1'</span>)
  .then(<span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'✅ axios result:'</span>, response.data))
  .catch(<span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'❌ axios error:'</span>, error));
</code></pre>
<ul>
<li><p>✅ Easy to use.</p>
</li>
<li><p>⚠️ Subject to CORS if used in renderer.</p>
</li>
<li><p>⚠️ Not ideal for large binary data or file downloads.</p>
</li>
</ul>
<hr />
<h3 id="heading-3-using-fetch-browser-like-but-restrictive">🌍 3. Using <code>fetch</code> (Browser-Like, but Restrictive)</h3>
<ul>
<li><p>✅ Native in the renderer.</p>
</li>
<li><p>⚠️ Blocked by CORS unless allowed by server.</p>
</li>
<li><p>❌ No file streaming or disk access.</p>
</li>
</ul>
<hr />
<h2 id="heading-how-to-bypass-proxy-in-electron">🔓 How to Bypass Proxy in Electron</h2>
<pre><code class="lang-javascript">fetch(<span class="hljs-string">'https://jsonplaceholder.typicode.com/todos/1'</span>)
  .then(<span class="hljs-function"><span class="hljs-params">res</span> =&gt;</span> res.json())
  .then(<span class="hljs-function"><span class="hljs-params">data</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'✅ fetch result:'</span>, data))
  .catch(<span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'❌ fetch error:'</span>, err));
</code></pre>
<h3 id="heading-disable-all-proxy-with-sessionsetproxy">🔥 Disable All Proxy with <code>session.setProxy</code></h3>
<p>This makes <strong>all requests direct</strong>, ignoring system proxies (like corporate firewalls or VPNs).</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">await</span> session.defaultSession.setProxy({ <span class="hljs-attr">proxyRules</span>: <span class="hljs-string">'direct://'</span> });
</code></pre>
<h3 id="heading-restore-to-system-proxy">🌐 Restore to System Proxy</h3>
<pre><code class="lang-javascript"><span class="hljs-keyword">await</span> session.defaultSession.setProxy({ <span class="hljs-attr">mode</span>: <span class="hljs-string">'system'</span> });
</code></pre>
<p>You can also set <strong>custom proxy rules</strong> like:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">await</span> session.defaultSession.setProxy({
  <span class="hljs-attr">proxyRules</span>: <span class="hljs-string">'http=my-proxy.com:8080'</span>
});
</code></pre>
<hr />
<h2 id="heading-when-to-use-what">💡 When to Use What?</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Scenario</td><td>Use This</td></tr>
</thead>
<tbody>
<tr>
<td>Download files</td><td><a target="_blank" href="http://electron.net"><code>electron.net</code></a></td></tr>
<tr>
<td>Fetch public APIs (CORS OK)</td><td><code>axios</code> or <code>fetch</code></td></tr>
<tr>
<td>Access private/internal APIs</td><td><a target="_blank" href="http://electron.net"><code>electron.net</code></a></td></tr>
<tr>
<td>Stream large data (video/audio)</td><td><a target="_blank" href="http://electron.net"><code>electron.net</code></a></td></tr>
<tr>
<td>You need JSON, no CORS issues</td><td><code>axios</code></td></tr>
<tr>
<td>Frontend UI fetch</td><td><code>fetch</code></td></tr>
</tbody>
</table>
</div><hr />
<h2 id="heading-final-thoughts">🧠 Final Thoughts</h2>
<p>Electron’s <code>net</code> module is often overlooked, but it unlocks <strong>true desktop-level networking</strong> — with none of the browser baggage.</p>
<p>If you're serious about:</p>
<ul>
<li><p>Creating an auto-updater</p>
</li>
<li><p>Managing downloads</p>
</li>
<li><p>Syncing background data</p>
</li>
<li><p>Bypassing CORS and proxy issues</p>
</li>
</ul>
<p>...then <code>net</code> is your best friend.</p>
<blockquote>
<p>🎯 <strong>Pro Tip:</strong> Use <code>fetch</code> or <code>axios</code> for simple frontend tasks, and delegate heavy or unrestricted networking to <code>net</code> in the main process.</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[🔐How JWT, Access Tokens & Refresh Tokens Work — Node.js vs AWS Lambda]]></title><description><![CDATA[🧠 What Are Access Tokens and Refresh Tokens?




Token TypePurposeLifespanStored Where



Access TokenGrants access to protected resourcesShort-lived (e.g., 15 min)In memory or localStorage

Refresh TokenGets a new access token when expiresLong-live...]]></description><link>https://blog.nishikanta.in/how-jwt-access-tokens-and-refresh-tokens-work-nodejs-vs-aws-lambda</link><guid isPermaLink="true">https://blog.nishikanta.in/how-jwt-access-tokens-and-refresh-tokens-work-nodejs-vs-aws-lambda</guid><dc:creator><![CDATA[Nishikanta Ray]]></dc:creator><pubDate>Sun, 15 Jun 2025 18:19:11 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1750011521814/c9b7f2ce-4e01-4c62-b16b-63b02e39053e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-what-are-access-tokens-and-refresh-tokens">🧠 What Are Access Tokens and Refresh Tokens?</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Token Type</td><td>Purpose</td><td>Lifespan</td><td>Stored Where</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Access Token</strong></td><td>Grants access to protected resources</td><td>Short-lived (e.g., 15 min)</td><td>In memory or localStorage</td></tr>
<tr>
<td><strong>Refresh Token</strong></td><td>Gets a new access token when expires</td><td>Long-lived (e.g., 7 days)</td><td>Secure httpOnly cookie / DB</td></tr>
</tbody>
</table>
</div><hr />
<h2 id="heading-real-life-analogy-the-movie-hall-entry-system">🎯 Real-Life Analogy: The Movie Hall Entry System</h2>
<p>Imagine walking into a <strong>movie theater</strong>.</p>
<ol>
<li><p>🎟️ At the entrance, you show your <strong>movie ticket</strong> to the guard — this is your <strong>Access Token</strong>.<br /> It allows you to enter and enjoy the movie.</p>
</li>
<li><p>🕒 But the ticket is only valid for <strong>one movie (say 2 hours)</strong>. If you want to watch another movie or re-enter after stepping out, you <strong>need a new ticket</strong>.</p>
</li>
<li><p>🤝 Instead of going to the main counter again and proving your identity (logging in), you carry a <strong>VIP pass or membership card (Refresh Token)</strong>.</p>
</li>
<li><p>🎫 You present this <strong>refresh token</strong> at a special desk, and they issue you a <strong>new movie ticket (new Access Token)</strong> without requiring you to log in again.</p>
</li>
</ol>
<hr />
<h3 id="heading-key-takeaway">🔐 Key Takeaway</h3>
<ul>
<li><p><strong>Access Token = Movie Ticket</strong></p>
<ul>
<li><p>Short-lived, used to access services</p>
</li>
<li><p>Shown often (like at the theatre gate)</p>
</li>
</ul>
</li>
<li><p><strong>Refresh Token = Membership Card</strong></p>
<ul>
<li><p>Long-lived, used to get new access tokens</p>
</li>
<li><p>Hidden and securely stored</p>
</li>
</ul>
</li>
</ul>
<h2 id="heading-jwt-authentication-flow-with-access-refresh-tokens">🔐 JWT Authentication Flow with Access + Refresh Tokens</h2>
<ol>
<li><p><strong>User logs in</strong> ⇒ Receives both tokens</p>
</li>
<li><p><strong>An access token</strong> is used to call protected APIs</p>
</li>
<li><p>If the access token <strong>expires</strong>, the client sends the <strong>refresh token</strong> to get a new access token</p>
</li>
<li><p>Refresh token is <strong>stored securely</strong> (e.g., in cookies)</p>
</li>
<li><p>If the refresh token is invalid or expired, ⇒ user must log in again</p>
</li>
</ol>
<hr />
<h2 id="heading-implementation-in-nodejs-express">🏗️ Implementation in Node.js (Express)</h2>
<h3 id="heading-install">📦 Install</h3>
<pre><code class="lang-bash">npm install express jsonwebtoken cookie-parser
</code></pre>
<hr />
<h3 id="heading-token-functions-authjs">🔐 Token Functions (<code>auth.js</code>)</h3>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> jwt = <span class="hljs-built_in">require</span>(<span class="hljs-string">"jsonwebtoken"</span>);

<span class="hljs-keyword">const</span> ACCESS_SECRET = <span class="hljs-string">"access-secret"</span>;
<span class="hljs-keyword">const</span> REFRESH_SECRET = <span class="hljs-string">"refresh-secret"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">generateTokens</span>(<span class="hljs-params">user</span>) </span>{
  <span class="hljs-keyword">const</span> accessToken = jwt.sign(user, ACCESS_SECRET, { <span class="hljs-attr">expiresIn</span>: <span class="hljs-string">"15m"</span> });
  <span class="hljs-keyword">const</span> refreshToken = jwt.sign(user, REFRESH_SECRET, { <span class="hljs-attr">expiresIn</span>: <span class="hljs-string">"7d"</span> });
  <span class="hljs-keyword">return</span> { accessToken, refreshToken };
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">verifyAccess</span>(<span class="hljs-params">token</span>) </span>{
  <span class="hljs-keyword">return</span> jwt.verify(token, ACCESS_SECRET);
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">verifyRefresh</span>(<span class="hljs-params">token</span>) </span>{
  <span class="hljs-keyword">return</span> jwt.verify(token, REFRESH_SECRET);
}

<span class="hljs-built_in">module</span>.exports = { generateTokens, verifyAccess, verifyRefresh };
</code></pre>
<hr />
<h3 id="heading-express-server-indexjs">🧠 Express Server (<code>index.js</code>)</h3>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express"</span>);
<span class="hljs-keyword">const</span> cookieParser = <span class="hljs-built_in">require</span>(<span class="hljs-string">"cookie-parser"</span>);
<span class="hljs-keyword">const</span> { generateTokens, verifyAccess, verifyRefresh } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./auth"</span>);

<span class="hljs-keyword">const</span> app = express();
app.use(express.json());
app.use(cookieParser());

app.post(<span class="hljs-string">"/login"</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> user = { <span class="hljs-attr">email</span>: req.body.email };
  <span class="hljs-keyword">const</span> { accessToken, refreshToken } = generateTokens(user);
  res.cookie(<span class="hljs-string">"refreshToken"</span>, refreshToken, { <span class="hljs-attr">httpOnly</span>: <span class="hljs-literal">true</span> });
  res.json({ accessToken });
});

app.get(<span class="hljs-string">"/protected"</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> auth = req.headers.authorization;
  <span class="hljs-keyword">if</span> (!auth) <span class="hljs-keyword">return</span> res.sendStatus(<span class="hljs-number">401</span>);
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> user = verifyAccess(auth.split(<span class="hljs-string">" "</span>)[<span class="hljs-number">1</span>]);
    res.json({ <span class="hljs-attr">message</span>: <span class="hljs-string">"Access granted"</span>, user });
  } <span class="hljs-keyword">catch</span> {
    res.status(<span class="hljs-number">403</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">"Access token expired"</span> });
  }
});

app.post(<span class="hljs-string">"/refresh"</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> refreshToken = req.cookies.refreshToken;
  <span class="hljs-keyword">if</span> (!refreshToken) <span class="hljs-keyword">return</span> res.sendStatus(<span class="hljs-number">401</span>);
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> user = verifyRefresh(refreshToken);
    <span class="hljs-keyword">const</span> { accessToken } = generateTokens(user);
    res.json({ accessToken });
  } <span class="hljs-keyword">catch</span> {
    res.status(<span class="hljs-number">403</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">"Refresh token invalid"</span> });
  }
});
</code></pre>
<hr />
<h2 id="heading-jwt-with-refresh-token-in-aws-lambda">🚀 JWT with Refresh Token in AWS Lambda</h2>
<p>Lambda is <strong>stateless</strong> — so no sessions! But you can use JWTs the same way.</p>
<h3 id="heading-structure">🗂️ Structure</h3>
<pre><code class="lang-javascript">pythonCopyEditlambda-jwt-auth/
├── handler.js
├── auth.js
</code></pre>
<hr />
<h3 id="heading-authjs-same-logic-as-express">🔐 <code>auth.js</code> (Same logic as Express)</h3>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> jwt = <span class="hljs-built_in">require</span>(<span class="hljs-string">"jsonwebtoken"</span>);
<span class="hljs-keyword">const</span> ACCESS_SECRET = <span class="hljs-string">"access-secret"</span>;
<span class="hljs-keyword">const</span> REFRESH_SECRET = <span class="hljs-string">"refresh-secret"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">generateTokens</span>(<span class="hljs-params">user</span>) </span>{
  <span class="hljs-keyword">const</span> accessToken = jwt.sign(user, ACCESS_SECRET, { <span class="hljs-attr">expiresIn</span>: <span class="hljs-string">"15m"</span> });
  <span class="hljs-keyword">const</span> refreshToken = jwt.sign(user, REFRESH_SECRET, { <span class="hljs-attr">expiresIn</span>: <span class="hljs-string">"7d"</span> });
  <span class="hljs-keyword">return</span> { accessToken, refreshToken };
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">verifyAccess</span>(<span class="hljs-params">token</span>) </span>{
  <span class="hljs-keyword">return</span> jwt.verify(token, ACCESS_SECRET);
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">verifyRefresh</span>(<span class="hljs-params">token</span>) </span>{
  <span class="hljs-keyword">return</span> jwt.verify(token, REFRESH_SECRET);
}

<span class="hljs-built_in">module</span>.exports = { generateTokens, verifyAccess, verifyRefresh };
</code></pre>
<hr />
<h3 id="heading-handlerjs">🧠 <code>handler.js</code></h3>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> { generateTokens, verifyAccess, verifyRefresh } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./auth"</span>);

<span class="hljs-built_in">exports</span>.login = <span class="hljs-keyword">async</span> (event) =&gt; {
  <span class="hljs-keyword">const</span> body = <span class="hljs-built_in">JSON</span>.parse(event.body);
  <span class="hljs-keyword">const</span> user = { <span class="hljs-attr">email</span>: body.email };
  <span class="hljs-keyword">const</span> { accessToken, refreshToken } = generateTokens(user);

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">statusCode</span>: <span class="hljs-number">200</span>,
    <span class="hljs-attr">headers</span>: {
      <span class="hljs-string">"Set-Cookie"</span>: <span class="hljs-string">`refreshToken=<span class="hljs-subst">${refreshToken}</span>; HttpOnly; Path=/; Max-Age=604800`</span>,
      <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span>,
    },
    <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify({ accessToken }),
  };
};

<span class="hljs-built_in">exports</span>.protected = <span class="hljs-keyword">async</span> (event) =&gt; {
  <span class="hljs-keyword">const</span> authHeader = event.headers.authorization;
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> user = verifyAccess(authHeader.split(<span class="hljs-string">" "</span>)[<span class="hljs-number">1</span>]);
    <span class="hljs-keyword">return</span> {
      <span class="hljs-attr">statusCode</span>: <span class="hljs-number">200</span>,
      <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify({ <span class="hljs-attr">message</span>: <span class="hljs-string">"Access granted"</span>, user }),
    };
  } <span class="hljs-keyword">catch</span> {
    <span class="hljs-keyword">return</span> { <span class="hljs-attr">statusCode</span>: <span class="hljs-number">403</span>, <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify({ <span class="hljs-attr">error</span>: <span class="hljs-string">"Token expired"</span> }) };
  }
};

<span class="hljs-built_in">exports</span>.refresh = <span class="hljs-keyword">async</span> (event) =&gt; {
  <span class="hljs-keyword">const</span> cookie = event.headers.cookie || <span class="hljs-string">""</span>;
  <span class="hljs-keyword">const</span> token = cookie.split(<span class="hljs-string">"refreshToken="</span>)[<span class="hljs-number">1</span>];
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> user = verifyRefresh(token);
    <span class="hljs-keyword">const</span> { accessToken } = generateTokens(user);
    <span class="hljs-keyword">return</span> {
      <span class="hljs-attr">statusCode</span>: <span class="hljs-number">200</span>,
      <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify({ accessToken }),
    };
  } <span class="hljs-keyword">catch</span> {
    <span class="hljs-keyword">return</span> { <span class="hljs-attr">statusCode</span>: <span class="hljs-number">403</span>, <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify({ <span class="hljs-attr">error</span>: <span class="hljs-string">"Refresh token invalid"</span> }) };
  }
};
</code></pre>
<hr />
<h2 id="heading-nodejs-server-vs-aws-lambda-with-refresh-token">📊 Node.js Server vs AWS Lambda (with Refresh Token)</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Feature</td><td>Node.js + JWT + Refresh Token</td><td>AWS Lambda + API Gateway + JWT</td></tr>
</thead>
<tbody>
<tr>
<td>Token Handling</td><td>In memory or a cookie</td><td>Via API Gateway headers/cookies</td></tr>
<tr>
<td>Refresh Logic</td><td>Simple via cookie</td><td>Cookie needs to be manually parsed</td></tr>
<tr>
<td>Storage Option</td><td>Can use Redis for refresh token blacklist</td><td>Store tokens in DB or skip blacklist</td></tr>
<tr>
<td>Deployment</td><td>Runs continuously</td><td>Cold start but lower cost</td></tr>
<tr>
<td>Use Case</td><td>Complex backend apps</td><td>Scalable microservices, low-traffic auth</td></tr>
</tbody>
</table>
</div><hr />
<h2 id="heading-summary">🧠 Summary</h2>
<ul>
<li><p><strong>Access tokens</strong> are short-lived and used to access protected resources.</p>
</li>
<li><p><strong>Refresh tokens</strong> are long-lived and used to generate new access tokens without login.</p>
</li>
<li><p>JWTs help us implement <strong>stateless</strong> authentication in both <strong>Node.js servers</strong> and <strong>AWS Lambda</strong> functions.</p>
</li>
<li><p>Store <strong>refresh tokens securely</strong> (e.g., httpOnly cookies) and <strong>never expose them on the frontend</strong>.</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[🛡️ Protect Your GitHub Token: Proxying Private Releases for Electron Apps]]></title><description><![CDATA[When building apps that use GitHub Releases to distribute updates (e.g., via Electron), things are straightforward if your repo is public. But for private GitHub repos, there's a problem:

⚠️ You can’t directly expose the GitHub Release URL to your a...]]></description><link>https://blog.nishikanta.in/protect-your-github-token-proxying-private-releases-for-electron-apps</link><guid isPermaLink="true">https://blog.nishikanta.in/protect-your-github-token-proxying-private-releases-for-electron-apps</guid><dc:creator><![CDATA[Nishikanta Ray]]></dc:creator><pubDate>Fri, 23 May 2025 20:21:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1748031662959/e73c95b7-3290-4dc7-97de-9ee5f6249d59.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When building apps that use GitHub Releases to distribute updates (e.g., via Electron), things are straightforward if your repo is public. But for <strong>private GitHub repos</strong>, there's a problem:</p>
<blockquote>
<p>⚠️ You can’t directly expose the GitHub Release URL to your app without leaking your GitHub token.</p>
</blockquote>
<p>In this post, we’ll walk through how to securely <strong>proxy GitHub Release downloads through your own server</strong>, so that your token stays hidden while your app can still download updates.</p>
<hr />
<h2 id="heading-why-you-cant-use-the-github-url-directly">🚩 Why You Can’t Use the GitHub URL Directly</h2>
<p>GitHub requires authentication for private releases. That means you need to include an Authorization header with a personal access token (PAT):</p>
<pre><code class="lang-javascript">Authorization: token ghp_123abc...
</code></pre>
<p>Including this directly in your frontend or Electron app would be a <strong>huge security risk</strong>, since users could extract the token and access your private repo.</p>
<hr />
<h2 id="heading-the-secure-approach-use-a-proxy-server">✅ The Secure Approach: Use a Proxy Server</h2>
<p>Instead of downloading from GitHub directly, you set up a <strong>backend server</strong> that:</p>
<ul>
<li><p>Authenticates with GitHub using your token</p>
</li>
<li><p>Downloads the release asset</p>
</li>
<li><p>Proxies the file to the requesting client (your app)</p>
</li>
</ul>
<p>This way, only your server knows the token — the app just talks to your proxy.</p>
<hr />
<h2 id="heading-step-by-step-proxy-setup">🛠 Step-by-Step: Proxy Setup</h2>
<h3 id="heading-1-create-a-github-token">1. <strong>Create a GitHub Token</strong></h3>
<ul>
<li><p>Go to <a target="_blank" href="https://github.com/settings/tokens">GitHub Developer Settings</a></p>
</li>
<li><p>Create a <strong>classic token</strong> with:</p>
<ul>
<li><p><code>repo</code> scope (for private repositories)</p>
</li>
<li><p><code>read:packages</code> (if you're using GitHub Packages too)</p>
</li>
</ul>
</li>
</ul>
<p>Save this token in your backend as an environment variable, e.g., <code>GH_TOKEN</code>.</p>
<hr />
<h3 id="heading-2-create-the-proxy-endpoint">2. <strong>Create the Proxy Endpoint</strong></h3>
<p>Here’s a full example using Node.js (Express or similar):</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// downloadProxy.js</span>
<span class="hljs-keyword">const</span> fetch = <span class="hljs-built_in">require</span>(<span class="hljs-string">"node-fetch"</span>);

<span class="hljs-keyword">const</span> GITHUB_OWNER = <span class="hljs-string">"your-org-or-username"</span>;
<span class="hljs-keyword">const</span> GITHUB_REPO = <span class="hljs-string">"your-repo"</span>;
<span class="hljs-keyword">const</span> GH_TOKEN = process.env.GH_TOKEN;

<span class="hljs-keyword">const</span> headers = {
  <span class="hljs-attr">Authorization</span>: <span class="hljs-string">`token <span class="hljs-subst">${GH_TOKEN}</span>`</span>,
  <span class="hljs-string">"User-Agent"</span>: <span class="hljs-string">"SecureReleaseProxy"</span>,
  <span class="hljs-attr">Accept</span>: <span class="hljs-string">"application/vnd.github.v3+json"</span>
};

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getLatestRelease</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">`https://api.github.com/repos/<span class="hljs-subst">${GITHUB_OWNER}</span>/<span class="hljs-subst">${GITHUB_REPO}</span>/releases/latest`</span>, { headers });
  <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 latest release"</span>);
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> res.json();
}

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getAssetDownloadUrl</span>(<span class="hljs-params">assetName</span>) </span>{
  <span class="hljs-keyword">const</span> release = <span class="hljs-keyword">await</span> getLatestRelease();
  <span class="hljs-keyword">const</span> asset = release.assets.find(<span class="hljs-function"><span class="hljs-params">a</span> =&gt;</span> a.name === assetName);
  <span class="hljs-keyword">if</span> (!asset) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`Asset <span class="hljs-subst">${assetName}</span> not found`</span>);
  <span class="hljs-keyword">return</span> asset.url; <span class="hljs-comment">// This is the API URL, not a direct download link</span>
}

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">proxyAssetDownload</span>(<span class="hljs-params">req, res</span>) </span>{
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> { filename } = req.params;
    <span class="hljs-keyword">const</span> assetApiUrl = <span class="hljs-keyword">await</span> getAssetDownloadUrl(filename);

    <span class="hljs-keyword">const</span> githubRes = <span class="hljs-keyword">await</span> fetch(assetApiUrl, {
      <span class="hljs-attr">headers</span>: {
        ...headers,
        <span class="hljs-attr">Accept</span>: <span class="hljs-string">"application/octet-stream"</span>
      }
    });

    <span class="hljs-keyword">if</span> (!githubRes.ok) {
      res.status(<span class="hljs-number">500</span>).send(<span class="hljs-string">"Error fetching asset"</span>);
      <span class="hljs-keyword">return</span>;
    }

    res.setHeader(<span class="hljs-string">"Content-Disposition"</span>, <span class="hljs-string">`attachment; filename="<span class="hljs-subst">${filename}</span>"`</span>);
    res.setHeader(<span class="hljs-string">"Content-Type"</span>, <span class="hljs-string">"application/octet-stream"</span>);

    githubRes.body.pipe(res);
  } <span class="hljs-keyword">catch</span> (err) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Proxy error:"</span>, err);
    res.status(<span class="hljs-number">500</span>).send(<span class="hljs-string">"Proxy error"</span>);
  }
}

<span class="hljs-built_in">module</span>.exports = { proxyAssetDownload };
</code></pre>
<hr />
<h3 id="heading-3-add-the-express-route">3. <strong>Add the Express Route</strong></h3>
<p>In your main server file:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express"</span>);
<span class="hljs-keyword">const</span> { proxyAssetDownload } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./downloadProxy"</span>);

<span class="hljs-keyword">const</span> app = express();
<span class="hljs-keyword">const</span> PORT = <span class="hljs-number">3200</span>;

app.get(<span class="hljs-string">"/updates/download/:filename"</span>, proxyAssetDownload);

app.listen(PORT, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Proxy server listening on http://localhost:<span class="hljs-subst">${PORT}</span>`</span>);
});
</code></pre>
<hr />
<h3 id="heading-4-configure-electron-auto-updater">4. <strong>Configure Electron Auto-Updater</strong></h3>
<p>In your Electron app (using <code>electron-updater</code>), point to your proxy instead of GitHub:</p>
<pre><code class="lang-javascript">{
  <span class="hljs-string">"publish"</span>: [
    {
      <span class="hljs-string">"provider"</span>: <span class="hljs-string">"generic"</span>,
      <span class="hljs-string">"url"</span>: <span class="hljs-string">"http://localhost:3200/updates"</span>
    }
  ]
}
</code></pre>
<p>Your <code>latest.yml</code> file should also have URLs that match your proxy endpoint:</p>
<pre><code class="lang-javascript">version: <span class="hljs-number">1.0</span><span class="hljs-number">.0</span>
<span class="hljs-attr">files</span>:
  - url: Lets-Flo-Desktop<span class="hljs-number">-1.0</span><span class="hljs-number">.0</span>-mac.zip
    <span class="hljs-attr">sha512</span>: abc123...
    size: <span class="hljs-number">12345678</span>
<span class="hljs-attr">path</span>: Lets-Flo-Desktop<span class="hljs-number">-1.0</span><span class="hljs-number">.0</span>-mac.zip
<span class="hljs-attr">sha512</span>: abc123...
releaseDate: <span class="hljs-string">'2025-05-24T08:00:00.000Z'</span>
</code></pre>
<hr />
<h2 id="heading-security-best-practices">🔐 Security Best Practices</h2>
<ul>
<li><p>Store the <code>GH_TOKEN</code> only on your backend</p>
</li>
<li><p>Set strict scopes (no <code>write</code>, <code>delete</code>, or <code>admin</code>)</p>
</li>
<li><p>Use environment variables, not hardcoded strings</p>
</li>
<li><p>Add rate limiting / IP restrictions if possible</p>
</li>
</ul>
<hr />
<h2 id="heading-bonus-cache-the-release-info">💡 Bonus: Cache the Release Info</h2>
<p>To reduce GitHub API calls, you can cache the latest release metadata in memory or Redis and refresh it every X minutes.</p>
<hr />
<h2 id="heading-summary">✅ Summary</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Step</td><td>What You Do</td></tr>
</thead>
<tbody>
<tr>
<td>Create GH token</td><td>With <code>repo</code> access only</td></tr>
<tr>
<td>Setup proxy server</td><td>Download and stream release via backend</td></tr>
<tr>
<td>Use Electron <code>generic</code></td><td>Point to your server instead of GitHub</td></tr>
<tr>
<td>Hide token</td><td>Never expose token to frontend/app</td></tr>
</tbody>
</table>
</div><p>By proxying downloads through a secure server, you protect your GitHub credentials while still delivering updates seamlessly to your users.</p>
]]></content:encoded></item></channel></rss>