<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: GeekyAnts</title>
    <description>The latest articles on DEV Community by GeekyAnts (geekyants).</description>
    <link>https://dev.to/geekyants</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F12611%2F12ba258b-c1e8-4802-9a86-9447eacb706a.png</url>
      <title>DEV Community: GeekyAnts</title>
      <link>https://dev.to/geekyants</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/geekyants"/>
    <language>en</language>
    <item>
      <title>How Playwright Agents Are Changing the Way We Write and Maintain Tests</title>
      <dc:creator>GeekyAnts Inc</dc:creator>
      <pubDate>Fri, 10 Jul 2026 08:10:24 +0000</pubDate>
      <link>https://dev.to/geekyants/how-playwright-agents-are-changing-the-way-we-write-and-maintain-tests-3abg</link>
      <guid>https://dev.to/geekyants/how-playwright-agents-are-changing-the-way-we-write-and-maintain-tests-3abg</guid>
      <description>&lt;p&gt;&lt;em&gt;Adapted from the original post on the &lt;a href="https://geekyants.com/blog/from-manual-testing-to-ai-assisted-automation-with-playwright-agents" rel="noopener noreferrer"&gt;GeekyAnts blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Ask any &lt;a href="https://geekyants.com/engineering/quality-assurance/qa-automation-testing" rel="noopener noreferrer"&gt;automation engineer&lt;/a&gt; what their week looks like and you'll hear some version of the same story: read the requirements, write the test cases, script the automation, watch a locator break, chase down why, fix it, run it again. That loop hasn't really gone anywhere — it just moved frameworks, from Selenium to Cypress to Playwright.&lt;/p&gt;

&lt;p&gt;So here's a question worth sitting with: what if the framework didn't just run your tests, but actually helped plan them, write them, and repair them when they broke? That's the pitch behind &lt;a href="https://geekyants.com/en-in/blog/how-ai-ml-are-transforming-quality-assurance-in-software-testing-with-playwright-examples" rel="noopener noreferrer"&gt;Playwright's Test Agents&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Shipped in Playwright 1.56, these agents bring &lt;a href="https://geekyants.com/ai/ai-agent-development-services" rel="noopener noreferrer"&gt;AI&lt;/a&gt; into three specific stages of the testing lifecycle — planning, writing, and healing — by looping against your actual running application rather than guessing from a prompt alone.&lt;/p&gt;

&lt;p&gt;This piece walks through what the agents do, how a project gets wired up to use them, what a seed test is for, what comes out the other end, and where this genuinely differs from just asking &lt;a href="https://geekyants.com/blog/top-8-ai-coding-tools-for-developers-in-the-usa-2025-edition" rel="noopener noreferrer"&gt;ChatGPT or Cursor&lt;/a&gt; to write you a test script.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Playwright Alone Wasn't Enough
&lt;/h2&gt;

&lt;p&gt;Playwright earned its popularity by killing off a lot of the pain that made older frameworks miserable — flaky waits, brittle timing, locators that broke for no obvious reason. Auto-waiting, role-based selectors, and built-in tracing meant engineers spent less time fighting the tool and more time actually testing.&lt;/p&gt;

&lt;p&gt;But none of that touched the manual work upstream and downstream of running the tests: translating a requirement doc into concrete scenarios, turning those scenarios into actual code, rewriting tests every time the UI shifts, and chasing broken selectors after every release. Agents are Playwright's attempt to close that gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Three Agents Actually Do
&lt;/h2&gt;

&lt;p&gt;Playwright Test Agents are &lt;a href="https://geekyants.com/blog/revolutionizing-business-process-automation-with-ai-agents" rel="noopener noreferrer"&gt;AI workflows&lt;/a&gt; that live inside a project and split the job into three roles.&lt;/p&gt;

&lt;p&gt;The Planner takes a plain-language description of what needs testing, then uses a "seed test" as its entry point into the app to explore flows and edge cases. It comes back with a structured written test plan — not code, but the kind of scenario breakdown a &lt;a href="https://geekyants.com/service/hire-quality-assurance-developers" rel="noopener noreferrer"&gt;QA engineer&lt;/a&gt; would write by hand.&lt;/p&gt;

&lt;p&gt;The Generator takes that plan and turns it into runnable Playwright code. Crucially, it does this while poking at the live app — checking that selectors actually resolve, that assertions match real UI state — rather than hallucinating locators from memory. Told to, it'll follow a Page Object Model layout too.&lt;/p&gt;

&lt;p&gt;The Healer steps in when a test fails. It re-runs the failing scenario, looks at the current DOM, works out what changed, and tries to patch the selector, wait, or interaction logic itself, instead of leaving that job to whoever's on call.&lt;/p&gt;

&lt;p&gt;Chain the three together and the loop becomes Planner, then Generator, then Healer — turning a sentence of intent into a working, self-repairing test suite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up a Project
&lt;/h2&gt;

&lt;p&gt;Getting started involves scaffolding a standard Node and Playwright project first, exactly as any engineer already familiar with Playwright would expect. From there, Playwright's own agent tooling can be initialized, which adds a new folder of agent definitions into the project alongside the usual test folders.&lt;/p&gt;

&lt;p&gt;That new folder holds three files, one for each agent: the planner, the generator, and the healer, each written as a Markdown definition. Whatever AI assistant is driving the workflow, whether that's Claude Code, VS Code's Copilot, or another agentic coding tool, reads these definitions to understand how to behave in each role.&lt;/p&gt;

&lt;p&gt;Alongside the agent definitions, a typical project ends up with a folder for the Markdown plans the Planner produces, a folder for the actual generated Playwright test files, and a single seed test that acts as the Planner's starting point into the application. This layout is the convention Playwright's own tooling expects, so sticking to it keeps things predictable.&lt;/p&gt;

&lt;p&gt;The seed test matters more than it sounds like it should. It's the Planner's foothold: where to navigate first, what login step to run, what "home base" looks like for the application before any exploration begins. Alongside it, a small file of sample data (a couple of test user credentials, say — one valid, one intentionally invalid) gives the Planner enough grounding to write scenarios that are actually relevant to the app rather than generic boilerplate.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Planner Agent in Practice
&lt;/h2&gt;

&lt;p&gt;Think of the Planner less like a code generator and more like a QA analyst that happens to run on AI. Instead of jumping straight to code, it produces a Markdown test plan: scenarios, steps, expected outcomes, and the data each one needs.&lt;/p&gt;

&lt;p&gt;A prompt as simple as asking for a test plan covering login with both valid and invalid credentials, using the seed test as context, is enough to get it moving. The Planner explores the app through that seed test and writes out a plan as a readable document, not a script, meant to be read and approved before any code gets written. This is the step where a bad assumption gets caught early, before it turns into a bad test.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Generator Agent: Plan to Code
&lt;/h2&gt;

&lt;p&gt;Once a plan looks right, the same assistant can be switched into Generator mode and asked to turn that Markdown plan into actual TypeScript tests, following a Page Object Model structure if that's the preference.&lt;/p&gt;

&lt;p&gt;The Generator reads the plan, checks selectors and assertions against the live app as it writes, and produces test files — typically one for a straightforward happy path, one covering an edge case, and others built around shared helper functions. Each generated file maps back to a scenario from the plan. Because it's validating against the running app rather than guessing, the output tends to be noticeably more stable than what comes out of a plain prompt-to-code tool with no live context.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Healer Agent: Fixing What Breaks
&lt;/h2&gt;

&lt;p&gt;Tests fail. A button gets relabeled, a locator moves, and something that passed yesterday doesn't today. Normally that means opening the inspector, finding the new selector, and patching it by hand.&lt;/p&gt;

&lt;p&gt;The Healer automates that chase. Pointed at a specific failing test, it re-runs it in debug mode, inspects the current DOM for an equivalent element or flow, proposes a fix such as a new selector or an adjusted wait, and re-runs until it either passes or the agent concludes the failure actually reflects a real product change rather than a stale test. That second outcome matters: the goal isn't to force a green checkmark at any cost, it's to cut down the routine busywork of chasing UI churn.&lt;/p&gt;

&lt;h2&gt;
  
  
  Under the Hood: What's Actually Happening
&lt;/h2&gt;

&lt;p&gt;From the outside, all of this looks deceptively simple: type a sentence, get back a test plan or a fix. Underneath, the agents run a loop that reads project files, executes real test runs, drives the browser, and inspects the live DOM at every stage. The Planner explores through the seed test, the Generator checks selectors as it writes, the Healer replays failures to diagnose them. That complexity stays hidden — a prompt goes in, a plan, script, or fix comes out, and the orchestration in between isn't something anyone has to manage by hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping the Output Organized with Page Object Model
&lt;/h2&gt;

&lt;p&gt;If the generated code needs to stay maintainable long-term, and it should, it's worth being explicit about that in the prompt: asking for a Page Object Model structure with locators kept in separate page files. That nudges the Generator toward splitting locator definitions and reusable actions into their own files, separate from the test logic itself, which saves a lot of refactoring down the line.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Full Loop, End to End
&lt;/h2&gt;

&lt;p&gt;Run all three agents together and the shape of the workflow is: a seed test plus a prompt goes to the Planner, which writes a Markdown plan; that plan goes to the Generator, which writes the tests; those tests run, and any failures go to the Healer, which fixes what it can. What comes out the other side is a test suite that largely maintains itself. It's the same lifecycle a human QA team already runs, just with AI doing the first draft at every stage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where This Differs From Generic AI Coding Tools
&lt;/h2&gt;

&lt;p&gt;It's tempting to lump this in with Cursor, ChatGPT, or any other "AI writes my code" tool, but the comparison doesn't hold up well. Generic AI code generation can write code from a prompt, but it doesn't run the tests it writes, doesn't fix failures on its own, and doesn't check the live DOM while generating. Playwright Agents do all three, and they do it wired directly into Playwright's own tooling through MCP (Model Context Protocol), staying in the loop with the running application throughout rather than generating code from a prompt in isolation.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Few Things That Make This Work Better
&lt;/h2&gt;

&lt;p&gt;Giving the agents real context matters more than clever prompting — a well-built seed test and structured seed data make a bigger difference than a cleverly worded instruction. Prompts should be specific about architecture preferences, data sources, and expected outputs rather than left open-ended. Generated tests are still worth a human read-through; good scaffolding isn't the same as a finished, reviewed test. And in CI, healed or freshly generated tests are best treated as drafts until someone's actually looked at them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Between the Planner, Generator, and Healer, Playwright's agents take a real bite out of the manual overhead in test automation, turning a plain-language description into scenarios, code, and self-repair without cutting humans out of the review loop.&lt;/p&gt;

&lt;p&gt;Anyone with basic Playwright experience already has most of what they need to try this. It's the same lifecycle, with AI doing more of the first draft. Worth trying on a small project first: set up the agents, write a decent seed test, and see how far a simple prompt actually gets.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Adapted from the original article on the &lt;a href="https://geekyants.com/blog/from-manual-testing-to-ai-assisted-automation-with-playwright-agents" rel="noopener noreferrer"&gt;GeekyAnts blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>automation</category>
      <category>playwright</category>
      <category>ai</category>
    </item>
    <item>
      <title>Building a Production-Ready Image Cropper in React Native</title>
      <dc:creator>GeekyAnts Inc</dc:creator>
      <pubDate>Thu, 09 Jul 2026 07:13:15 +0000</pubDate>
      <link>https://dev.to/geekyants/building-a-production-ready-image-cropper-in-react-native-5ckm</link>
      <guid>https://dev.to/geekyants/building-a-production-ready-image-cropper-in-react-native-5ckm</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Originally published on the &lt;a href="https://geekyants.com/blog/building-a-production-ready-image-cropper-in-react-native" rel="noopener noreferrer"&gt;GeekyAnts Blog&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Build a production-ready, gesture-driven image cropper from scratch — supporting circular profile crops, rectangular cover crops, draggable windows, resizable corners, and pixel-perfect coordinate mapping back to the original image.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://geekyants.com/service/hire-mobile-app-development-services" rel="noopener noreferrer"&gt;modern mobile applications&lt;/a&gt;, image handling is a core experience. Whether uploading a profile picture or setting a cover image, users expect precision, control, and real-time feedback.&lt;/p&gt;

&lt;p&gt;While third-party libraries exist, they often introduce heavy dependencies or limit customization. In this article, we'll build a fully custom, gesture-driven image cropper using &lt;a href="https://geekyants.com/hire-react-native-developers" rel="noopener noreferrer"&gt;React Native&lt;/a&gt; and expo-image-manipulator — supporting both circular profile crops and rectangular cover crops.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why expo-image-manipulator?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;expo-image-manipulator&lt;/strong&gt; provides a native bridge for performing image transformations efficiently. All operations — crop, resize, rotate, flip — execute on the native thread, which means they don't block the JS runtime and scale well even on lower-end Android devices.&lt;/p&gt;

&lt;p&gt;Compared to pure JS image-processing approaches, the native execution path avoids decoding the full bitmap into a JS array buffer, which can easily exhaust memory on high-resolution photos. The library also handles format conversion (JPEG, PNG, WEBP) and compression in a single pass, so you aren't writing a cropped image to disk and then re-encoding it for upload — the final &lt;strong&gt;manipulateAsync&lt;/strong&gt; call produces the upload-ready file directly.&lt;/p&gt;

&lt;p&gt;The package ships two API styles. The legacy imperative API (&lt;strong&gt;manipulateAsync&lt;/strong&gt;) applies a list of transforms in a single async call — ideal for a one-shot crop like ours. The newer context-based API (&lt;strong&gt;useImageManipulator / ImageManipulator.manipulate&lt;/strong&gt;) enables chainable, background-threaded transforms and shines when you need to compose many operations interactively without writing intermediate files to disk. We'll use the imperative API throughout this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation &amp;amp; Setup
&lt;/h2&gt;

&lt;p&gt;Install via the Expo CLI so the correct native version is automatically matched to your SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx expo install expo-image-manipulator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a bare React Native project (without Expo Go), also run the iOS pod install:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx pod-install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No additional permissions are required. The manipulator operates entirely on local file URIs produced by your image picker — it never touches the camera roll or network directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  expo-image-manipulator API Overview
&lt;/h2&gt;

&lt;p&gt;We'll use the imperative API — it's perfectly suited to a one-shot crop operation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Core Function
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ImageManipulator.manipulateAsync(
  uri,        // local file URI or base64 data URI
  actions,    // array of transformation objects
  saveOptions // format, compress, base64
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It returns a &lt;strong&gt;Promise&lt;/strong&gt; which resolves to:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;url&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;Local URI to the new file (cached)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;width&lt;/td&gt;
&lt;td&gt;number&lt;/td&gt;
&lt;td&gt;Width of the output image in pixels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;height&lt;/td&gt;
&lt;td&gt;number&lt;/td&gt;
&lt;td&gt;Height of the output image in pixels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;base64&lt;/td&gt;
&lt;td&gt;string?&lt;/td&gt;
&lt;td&gt;Base64 payload if base64: true was passed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Available Actions
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action key&lt;/th&gt;
&lt;th&gt;Parameters&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;crop&lt;/td&gt;
&lt;td&gt;originX, originY, width, height&lt;/td&gt;
&lt;td&gt;Crops a rectangular region (in original image pixels)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;resize&lt;/td&gt;
&lt;td&gt;width, height&lt;/td&gt;
&lt;td&gt;Scales the image; omit one dimension to keep ratio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rotate&lt;/td&gt;
&lt;td&gt;degrees&lt;/td&gt;
&lt;td&gt;Clockwise rotation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;flip&lt;/td&gt;
&lt;td&gt;"horizontal" | "vertical"&lt;/td&gt;
&lt;td&gt;Mirror along an axis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;extent&lt;/td&gt;
&lt;td&gt;originX, originY, width, height, backgroundColor&lt;/td&gt;
&lt;td&gt;Canvas resize with optional padding fill&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Save Options
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;format&lt;/td&gt;
&lt;td&gt;SaveFormat&lt;/td&gt;
&lt;td&gt;JPEG&lt;/td&gt;
&lt;td&gt;JPEG, PNG, or WEBP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;compress&lt;/td&gt;
&lt;td&gt;0.0 – 1.0&lt;/td&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;td&gt;1 = highest quality, 0 = maximum compression&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;base64&lt;/td&gt;
&lt;td&gt;boolean&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;Include raw base64 in the result&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Component Architecture
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;&lt;/strong&gt; component lives inside an action sheet and owns three pieces of state that together describe the current crop selection:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;containerSize&lt;/strong&gt; — measured width and height of the image container, captured via onLayout&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;cropPosition&lt;/strong&gt; — the top-left corner of the crop window in container-space coordinates ({ x, y })&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;windowDimensions&lt;/strong&gt; — the current width and height of the crop window in container space&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All crop math operates in container space first, then gets converted to original image pixel space at the moment the user taps Crop. This separation keeps gesture handling fast and stateless — no expensive native calls happen during a drag.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnyytw8vyz9w9qpmrj0jg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnyytw8vyz9w9qpmrj0jg.png" alt="React Native image crop architecture with container sizing, crop position, and upload pipeline" width="800" height="157"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Props
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;interface ImageCropProps {
  showCropper:        boolean;
  handleClose:        () =&amp;gt; void;
  selectedUploadType: 'profile' | 'cover';
  uploadFunction:     (data: ImageManipulator.ImageResult) =&amp;gt; void;
  tempImage:          string;  // local URI from image picker
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On mount, two effects run in sequence: the first sets &lt;strong&gt;windowDimensions&lt;/strong&gt; based on the upload type (square for profile, wide rectangle for cover), and the second centers the crop window inside the container once both sizes are known.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Profile: square up to 200 px. Cover: full width × 150 px.
useEffect(() =&amp;gt; {
  if (containerSize.width &amp;gt; 0) {
    if (selectedUploadType === 'profile') {
      const size = Math.min(containerSize.width, 200);
      setWindowDimensions({ width: size, height: size });
    } else {
      setWindowDimensions({ width: containerSize.width, height: 150 });
    }
  }
}, [containerSize, selectedUploadType]);

// Center the window once both sizes are known
useEffect(() =&amp;gt; {
  if (containerSize.width &amp;gt; 0 &amp;amp;&amp;amp; windowDimensions.width &amp;gt; 0) {
    setCropPosition({
      x: (containerSize.width  - windowDimensions.width)  / 2,
      y: (containerSize.height - windowDimensions.height) / 2,
    });
  }
}, [containerSize, windowDimensions]);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Building The Drag &amp;amp; Resize System
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Dragging the crop window
&lt;/h3&gt;

&lt;p&gt;The crop window is a positioned &lt;strong&gt;View&lt;/strong&gt; sitting absolutely on top of the image. A &lt;strong&gt;PanResponder&lt;/strong&gt; attached to its inner area tracks the gesture delta (&lt;strong&gt;dx&lt;/strong&gt;, &lt;strong&gt;dy&lt;/strong&gt;) and updates &lt;strong&gt;cropPosition&lt;/strong&gt;, clamping it so the window never exits the container boundary.&lt;/p&gt;

&lt;p&gt;One subtle requirement: the pan responder must not activate when the touch originates inside one of the 20 px corner zones — otherwise the drag and resize responders fire simultaneously and produce jittery, conflicting movement. We check touch coordinates in &lt;strong&gt;onStartShouldSetPanResponder&lt;/strong&gt; and return &lt;strong&gt;false&lt;/strong&gt; for corner touches.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const panResponder = useMemo(() =&amp;gt;
  PanResponder.create({
    onStartShouldSetPanResponder: (evt) =&amp;gt; {
      const { locationX: x, locationY: y } = evt.nativeEvent;
      const cs = 20;
      const inCorner =
        (x &amp;lt;= cs || x &amp;gt;= windowDimensions.width  - cs) &amp;amp;&amp;amp;
        (y &amp;lt;= cs || y &amp;gt;= windowDimensions.height - cs);
      return !inCorner;
    },
    onMoveShouldSetPanResponder: (evt) =&amp;gt; {
      // Mirrors onStartShouldSetPanResponder — keeps both predicates in sync
      const { locationX: x, locationY: y } = evt.nativeEvent;
      const cs = 20;
      return !(
        (x &amp;lt;= cs || x &amp;gt;= windowDimensions.width  - cs) &amp;amp;&amp;amp;
        (y &amp;lt;= cs || y &amp;gt;= windowDimensions.height - cs)
      );
    },
    onPanResponderMove: (_, { dx, dy }) =&amp;gt; {
      setCropPosition({
        x: Math.max(0, Math.min(
          cropPosition.x + dx,
          containerSize.width - windowDimensions.width
        )),
        y: Math.max(0, Math.min(
          cropPosition.y + dy,
          containerSize.height - windowDimensions.height
        )),
      });
    },
  }),
  [containerSize, cropPosition, windowDimensions]
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why useMemo matters here
&lt;/h3&gt;

&lt;p&gt;The pan responder captures &lt;strong&gt;cropPosition&lt;/strong&gt; and &lt;strong&gt;windowDimensions&lt;/strong&gt; in its closure at creation time. Without &lt;strong&gt;useMemo&lt;/strong&gt;, a new responder is created on every render, but the gesture system holds a reference to the original — meaning your move handler reads stale state and the crop window drifts noticeably during a fast drag. Wrapping in &lt;strong&gt;useMemo&lt;/strong&gt; with the correct dependency array ensures the responder is always reading current values.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resizing with corner handles
&lt;/h2&gt;

&lt;p&gt;Each of the four corner handles has its own &lt;strong&gt;PanResponder&lt;/strong&gt;, created by a factory function. The geometry differs per corner: dragging bottom-right simply extends width and height, while dragging top-left must simultaneously shrink the window and shift its origin so the opposite corner stays anchored in place.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const createCornerPanResponder = (
  corner: 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight'
) =&amp;gt; PanResponder.create({
  onStartShouldSetPanResponder: () =&amp;gt; true,
  onPanResponderMove: (_, { dx, dy }) =&amp;gt; {
    let newW = windowDimensions.width;
    let newH = windowDimensions.height;
    let newX = cropPosition.x;
    let newY = cropPosition.y;
    switch (corner) {
      case 'bottomRight':
        newW = Math.max(minDimensions.width,  windowDimensions.width  + dx);
        newH = isProfile ? newW : Math.max(minDimensions.height, windowDimensions.height + dy);
        break;
      case 'topLeft':
        newW = Math.max(minDimensions.width,  windowDimensions.width  - dx);
        newH = isProfile ? newW : Math.max(minDimensions.height, windowDimensions.height - dy);
        newX = cropPosition.x - (newW - windowDimensions.width);
        newY = cropPosition.y - (newH - windowDimensions.height);
        break;
      case 'topRight':
        newW = Math.max(minDimensions.width,  windowDimensions.width  + dx);
        newH = isProfile ? newW : Math.max(minDimensions.height, windowDimensions.height - dy);
        newY = cropPosition.y + (windowDimensions.height - newH);
        break;
      case 'bottomLeft':
        newW = Math.max(minDimensions.width,  windowDimensions.width  - dx);
        newH = isProfile ? newW : Math.max(minDimensions.height, windowDimensions.height + dy);
        newX = cropPosition.x - (newW - windowDimensions.width);
        break;
    }
    // Commit only if the new geometry fits entirely within the container
    if (
      newX &amp;gt;= 0 &amp;amp;&amp;amp; newY &amp;gt;= 0 &amp;amp;&amp;amp;
      newX + newW &amp;lt;= containerSize.width &amp;amp;&amp;amp;
      newY + newH &amp;lt;= containerSize.height
    ) {
      setWindowDimensions({ width: newW, height: newH });
      setCropPosition({ x: newX, y: newY });
    }
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For profile mode, height is always forced equal to width (&lt;strong&gt;newH = newW&lt;/strong&gt;) regardless of which corner is dragged, preserving a perfect 1:1 aspect ratio at every resize step. Cover mode allows independent width and height resizing, subject to configurable minimum dimensions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coordinate Mapping: Display → Original Pixels
&lt;/h2&gt;

&lt;p&gt;This is the most critical — and most commonly misunderstood — part of building a &lt;a href="https://geekyants.com/blog/image-cropping-and-optimised-transfer-in-flutter" rel="noopener noreferrer"&gt;custom cropper&lt;/a&gt;. Your crop window lives in &lt;em&gt;container space&lt;/em&gt; (screen pixels inside the &lt;strong&gt;View&lt;/strong&gt;), but &lt;strong&gt;manipulateAsync&lt;/strong&gt; expects coordinates in &lt;em&gt;original image pixel space&lt;/em&gt;. The two are not the same.&lt;/p&gt;

&lt;p&gt;When an image is rendered with &lt;strong&gt;contentFit="cover"&lt;/strong&gt;, React Native scales it so the shorter dimension fills the container exactly — and the longer dimension overflows and is clipped. This means part of the image is hidden off-screen, and any crop coordinate you compute from container space will be offset by exactly that hidden amount unless you correct for it.&lt;/p&gt;

&lt;p&gt;The conversion has five steps:&lt;/p&gt;

&lt;p&gt;Step 1 — Get the original image dimensions. Call &lt;strong&gt;manipulateAsync&lt;/strong&gt; with an empty actions array. This is a lightweight metadata read with no transformation cost.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const imageInfo = await ImageManipulator.manipulateAsync(
  tempImage, [], { compress: 1 }
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 2 — Determine the displayed dimensions. Compare aspect ratios to find which axis is the constraining one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const imgAR       = imageInfo.width / imageInfo.height;
const containerAR = containerSize.width / containerSize.height;
let displayedW, displayedH;
if (imgAR &amp;gt; containerAR) {
  // Image is wider than container — height fills, width overflows left/right
  displayedH = containerSize.height;
  displayedW = containerSize.height * imgAR;
} else {
  // Image is taller than container — width fills, height overflows top/bottom
  displayedW = containerSize.width;
  displayedH = containerSize.width / imgAR;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 3 — Compute the overflow offset. The image is centered inside the container, so the hidden portion is split equally on both sides.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const offsetX = (displayedW - containerSize.width)  / 2;  // 0 when image is taller
const offsetY = (displayedH - containerSize.height) / 2;  // 0 when image is wider
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 4 — Compute scale factors between the original image and the displayed image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const scaleX = imageInfo.width  / displayedW;
const scaleY = imageInfo.height / displayedH;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 5 — Convert UI coordinates to image coordinates.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const actualCropX = Math.round((cropPosition.x + offsetX) * scaleX);
const actualCropY = Math.round((cropPosition.y + offsetY) * scaleY);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Executing The Crop — Profile &amp;amp; Cover
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Profile Photo (square, resized to 200 x 200)
&lt;/h3&gt;

&lt;p&gt;For a profile photo, we want a square crop. We clamp the crop size so it never requests pixels beyond the image boundary, then chain a resize to normalise all uploads to 200 × 200 regardless of how large the user's original photo was.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const actualCropSize = Math.round(windowDimensions.width * scaleX);
const finalSize = Math.min(
  actualCropSize,
  imageInfo.width  - actualCropX,
  imageInfo.height - actualCropY,
);
const croppedImage = await ImageManipulator.manipulateAsync(
  tempImage,
  [
    { crop: {
        originX: actualCropX, originY: actualCropY,
        width:   finalSize,   height:  finalSize,
    }},
    { resize: { width: 200, height: 200 }},
  ],
  { compress: 1, format: ImageManipulator.SaveFormat.JPEG }
);
uploadFunction(croppedImage);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Cover photo (rectangular)
&lt;/h3&gt;

&lt;p&gt;The cover crop uses independent width and height scale factors and clamps each dimension separately so neither ever exceeds the remaining pixels after the crop origin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const actualCropW = Math.min(
  Math.round(windowDimensions.width  * scaleX),
  imageInfo.width  - actualCropX,
);
const actualCropH = Math.min(
  Math.round(windowDimensions.height * scaleY),
  imageInfo.height - actualCropY,
);


const croppedImage = await ImageManipulator.manipulateAsync(
  tempImage,
  [{ crop: {
      originX: actualCropX, originY: actualCropY,
      width:   actualCropW,  height:  actualCropH,
  }}],
  { compress: 1, format: ImageManipulator.SaveFormat.JPEG }
);
uploadFunction(croppedImage);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Darkened Overlay Cutout
&lt;/h3&gt;

&lt;p&gt;Rather than a single semi-transparent layer with a punched-out hole (which requires SVG clip-paths or canvas rendering), we use four darkened Views — one for each side of the crop window. Their positions and sizes are derived directly from &lt;strong&gt;cropPosition&lt;/strong&gt; and &lt;strong&gt;windowDimensions&lt;/strong&gt;, so they update in real time as the user drags or resizes without any additional state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{/* Top — spans full width, height = distance from top to crop window */}
&amp;lt;View style={{ position: 'absolute', top: 0, left: 0, right: 0,
  height: cropPosition.y, backgroundColor: 'rgba(0,0,0,0.5)' }} /&amp;gt;
{/* Bottom — starts where the crop window ends */}
&amp;lt;View style={{ position: 'absolute', bottom: 0, left: 0, right: 0,
  height: containerSize.height - (cropPosition.y + windowDimensions.height),
  backgroundColor: 'rgba(0,0,0,0.5)' }} /&amp;gt;
{/* Left — only spans the crop window's row to avoid double-darkening corners */}
&amp;lt;View style={{ position: 'absolute', top: cropPosition.y, left: 0,
  width: cropPosition.x, height: windowDimensions.height,
  backgroundColor: 'rgba(0,0,0,0.5)' }} /&amp;gt;
{/* Right — mirrors left */}
&amp;lt;View style={{ position: 'absolute', top: cropPosition.y, right: 0,
  width: containerSize.width - (cropPosition.x + windowDimensions.width),
  height: windowDimensions.height,
  backgroundColor: 'rgba(0,0,0,0.5)' }} /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside the crop window, the border style switches between a full-radius dashed circle (borderRadius: 999, borderStyle: 'dashed') for profile mode and a plain solid rectangle for cover mode. A rule-of-thirds grid is drawn with four thin Views at 33% and 66% positions, giving users a professional alignment guide without any additional library.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { ImageCrop } from './ImageCrop';


const MyScreen = () =&amp;gt; {
  const [showCropper, setShowCropper] = useState(false);
  const [pickedImage,  setPickedImage]  = useState&amp;lt;string | null&amp;gt;(null);
  const handleUpload = (result: ImageManipulator.ImageResult) =&amp;gt; {
    uploadToServer(result.uri);
    setShowCropper(false);
  };
  return (
    &amp;lt;&amp;gt;
      {/* ... rest of your screen ... */}
      {pickedImage &amp;amp;&amp;amp;
        &amp;lt;ImageCrop
          showCropper={showCropper}
          handleClose={() =&amp;gt; setShowCropper(false)}
          selectedUploadType="profile"
          tempImage={pickedImage}
          uploadFunction={handleUpload}
        /&amp;gt;
      }
    &amp;lt;/&amp;gt;
  );
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  UX Enhancements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Grid lines (rule of thirds)&lt;/li&gt;
&lt;li&gt;Corner handles for resizing&lt;/li&gt;
&lt;li&gt;Loading overlay during processing&lt;/li&gt;
&lt;li&gt;Auto-centered crop window&lt;/li&gt;
&lt;li&gt;Dynamic resizing based on container&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Performance Considerations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Avoid repeated &lt;strong&gt;manipulateAsync&lt;/strong&gt; calls&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;useMemo&lt;/strong&gt; for gesture handlers&lt;/li&gt;
&lt;li&gt;Perform crop only on user confirmation&lt;/li&gt;
&lt;li&gt;Use compression wisely (&lt;strong&gt;0.8–1&lt;/strong&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Advanced Extensions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Pinch-to-zoom support&lt;/li&gt;
&lt;li&gt;Rotation &amp;amp; flipping controls&lt;/li&gt;
&lt;li&gt;Circular live preview&lt;/li&gt;
&lt;li&gt;Cloud upload integration (S3/Firebase)&lt;/li&gt;
&lt;li&gt;Combined zoom + crop gestures&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary &amp;amp; Key Takeaways
&lt;/h2&gt;

&lt;p&gt;Building a custom image cropper in React Native gives you &lt;strong&gt;full control over &lt;a href="https://geekyants.com/service/ui-ux-design-services" rel="noopener noreferrer"&gt;UX&lt;/a&gt;, performance, and flexibility&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Key learnings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;expo-image-manipulator&lt;/strong&gt; handles heavy image processing natively&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PanResponder&lt;/strong&gt; is sufficient for complex gesture interactions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coordinate mapping is the backbone&lt;/strong&gt; of accurate cropping&lt;/li&gt;
&lt;li&gt;Overlay-based masking provides a clean UI without extra dependencies&lt;/li&gt;
&lt;li&gt;Supporting multiple crop modes requires thoughtful architecture&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach is particularly valuable for applications handling user-generated content, where performance, flexibility, and consistent media output directly impact user experience and scalability.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Special thanks to Deepanshu Goyal for architectural guidance and review.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was originally published on the &lt;a href="https://geekyants.com/blog/building-a-production-ready-image-cropper-in-react-native" rel="noopener noreferrer"&gt;GeekyAnts Blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Geeklego: The Open-Source Design System Built to Work With AI</title>
      <dc:creator>GeekyAnts Inc</dc:creator>
      <pubDate>Wed, 08 Jul 2026 11:02:49 +0000</pubDate>
      <link>https://dev.to/geekyants/geeklego-the-open-source-design-system-built-to-work-with-ai-2nmi</link>
      <guid>https://dev.to/geekyants/geeklego-the-open-source-design-system-built-to-work-with-ai-2nmi</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Fork it. Define your 3-tier design system. Let Claude Code, Codex, Gemini CLI — or any AI coding tool — generate a consistent, accessible UI that can never break your design rules.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Originally published on the &lt;a href="https://geekyants.com/blog/geeklego-the-open-source-design-system-built-to-work-with-ai" rel="noopener noreferrer"&gt;GeekyAnts Blog&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Problem With Design Systems in the Age of AI
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://geekyants.com/solution/design-system-development-service" rel="noopener noreferrer"&gt;Design systems&lt;/a&gt; are supposed to bring consistency. But in practice, they are a document that developers mean to follow, a Notion page that's three months out of date, and a set of tokens that might or might not match what's actually in the code.&lt;/p&gt;

&lt;p&gt;Now add &lt;a href="https://geekyants.com/blog/top-8-ai-coding-tools-for-developers-in-the-usa-2025-edition" rel="noopener noreferrer"&gt;AI coding tools&lt;/a&gt; to the picture. Claude Code, Codex, Gemini CLI, and Cursor — these tools generate components fast. Impressively fast. But fast without constraints is how you end up with hardcoded hex values, skipped token tiers, and five different ways to truncate text across a codebase that was supposed to be consistent.&lt;/p&gt;

&lt;p&gt;The problem is structural: design systems are defined separately from the rules that enforce them. &lt;a href="https://geekyants.com/blog/top-10-ai-tools-every-uiux-designer-should-master" rel="noopener noreferrer"&gt;AI tools&lt;/a&gt; have no way to know that &lt;code&gt;--color-brand-500&lt;/code&gt; should never appear directly in a component, or that every spacing value must come from a semantic token, or that dark mode only works if you respect the semantic tier. No one told the AI. So it guesses — and it guesses consistently wrong in different ways.&lt;/p&gt;

&lt;p&gt;Geeklego is our answer to this problem. And it's open source.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Geeklego?
&lt;/h2&gt;

&lt;p&gt;Geeklego is an open-source, &lt;a href="https://geekyants.com/blog/reimagining-the-future-of-design-systems-with-ai" rel="noopener noreferrer"&gt;AI-native design system&lt;/a&gt; built on Tailwind CSS v4. It ships three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A 3-tier design system — a single CSS file (&lt;code&gt;geeklego.css&lt;/code&gt;) that defines every visual value across three strict tiers: raw primitives, purpose-driven semantics, and component-specific tokens. It is the authoritative, machine-readable source of truth for the entire project.&lt;/li&gt;
&lt;li&gt;A skill layer — six AI skills (for Claude Code, Codex, Gemini CLI, or any agent-capable tool) that generate components, sync with Figma, audit for accessibility and security, and add internationalization — all while enforcing the 3-tier architecture automatically.&lt;/li&gt;
&lt;li&gt;81 production-ready components — atoms, molecules, and organisms that are expressions of the design system, each generated by the skill layer and proven to follow the rules.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Geeklego is not a component library you install and override. It's a design system you own and extend — with an AI layer that makes extension fast, consistent, and safe.&lt;/p&gt;

&lt;p&gt;The philosophy is simple: define the system first. Let AI build from it, not around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Vision: A Design System That Teaches Itself to AI
&lt;/h2&gt;

&lt;p&gt;Every AI coding tool — Claude Code, Codex, Gemini CLI, Cursor — is only as good as the context it has. Give it no context, and it invents conventions. Give it a good context, and it follows yours.&lt;/p&gt;

&lt;p&gt;Geeklego's vision is to make the design system itself that context. Not a README that someone has to remember to reference. Not a Storybook that AI can't read. The actual architecture — the 3-tier token chain, the 5-file component structure, the 45 explicit rules — is encoded in a machine-readable spec (&lt;code&gt;CLAUDE.md&lt;/code&gt;) that any AI coding tool can consume before it writes a single line.&lt;/p&gt;

&lt;p&gt;AI that is constrained by your design system, not set loose on your codebase.&lt;/p&gt;

&lt;p&gt;When any AI tool generates a component in Geeklego, it can't hardcode &lt;code&gt;#6366f1&lt;/code&gt;. It can't use &lt;code&gt;h-[40px]&lt;/code&gt;. It can't reference a Tier 1 primitive directly from a component. The architecture makes inconsistency structurally impossible — not a guideline someone has to remember, not a lint rule someone can suppress.&lt;/p&gt;

&lt;p&gt;The long-term vision: you define the system once. Every &lt;a href="https://geekyants.com/ai" rel="noopener noreferrer"&gt;AI&lt;/a&gt; that ever touches your codebase — today's tools, next year's tools, tools that don't exist yet — generates components that follow it perfectly. Hundreds of components, zero design drift. That's what Geeklego is building toward.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design-System-First: The 3-Tier Architecture That Changes Everything
&lt;/h2&gt;

&lt;p&gt;In every component library you've used, the design system emerges from component code. You build a Button, extract its colors into variables, build a Card, extract its spacing, and over time something resembling a design system appears.&lt;/p&gt;

&lt;p&gt;Geeklego inverts this completely.&lt;/p&gt;

&lt;p&gt;Before a single component is written, the design system is defined and published across three distinct tiers. Components are built as expressions of the system, constrained to use only what the system provides — and only in the order the system prescribes.&lt;/p&gt;

&lt;h3&gt;
  
  
  The 3-Tier Token Chain
&lt;/h3&gt;

&lt;p&gt;This is the central idea in Geeklego. Everything else flows from it.&lt;/p&gt;

&lt;p&gt;Tier 1 — Primitives are raw values: &lt;code&gt;--color-brand-500: #6366f1&lt;/code&gt;, &lt;code&gt;--spacing-4: 1rem&lt;/code&gt;. They are the foundation of the entire system. No component ever references a primitive directly. That's the rule.&lt;/p&gt;

&lt;p&gt;Tier 2 — Semantics give purpose to primitives: &lt;code&gt;--color-action-primary: var(--color-brand-500)&lt;/code&gt;. This is where theming happens. When you switch to dark mode, semantic tokens update. Primitives stay unchanged. The semantic tier is the single mechanism that makes theme switching work correctly across every component in the library simultaneously.&lt;/p&gt;

&lt;p&gt;Tier 3 — Component tokens bind individual components to semantics: &lt;code&gt;--button-bg: var(--color-action-primary)&lt;/code&gt;. A component token must always reference a Tier 2 semantic. Referencing a primitive directly — skipping Tier 2 — is forbidden. This is non-negotiable, encoded in the architecture, and enforced by the Claude Code skill before it writes a single line of component code.&lt;/p&gt;

&lt;p&gt;Here's the full chain for a Button's background color:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* Tier 1 — Primitives*/&lt;/span&gt;
&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;--color-brand-500&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;#6366&lt;/span&gt;&lt;span class="nt"&gt;f1&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c"&gt;/* Tier 2 — Semantics*/&lt;/span&gt;
&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;--color-action-primary&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--color-brand-500&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nt"&gt;Tier&lt;/span&gt; &lt;span class="err"&gt;3&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="nt"&gt;Component&lt;/span&gt; &lt;span class="nt"&gt;tokens&lt;/span&gt;&lt;span class="o"&gt;*/&lt;/span&gt;
&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;--button-bg&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--color-action-primary&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in a component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"bg-[var(--button-bg)] ..."&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Submit&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's why all three tiers are necessary. When you toggle dark mode, you update one Tier 2 semantic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-theme&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"dark"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  &lt;span class="py"&gt;--color-action-primary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--color-brand-400&lt;/span&gt;&lt;span class="p"&gt;);}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the only change. Every component that reads &lt;code&gt;--button-bg&lt;/code&gt; picks up the new value through the chain — automatically, correctly, everywhere. No per-component dark mode overrides. No &lt;code&gt;!important&lt;/code&gt; patches. No missing cases discovered in production.&lt;/p&gt;

&lt;p&gt;If a component token had skipped Tier 2 and referenced a Tier 1 primitive directly, that component would be invisible to theme switches. It would render the same color in light and dark mode, and you'd never know why until a designer filed a bug report.&lt;/p&gt;

&lt;p&gt;The 3-tier chain is not a bureaucracy. It's the mechanism that makes the entire system coherent.&lt;/p&gt;

&lt;h3&gt;
  
  
  No Arbitrary Values. Ever.
&lt;/h3&gt;

&lt;p&gt;The 3-tier chain only works if it's airtight. So Geeklego forbids Tailwind arbitrary values like &lt;code&gt;bg-[#6366f1]&lt;/code&gt; or &lt;code&gt;h-[40px]&lt;/code&gt;. Every value — every color, every size, every spacing unit — must flow through all three tiers and arrive as a Tier 3 component token.&lt;/p&gt;

&lt;p&gt;The only permitted syntax is &lt;code&gt;bg-[var(--button-bg)]&lt;/code&gt;, wrapping a Tier 3 CSS variable in Tailwind v4's arbitrary value syntax.&lt;/p&gt;

&lt;p&gt;This constraint has a profound side effect: AI can only generate valid, chain-compliant code. When Claude Code generates a component, there's nowhere to put a hardcoded value. The language doesn't permit it. Design integrity is enforced by architecture, not code review.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Ships With Geeklego Today
&lt;/h2&gt;

&lt;h3&gt;
  
  
  81 Production-Ready Components
&lt;/h3&gt;

&lt;p&gt;Geeklego currently ships three levels of the component hierarchy:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;L1 Atoms (38 components)&lt;/strong&gt; — the building blocks: Avatar, Badge, Button, Checkbox, Input, Select, Spinner, Switch, Tooltip, and 29 more. Atoms import nothing — they're pure expressions of the design system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;L2 Molecules (27 components)&lt;/strong&gt; — combinations of atoms: AlertBanner, Breadcrumb, ButtonGroup, Calendar, Card, Combobox, DateInput, DropdownMenu, Fieldset, FormField, InputGroup, Navbar, NumberInput, Pagination, Popover, RadioGroup, SearchBar, StatCard, Stepper, Toast, Tooltip, TreeView, and more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;L3 Organisms (16 components)&lt;/strong&gt; — full UI sections: Accordion, AreaChart, BarChart, Carousel, Chat, ColorPicker, DataTable, Datepicker, Drawer, Footer, Form, Header, Modal, PieChart, Sidebar, Tabs, and more in active development.&lt;/p&gt;

&lt;p&gt;L4 Templates and L5 Pages are in active development. DashboardLayout, AuthLayout, and LandingLayout are coming next.&lt;/p&gt;

&lt;p&gt;Every component in the fork ships with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full TypeScript types and JSDoc-annotated props&lt;/li&gt;
&lt;li&gt;Light and dark mode support out of the box, via the 3-tier token chain&lt;/li&gt;
&lt;li&gt;WCAG 2.2 Level AA accessibility (keyboard nav, ARIA, focus management)&lt;/li&gt;
&lt;li&gt;Responsive design via Tailwind responsive prefixes&lt;/li&gt;
&lt;li&gt;8 Storybook stories per component (used as automated tests via Vitest)&lt;/li&gt;
&lt;li&gt;Optional Schema.org markup for SEO&lt;/li&gt;
&lt;li&gt;Optional i18n string support&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  A Visual Token Editor
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;npm run dev&lt;/code&gt; opens a full React app at &lt;code&gt;localhost:5176&lt;/code&gt; for editing the design system without touching code.&lt;/p&gt;

&lt;p&gt;A three-panel layout: a left NavRail organizes tokens into three tiers (Foundations with 8 sub-categories like color, spacing, typography, and shadow; Semantic with purpose-driven alias groups; Components listed by level) with drill-down sub-categories, a center pane shows the selected category or component, and a right Inspector panel displays token details with value editing and alias chain visualization. Export is available from the header bar. Changes preview instantly across Storybook via WebSocket. Full undo/redo history. Automatic backup. Token validation. Export as CSS or JSON.&lt;/p&gt;

&lt;p&gt;This means designers can edit the design system directly. Not in a Notion doc that may or may not reflect the code. In the actual source of truth, with live preview.&lt;/p&gt;

&lt;h3&gt;
  
  
  Storybook as Living Tests
&lt;/h3&gt;

&lt;p&gt;Geeklego treats Storybook stories as executable tests. Each &lt;code&gt;.stories.tsx&lt;/code&gt; file becomes a Vitest test running headless in Chromium via Playwright. No separate test infrastructure. Stories serve double duty as documentation and automated regression tests.&lt;/p&gt;

&lt;p&gt;Every component requires eight stories: default, variant A, variant B, size/scale, disabled state, loading state, dark mode, and an accessibility story tagged for WCAG audit.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Skill Layer: AI That Knows the Rules
&lt;/h2&gt;

&lt;p&gt;This is the part of Geeklego that has no equivalent in any other design system.&lt;/p&gt;

&lt;p&gt;Geeklego ships with six skills — self-contained, machine-readable workflow definitions that any capable AI coding tool can execute. They work today with Claude Code, and are designed to be portable to Codex, Gemini CLI, or any agent-capable environment that can read a prompt file and call tools.&lt;/p&gt;

&lt;p&gt;Each skill is not a prompt template. It's a disciplined workflow that reads the design system state first, validates the 3-tier chain, applies the change, then runs a verification pass. The AI doesn't guess. It follows a documented process.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Six Skills
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/component-builder&lt;/code&gt;&lt;/strong&gt; — The flagship skill. Invoke it with any component description and the AI reads &lt;code&gt;geeklego.css&lt;/code&gt;, verifies no token block already exists, writes Tier 3 component tokens (each correctly referencing a Tier 2 semantic), generates all five required files, then runs four sequential audits: accessibility, performance, SEO, and security. A component that takes a human two to three hours ships in two to three minutes — and is structurally guaranteed to follow the token chain.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/component-builder build a Tooltip atom/component-builder build a StatCard molecule/component-builder build a DataTable organism with sorting and pagination
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/figma-sync&lt;/code&gt;&lt;/strong&gt; — Sync your Tier 1–3 token definitions to Figma variable collections and text styles automatically. The skill diffs the current Figma state against &lt;code&gt;geeklego.css&lt;/code&gt;, reports what's out of sync, then creates or updates variables to match. Design and code never drift apart.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/i18n&lt;/code&gt;&lt;/strong&gt; — Add internationalization to any component without committing to a specific i18n library. Library-agnostic, prop-first architecture with RTL support via logical CSS properties. Reads the string inventory to know which system strings already have i18n coverage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/state-handling&lt;/code&gt;&lt;/strong&gt; — Audit and generate visual state patterns. Every interactive component needs correct loading, disabled, error, and selected states with proper ARIA attributes. This skill reads the state-handling reference, validates what exists, and generates what's missing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/security-review&lt;/code&gt;&lt;/strong&gt; — Audit components for XSS vulnerabilities, unsafe href handling, missing &lt;code&gt;rel="noopener"&lt;/code&gt; on external links, and unsanitized user input. Runs against the full component tree.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/screenshot-workflow&lt;/code&gt;&lt;/strong&gt; — Generate component screenshots automatically for documentation, changelogs, or CI/CD visual regression pipelines.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  On-Demand Audit Commands
&lt;/h3&gt;

&lt;p&gt;Beyond the skills, natural-language audit commands run across the entire codebase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;do a refactor check     # Memoization, hooks compliance, performance utility usagedo a reuse audit        # Find inlined markup that should use existing atomsdo an SEO audit         # Schema.org coverage, semantic HTML, heading hierarchy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Machine-Readable Spec
&lt;/h3&gt;

&lt;p&gt;The skills work because the design system's rules are encoded in a machine-readable spec — &lt;code&gt;CLAUDE.md&lt;/code&gt; — that any AI coding tool reads before starting any task. It contains 45 explicit "never do" rules, 49 "always do" rules, naming conventions, the token chain hierarchy, component level definitions, and reference documents for worked examples, semantic HTML, ARIA patterns, and Schema.org mappings.&lt;/p&gt;

&lt;p&gt;This is not a README for humans. It's a contract for AI. Every tool that reads it gets the same constraints. Design integrity is preserved not by hoping the AI guesses right, but by telling it exactly what right means — before it generates a single token.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Geeklego Differs From Other Design Systems
&lt;/h2&gt;

&lt;p&gt;Most design systems are collections of decisions: here are the colors, here are the spacing values, here is a Figma file. Implementation is left to the team. Consistency depends on culture, code review, and people remembering the rules.&lt;/p&gt;

&lt;p&gt;Geeklego is a design system that enforces its own rules. Not through linting, not through code review, not through policy — through architecture. The 3-tier chain makes it structurally impossible to introduce a hardcoded value or skip a token tier, because the skill that generates components checks the chain before writing anything. And the machine-readable spec means any AI tool you bring to the project inherits those constraints immediately.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Typical design system&lt;/th&gt;
&lt;th&gt;Geeklego&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Token architecture&lt;/td&gt;
&lt;td&gt;Ad hoc or flat&lt;/td&gt;
&lt;td&gt;Strict 3-tier chain: primitives → semantics → component tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dark mode&lt;/td&gt;
&lt;td&gt;Per-component overrides&lt;/td&gt;
&lt;td&gt;Automatic — update Tier 2 semantics, all components cascade&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI compatibility&lt;/td&gt;
&lt;td&gt;Unstructured — AI must guess conventions&lt;/td&gt;
&lt;td&gt;Machine-readable spec — AI reads the rules before generating anything&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Component generation&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;Skill-driven with full 3-tier validation and audit pipeline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Figma integration&lt;/td&gt;
&lt;td&gt;Manual, periodic sync&lt;/td&gt;
&lt;td&gt;Automated sync skill, diff-aware&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Token editor&lt;/td&gt;
&lt;td&gt;External tools or none&lt;/td&gt;
&lt;td&gt;Built-in visual editor with live preview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accessibility&lt;/td&gt;
&lt;td&gt;Depends on the team&lt;/td&gt;
&lt;td&gt;WCAG 2.2 AA baked into the component-builder skill&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;i18n&lt;/td&gt;
&lt;td&gt;Separate effort&lt;/td&gt;
&lt;td&gt;Library-agnostic pattern, built into the skill layer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-brand support&lt;/td&gt;
&lt;td&gt;Fork and diverge&lt;/td&gt;
&lt;td&gt;Fork and share component logic; only tokens differ&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The shift is from "design system as document" to "design system as enforced architecture." One you reference. The other you can't break.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Use Cases
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Rapid prototyping.&lt;/strong&gt; Edit the design system in the token editor. Use &lt;code&gt;/component-builder&lt;/code&gt; to generate components that match. Preview in Storybook. Sync tokens to Figma for designer feedback. When the design is locked, your production components are ready. Two days instead of two weeks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Design system governance at scale.&lt;/strong&gt; Define your tokens. Every team generates components via &lt;code&gt;/component-builder&lt;/code&gt; — they can't deviate because the architecture prevents it. Run audits on PRs. The design system becomes enforceable by architecture, not by policy. Code reviews drop from 30 minutes to 5 minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-brand products.&lt;/strong&gt; Fork Geeklego twice. Each fork has its own &lt;code&gt;geeklego.css&lt;/code&gt; with its own colors, fonts, and spacing. Both forks share identical component logic. Update the logic once, both brands benefit. Maintain one codebase, support multiple brands.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accessible component libraries.&lt;/strong&gt; Accessibility is built into the component-builder skill — not bolted on after. Every generated component includes the right ARIA attributes, keyboard navigation patterns, touch targets, and semantic HTML. WCAG 2.2 AA by default, not by accident.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started: Fork and Make It Yours
&lt;/h2&gt;

&lt;p&gt;Geeklego is designed to be forked, not installed. Forking gives you the full 3-tier design system, the visual token editor, all 81 components, and every AI skill — everything you need to customize the system and build from it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Fork the repo on GitHub, then clone your fork&lt;/span&gt;
git clone https://github.com/YOUR-USERNAME/geeklego.git
&lt;span class="nb"&gt;cd &lt;/span&gt;geeklego

&lt;span class="c"&gt;# 2. Install dependenciesnpm install&lt;/span&gt;

&lt;span class="c"&gt;# 3. Start both the token editor and Storybooknpm run dev:all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two apps open:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;localhost:5176&lt;/code&gt; — the token editor: customize your Tier 1–3 tokens visually, no code required&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;localhost:6006&lt;/code&gt; — Storybook with all 81 components, live-connected to the token editor&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Open your AI tool and start building:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# With Claude Code&lt;/span&gt;

/help                                &lt;span class="c"&gt;# see all available skills&lt;/span&gt;
/component-builder build a &lt;span class="o"&gt;[&lt;/span&gt;ComponentName]  &lt;span class="c"&gt;# generate a new component end-to-end&lt;/span&gt;

&lt;span class="c"&gt;# With Gemini CLI or Codex&lt;/span&gt;

&lt;span class="c"&gt;# Point your agent at CLAUDE.md as system context,&lt;/span&gt;

&lt;span class="c"&gt;# then trigger the same skill workflows from the .claude/skills/ directory&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The machine-readable spec in &lt;code&gt;CLAUDE.md&lt;/code&gt; is read automatically by Claude Code before every task. For Codex, Gemini CLI, or other agent-capable tools, pass &lt;code&gt;CLAUDE.md&lt;/code&gt; as your system context — it's plain markdown and works with any tool that accepts instruction files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Customize your design system:
&lt;/h3&gt;

&lt;p&gt;Open the token editor at &lt;code&gt;localhost:5176&lt;/code&gt;. Edit Tier 1 primitives (your brand colors, spacing scale, type sizes), Tier 2 semantics (what each primitive means in context), and watch every component in Storybook update in real time. When you're ready, export as CSS or JSON.&lt;/p&gt;

&lt;p&gt;That's it. The design system is yours. The components follow it automatically. The AI skill layer keeps new components consistent with it, forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Contribute
&lt;/h2&gt;

&lt;p&gt;Geeklego is &lt;a href="https://geekyants.com/open-source" rel="noopener noreferrer"&gt;open source&lt;/a&gt; and actively welcoming contributors. The remaining work on the roadmap includes HeroSection (L3), DashboardLayout, AuthLayout, and LandingLayout (L4 Templates).&lt;/p&gt;

&lt;p&gt;Every contribution follows the same pattern — 5 files, the 3-tier token chain, 8 Storybook stories. The machine-readable spec and the skill layer make it straightforward to use your AI tool of choice to generate a contribution that meets the bar from the first commit. You don't need to memorize the rules — the spec teaches the AI, and the AI generates compliant code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/GeekyAnts/geeklego" rel="noopener noreferrer"&gt;https://github.com/GeekyAnts/geeklego&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Website: &lt;a href="https://geeklego.io/?utm_source=geekyants.com&amp;amp;utm_medium=referral&amp;amp;utm_campaign=geeklego_cross_promotion" rel="noopener noreferrer"&gt;https://geeklego.io&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Issues &amp;amp; discussions: open a thread on GitHub&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Give it a star, open an issue, or fork it — use &lt;code&gt;/component-builder&lt;/code&gt; to generate your contribution and it'll follow the rules from the first commit. We'd love to see what you build.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Geeklego is open source under the MIT license. Built with React 19, Tailwind CSS v4, Vite 6, Storybook 10, and Vitest.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://geekyants.com/blog/geeklego-the-open-source-design-system-built-to-work-with-ai" rel="noopener noreferrer"&gt;geekyants.com/blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>designsystem</category>
      <category>ai</category>
    </item>
    <item>
      <title>The Keyboard Bounce of Death: Handling Inputs on Complex React Native Screens</title>
      <dc:creator>GeekyAnts Inc</dc:creator>
      <pubDate>Fri, 03 Jul 2026 13:01:37 +0000</pubDate>
      <link>https://dev.to/geekyants/the-keyboard-bounce-of-death-handling-inputs-on-complex-react-native-screens-1682</link>
      <guid>https://dev.to/geekyants/the-keyboard-bounce-of-death-handling-inputs-on-complex-react-native-screens-1682</guid>
      <description>&lt;p&gt;Modern &lt;a href="https://geekyants.com/hire-react-native-developers" rel="noopener noreferrer"&gt;React Native&lt;/a&gt; has evolved significantly—Expo's new architecture is stable, Hermes is the default engine, layout rendering has improved, and libraries like Reanimated and React Native Screens are widely adopted. Yet one issue continues to frustrate even experienced engineers:&lt;/p&gt;

&lt;h2&gt;
  
  
  The Keyboard Bounce of Death
&lt;/h2&gt;

&lt;p&gt;Your inputs jump, shift, or completely hide behind the keyboard—a frustrating experience that makes your app feel buggy and cheap.&lt;/p&gt;

&lt;p&gt;If you are building login screens, onboarding flows, multi-step forms, or bottom-sheet-based UIs… you've likely seen this happen.&lt;/p&gt;

&lt;p&gt;This article explains why this problem still occurs in modern React Native setups and provides &lt;strong&gt;battle-tested solutions&lt;/strong&gt; you can confidently use in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Version Context
&lt;/h2&gt;

&lt;p&gt;The examples and recommendations are written with &lt;strong&gt;current (2024–2025) React Native stacks in mind&lt;/strong&gt;, commonly seen in production apps today:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;React Native:&lt;/strong&gt; 0.74+&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React:&lt;/strong&gt; 18&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expo SDK:&lt;/strong&gt; 50+ (New Architecture enabled)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hermes:&lt;/strong&gt; Enabled (default)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;react-native-keyboard-controller:&lt;/strong&gt; 1.20.x&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;react-native-reanimated:&lt;/strong&gt; 3.x&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;react-native-screens:&lt;/strong&gt; 3.x&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;@shopify/flash-list:&lt;/strong&gt; 1.x&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These versions are provided &lt;strong&gt;for context rather than certification&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Exact behavior may vary depending on project configuration, &lt;a href="https://geekyants.com/solution/universal-and-cross-platform-app-development-services" rel="noopener noreferrer"&gt;cross-platform&lt;/a&gt; (iOS / Android), and library versions. Readers should validate keyboard behavior against their own application stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Problem: Keyboard Handling Breaks at the Architecture Level
&lt;/h2&gt;

&lt;p&gt;In modern &lt;a href="https://geekyants.com/blog/the-use-of-ai-for-development-and-debugging-of-react-native-apps" rel="noopener noreferrer"&gt;React Native applications&lt;/a&gt;, keyboard issues are no longer caused by misconfigured props or missing offsets. The failure is architectural.&lt;/p&gt;

&lt;p&gt;Classic solutions such as &lt;strong&gt;KeyboardAvoidingView&lt;/strong&gt; assume a synchronous, stable layout model. That assumption no longer holds in React Native's current execution environment, where rendering, layout, and native events operate on partially decoupled timelines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the breakdown happens
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Layout virtualization (React Native Screens ~3.30.x)
&lt;/h3&gt;

&lt;p&gt;Navigation stacks aggressively detach inactive screens to reduce memory and improve performance. This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The active screen's layout is often unresolved during transitions&lt;/li&gt;
&lt;li&gt;Height measurements are stale at the moment keyboard events fire&lt;/li&gt;
&lt;li&gt;Offset calculations are applied against incomplete layout data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keyboard avoidance logic executes correctly—but against the wrong inputs.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Concurrent Rendering Changes Layout Timing (RN 0.74+)
&lt;/h3&gt;

&lt;p&gt;With React 18 integration and the &lt;a href="https://geekyants.com/service/scalable-architecture-design-development-service" rel="noopener noreferrer"&gt;New Architecture&lt;/a&gt;, React Native now supports concurrent rendering. Unlike legacy synchronous rendering, concurrent rendering allows work to be interruptible and scheduled across frames.&lt;/p&gt;

&lt;p&gt;This changes long-standing assumptions about layout timing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Layout commits are no longer guaranteed to be synchronous.&lt;/li&gt;
&lt;li&gt;Native keyboard events can fire before the React tree has fully committed.&lt;/li&gt;
&lt;li&gt;Measurements taken during focus events may reflect intermediate layout states.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In complex screens (navigation stacks, bottom sheets, nested scroll views), this can produce visible misalignment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keyboard height is computed.&lt;/li&gt;
&lt;li&gt;Offset is applied.&lt;/li&gt;
&lt;li&gt;Layout commits afterward.&lt;/li&gt;
&lt;li&gt;The UI shifts again.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result appears as a "bounce," but the underlying issue is timing desynchronization between native keyboard animations and React layout commits.&lt;/p&gt;

&lt;p&gt;This is not a bug in React Native — it is a natural consequence of concurrent rendering's scheduling model.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Android keyboard behavior is non-deterministic by design
&lt;/h3&gt;

&lt;p&gt;On Android 12–14, keyboard behavior varies significantly by OEM:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Heights are percentage-based, not pixel-stable&lt;/li&gt;
&lt;li&gt;Gesture navigation dynamically alters safe areas&lt;/li&gt;
&lt;li&gt;Floating and split keyboards introduce transient viewports&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;React Native receives incomplete or rapidly changing inset information, making consistent keyboard avoidance mathematically unreliable.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Nested, stateful scroll containers amplify the problem
&lt;/h3&gt;

&lt;p&gt;Most production forms are embedded inside:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bottom sheets&lt;/li&gt;
&lt;li&gt;Modals&lt;/li&gt;
&lt;li&gt;Tab navigators&lt;/li&gt;
&lt;li&gt;Collapsible or animated headers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These containers continuously mutate their layout constraints. Auto-scroll and focus calculations depend on deterministic container heights—conditions that no longer exist in deeply composed UI trees.&lt;/p&gt;

&lt;p&gt;All this means classic keyboard handling methods break on multi-layered screens.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example Problem Scenario (What Actually Happens)
&lt;/h3&gt;

&lt;p&gt;You have a complex screen with a header, multi-section form inputs, a bottom CTA, all inside a ScrollView and wrapped inside a navigation stack.&lt;/p&gt;

&lt;p&gt;When the user taps the last input:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The input jumps upward suddenly, OR&lt;/li&gt;
&lt;li&gt;Gets partially hidden, OR&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ScrollView&lt;/strong&gt; auto-scrolls too late, OR&lt;/li&gt;
&lt;li&gt;The keyboard overlaps your crucial bottom CTA button.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the reality of real-world apps, especially those built on the modern Expo/new architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Modern Solution Stack (Production-Proven)
&lt;/h2&gt;

&lt;p&gt;These are the modern, recommended approaches that actually fix the issue in today's &lt;a href="https://geekyants.com/blog/rethinking-react-native-architecture-part-i" rel="noopener noreferrer"&gt;React Native architecture&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution 1 — Native Keyboard Synchronization with KeyboardProvider (react-native-keyboard-controller 1.20.x)
&lt;/h3&gt;

&lt;p&gt;Modern React Native apps fail at keyboard handling not because of incorrect offsets or missing props, but because keyboard animations and layout updates are not synchronized at the native level.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;react-native-keyboard-controller&lt;/strong&gt; addresses this root cause by coordinating keyboard state, animation timing, and layout updates directly in native code.&lt;/p&gt;

&lt;h4&gt;
  
  
  Recommended Integration: Root-Level KeyboardProvider
&lt;/h4&gt;

&lt;p&gt;The primary integration point is &lt;strong&gt;KeyboardProvider&lt;/strong&gt;, which enables native keyboard synchronization across the entire application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;KeyboardProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-native-keyboard-controller&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;KeyboardProvider&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;RootNavigator&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/KeyboardProvider&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This enables native keyboard coordination for the entire app.&lt;/p&gt;

&lt;h4&gt;
  
  
  Scoped Keyboard Handling with KeyboardControllerView
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;KeyboardControllerView&lt;/strong&gt; is an internal building block used by KeyboardProvider.&lt;/p&gt;

&lt;p&gt;It should only be used when keyboard coordination must be explicitly scoped to a specific layout subtree.&lt;/p&gt;

&lt;p&gt;Typical use cases include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Forms inside modals or bottom sheets&lt;/li&gt;
&lt;li&gt;Screens where only a portion of the UI should respond to keyboard movement&lt;/li&gt;
&lt;li&gt;Preventing unnecessary re-layout of non-interactive UI&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Why is it better
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Computes keyboard height precisely&lt;/li&gt;
&lt;li&gt;Works flawlessly with bottom sheets, tabs, and modals.&lt;/li&gt;
&lt;li&gt;Better on Android 12–14&lt;/li&gt;
&lt;li&gt;No layout jumps&lt;/li&gt;
&lt;li&gt;Supports concurrent rendering&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Runtime Control with useKeyboardController
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;useKeyboardController&lt;/strong&gt; is a hook that gives access to the state of the react-native-keyboard-controller library. It returns two values:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;enabled - a boolean value which indicates whether the library is enabled in the app;&lt;/li&gt;
&lt;li&gt;setEnabled - a function that changes the state of the enabled property.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This hook is used &lt;strong&gt;only&lt;/strong&gt; to enable or disable the native keyboard controller—primarily for gradual Android integration or fallback to &lt;strong&gt;adjustResize&lt;/strong&gt; behavior when required.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution 2 — Native-Friendly Scroll Containers (Reanimated 3.10+, FlashList 1.7.x)
&lt;/h3&gt;

&lt;p&gt;Keyboard issues on complex screens often occur when scroll position and keyboard movement are driven by JavaScript.&lt;/p&gt;

&lt;p&gt;This causes delayed measurements and visible layout jumps during focus transitions.&lt;/p&gt;

&lt;p&gt;To remove JavaScript from the critical layout path, use scroll containers that integrate cleanly with native animation timing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Animated.ScrollView (Reanimated) for small, controlled layouts&lt;/li&gt;
&lt;li&gt;FlashList for long, dynamic, or virtualized content&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both integrate better with native-driven keyboard animations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reanimated ScrollView Example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Animated&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ScrollView&lt;/span&gt;
  &lt;span class="nx"&gt;keyboardShouldPersistTaps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;handled&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;contentContainerStyle&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;paddingBottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;keyboardHeight&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Animated.ScrollView&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;FlashList Example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FlashList&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@shopify/flash-list&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;FlashList&lt;/span&gt;
  &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;estimatedItemSize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;keyboardShouldPersistTaps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;handled&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;contentContainerStyle&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;
    &lt;span class="na"&gt;paddingBottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;keyboardHeight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}}&lt;/span&gt;
  &lt;span class="nx"&gt;renderItem&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;renderItem&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Solution 3 — If You Use Bottom Sheets (Reanimated / Gorhom)
&lt;/h3&gt;

&lt;p&gt;Bottom sheets introduce an additional animated layer that can conflict with keyboard transitions and scrolling.&lt;/p&gt;

&lt;p&gt;Without careful configuration, this often results in keyboard bounce, input focus jumps, or unstable drag behavior.&lt;/p&gt;

&lt;h4&gt;
  
  
  Bottom Sheet Gesture Configuration
&lt;/h4&gt;

&lt;p&gt;Disable content panning only when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The bottom sheet contains multiple text inputs&lt;/li&gt;
&lt;li&gt;Nested scroll views compete with drag gestures&lt;/li&gt;
&lt;li&gt;Keyboard interaction feels jumpy or unstable
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;enableContentPanningGesture={false}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Android Keyboard Mode
&lt;/h4&gt;

&lt;p&gt;Use this only as a fallback when relying on the default Android window resizing behavior.&lt;/p&gt;

&lt;p&gt;Forcing &lt;strong&gt;adjustResize&lt;/strong&gt; can reintroduce layout jumps if combined with native keyboard synchronization.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;android_keyboardInputMode="adjustResize"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Solution 4 — Use useWindowDimensions() Instead of Static Screen Height
&lt;/h3&gt;

&lt;p&gt;Keyboard and screen size changes often break layouts that rely on static dimensions:&lt;/p&gt;

&lt;p&gt;The problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dimensions.get('window')&lt;/strong&gt; is &lt;strong&gt;static&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;It does not update when the keyboard appears, the screen rotates, or the device enters split-screen&lt;/li&gt;
&lt;li&gt;Any layout based on this value may be clipped, overlapped by the keyboard, or cause scroll jumps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useWindowDimensions&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It adapts when the keyboard reduces available height dynamically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution 5 — Use KeyboardAwareScrollView for Automatic Input Scrolling
&lt;/h3&gt;

&lt;p&gt;When your form contains multiple inputs, simply preventing layout jumps is often not enough.&lt;/p&gt;

&lt;p&gt;You also want the focused input to scroll into view automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;react-native-keyboard-aware-scroll-view&lt;/strong&gt; handles this efficiently.&lt;/p&gt;

&lt;h4&gt;
  
  
  Why This Works
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Automatically scrolls the focused input above the keyboard&lt;/li&gt;
&lt;li&gt;Works with nested forms and long lists&lt;/li&gt;
&lt;li&gt;Supports Android and iOS, including safe handling of bottom tabs&lt;/li&gt;
&lt;li&gt;Integrates well with native keyboard controllers (like &lt;strong&gt;react-native-keyboard-controller&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;Minimal setup — reduces boilerplate scroll logic&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Practical Real-World Implementation (Full Code Example)
&lt;/h3&gt;

&lt;p&gt;This is a rock-solid implementation used in production onboarding flows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;KeyboardAwareScrollView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useKeyboardState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-native-keyboard-controller&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;TextInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;View&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-native&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;RegistrationForm&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;keyboardHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useKeyboardState&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;KeyboardAwareScrollView&lt;/span&gt;
      &lt;span class="nx"&gt;contentContainerStyle&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;paddingBottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;keyboardHeight&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
      &lt;span class="nx"&gt;bottomOffset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// optional offset for bottom CTA buttons&lt;/span&gt;
      &lt;span class="nx"&gt;keyboardShouldPersistTaps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;handled&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;InputField&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;InputField&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;InputField&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/KeyboardAwareScrollView&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;InputField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TextInput&lt;/span&gt;
      &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;
        &lt;span class="na"&gt;borderWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;marginVertical&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;borderRadius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}}&lt;/span&gt;
    &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;iOS + Android&lt;/li&gt;
&lt;li&gt;Gesture navigation&lt;/li&gt;
&lt;li&gt;Variable keyboards&lt;/li&gt;
&lt;li&gt;Concurrent rendering&lt;/li&gt;
&lt;li&gt;ScrollView-based layouts&lt;/li&gt;
&lt;li&gt;Bottom CTA buttons&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ALL without layout jumps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Debugging Checklist (What To Test Before Shipping)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Test split-screen mode on Android&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your input may be hidden due to reduced viewport height.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Test floating keyboard mode&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;TikTok/Samsung keyboards break height assumptions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Test device rotation&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keyboard height changes drastically in landscape.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Test inside modals &amp;amp; bottom sheets&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most keyboard bugs occur inside nested containers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Measure layout flicker using this:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adb logcat | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"keyboard"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;adb logcat&lt;/strong&gt; → streams &lt;strong&gt;real-time Android system logs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;grep "keyboard" → filters logs related to keyboard / IME events&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you will see&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Logs like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keyboard show/hide events&lt;/li&gt;
&lt;li&gt;IME height changes&lt;/li&gt;
&lt;li&gt;Window resize calls&lt;/li&gt;
&lt;li&gt;Focus changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why this matters&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple keyboard show/hide events for &lt;strong&gt;one input focus&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Rapid resize → unresize cycles&lt;/li&gt;
&lt;li&gt;Keyboard height changing more than once&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the root &lt;strong&gt;cause of flicker/bounce&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;or for iOS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;xcrun simctl spawn booted log stream &lt;span class="nt"&gt;--level&lt;/span&gt; debug | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"keyboard"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What this does&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Streams &lt;strong&gt;iOS system logs&lt;/strong&gt; from the Simulator&lt;/li&gt;
&lt;li&gt;Filters keyboard-related notifications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What you'll see&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UIKeyboardWillShow&lt;/li&gt;
&lt;li&gt;UIKeyboardDidShow&lt;/li&gt;
&lt;li&gt;Frame and height updates&lt;/li&gt;
&lt;li&gt;Animation duration mismatches&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why this matters&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WillShow fires multiple times&lt;/li&gt;
&lt;li&gt;Keyboard frame changes mid-animation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your layout is reacting out of sync with the keyboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion: Keyboard Handling in Modern React Native Requires a New Approach
&lt;/h2&gt;

&lt;p&gt;The assumptions behind &lt;strong&gt;KeyboardAvoidingView&lt;/strong&gt; no longer hold up for modern React Native applications that rely on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Concurrent rendering&lt;/li&gt;
&lt;li&gt;react-native-screens&lt;/li&gt;
&lt;li&gt;Bottom sheets and modals&lt;/li&gt;
&lt;li&gt;The new architecture and Hermes&lt;/li&gt;
&lt;li&gt;Deeply layered, animated UIs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In these environments, keyboard behavior becomes a cross-cutting concern, involving layout measurement, animation timing, gesture handling, and native window resizing.&lt;/p&gt;

&lt;p&gt;To build reliable, input-heavy screens, production apps increasingly rely on a combination of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Native keyboard synchronization (&lt;strong&gt;react-native-keyboard-controller&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;Reanimated or FlashList-based scrolling&lt;/li&gt;
&lt;li&gt;Dynamic keyboard height handling&lt;/li&gt;
&lt;li&gt;Scroll-to-focused-input utilities&lt;/li&gt;
&lt;li&gt;Intentional layout stabilization techniques&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This layered approach reflects how high-quality production apps—including financial, social, and AI-driven products—handle keyboard interactions today.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally published on the &lt;a href="https://geekyants.com/blog/the-keyboard-bounce-of-death-handling-inputs-on-complex-react-native-screens" rel="noopener noreferrer"&gt;GeekyAnts blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>reactnative</category>
    </item>
    <item>
      <title>Your Vibe Code Has No Memory. DESIGN.md Fixes That.</title>
      <dc:creator>GeekyAnts Inc</dc:creator>
      <pubDate>Fri, 03 Jul 2026 05:13:24 +0000</pubDate>
      <link>https://dev.to/geekyants/your-vibe-code-has-no-memory-designmd-fixes-that-1jm3</link>
      <guid>https://dev.to/geekyants/your-vibe-code-has-no-memory-designmd-fixes-that-1jm3</guid>
      <description>&lt;p&gt;&lt;em&gt;A single Markdown file called DESIGN.md gives your AI agent the design memory it lacks — keeping your UI consistent across every session.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every session, your &lt;a href="https://geekyants.com/ai/ai-agent-development-services" rel="noopener noreferrer"&gt;AI agent&lt;/a&gt; starts fresh. It picks generic colors, invents its own spacing, and drifts further from your actual UI. One Markdown file ends that.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://geekyants.com/blog/from-vibe-coding-to-scalable-systems-navigating-the-era-of-ai-assisted-engineering" rel="noopener noreferrer"&gt;Vibe coding&lt;/a&gt; works. You describe a feature, the &lt;a href="https://geekyants.com/ai" rel="noopener noreferrer"&gt;AI builds&lt;/a&gt; it, you ship faster than ever. But spend a week on a product and you notice something: the button on page three uses a different blue than page one. The card padding is inconsistent. The font size on the new modal does not match anything else.&lt;/p&gt;

&lt;p&gt;The AI did not forget. It never knew. Every prompt starts from zero.&lt;/p&gt;

&lt;p&gt;This is the actual problem with vibe coding — not the code quality, not hallucinations, not even bugs. It is the &lt;em&gt;drift&lt;/em&gt;. Session by session, page by page, the UI quietly loses coherence.&lt;/p&gt;

&lt;p&gt;DESIGN.md is the fix. And paired with Google Stitch and Claude Code, it is quickly becoming the standard way serious builders approach AI-assisted frontend work.&lt;/p&gt;

&lt;h2&gt;
  
  
  What DESIGN.md actually is
&lt;/h2&gt;

&lt;p&gt;It is a plain Markdown file. No proprietary format, no JSON schema, no Figma plugin required. You drop it in the root of your repo, and every AI agent that reads your project now knows your color palette, your type scale, your spacing system, your component rules.&lt;/p&gt;

&lt;p&gt;A basic one looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Design System&lt;/span&gt;

&lt;span class="gu"&gt;## Colors&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Primary: #1A73E8
&lt;span class="p"&gt;-&lt;/span&gt; Background: #F8F9FA
&lt;span class="p"&gt;-&lt;/span&gt; Text: #1F2937

&lt;span class="gu"&gt;## Typography&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Font: Inter, sans-serif
&lt;span class="p"&gt;-&lt;/span&gt; H1: 32px / 500 weight
&lt;span class="p"&gt;-&lt;/span&gt; Body: 16px / 400 weight

&lt;span class="gu"&gt;## Spacing&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Base unit: 8px
&lt;span class="p"&gt;-&lt;/span&gt; sm: 8px, md: 16px, lg: 32px

&lt;span class="gu"&gt;## Components&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Button (primary): blue bg, white text, 4px radius
&lt;span class="p"&gt;-&lt;/span&gt; Input: light gray border, 12px radius, focus outline
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is it. No tooling to install. Markdown is what LLMs read best — structured enough to parse, open enough to extend.&lt;/p&gt;

&lt;p&gt;The file is not a design system tool. It is a design system memory. It tells the agent what decisions you already made so it stops inventing its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Google Stitch comes in
&lt;/h2&gt;

&lt;p&gt;The blank-page problem with DESIGN.md is that you still have to write it in the first place.&lt;/p&gt;

&lt;p&gt;Google Stitch solves this. It is an &lt;a href="https://geekyants.com/blog/top-10-ai-tools-every-uiux-designer-should-master" rel="noopener noreferrer"&gt;AI design tool&lt;/a&gt; from Google Labs that takes a natural language prompt — "dark SaaS dashboard, clean, muted greens" — and generates a &lt;a href="https://geekyants.com/service/ui-ux-design-services" rel="noopener noreferrer"&gt;full UI design&lt;/a&gt;. More importantly, it exports a DESIGN.md alongside the visual output.&lt;/p&gt;

&lt;p&gt;So instead of writing the spec, you generate it. You explore in Stitch, pick a direction that feels right, and walk away with a Markdown file that encodes every visual decision that went into that design: the exact hex values, the type scale, the component patterns.&lt;/p&gt;

&lt;p&gt;For most teams getting started, the file-based export is simpler; the MCP route is useful when you are actively iterating and want the code agent to stay in sync automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Claude Code picks it up
&lt;/h2&gt;

&lt;p&gt;Claude Code reads your repo. It picks up CLAUDE.md at the start of every session as a persistent instruction file — a place to write rules the agent follows across every run.&lt;/p&gt;

&lt;p&gt;Add this to your &lt;strong&gt;CLAUDE.md&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Design System&lt;/span&gt;
This project uses design.md at the project root.
Always refer to it when generating or modifying UI.
&lt;span class="p"&gt;-&lt;/span&gt; Use only colors, fonts, and spacing from design.md.
&lt;span class="p"&gt;-&lt;/span&gt; Do not introduce new values or defaults.
&lt;span class="p"&gt;-&lt;/span&gt; Follow the component rules exactly.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when you ask Claude Code to build a settings page or refactor a button, it does not guess. It reads &lt;strong&gt;DESIGN.md&lt;/strong&gt;, finds the primary color, finds the button spec, and generates code that matches what already exists in your product.&lt;/p&gt;

&lt;p&gt;The UI does not drift because the agent no longer starts from zero — it starts from your spec.&lt;/p&gt;

&lt;h3&gt;
  
  
  The workflow, step by step
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Explore in Stitch.&lt;/strong&gt; Describe your UI in natural language. Iterate on a few directions using Stitch's redesign tools until the look feels right.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Export DESIGN.md.&lt;/strong&gt; Download the design file. Review it — adjust token names, add platform-specific notes, remove anything irrelevant. Treat it as a proposal, not a final truth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commit it to the repo.&lt;/strong&gt; Drop &lt;strong&gt;DESIGN.md&lt;/strong&gt; at the project root alongside &lt;strong&gt;CLAUDE.md&lt;/strong&gt;. It is now version-controlled. Changes go through your normal PR process.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instruct Claude Code.&lt;/strong&gt; Reference DESIGN.md in your CLAUDE.md. From this point, every UI task Claude Code runs stays within your defined system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build and iterate.&lt;/strong&gt; Add pages, components, and flows with natural language prompts. When the design evolves, update &lt;strong&gt;DESIGN.md&lt;/strong&gt; first — the agent inherits the change on the next run.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why this changes how vibe coding works
&lt;/h2&gt;

&lt;p&gt;Vibe coding without DESIGN.md is fast but brittle. Every new session is a coin flip on whether the AI remembers your visual language. With DESIGN.md, the spec is always in context. Different contributors, different sessions, different prompts — they all produce UIs that share the same tokens.&lt;/p&gt;

&lt;p&gt;The AI has constraints, and it works within them.&lt;/p&gt;

&lt;p&gt;There is also a practical benefit beyond consistency: less rework. The biggest time sink in vibe coding is not writing prompts — it is fixing the output. "Change this blue to our brand blue." "Make the padding consistent." "Why does this button look different from the one on the home page?" DESIGN.md cuts that loop significantly. The first pass is much closer to correct.&lt;/p&gt;

&lt;p&gt;And because &lt;strong&gt;DESIGN.md&lt;/strong&gt; lives in version control, your design decisions are auditable. You can diff them, review them in PRs, and roll them back. Design intent stops living in chat history and starts living in code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does not fix
&lt;/h2&gt;

&lt;p&gt;Budget some time to clean up what it exports.&lt;/p&gt;

&lt;p&gt;The agent still drifts occasionally. A large or ambiguous &lt;strong&gt;DESIGN.md&lt;/strong&gt; gives Claude less to anchor to. Keep it focused — under 200 lines if possible. Specificity beats completeness.&lt;/p&gt;

&lt;p&gt;It does not handle everything that design actually involves.&lt;/p&gt;

&lt;p&gt;Format fragmentation is real. Different tools structure DESIGN.md differently. Until something like a common token schema emerges, expect to normalize the file when switching between tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start today
&lt;/h2&gt;

&lt;p&gt;You do not need to overhaul your workflow to try this. Pick one project. Open Stitch, generate a UI direction you like, and export the &lt;strong&gt;DESIGN.md&lt;/strong&gt;. Drop it in your repo. Add three lines to your &lt;strong&gt;CLAUDE.md&lt;/strong&gt; pointing at it. Then ask Claude Code to build your next component and watch what changes.&lt;/p&gt;

&lt;p&gt;Add this to your CLAUDE.md:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The stack: design.md (the spec) + CLAUDE.md (the instruction) + Claude Code (the agent).

Stitch handles the blank page. DESIGN.md handles the memory. Claude Code handles the build.

That is the workflow. It is not complicated. It just works better than prompting from zero every time.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;This article was originally published on the &lt;a href="https://geekyants.com/blog/your-vibe-code-has-no-memory-designmd-fixes-that" rel="noopener noreferrer"&gt;GeekyAnts Blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>vibecoding</category>
    </item>
    <item>
      <title>Building a Resilient Hybrid-Cloud Network with WireGuard HA, Route-Based Failover, and Deep Observability</title>
      <dc:creator>GeekyAnts Inc</dc:creator>
      <pubDate>Wed, 01 Jul 2026 12:49:17 +0000</pubDate>
      <link>https://dev.to/geekyants/building-a-resilient-hybrid-cloud-network-with-wireguard-ha-route-based-failover-and-deep-184b</link>
      <guid>https://dev.to/geekyants/building-a-resilient-hybrid-cloud-network-with-wireguard-ha-route-based-failover-and-deep-184b</guid>
      <description>&lt;p&gt;Most &lt;a href="https://geekyants.com/blog/a-complete-guide-to-cloud-migration-strategies-for-web-mobile-apps" rel="noopener noreferrer"&gt;hybrid-cloud networking&lt;/a&gt; discussions eventually converge toward the same set of enterprise-heavy solutions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Managed VPN gateways&lt;/li&gt;
&lt;li&gt;MPLS connectivity&lt;/li&gt;
&lt;li&gt;Transit Gateway&lt;/li&gt;
&lt;li&gt;BGP-based active-active architectures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice, many &lt;a href="https://geekyants.com/ai-powered-product-engineering/fractional-engineering-teams" rel="noopener noreferrer"&gt;engineering teams&lt;/a&gt; are solving a much narrower — but operationally critical — problem:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Public &lt;a href="https://geekyants.com/enterprise-system-modernization/cloud-infrastructure-services" rel="noopener noreferrer"&gt;cloud applications&lt;/a&gt; need reliable, secure access to private on-premises systems without introducing unnecessary networking complexity.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In our case, the requirement initially looked deceptively simple.&lt;/p&gt;

&lt;p&gt;We had:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Public-facing APIs running on AWS&lt;/li&gt;
&lt;li&gt;Internal APIs and databases hosted on-premises&lt;/li&gt;
&lt;li&gt;Strict requirements around resilience and observability&lt;/li&gt;
&lt;li&gt;A need to avoid expensive enterprise networking stacks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As traffic increased and failover testing became more realistic, several problems started emerging:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tunnel health appeared healthy while application traffic silently failed&lt;/li&gt;
&lt;li&gt;Intermittent packet forwarding issues became difficult to diagnose&lt;/li&gt;
&lt;li&gt;Failover behavior became inconsistent&lt;/li&gt;
&lt;li&gt;Route ambiguity started appearing during dual-path testing&lt;/li&gt;
&lt;li&gt;Monitoring produced false-positive tunnel health&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The problem slowly stopped being: &lt;em&gt;"How do we create secure connectivity?"&lt;/em&gt; and became: &lt;em&gt;"How do we build predictable, observable, and operationally recoverable hybrid-cloud networking?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That distinction ended up shaping the entire architecture.&lt;/p&gt;

&lt;p&gt;Instead of optimizing for networking sophistication, the final system intentionally optimized for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deterministic failover&lt;/li&gt;
&lt;li&gt;Operational simplicity&lt;/li&gt;
&lt;li&gt;Infrastructure-driven recovery&lt;/li&gt;
&lt;li&gt;Deep packet-forwarding visibility&lt;/li&gt;
&lt;li&gt;Cloud-agnostic portability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result was a lightweight hybrid-cloud architecture built using WireGuard, Linux-native routing, AWS Route Table failover, Prometheus observability, and active-standby tunnel paths.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Initial Assumption: One Tunnel Is Enough
&lt;/h2&gt;

&lt;p&gt;The earliest implementation used a single WireGuard tunnel between AWS and the on-premises environment.&lt;/p&gt;

&lt;p&gt;At small scale, this worked well — latency stayed low, operational overhead was minimal, and the architecture remained easy to reason about.&lt;/p&gt;

&lt;p&gt;However, failover testing immediately exposed an operational weakness: the tunnel itself became a single recovery dependency.&lt;/p&gt;

&lt;p&gt;Any degradation introduced:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Backend API timeouts&lt;/li&gt;
&lt;li&gt;Intermittent request failures&lt;/li&gt;
&lt;li&gt;Delayed recovery&lt;/li&gt;
&lt;li&gt;Manual operational intervention&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this stage, the tunnel was technically functional, but operationally fragile.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Traditional Enterprise Networking Was Avoided
&lt;/h2&gt;

&lt;p&gt;The obvious next step was evaluating managed VPN services, Transit Gateway, BGP-based routing, and active-active tunnel fabrics. On paper, these architectures looked attractive. Operationally, they introduced several concerns.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concern&lt;/th&gt;
&lt;th&gt;Operational Impact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Managed VPN pricing&lt;/td&gt;
&lt;td&gt;Difficult to justify at mid-scale&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dynamic route convergence&lt;/td&gt;
&lt;td&gt;Harder to predict during failures&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vendor abstractions&lt;/td&gt;
&lt;td&gt;Reduced debugging visibility&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Active-active routing&lt;/td&gt;
&lt;td&gt;Increased troubleshooting complexity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BGP failover behavior&lt;/td&gt;
&lt;td&gt;Operationally non-deterministic&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One operational realization became increasingly clear: faster troubleshooting was consistently more valuable than maximizing tunnel utilization efficiency. At mid-scale, operational determinism mattered more than networking elegance.&lt;/p&gt;




&lt;h2&gt;
  
  
  Early Active-Active Experiments Created More Problems Than They Solved
&lt;/h2&gt;

&lt;p&gt;The next iteration experimented with dual active tunnels. The idea initially seemed straightforward: two active paths, traffic balancing, higher availability, improved utilization.&lt;/p&gt;

&lt;p&gt;Operationally, this introduced several difficult behaviors. During partial degradation testing, we observed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Asymmetric return traffic&lt;/li&gt;
&lt;li&gt;Intermittent packet forwarding failures&lt;/li&gt;
&lt;li&gt;Route ambiguity inside Linux routing tables&lt;/li&gt;
&lt;li&gt;Inconsistent failover timing&lt;/li&gt;
&lt;li&gt;Hard-to-debug intermittent API failures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One particularly difficult issue appeared when outbound and return traffic took different paths during transient failures. From the application perspective, some requests succeeded while others silently timed out — even though tunnel interfaces still appeared healthy.&lt;/p&gt;

&lt;p&gt;The architecture technically achieved redundancy. Operationally, it reduced predictability.&lt;/p&gt;

&lt;p&gt;Eventually, the team realized: &lt;strong&gt;deterministic failover behavior was more operationally valuable than active-active utilization efficiency.&lt;/strong&gt; That became the turning point in the architecture.&lt;/p&gt;




&lt;h2&gt;
  
  
  Moving Toward Deterministic Routing
&lt;/h2&gt;

&lt;p&gt;Instead of active-active routing, the architecture evolved toward isolated tunnel paths, active-standby failover, infrastructure-driven recovery, and strict subnet separation.&lt;/p&gt;

&lt;p&gt;Traffic would always prefer a primary path and shift only during validated degradation. This dramatically simplified troubleshooting, observability, routing behavior, and operational recovery.&lt;/p&gt;

&lt;p&gt;The system intentionally sacrificed maximum tunnel utilization and dynamic traffic balancing in exchange for deterministic failover, operational transparency, and predictable recovery.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Hybrid-Cloud Architecture
&lt;/h2&gt;

&lt;p&gt;The final design used two completely isolated WireGuard paths, dedicated subnet ownership, AWS Route Table failover, and infrastructure-level recovery automation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Two Completely Separate Tunnel Paths Worked Better
&lt;/h3&gt;

&lt;p&gt;Operationally, shared tunnel systems introduced several recurring problems: overlapping route ownership, shared interface state, route recursion issues, asymmetric forwarding, and failover race conditions.&lt;/p&gt;

&lt;p&gt;Instead, each tunnel path operated independently with isolated interfaces, isolated route ownership, isolated health validation, and isolated failover behavior. Failures became easier to isolate, observe, and recover from.&lt;/p&gt;

&lt;p&gt;The secondary path remained continuously available but did not actively serve traffic until route failover occurred — a lightweight HA model without requiring BGP convergence, mesh synchronization, or overlay routing complexity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Subnet Isolation Became Critical
&lt;/h3&gt;

&lt;p&gt;Each tunnel path owned an isolated subnet slice.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Environment&lt;/th&gt;
&lt;th&gt;Interface&lt;/th&gt;
&lt;th&gt;Tunnel IP&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;On-Premises&lt;/td&gt;
&lt;td&gt;wg0 / wg1&lt;/td&gt;
&lt;td&gt;10.100.0.2 / 10.200.0.2&lt;/td&gt;
&lt;td&gt;Core Plane&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS Primary&lt;/td&gt;
&lt;td&gt;wg0&lt;/td&gt;
&lt;td&gt;10.100.0.1&lt;/td&gt;
&lt;td&gt;ACTIVE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS Secondary&lt;/td&gt;
&lt;td&gt;wg1&lt;/td&gt;
&lt;td&gt;10.200.0.1&lt;/td&gt;
&lt;td&gt;STANDBY&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One operational lesson became very clear: simpler routing topologies fail in more understandable ways.&lt;/p&gt;

&lt;h3&gt;
  
  
  Separating VPN Edges from Backend Systems
&lt;/h3&gt;

&lt;p&gt;Separating VPN edge gateways from backend APIs, databases, and public ingress layers reduced routing recursion, asymmetric local forwarding, interface overlap, and expanded blast radius — while improving maintenance flexibility, backend isolation, and operational scaling independence.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Biggest Monitoring Mistake We Initially Made
&lt;/h2&gt;

&lt;p&gt;Early monitoring focused primarily on interface UP state and WireGuard handshake timestamps.&lt;/p&gt;

&lt;p&gt;During testing, we observed tunnels remaining "healthy" from WireGuard's perspective while real application traffic silently failed. In several cases, handshake timestamps continued updating and interfaces remained UP — but packet forwarding had already degraded.&lt;/p&gt;

&lt;p&gt;One particularly difficult debugging session involved intermittent API failures where ICMP occasionally worked, application traffic intermittently timed out, and tunnel interfaces remained healthy. The root issue turned out to be partial packet-forwarding degradation rather than tunnel failure itself.&lt;/p&gt;

&lt;p&gt;That incident fundamentally changed the monitoring strategy.&lt;/p&gt;




&lt;h2&gt;
  
  
  Evolving Toward Data-Plane Validation
&lt;/h2&gt;

&lt;p&gt;The monitoring model evolved into three distinct validation layers.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Validation Layer&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Interface Validation&lt;/td&gt;
&lt;td&gt;Verify interface availability&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Handshake Validation&lt;/td&gt;
&lt;td&gt;Verify cryptographic synchronization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data Plane Validation&lt;/td&gt;
&lt;td&gt;Verify actual packet forwarding&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Production Health Validation Script
&lt;/h3&gt;

&lt;p&gt;Tunnel health metrics were exported using a lightweight Linux validation script integrated into the Node Exporter textfile collector.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nv"&gt;METRIC_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/var/lib/node_exporter/textfile_collector/wg.prom"&lt;/span&gt;
&lt;span class="nv"&gt;WG_INTERFACE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"wg0"&lt;/span&gt;
&lt;span class="nv"&gt;WG_PEER_IP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"10.100.0.1"&lt;/span&gt;
&lt;span class="nv"&gt;THRESHOLD_SECONDS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;180

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; wg show &lt;span class="nv"&gt;$WG_INTERFACE&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"wireguard_wg0_healthy 0"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$METRIC_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nv"&gt;NOW&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%s&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;LAST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;wg show &lt;span class="nv"&gt;$WG_INTERFACE&lt;/span&gt; latest-handshakes | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $2}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LAST&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LAST&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"wireguard_wg0_healthy 0"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$METRIC_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nv"&gt;DIFF&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;NOW &lt;span class="o"&gt;-&lt;/span&gt; LAST&lt;span class="k"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$DIFF&lt;/span&gt; &lt;span class="nt"&gt;-gt&lt;/span&gt; &lt;span class="nv"&gt;$THRESHOLD_SECONDS&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"wireguard_wg0_healthy 0"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$METRIC_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="k"&gt;fi

if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; ping &lt;span class="nt"&gt;-c&lt;/span&gt; 2 &lt;span class="nt"&gt;-W&lt;/span&gt; 2 &lt;span class="nv"&gt;$WG_PEER_IP&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"wireguard_wg0_healthy 0"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$METRIC_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"wireguard_wg0_healthy 1"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$METRIC_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The monitoring flow validated interface state, handshake freshness, and actual packet forwarding.&lt;/p&gt;

&lt;p&gt;That final packet-forwarding validation ended up becoming the most operationally valuable signal in the system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prometheus Observability Pipeline
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dot"&gt;&lt;code&gt;&lt;span class="k"&gt;graph&lt;/span&gt; &lt;span class="nv"&gt;LR&lt;/span&gt;

    &lt;span class="nv"&gt;Script&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Health&lt;/span&gt; &lt;span class="nv"&gt;Validation&lt;/span&gt; &lt;span class="nv"&gt;Script&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
        &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;|&lt;/span&gt;&lt;span class="nv"&gt;Writes&lt;/span&gt; &lt;span class="nv"&gt;Metrics&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;File&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"wg.prom"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;

    &lt;span class="nv"&gt;NodeExporter&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Node&lt;/span&gt; &lt;span class="nv"&gt;Exporter&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
        &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;|&lt;/span&gt;&lt;span class="nv"&gt;Expose&lt;/span&gt; &lt;span class="nv"&gt;Metrics&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;Prometheus&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Prometheus&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;

    &lt;span class="nv"&gt;Prometheus&lt;/span&gt;
        &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;Alertmanager&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Alertmanager&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;

    &lt;span class="nv"&gt;Alertmanager&lt;/span&gt;
        &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;Automation&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Failover&lt;/span&gt; &lt;span class="nv"&gt;Automation&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;

    &lt;span class="nv"&gt;Automation&lt;/span&gt;
        &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;AWSRT&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;AWS&lt;/span&gt; &lt;span class="nv"&gt;Route&lt;/span&gt; &lt;span class="nv"&gt;Tables&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Infrastructure-Driven Failover
&lt;/h2&gt;

&lt;p&gt;Instead of rebuilding tunnels or restarting applications during failures, failover occurred entirely through AWS Route Table updates.&lt;/p&gt;

&lt;p&gt;When the primary tunnel degraded:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Prometheus detected failure&lt;/li&gt;
&lt;li&gt;Alertmanager triggered automation&lt;/li&gt;
&lt;li&gt;Route targets were updated&lt;/li&gt;
&lt;li&gt;Traffic shifted to the standby WireGuard node&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;During testing, primary degradation was typically detected within seconds, route updates remained operationally predictable, and recovery became significantly more deterministic compared to earlier active-active experiments.&lt;/p&gt;

&lt;p&gt;One operational pattern became consistently clear: the less failover logic applications contained, the more predictable recovery became.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Architecture Worked Operationally
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Operational Simplicity
&lt;/h3&gt;

&lt;p&gt;The final design intentionally avoided BGP convergence, overlay routing complexity, active-active synchronization, and enterprise VPN orchestration. Everything relied on Linux routing, lightweight EC2 instances, WireGuard, and route-table automation — which significantly reduced operational overhead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Better Failure Isolation
&lt;/h3&gt;

&lt;p&gt;Separating tunnel paths, subnet ownership, route ownership, and &lt;a href="https://geekyants.com/engineering/backend" rel="noopener noreferrer"&gt;backend systems&lt;/a&gt; made failures significantly easier to isolate, debug, and recover from.&lt;/p&gt;

&lt;h3&gt;
  
  
  Observability Became More Valuable Than Redundancy Alone
&lt;/h3&gt;

&lt;p&gt;Redundant tunnels without packet-forwarding visibility still create operational risk. The architecture became reliable not simply because of redundancy, but because degradation became observable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cloud-Agnostic Portability
&lt;/h3&gt;

&lt;p&gt;Because the architecture depended primarily on WireGuard, Linux-native routing, Prometheus, and &lt;a href="https://geekyants.com/engineering/devops/infrastructure-deployment-automation" rel="noopener noreferrer"&gt;infrastructure automation&lt;/a&gt;, the same design could be replicated across &lt;a href="https://geekyants.com/aws-consulting-services" rel="noopener noreferrer"&gt;AWS&lt;/a&gt;, Azure, Google Cloud, private cloud environments, and bare-metal infrastructure with minimal architectural changes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;This architecture reinforced several operational principles repeatedly throughout testing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First:&lt;/strong&gt; Simpler networking topologies are easier to recover from during real incidents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second:&lt;/strong&gt; Deterministic failover is often operationally more valuable than maximizing tunnel utilization.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And finally:&lt;/strong&gt; Observability matters more than redundancy alone.&lt;/p&gt;

&lt;p&gt;Using WireGuard, Linux-native routing, Prometheus observability, and AWS Route Table automation, it was possible to build resilient hybrid-cloud connectivity, predictable failover behavior, observable tunnel infrastructure, and &lt;a href="https://geekyants.com/blog/cloud-native-and-cloud-agnostic-are-not-ideologies-they-are-business-stage-decisions" rel="noopener noreferrer"&gt;cloud-agnostic deployment patterns&lt;/a&gt; — without introducing unnecessary enterprise networking complexity.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://geekyants.com/blog/building-a-resilient-hybrid-cloud-network-with-wireguard-ha-route-based-failover-and-deep-observability" rel="noopener noreferrer"&gt;GeekyAnts Blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Cloud-Native and Cloud-Agnostic Are Not Ideologies; They Are Business-Stage Decisions</title>
      <dc:creator>GeekyAnts Inc</dc:creator>
      <pubDate>Thu, 25 Jun 2026 06:38:02 +0000</pubDate>
      <link>https://dev.to/geekyants/cloud-native-and-cloud-agnostic-are-not-ideologies-they-are-business-stage-decisions-3fi9</link>
      <guid>https://dev.to/geekyants/cloud-native-and-cloud-agnostic-are-not-ideologies-they-are-business-stage-decisions-3fi9</guid>
      <description>&lt;p&gt;One of the biggest problems in &lt;a href="https://geekyants.com/devops-services" rel="noopener noreferrer"&gt;modern DevOps&lt;/a&gt; is that the industry often romanticizes scalability long before organizations develop the operational maturity required to support it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Startups deploy Kubernetes before achieving product-market fit.&lt;/li&gt;
&lt;li&gt;Engineering leadership demands "future-proof" architecture before deployment consistency even exists.&lt;/li&gt;
&lt;li&gt;Teams design for multi-region scale while basic observability, rollback strategy, and operational ownership remain undefined.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A surprising amount of "&lt;a href="https://geekyants.com/engineering/backend/cloud-platform-engineering-services" rel="noopener noreferrer"&gt;platform engineering&lt;/a&gt;" today is simply premature complexity with better branding.&lt;/p&gt;

&lt;p&gt;And yet the opposite problem exists too. Organizations that optimize aggressively for short-term delivery speed often discover later that those same infrastructure choices now limit enterprise onboarding, regional expansion, governance, or operational flexibility.&lt;/p&gt;

&lt;p&gt;The problem is not cloud-native vs. cloud-agnostic. The real problem is treating infrastructure strategy as an ideological decision instead of a business-stage decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Infrastructure Strategy Is Often a GTM Strategy
&lt;/h2&gt;

&lt;p&gt;Infrastructure decisions directly influence go-to-market speed.&lt;/p&gt;

&lt;p&gt;A startup trying to validate product-market fit is optimizing for release velocity, fast experimentation, lean operational overhead, and developer productivity. In that environment, &lt;a href="https://geekyants.com/blog/cloud-native-approach-for-software-engineering-part-1---cloud-native-apps" rel="noopener noreferrer"&gt;cloud-native infrastructure&lt;/a&gt; is often the rational choice. Managed databases, serverless execution, provider-native IAM, CDN ecosystems, and integrated observability dramatically reduce operational burden. A small engineering team can move incredibly fast by leveraging managed cloud ecosystems instead of building operational layers internally.&lt;/p&gt;

&lt;p&gt;In early-stage companies, speed matters more than theoretical portability — because early-stage companies are not optimizing for infrastructure independence. They are optimizing for survival.&lt;/p&gt;

&lt;p&gt;This is where many infrastructure discussions lose context. The industry often frames &lt;a href="https://geekyants.com/blog/a-complete-guide-to-cloud-migration-strategies-for-web-mobile-apps" rel="noopener noreferrer"&gt;cloud-native adoption&lt;/a&gt; as architectural compromise, when in reality it is frequently the fastest path to GTM execution.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A startup validating product-market fit may choose Firebase, Lambda, or DynamoDB because operational simplicity matters more than portability.&lt;/li&gt;
&lt;li&gt;A small SaaS company may rely heavily on AWS-native observability and IAM because maintaining internal platform tooling would slow product delivery.&lt;/li&gt;
&lt;li&gt;A lean engineering team may intentionally avoid Kubernetes early because operational ownership would consume too much engineering bandwidth.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are bad decisions. In fact, they are often the correct decisions for that stage of the business.&lt;/p&gt;

&lt;h2&gt;
  
  
  But GTM Priorities Eventually Change
&lt;/h2&gt;

&lt;p&gt;The architecture that accelerates early growth is rarely the same architecture that supports long-term operational evolution.&lt;/p&gt;

&lt;p&gt;As organizations mature, infrastructure priorities begin shifting toward enterprise onboarding, compliance requirements, regional expansion, customer-hosted deployments, operational standardization, cost governance, and &lt;a href="https://geekyants.com/blog/the-dynamics-of-collaboration-for-organizational-excellence" rel="noopener noreferrer"&gt;cross-team coordination&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is where many companies discover that infrastructure decisions made during the GTM phase quietly become long-term operational assumptions. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;a href="https://geekyants.com/blog/scalable-ai-saas-development-guide-for-the-us-market" rel="noopener noreferrer"&gt;SaaS platform&lt;/a&gt; built heavily around AWS-native services may later struggle when enterprise customers require customer-hosted deployments.&lt;/li&gt;
&lt;li&gt;A company optimized around single-region cloud-native architecture may face operational friction when expanding into regulated geographies with data residency requirements.&lt;/li&gt;
&lt;li&gt;A growing engineering organization may discover that deployment workflows built for one small team no longer scale across multiple product squads.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The problem is not that the original decisions were wrong. The problem is assuming they remain optimal forever. Infrastructure decisions often outlive the stage of the business they were designed for.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Should Prototype Architecture Evolve?
&lt;/h2&gt;

&lt;p&gt;One of the biggest misconceptions in infrastructure strategy is assuming scalability concerns should dominate from day one. In reality, most organizations should not optimize heavily for portability, multi-region architecture, or platform abstraction during the prototype phase.&lt;/p&gt;

&lt;p&gt;But there are signals that the infrastructure strategy needs to evolve. That transition usually begins when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;revenue-generating customers appear,&lt;/li&gt;
&lt;li&gt;uptime commitments become contractual,&lt;/li&gt;
&lt;li&gt;on-call rotations become operationally painful,&lt;/li&gt;
&lt;li&gt;multiple engineering teams begin contributing simultaneously,&lt;/li&gt;
&lt;li&gt;compliance requirements emerge, or&lt;/li&gt;
&lt;li&gt;enterprise customers introduce deployment constraints.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is often the point where &lt;a href="https://geekyants.com/ai-powered-product-engineering/prototype-to-production" rel="noopener noreferrer"&gt;prototype architecture&lt;/a&gt; starts becoming production architecture — and where many organizations struggle. Some continue operating startup-grade infrastructure long after the business has outgrown it. Others introduce enterprise-scale complexity far too early. Both create friction, just at different stages of maturity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cloud-Agnostic Thinking Becomes More Valuable Over Time
&lt;/h2&gt;

&lt;p&gt;One of the biggest misconceptions in platform engineering is assuming cloud-native and cloud-agnostic are opposing philosophies. Mature infrastructure strategies often combine both.&lt;/p&gt;

&lt;p&gt;Cloud-native acceleration can coexist with cloud-agnostic operational patterns. For example, CI/CD workflows can remain portable, observability can stay unified, workloads can remain containerized, and deployment patterns can stay provider-independent — all while selectively using provider-native services where they provide meaningful leverage.&lt;/p&gt;

&lt;p&gt;This is where cloud-agnostic thinking becomes strategically valuable. Not because portability itself is the goal, but because operational flexibility becomes increasingly important as organizations scale. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enterprise customers may require hybrid deployments.&lt;/li&gt;
&lt;li&gt;Regulated industries may introduce region-specific infrastructure constraints.&lt;/li&gt;
&lt;li&gt;Multi-cloud operational visibility may become necessary after acquisitions or expansion.&lt;/li&gt;
&lt;li&gt;Customer-hosted environments may require standardized deployment models outside the original cloud ecosystem.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The strongest infrastructure strategies are usually not fully cloud-native or fully cloud-agnostic. They evolve intentionally as business requirements evolve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Portability Has an Operational Cost Too
&lt;/h2&gt;

&lt;p&gt;Portability is often discussed as freedom. What gets discussed less frequently is the operational cost required to maintain it.&lt;/p&gt;

&lt;p&gt;Portable systems typically introduce additional abstraction layers, broader platform engineering responsibility, stronger standardization requirements, self-managed tooling, and significantly higher operational maturity expectations.&lt;/p&gt;

&lt;p&gt;Running &lt;a href="https://geekyants.com/hire-postgresql-developers" rel="noopener noreferrer"&gt;PostgreSQL&lt;/a&gt; inside Kubernetes improves portability. It also transfers failover ownership, backup management, patching responsibility, storage reliability, and operational risk directly onto the engineering organization.&lt;/p&gt;

&lt;p&gt;Similarly, organizations pursuing aggressive multi-cloud strategies often underestimate the operational duplication introduced across IAM models, networking controls, observability pipelines, Terraform abstractions, security posture management, CI/CD orchestration, and cloud-specific operational edge cases.&lt;/p&gt;

&lt;p&gt;Many multi-cloud initiatives fail not because of infrastructure limitations, but because organizational maturity never caught up with architectural ambition. The industry talks heavily about avoiding vendor lock-in. It talks far less about the operational ownership required to replace what managed ecosystems already solved.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hardest Scaling Problem Is Usually Coordination
&lt;/h2&gt;

&lt;p&gt;One of the clearest patterns across growing engineering organizations is that infrastructure complexity scales faster than operational clarity.&lt;/p&gt;

&lt;p&gt;The hardest scaling problem in many companies is no longer compute — it is coordination. As engineering organizations grow, ownership boundaries blur, deployment consistency drifts, observability becomes fragmented, operational tribal knowledge increases, and infrastructure decisions made by small teams become inherited organizational constraints.&lt;/p&gt;

&lt;p&gt;This is why modern DevOps is increasingly less about tooling and increasingly more about organizational scalability. The conversations are shifting toward operational consistency, governance, reusable workflows, platform standardization, developer enablement, and &lt;a href="https://geekyants.com/blog/cloud-cost-optimization-how-to-migrate-without-breaking-the-bank-us-guide" rel="noopener noreferrer"&gt;reducing the future cost&lt;/a&gt; of change.&lt;/p&gt;

&lt;p&gt;Because eventually every growing organization discovers the same thing: scaling infrastructure is relatively easy. Scaling operational consistency is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;Perhaps the biggest infrastructure mistake modern engineering organizations make is not &lt;a href="https://geekyants.com/blog/the-hidden-cost-of-choosing-the-wrong-software-development-partner-in-the-usa" rel="noopener noreferrer"&gt;choosing the wrong cloud strategy&lt;/a&gt; — it is solving for the wrong stage of the business.&lt;/p&gt;

&lt;p&gt;A startup struggling to achieve product-market fit does not have the same operational priorities as an enterprise platform operating across regions, compliance boundaries, and customer-hosted environments. Yet the industry often treats infrastructure decisions as ideological commitments instead of evolving business-stage strategies.&lt;/p&gt;

&lt;p&gt;Cloud-native and cloud-agnostic architectures are not opposing belief systems. They are tools. And the real challenge is recognizing when the business has evolved enough that the infrastructure strategy must evolve with it.&lt;/p&gt;

&lt;p&gt;Because the architecture that accelerates go-to-market is rarely the same architecture that optimizes long-term operational flexibility. The strongest platforms are not the ones that predict the future perfectly. They are the ones designed to evolve before the business outgrows them.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://geekyants.com/blog/cloud-native-and-cloud-agnostic-are-not-ideologies-they-are-business-stage-decisions" rel="noopener noreferrer"&gt;GeekyAnts blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
    </item>
    <item>
      <title>We Built a 114-Second AWS-to-Azure Failover. Here's What We Learned</title>
      <dc:creator>GeekyAnts Inc</dc:creator>
      <pubDate>Thu, 25 Jun 2026 06:20:37 +0000</pubDate>
      <link>https://dev.to/geekyants/we-built-a-114-second-aws-to-azure-failover-heres-what-we-learned-2i1k</link>
      <guid>https://dev.to/geekyants/we-built-a-114-second-aws-to-azure-failover-heres-what-we-learned-2i1k</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A practical guide to building a 114-second multi-cloud disaster recovery failover between AWS and Azure — what we built, what broke, and what we learned.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Cloud outages are no longer rare edge cases.&lt;/p&gt;

&lt;p&gt;AWS has had seven major incidents since 2021. Azure has had its share too. And every time, the story splits in two: teams with tested DR plans treated it as an operational issue. Teams without them made infrastructure decisions in the middle of production downtime.&lt;/p&gt;

&lt;p&gt;The most visible example came from the December 2021 AWS us-east-1 outage. Slack went down. McDonald's mobile ordering collapsed. Even some of Amazon's own internal systems were affected.&lt;/p&gt;

&lt;p&gt;But the interesting part wasn't the outage itself.&lt;/p&gt;

&lt;p&gt;It was how differently companies experienced it.&lt;/p&gt;

&lt;p&gt;We decided to be the first kind of team. This is what we built, what broke, and what we'd do differently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Single-Cloud Is Still a Risk
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://geekyants.com/aws-consulting-services" rel="noopener noreferrer"&gt;AWS&lt;/a&gt;, Azure, and GCP publish SLAs that look reassuring on paper. AWS EC2 targets 99.99% uptime — roughly 52 minutes of downtime per year.&lt;/p&gt;

&lt;p&gt;That sounds fine until you realize production outages rarely happen because a single service disappears completely. They happen because of networking issues, IAM failures, degraded DNS, control plane problems — things that make your application unavailable while the provider technically considers it "running."&lt;/p&gt;

&lt;p&gt;And timing matters more than percentages. A short outage at 3am is a footnote. The same outage during a product launch or payment cycle is a very different business problem.&lt;/p&gt;

&lt;p&gt;SLA credits don't solve that. Lost transactions, incident response fatigue, customer trust erosion — these cost far more than the infrastructure bill.&lt;/p&gt;

&lt;p&gt;That's why DR is ultimately less about redundancy and more about business risk.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Built
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgeekyants.com%2F_next%2Fimage%3Furl%3Dhttps%253A%252F%252Fstatic-cdn.geekyants.com%252Farticleblogcomponent%252F56349%252F2026-06-19%252F953154507-1781861578.png%26w%3D3840%26q%3D75" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgeekyants.com%2F_next%2Fimage%3Furl%3Dhttps%253A%252F%252Fstatic-cdn.geekyants.com%252Farticleblogcomponent%252F56349%252F2026-06-19%252F953154507-1781861578.png%26w%3D3840%26q%3D75" alt="Architecture overview of the AWS EKS to Azure AKS warm standby disaster recovery setup with PostgreSQL streaming replication and Route 53 failover routing."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Architecture overview of the AWS EKS → Azure AKS warm standby disaster recovery setup with PostgreSQL streaming replication and Route 53 failover routing.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Our setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AWS EKS&lt;/strong&gt; — primary cluster, serving all production traffic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Azure AKS&lt;/strong&gt; — warm standby, nodes running but pods scaled to zero&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL 16 streaming replication&lt;/strong&gt; — Azure DB always in sync, zero lag&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Route 53 failover routing&lt;/strong&gt; — DNS switches automatically when AWS goes unhealthy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The standby cluster runs at roughly 18% of the primary cost. Full DR capability at 18 cents per primary dollar.&lt;/p&gt;

&lt;p&gt;The entire failover — database promotion, &lt;a href="https://geekyants.com/blog/why-i-stopped-managing-kubernetes-the-traditional-way" rel="noopener noreferrer"&gt;Kubernetes rollout&lt;/a&gt;, DNS switching, smoke tests — averaged &lt;strong&gt;114 seconds&lt;/strong&gt; during drills.&lt;/p&gt;

&lt;p&gt;We didn't get there on the first try.&lt;/p&gt;

&lt;h2&gt;
  
  
  The DR Maturity Ladder
&lt;/h2&gt;

&lt;p&gt;Before getting into what broke, it's worth being honest about where most teams actually sit:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Level 0&lt;/strong&gt; — Recovery depends entirely on people figuring things out during the incident. Backups exist somewhere, probably.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Level 1&lt;/strong&gt; — Backups exist and can be restored. Slow, stressful, but survivable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Level 2&lt;/strong&gt; — Cold standby. Infrastructure exists elsewhere but isn't running. Expect hours of recovery time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Level 3&lt;/strong&gt; — Warm standby. Infrastructure running, workloads at zero, replication active. This is what we built. Recovery in minutes, not hours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Level 4&lt;/strong&gt; — Hot standby. Secondary environment fully live, failover is purely a routing event.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Level 5&lt;/strong&gt; — Active-active. Both clouds serve traffic simultaneously. Powerful, but expensive and operationally complex. Most teams don't need this.&lt;/p&gt;

&lt;p&gt;Most organizations sit between Level 0 and Level 2 and think they're at Level 3. The drills are what reveal the truth.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Broke
&lt;/h2&gt;

&lt;p&gt;The first drill took 35 minutes and failed in three places. That ended up being the most valuable part of the project.&lt;/p&gt;

&lt;h3&gt;
  
  
  PostgreSQL promotion timing
&lt;/h3&gt;

&lt;p&gt;We promoted the Azure replica and immediately started scaling &lt;a href="https://geekyants.com/blog/ai-pods-bridging-the-6-month-gap-between-prototype-and-production" rel="noopener noreferrer"&gt;AKS pods&lt;/a&gt;. Sometimes it worked. Sometimes &lt;a href="https://geekyants.com/hire-postgresql-developers" rel="noopener noreferrer"&gt;PostgreSQL&lt;/a&gt; was technically promoted but not yet accepting connections.&lt;/p&gt;

&lt;p&gt;Pods would start healthy. Database connections would fail silently. It looked random. It wasn't.&lt;/p&gt;

&lt;p&gt;The issue: we assumed promotion completion and connection readiness happened at the same time. They don't.&lt;/p&gt;

&lt;p&gt;Fix: replace &lt;code&gt;sleep 5&lt;/code&gt; with polling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;Original &lt;span class="nb"&gt;source &lt;/span&gt;code block — not preserved by the page&lt;span class="s1"&gt;'s renderer.
&amp;gt; Paste the polling script from the original article here before publishing:
&amp;gt; https://geekyants.com/blog/we-built-a-114-second-aws-to-azure-failover-heres-what-we-learned ]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't move on until the database confirms it's ready. That single change removed most of the inconsistent failover behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  The circular DNS dependency
&lt;/h3&gt;

&lt;p&gt;This one cost us days.&lt;/p&gt;

&lt;p&gt;Our Route 53 health check pointed at &lt;code&gt;api.nagacharan.store&lt;/code&gt;. During a drill, when DNS switched to Azure, the health check started getting 200 OK — from Azure. Route 53 concluded AWS was healthy and switched DNS back to AWS. Which had no pods running. Which failed the health check. Which switched back to Azure. Loop.&lt;/p&gt;

&lt;p&gt;The fix: a dedicated subdomain that never participates in failover routing.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;health.nagacharan.store&lt;/code&gt; → always points to AWS ALB (never changes)&lt;/p&gt;

&lt;p&gt;Route 53 checks this every 30 seconds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;200 OK → PRIMARY healthy → serve AWS&lt;/li&gt;
&lt;li&gt;503 → PRIMARY unhealthy → serve Azure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;health.nagacharan.store&lt;/code&gt; is a plain alias to the ALB. It never gets swapped. No circular dependency. Ever.&lt;/p&gt;

&lt;h3&gt;
  
  
  The double-failover state
&lt;/h3&gt;

&lt;p&gt;Run the failover script twice without running failback in between. &lt;a href="https://geekyants.com/blog/data-streaming-and-data-processing-with-azure" rel="noopener noreferrer"&gt;PostgreSQL on Azure&lt;/a&gt; is already promoted. &lt;code&gt;pg_promote()&lt;/code&gt; throws:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERROR: recovery is not in progress
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script continues. Nothing works correctly.&lt;/p&gt;

&lt;p&gt;One guard check at the start fixed this permanently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;Original &lt;span class="nb"&gt;source &lt;/span&gt;code block — not preserved by the page&lt;span class="s1"&gt;'s renderer.
&amp;gt; Paste the guard-check snippet from the original article here before publishing.]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Sequence conflicts on failback
&lt;/h3&gt;

&lt;p&gt;After multiple drill cycles, the PostgreSQL sequence on the newly-promoted instance falls behind the actual max ID. The next write throws a duplicate key violation.&lt;/p&gt;

&lt;p&gt;Fix — one line, run before every write test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;Original &lt;span class="nb"&gt;source &lt;/span&gt;code block — not preserved by the page&lt;span class="s1"&gt;'s renderer.
&amp;gt; Paste the sequence-fix one-liner from the original article here before publishing.]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Shell scripts that fail quietly
&lt;/h3&gt;

&lt;p&gt;Early scripts continued execution after partial failures. A missing variable or failed command would let the workflow proceed and leave infrastructure in a broken intermediate state.&lt;/p&gt;

&lt;p&gt;That's it. Failures became loud and early instead of silent and late. It sounds minor. It dramatically improved debugging.&lt;/p&gt;

&lt;h3&gt;
  
  
  The part most DR writeups skip
&lt;/h3&gt;

&lt;p&gt;During the DR period, your application keeps writing data. New rows. Advanced sequences. Committed transactions.&lt;/p&gt;

&lt;p&gt;When you fail back to AWS, your AWS database is stale.&lt;/p&gt;

&lt;p&gt;Our failback script handles this explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Export everything written to Azure during DR&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;Original &lt;span class="nb"&gt;source &lt;/span&gt;code block — not preserved by the page&lt;span class="s1"&gt;'s renderer.
&amp;gt; Paste the failback export script from the original article here before publishing.]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then rebuild streaming replication from scratch — &lt;code&gt;pg_basebackup&lt;/code&gt; from AWS to Azure in 12 seconds. Azure is a replica again. The system is back to normal state.&lt;/p&gt;

&lt;p&gt;No manual reconciliation. No data loss.&lt;/p&gt;

&lt;h3&gt;
  
  
  The numbers
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;th&gt;Achieved&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Total RTO&lt;/td&gt;
&lt;td&gt;60 minutes&lt;/td&gt;
&lt;td&gt;114 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DB promotion&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;td&gt;12s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AKS pod startup&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;td&gt;33s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DNS propagation&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;td&gt;60–90s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RPO&lt;/td&gt;
&lt;td&gt;5 minutes&lt;/td&gt;
&lt;td&gt;0 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failback total&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;td&gt;~3 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DR cost vs primary&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;td&gt;18%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The RPO of 0 comes from streaming replication. At the moment of failure, Azure has already applied every transaction AWS committed. The data is already there before the incident happens.&lt;/p&gt;

&lt;p&gt;The progression across drills:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Drill&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Manual Fixes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;~35 min&lt;/td&gt;
&lt;td&gt;Multiple&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;~8 min&lt;/td&gt;
&lt;td&gt;One&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;114 seconds&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The difference wasn't better cloud infrastructure. It was fixing the assumptions that only reveal themselves when things break.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Multi-Cloud Over Multi-Region?
&lt;/h2&gt;

&lt;p&gt;Multi-region inside AWS is genuinely better than single-region. But it still assumes AWS itself is operational.&lt;/p&gt;

&lt;p&gt;Some failures don't respect regional boundaries: IAM outages, control plane failures, DNS disruptions, provider-wide networking issues. In those situations, &lt;code&gt;us-west-2&lt;/code&gt; doesn't help if &lt;code&gt;us-east-1&lt;/code&gt; is the symptom and IAM is the disease.&lt;/p&gt;

&lt;p&gt;Multi-cloud also changes your negotiating position. When you can realistically move workloads, pricing conversations with providers get a lot more interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We'd Tell Teams Starting This Today
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Start with a single non-critical workload.&lt;/strong&gt; Get replication working. Automate a basic failover. Run drills until it's boring. That teaches more than months of architecture discussions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test failure intentionally.&lt;/strong&gt; Most of the useful fixes came from deliberately breaking things — GameDays, controlled outages, introducing race conditions. The circular DNS bug only became obvious because we kept drilling until edge cases surfaced.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Monitor the standby continuously.&lt;/strong&gt; The standby environment cannot be something teams only look at during outages. Replication lag, cluster health, DNS state — these should already be visible. The worst time to discover standby drift is during a production incident.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automation is not the hard part. Trust is.&lt;/strong&gt; Most teams still want a human involved before traffic moves between clouds during a real incident. That's probably healthy. Building trust in the process is what makes automated failover eventually feel safe.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;Before this project, multi-cloud DR felt like something only very large organizations could operate.&lt;/p&gt;

&lt;p&gt;After five drills, it felt achievable. Not easy. Achievable.&lt;/p&gt;

&lt;p&gt;Most of the problems weren't exotic &lt;a href="https://geekyants.com/engineering/devops/cloud-infrastructure-management-services" rel="noopener noreferrer"&gt;cloud infrastructure challenges&lt;/a&gt;. They were timing assumptions, sequencing gaps, incomplete automation, and replication edge cases. Solvable problems. Problems you can practice.&lt;/p&gt;

&lt;p&gt;The 114-second failover didn't come from a single architectural insight.&lt;/p&gt;

&lt;p&gt;It came from running the process until it broke, fixing what broke, and running it again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disaster recovery is less about having the right architecture and more about having a process your team actually trusts.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;During a real incident, nobody reads the architecture diagram.&lt;/p&gt;

&lt;p&gt;What matters is whether traffic recovers safely, whether data stays consistent, and whether the engineers running the recovery have done it enough times that it feels routine.&lt;/p&gt;

&lt;p&gt;The best time to get there is before the outage forces the conversation.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://geekyants.com/blog/we-built-a-114-second-aws-to-azure-failover-heres-what-we-learned" rel="noopener noreferrer"&gt;GeekyAnts blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Locust Performance Testing</title>
      <dc:creator>GeekyAnts Inc</dc:creator>
      <pubDate>Thu, 25 Jun 2026 05:29:47 +0000</pubDate>
      <link>https://dev.to/geekyants/locust-performance-testing-eaf</link>
      <guid>https://dev.to/geekyants/locust-performance-testing-eaf</guid>
      <description>&lt;p&gt;What is Performance Testing?&lt;br&gt;
Performance testing is a crucial aspect of software development that evaluates how an application behaves under various conditions and loads. It includes several types of testing, such as load testing, stress testing, and endurance testing. The goal is to ensure that the application performs well under expected and peak loads, providing a smooth user experience and maintaining reliability.&lt;/p&gt;

&lt;p&gt;Locust&lt;br&gt;
Why is Performance Testing Important?&lt;br&gt;
Load testing is the process of simulating real-world user traffic on your application to see how it performs under pressure. It helps you:&lt;/p&gt;

&lt;p&gt;Identify Bottlenecks: Discover which parts of your application (database queries, API endpoints, specific views) slow down under load.&lt;br&gt;
Ensure Scalability: Understand how many concurrent users your current setup can handle and plan for growth.&lt;br&gt;
Prevent Crashes: Find breaking points before your users do.&lt;br&gt;
Improve User Experience: Ensure your app remains fast and responsive, even during peak times.&lt;br&gt;
About Locust&lt;br&gt;
Any application's performance must be evaluated and improved through load testing. We utilise it to assess whether our application can survive the demands of actual use. Locust is a potent tool in every developer's toolbox for efficient load testing. With this free, open-source Python programme, you can simulate millions of concurrent users and describe user behaviour using Python code. This article will serve as your comprehensive, example-filled guide to load testing using Locust.&lt;/p&gt;

&lt;p&gt;What is a Locust?&lt;br&gt;
Locust is a distributed, scalable, and user-friendly load testing tool. Simulating traffic patterns aids engineers in understanding how many concurrent users a system can support. The key benefit of using Python code to describe user behaviour is that Locust is extremely flexible and configurable.&lt;/p&gt;

&lt;p&gt;Installing Locust&lt;br&gt;
Ensure you have Python 3.6 or higher installed before installing Locust. Pip may then be used to install Locust:&lt;/p&gt;

&lt;p&gt;pip install locust&lt;br&gt;
Getting Started with Locust&lt;br&gt;
You must provide user behaviour in a Python file to utilise Locust for the first time. The actions that the simulated users will take are listed in this file, which is sometimes called locustfile.py.&lt;/p&gt;

&lt;p&gt;from locust import HttpUser, task, between&lt;/p&gt;

&lt;p&gt;class MyUser(HttpUser):&lt;br&gt;
   wait_time = between(1, 3)&lt;/p&gt;

&lt;p&gt;@task&lt;br&gt;
   def hello_world(self):&lt;br&gt;
       self.client.get("/")&lt;br&gt;
In this illustration, the behaviour of a simulated user is defined by WebsiteUser. The homepage task is executed after the user has waited between 5 and 15 seconds (wait_time = between(5, 15)) and sends a GET request to the home page (self.client.get("/")).&lt;/p&gt;

&lt;p&gt;Running a Locust Test&lt;br&gt;
Navigate to the directory containing your locustfile.py and issue the locust command to conduct a Locust test:&lt;/p&gt;

&lt;p&gt;locust -f sample.py&lt;br&gt;
The web interface for Locust then launches, and its address is &lt;a href="http://localhost:8089" rel="noopener noreferrer"&gt;http://localhost:8089&lt;/a&gt;. Here, you can define the destination website, the total number of users to simulate, and the spawn rate.&lt;/p&gt;

&lt;p&gt;Open  &lt;a href="http://localhost:8089" rel="noopener noreferrer"&gt;http://localhost:8089&lt;/a&gt;&lt;br&gt;
Locust web interface for Load testing&lt;br&gt;
Provide the hostname of your server and try it out!&lt;/p&gt;

&lt;p&gt;The following screenshots show what it might look like when running this test using 50 concurrent users, with a ramp-up rate of 1 user/s&lt;br&gt;
Failed request and response time for Load testing results in Locust&lt;br&gt;
Under the Charts tab, you’ll find things like requests per second (RPS), response times and number of running users:&lt;/p&gt;

&lt;p&gt;Locust charts for RPS, response times, and number of users during load test.&lt;br&gt;
Direct command line usage / headless&lt;br&gt;
Using the Locust web UI is entirely optional. You can supply the load parameters on the command line and get reports on the results in text form:&lt;/p&gt;

&lt;p&gt;locust --headless --users 10 --spawn-rate 1 -H &lt;a href="http://your-server.com" rel="noopener noreferrer"&gt;http://your-server.com&lt;/a&gt;&lt;br&gt;
[2021-07-24 10:41:10,947] .../INFO/locust.main: No run time limit set, use CTRL+C to interrupt.&lt;br&gt;
[2021-07-24 10:41:10,947] .../INFO/locust.main: Starting Locust 2.37.12&lt;br&gt;
[20&lt;br&gt;
21-07-24 10:41:10,949] .../INFO/locust.runners: Ramping to 10 users using a 1.00 spawn rate&lt;/p&gt;

&lt;h2&gt;
  
  
  Name              # reqs      # fails  |     Avg     Min     Max  Median  |   req/s failures/s
&lt;/h2&gt;

&lt;p&gt;GET /hello             1     0(0.00%)  |     115     115     115     115  |    0.00    0.00&lt;/p&gt;

&lt;h2&gt;
  
  
  GET /world             1     0(0.00%)  |     119     119     119     119  |    0.00    0.00
&lt;/h2&gt;

&lt;p&gt;Aggregated             2     0(0.00%)  |     117     115     119     117  |    0.00    0.00&lt;/p&gt;

&lt;p&gt;[2021-07-24 10:44:42,484] .../INFO/locust.runners: All users spawned: {"HelloWorldUser": 10} (10 total users)&lt;br&gt;
More about running without a web UI&lt;/p&gt;

&lt;p&gt;Key Differences Between Locust, K6, and JMeter&lt;br&gt;
Performance Testing: Key Differences Between Locust, K6, and JMeter&lt;br&gt;
Feature k6  Locust  JMeter&lt;br&gt;
Scripting language  JavaScript  Python  GUI-based; optional Beanshell/Groovy scripting&lt;br&gt;
Developer-Friendly  Designed for developers with a clean API    Highly flexible with python scripting   Less developer-friendly; GUI-based with scripting as an add-on&lt;br&gt;
GUI Interface   CLI &amp;amp; Cloud/Grafana dashboards  Web-based dashboard Full graphical UI&lt;br&gt;
Real-Time Monitoring    Grafana/Cloud integrations  Built-in web UI Basic GUI reports; needs plugins for advanced monitoring&lt;br&gt;
CI/CD Integration   Native support for pipelines (GitHub Actions, Jenkins, etc.)    Possible with scripts and tools Requires extra setup and plugins&lt;br&gt;
Protocol Support    HTTP/HTTPS (limited other protocol support) HTTP/HTTPS (extensible via Python)  HTTP, FTP, JDBC, SOAP, JMS, and more&lt;br&gt;
Custom Scenarios    JavaScript-based flow control   Python-based; excellent for complex logic   Limited to GUI flows or custom scripts&lt;br&gt;
Scalability Supports distributed &amp;amp; cloud testing    Can simulate millions of users  Scales with effort; memory-intensive&lt;br&gt;
Resource Usage  Lightweight Efficient, low hardware footprint   Heavy memory usage on large tests&lt;br&gt;
Performance Metrics Rich metrics, InfluxDB/Grafana integration  Real-time metrics in the dashboard  Basic by default; extensible with plugins&lt;br&gt;
Distributed Testing Native support via k6 Cloud or output streaming Via worker nodes, easily configured Via remote servers, but requires configuration&lt;br&gt;
Best Use Case   API load testing, DevOps pipelines, scalable and scriptable load tests  Scalable web app testing, custom behavior scripting Broad protocol support, functional + load testing, GUI-centric teams&lt;br&gt;
Best Practices for Performance Testing&lt;br&gt;
Define Clear Objectives: Establish what you want to achieve with performance testing, including setting benchmarks, understanding acceptable load thresholds, and identifying critical metrics.&lt;br&gt;
Test in a Production-Like Environment: Conduct tests in an environment that closely mirrors your production setup to get accurate and actionable results.&lt;br&gt;
Use Realistic Test Data: Utilize data that represents real-world usage patterns, including a mix of user types, transactions, and data sizes.&lt;br&gt;
Automate Testing: Integrate performance testing into your CI/CD pipeline to catch performance regressions early and ensure every deployment is automatically tested.&lt;br&gt;
Analyze and Act on Results: Collect and analyze performance data to identify bottlenecks and areas for improvement, optimizing your application and infrastructure accordingly.&lt;br&gt;
Conclusion:&lt;br&gt;
Locust is a versatile and powerful tool for load testing, offering flexibility through Python scripting and ease of use with its web-based interface. Whether you are stress testing a simple &lt;a href="https://geekyants.com/service/hire-web-app-development-services" rel="noopener noreferrer"&gt;web application&lt;/a&gt; or a complex API, Locust provides the necessary tools to simulate real-world traffic and identify performance bottlenecks. Its scalability ensures that it can handle projects of any size, making it an essential tool in the arsenal of performance engineers.&lt;/p&gt;

&lt;p&gt;By leveraging Locust, teams can proactively address performance issues, ensuring that their systems remain robust and responsive under peak loads.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>tRPC in TypeScript: Simplify API Development Without Boilerplate</title>
      <dc:creator>GeekyAnts Inc</dc:creator>
      <pubDate>Fri, 19 Jun 2026 11:00:30 +0000</pubDate>
      <link>https://dev.to/geekyants/trpc-in-typescript-simplify-api-development-without-boilerplate-3lm3</link>
      <guid>https://dev.to/geekyants/trpc-in-typescript-simplify-api-development-without-boilerplate-3lm3</guid>
      <description>&lt;p&gt;For most developers, building an API means choosing between REST and GraphQL, setting up routes or resolvers, and managing a separate schema to keep the frontend and backend in sync. It's a familiar workflow, one that's been refined over the years with tools, conventions, and plenty of boilerplate.&lt;/p&gt;

&lt;p&gt;But when working in a TypeScript-first codebase, especially one where you control both the client and server, this approach starts to feel unnecessarily heavy. You're writing types twice, wiring up handlers that mirror database functions, and dealing with schema drift that shouldn't exist in the first place.&lt;/p&gt;

&lt;p&gt;This is the gap that &lt;strong&gt;tRPC&lt;/strong&gt; fills. It doesn't try to replace REST or GraphQL everywhere, but it does challenge the idea that an API layer always needs to be a separate contract. By leaning fully into TypeScript's type system, tRPC offers a way to define backend procedures and instantly infer their types on the client without any code generation, schema stitching, or serialization layer in between.&lt;/p&gt;

&lt;p&gt;The result is a lighter, more intuitive developer experience that feels like calling functions across files, not across a network.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Boilerplate Problem
&lt;/h2&gt;

&lt;p&gt;API development has always involved some level of duplication, especially when trying to keep the frontend and backend in sync. In a typical REST setup, you define your route on the server, write a handler, and then manually create a corresponding fetch call on the client. If you're using TypeScript, you'll probably also define the input and response types in a shared file or risk getting out of sync.&lt;/p&gt;

&lt;p&gt;GraphQL improves this a bit by centralizing the schema, but now you're managing resolvers, schema definitions, codegen, and possibly another layer like Apollo or urql. You're still stitching together multiple parts just to send and receive a piece of data.&lt;/p&gt;

&lt;p&gt;Take a simple example: creating a new user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// server: REST route&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// client: manual fetch + duplicated types&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;CreateUserInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;CreateUserResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CreateUserInput&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CreateUserResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even in this basic flow, there's duplication in types, manual request handling, and room for drift. Change a field name on the server and forget to update the client? TypeScript won't catch it unless you're explicitly sharing types, which introduces its own overhead.&lt;/p&gt;

&lt;p&gt;tRPC gets rid of this ceremony. You write a function once, on the server, and the client automatically understands how to call it, what inputs it expects, and what it returns. No manual fetch calls. No custom hooks. No syncing types.&lt;/p&gt;

&lt;h2&gt;
  
  
  How tRPC Works: A Mental Model
&lt;/h2&gt;

&lt;p&gt;At its core, tRPC treats your backend like a collection of type-safe functions. Instead of defining endpoints or resolvers, you define &lt;strong&gt;procedures&lt;/strong&gt;, which are functions that live inside &lt;strong&gt;routers&lt;/strong&gt;. These routers group related logic together, and the entire structure can be consumed directly by the client, with full type safety.&lt;/p&gt;

&lt;p&gt;A simple example looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// server: router with a procedure&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;publicProcedure&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./trpc&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userRouter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;router&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;getById&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;publicProcedure&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findUnique&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appRouter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;router&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userRouter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AppRouter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;appRouter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;getById&lt;/code&gt; is just a function. It receives input (validated using Zod), runs server-side logic, and returns a result. No need to declare a route or response type separately — everything is inferred from the function itself.&lt;/p&gt;

&lt;p&gt;The real magic happens on the client. After defining your procedures and grouping them into routers, you create an &lt;code&gt;appRouter&lt;/code&gt;, essentially a central object that represents your entire backend API. This is passed into the tRPC server handler (like in a Next.js API route or app handler), which exposes the procedure tree to the client. From there, the client can import a &lt;code&gt;trpc&lt;/code&gt; instance and start calling these procedures directly, with full type safety out of the box.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// client: fully typed call, no manual types needed&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;trpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getById&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's no need to define &lt;code&gt;getById&lt;/code&gt; on the client, or manually specify its input/output types. tRPC knows exactly what this function expects, because it's inferred directly from the backend definition.&lt;/p&gt;

&lt;p&gt;This model — "define it once, use it everywhere" — is what makes tRPC feel so different. It removes the artificial API boundary between server and client in projects where both are written in TypeScript. Instead of maintaining contracts, you're just calling strongly-typed functions.&lt;/p&gt;

&lt;p&gt;It's not just about convenience; it's about removing entire categories of bugs that stem from type mismatches and outdated API definitions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Simplifying the Stack with tRPC
&lt;/h2&gt;

&lt;p&gt;tRPC works especially well in setups where the backend and frontend live in the same codebase, or at least speak the same language. That's why it's become a popular choice in full-stack TypeScript apps built with frameworks like &lt;a href="https://geekyants.com/hire-next-js-developers" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt;, paired with an ORM like Drizzle for database access and React Query for frontend data fetching.&lt;/p&gt;

&lt;p&gt;In these environments, tRPC doesn't just simplify API calls, it removes the need to even think about them as "API calls" in the traditional sense. You're just calling functions with inputs and getting typed results back, whether you're working in a backend file or a frontend component.&lt;/p&gt;

&lt;p&gt;It also integrates cleanly with tools you're probably already using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Next.js&lt;/strong&gt;: tRPC can be wired up to API routes or used with server handlers, making it a natural fit for apps built on the App Router or Pages Router.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React Query&lt;/strong&gt;: When paired with the &lt;code&gt;@trpc/react-query&lt;/code&gt; adapter, procedures can be treated as fully type-safe queries or mutations — with caching, retries, and background refetching handled out of the box.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ORMs&lt;/strong&gt;: Since most modern ORMs (like Prisma, Drizzle, or Kysely) support static typing, combining them with tRPC creates a fully type-safe path from your database to your UI without needing to define types or schemas more than once.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The benefit isn't just fewer lines of code, it's clearer boundaries, safer data flow, and faster iteration. When everything speaks TypeScript, from your database to your frontend, you're less likely to ship runtime bugs caused by broken contracts or outdated assumptions.&lt;/p&gt;

&lt;h2&gt;
  
  
  When tRPC Makes the Most Sense
&lt;/h2&gt;

&lt;p&gt;Like most tools in the TypeScript ecosystem, tRPC isn't trying to solve everything for everyone — it's optimized for a specific kind of project.&lt;/p&gt;

&lt;p&gt;tRPC shines when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The frontend and backend are both written in TypeScript&lt;/li&gt;
&lt;li&gt;The same team controls both ends of the stack&lt;/li&gt;
&lt;li&gt;There's no need for public-facing APIs or multi-language support&lt;/li&gt;
&lt;li&gt;Fast iteration, tight feedback loops, and strong type safety matter more than API versioning or schema generation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes it a great fit for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Internal tools and admin dashboards&lt;/li&gt;
&lt;li&gt;Full-stack TypeScript apps built with frameworks like Next.js&lt;/li&gt;
&lt;li&gt;Rapid prototypes or early-stage products&lt;/li&gt;
&lt;li&gt;Monorepos, where backend and frontend live in the same codebase&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In these kinds of projects, tRPC often replaces traditional API layers with something that feels closer to function calls than network requests. You don't need to think about routes, schemas, or response formats — just write the logic and let TypeScript handle the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  But What About REST and GraphQL?
&lt;/h2&gt;

&lt;p&gt;tRPC isn't trying to replace REST or GraphQL across the board, but it does challenge some long-held assumptions about how API layers should be built.&lt;/p&gt;

&lt;p&gt;REST is still the default for many teams, largely because of its simplicity and ubiquity. It works well when you need predictable URLs, clear separation between resources, and broad tooling support. GraphQL, on the other hand, offers flexibility and strong typing, especially useful when dealing with complex relationships or multiple consumers querying the same data differently.&lt;/p&gt;

&lt;p&gt;But both come with trade-offs — schemas to maintain, types to sync, code to generate, and sometimes extra layers that don't add much value when you're working in a fully TypeScript-based stack. In apps where the frontend and backend are tightly coupled and written in the same language, the "API contract" starts to feel more like overhead than architecture.&lt;/p&gt;

&lt;p&gt;tRPC takes a different route. It skips the schema and instead relies on TypeScript's type inference to bridge the client-server gap. That means no manual type definitions, no GraphQL SDL, and no OpenAPI spec unless you explicitly need it. For internal tools, admin panels, or products built by small teams moving fast, this is often a worthwhile trade.&lt;/p&gt;

&lt;p&gt;That said, tRPC isn't a silver bullet. If you're building a public API, need strong tooling support across multiple languages, or have complex API lifecycle requirements, REST and GraphQL are still better-suited. The goal isn't to abandon those tools, but to recognize when they're more complex than necessary for the problem at hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;tRPC doesn't reinvent APIs, it rethinks how we build them when the frontend and backend are both in TypeScript. By removing the need for separate contracts, schemas, or generated types, it reduces friction without compromising on safety. You're still writing queries and mutations, but they feel more like calling functions than building API endpoints.&lt;/p&gt;

&lt;p&gt;This shift works especially well in full-stack projects where speed, safety, and developer experience matter more than long-term API portability. It's not trying to replace REST or GraphQL in every case, but for a growing number of teams working in a shared TypeScript environment, it offers a more streamlined and intuitive alternative.&lt;/p&gt;

&lt;p&gt;The next step is understanding what this new API layer makes possible. Whether it's cleaner validation, reusable auth middleware, or a more scalable router structure, tRPC opens up patterns that deserve a closer look.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally published on the &lt;a href="https://geekyants.com/blog/trpc-in-typescript-simplify-api-development-without-boilerplate" rel="noopener noreferrer"&gt;GeekyAnts blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>How to Mock in Integration Tests: Tools and Implementation</title>
      <dc:creator>GeekyAnts Inc</dc:creator>
      <pubDate>Tue, 16 Jun 2026 07:36:11 +0000</pubDate>
      <link>https://dev.to/geekyants/how-to-mock-in-integration-tests-tools-and-implementation-37oe</link>
      <guid>https://dev.to/geekyants/how-to-mock-in-integration-tests-tools-and-implementation-37oe</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This article was originally published on the &lt;a href="https://geekyants.com/blog/how-to-mock-in-integration-tests-tools-and-implementation" rel="noopener noreferrer"&gt;GeekyAnts Blog&lt;/a&gt;. Author: &lt;a href="https://geekyants.com/nilesh-kumar" rel="noopener noreferrer"&gt;Nilesh Kumar&lt;/a&gt;, Software Engineer II at GeekyAnts.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Having established when and why to mock in integration tests, it's time to explore the practical implementation of mocking strategies. This comprehensive guide covers the essential tools, techniques, and step-by-step processes for implementing effective mocks in your integration tests. We'll dive deep into popular mocking libraries, provide detailed examples, and explore advanced techniques for maintaining reliable and accurate mocks.&lt;/p&gt;




&lt;h2&gt;
  
  
  Essential Mocking Tools and Libraries
&lt;/h2&gt;

&lt;p&gt;The JavaScript ecosystem offers several powerful tools for implementing mocks in integration tests. Each tool serves different purposes and excels in specific scenarios. Understanding their strengths and use cases will help you choose the right tool for your specific &lt;a href="https://geekyants.com/service/hire-quality-assurance-developers" rel="noopener noreferrer"&gt;testing needs&lt;/a&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Nock: HTTP Request Mocking
&lt;/h3&gt;

&lt;p&gt;Nock is the most popular and powerful HTTP request mocking library for Node.js. It allows you to intercept and mock HTTP requests at the network level, making it ideal for testing applications that interact with external APIs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Intercepts HTTP requests at the network level&lt;/li&gt;
&lt;li&gt;Supports complex request matching patterns&lt;/li&gt;
&lt;li&gt;Provides detailed request/response validation&lt;/li&gt;
&lt;li&gt;Offers recording and playback capabilities&lt;/li&gt;
&lt;li&gt;Integrates seamlessly with all testing frameworks&lt;/li&gt;
&lt;li&gt;Supports both REST and GraphQL APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When to Use Nock:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Testing interactions with external REST APIs&lt;/li&gt;
&lt;li&gt;Mocking third-party services like payment gateways&lt;/li&gt;
&lt;li&gt;Testing error handling for HTTP failures&lt;/li&gt;
&lt;li&gt;Validating request formats and headers&lt;/li&gt;
&lt;li&gt;Creating deterministic responses for external services&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Basic Nock Usage:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nock&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Mock a GET request&lt;/span&gt;
&lt;span class="nf"&gt;nock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/users/1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;John Doe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;john@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Mock a POST request with request body matching&lt;/span&gt;
&lt;span class="nf"&gt;nock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Jane Doe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jane@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Jane Doe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Mock with headers&lt;/span&gt;
&lt;span class="nf"&gt;nock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/protected-resource&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matchHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bearer my-token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;secret&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Mock an error response&lt;/span&gt;
&lt;span class="nf"&gt;nock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/flaky-endpoint&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replyWithError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Connection refused&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Clean up after tests&lt;/span&gt;
&lt;span class="nf"&gt;afterEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;nock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cleanAll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Sinon: Comprehensive Function Mocking
&lt;/h3&gt;

&lt;p&gt;Sinon is a versatile library that provides spies, stubs, and mocks for JavaScript functions and objects. It's particularly useful for mocking internal dependencies and complex object interactions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Function spies for monitoring calls&lt;/li&gt;
&lt;li&gt;Stubs for replacing function behavior&lt;/li&gt;
&lt;li&gt;Mocks for complex object interactions&lt;/li&gt;
&lt;li&gt;Fake timers for time-based testing&lt;/li&gt;
&lt;li&gt;Extensive assertion capabilities&lt;/li&gt;
&lt;li&gt;Works with any testing framework&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When to Use Sinon:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mocking internal service dependencies&lt;/li&gt;
&lt;li&gt;Testing time-based functionality&lt;/li&gt;
&lt;li&gt;Spying on function calls and arguments&lt;/li&gt;
&lt;li&gt;Stubbing complex object methods&lt;/li&gt;
&lt;li&gt;Testing callback and promise behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Basic Sinon Usage:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sinon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sinon&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UserService&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;sandbox&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;sandbox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sinon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createSandbox&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;afterEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;sandbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;restore&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should call the email service on registration&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Create a spy&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;emailSpy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sandbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;emailService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sendWelcomeEmail&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Alice&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alice@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;sinon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;calledOnce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;emailSpy&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;sinon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;calledWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;emailSpy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alice@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should handle email service failure gracefully&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Stub to simulate failure&lt;/span&gt;
    &lt;span class="nx"&gt;sandbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;emailService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sendWelcomeEmail&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;rejects&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SMTP error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bob&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bob@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;registered_without_email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should expire tokens after timeout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;clock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sandbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;useFakeTimers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;authService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user-123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3600&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Advance 1 hour&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isValid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;authService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validateToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isValid&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;restore&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Jest Built-in Mocking
&lt;/h3&gt;

&lt;p&gt;Jest provides powerful built-in mocking capabilities that integrate seamlessly with the testing framework. While not as specialized as Nock or Sinon, Jest mocking is convenient for simple scenarios and module-level mocking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Module mocking with automatic mock generation&lt;/li&gt;
&lt;li&gt;Function mocking with call tracking&lt;/li&gt;
&lt;li&gt;Timer mocking for time-based tests&lt;/li&gt;
&lt;li&gt;Snapshot testing for mocked responses&lt;/li&gt;
&lt;li&gt;Mock clearing and restoration utilities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When to Use Jest Mocking:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simple function mocking scenarios&lt;/li&gt;
&lt;li&gt;Module-level mocking&lt;/li&gt;
&lt;li&gt;Quick prototyping of mocks&lt;/li&gt;
&lt;li&gt;Integration with Jest snapshot testing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Basic Jest Mocking:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Auto-mock an entire module&lt;/span&gt;
&lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./emailService&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Manual mock with implementation&lt;/span&gt;
&lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./paymentGateway&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;mockResolvedValue&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;transactionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;txn_123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;refund&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;mockResolvedValue&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OrderService&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clearAllMocks&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should process payment on order placement&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;charge&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./paymentGateway&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;orderService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;placeOrder&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...],&lt;/span&gt; &lt;span class="na"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;99.99&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;99.99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;USD&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should handle payment failure&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;charge&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./paymentGateway&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mockRejectedValueOnce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Card declined&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;orderService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;placeOrder&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...],&lt;/span&gt; &lt;span class="na"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;99.99&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;rejects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toThrow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Payment failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step-by-Step Implementation Guide
&lt;/h2&gt;

&lt;p&gt;Let's walk through a comprehensive example implementing integration tests with strategic mocking for a &lt;strong&gt;user notification system&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Application Architecture
&lt;/h3&gt;

&lt;p&gt;Our example notification system includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NotificationService&lt;/strong&gt; — orchestrates sending notifications&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EmailProvider&lt;/strong&gt; — external SMTP/email API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SMSProvider&lt;/strong&gt; — external SMS gateway&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UserRepository&lt;/strong&gt; — database layer for user data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NotificationLog&lt;/strong&gt; — records sent notifications&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Step 1: Test Environment Setup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// test/setup.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nock&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MongoMemoryServer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mongodb-memory-server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;mongoServer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;beforeAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Spin up in-memory MongoDB&lt;/span&gt;
  &lt;span class="nx"&gt;mongoServer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;MongoMemoryServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MONGO_URI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mongoServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUri&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Disable real HTTP calls during tests&lt;/span&gt;
  &lt;span class="nx"&gt;nock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disableNetConnect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;nock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enableNetConnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;127.0.0.1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// allow localhost&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;afterAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;mongoServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;nock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enableNetConnect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;afterEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;nock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cleanAll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Step 2: Basic Integration Test with Mocking
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// test/notification.integration.test.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;supertest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nock&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../src/app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;seedUser&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./helpers/seed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST /notifications/send&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;seedUser&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;+1234567890&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should send email and SMS notifications successfully&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Mock email provider&lt;/span&gt;
    &lt;span class="nf"&gt;nock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.sendgrid.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/v3/mail/send&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;202&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Accepted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Mock SMS provider&lt;/span&gt;
    &lt;span class="nf"&gt;nock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.twilio.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/2010-04-01/Accounts/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TWILIO_SID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/Messages.json`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;sid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SM123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;queued&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/notifications/send&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Your order has shipped!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TEST_API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toMatchObject&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;sms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;queued&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should still send SMS if email provider fails&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;nock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.sendgrid.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/v3/mail/send&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Internal Server Error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;nock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.twilio.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/2010-04-01/Accounts/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TWILIO_SID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/Messages.json`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;sid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SM456&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;queued&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/notifications/send&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Your order has shipped!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TEST_API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;207&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Partial success&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;queued&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Step 3: Advanced Mocking Scenarios
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Dynamic Response Generation:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Respond differently based on request body&lt;/span&gt;
&lt;span class="nf"&gt;nock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.sendgrid.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/v3/mail/send&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vip@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;202&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;high&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;nock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.sendgrid.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/v3/mail/send&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;202&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;normal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Simulating Rate Limits:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// First 3 calls succeed, then rate limit kicks in&lt;/span&gt;
&lt;span class="nf"&gt;nock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.sendgrid.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/v3/mail/send&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;202&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/v3/mail/send&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Too Many Requests&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Conditional Mocking Based on Environment:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mockExternalServices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;nock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.sendgrid.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/v3/mail/send&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;202&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;nock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.twilio.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/Messages&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;json$/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;queued&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Step 4: Performance Testing with Mocks
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should handle 100 concurrent notification requests&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;nock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.sendgrid.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/v3/mail/send&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;202&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;nock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.twilio.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/Messages&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;json$/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;queued&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/notifications/send&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`user-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Test notification&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TEST_API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;responses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;every&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeLessThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Should complete in under 5 seconds&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Advanced Mocking Techniques
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Request Recording and Playback
&lt;/h3&gt;

&lt;p&gt;Use Nock's record mode to capture real API responses and replay them in tests — great for staying in sync with real API behaviour without hitting production systems on every run.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Record mode (run once against real APIs)&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RECORD_MOCKS&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;nock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recorder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;output_objects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Playback mode (default)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;recordings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./fixtures/api-recordings.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;nock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;recordings&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Mock Validation and Maintenance
&lt;/h3&gt;

&lt;p&gt;Always assert that all registered mocks were actually called — this catches dead code and stale mocks early:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;afterEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Verify all registered nock interceptors were used&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pendingMocks&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toHaveLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;nock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cleanAll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Best Practices for Mock Implementation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Keep Mocks Simple and Focused&lt;/strong&gt;&lt;br&gt;
Each mock should represent exactly one scenario. Avoid overloading a single mock with conditional logic — create separate test cases instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Use Realistic Data and Delays&lt;/strong&gt;&lt;br&gt;
Make your mocks representative of real system behaviour. Add response delays where appropriate and use production-like payloads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;nock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.sendgrid.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/v3/mail/send&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Simulate realistic network latency&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;202&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Accepted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Mock at the Appropriate Level&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;strong&gt;Nock&lt;/strong&gt; for external HTTP services&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;Sinon stubs&lt;/strong&gt; for internal module dependencies&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;in-memory databases&lt;/strong&gt; rather than mocking the DB layer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. Document Mock Decisions&lt;/strong&gt;&lt;br&gt;
Leave comments explaining &lt;em&gt;why&lt;/em&gt; a mock was introduced, what real behaviour it replaces, and when it should be revisited:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Mocking SendGrid because the free tier has strict rate limits&lt;/span&gt;
&lt;span class="c1"&gt;// in CI. Review if we upgrade to a paid plan — real integration&lt;/span&gt;
&lt;span class="c1"&gt;// tests would be preferable.&lt;/span&gt;
&lt;span class="nf"&gt;nock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.sendgrid.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/v3/mail/send&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;202&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Implementing effective mocks in integration tests requires careful consideration of tools, techniques, and maintenance strategies. The key is to use mocks strategically to eliminate problematic external dependencies while preserving the authentic integrations that provide the most value.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nock&lt;/strong&gt; excels at HTTP request mocking and is essential for testing external API integrations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sinon&lt;/strong&gt; provides comprehensive function and object mocking capabilities for internal dependencies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jest's built-in mocking&lt;/strong&gt; works well for simple scenarios and integrates seamlessly with the testing framework.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By following the patterns and best practices in this guide, you can create integration tests that give you genuine confidence in your system's behaviour while remaining practical to execute and maintain.&lt;/p&gt;

&lt;p&gt;Remember: mocks are tools to &lt;em&gt;enable&lt;/em&gt; effective testing, not goals in themselves. Always evaluate whether your mocking strategy is serving your testing objectives — and adjust as your application evolves.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://geekyants.com/blog/how-to-mock-in-integration-tests-tools-and-implementation" rel="noopener noreferrer"&gt;GeekyAnts Blog&lt;/a&gt; by &lt;a href="https://geekyants.com/nilesh-kumar" rel="noopener noreferrer"&gt;Nilesh Kumar&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>tools</category>
    </item>
    <item>
      <title>Katalon and the Rise of Low-Code Test Automation</title>
      <dc:creator>GeekyAnts Inc</dc:creator>
      <pubDate>Thu, 11 Jun 2026 09:54:05 +0000</pubDate>
      <link>https://dev.to/geekyants/katalon-and-the-rise-of-low-code-test-automation-2p64</link>
      <guid>https://dev.to/geekyants/katalon-and-the-rise-of-low-code-test-automation-2p64</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This article was originally published on the &lt;a href="https://geekyants.com/blog/katalon-and-the-rise-of-low-code-test-automation" rel="noopener noreferrer"&gt;GeekyAnts Blog&lt;/a&gt; by &lt;a href="https://geekyants.com/sankalp-nihal-pandey" rel="noopener noreferrer"&gt;Sankalp Nihal Pandey&lt;/a&gt;, Software Engineer at GeekyAnts.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Modern &lt;a href="https://geekyants.com/service/custom-software-development-services" rel="noopener noreferrer"&gt;software development&lt;/a&gt; is faster than ever, with Agile and &lt;a href="https://geekyants.com/devops-services" rel="noopener noreferrer"&gt;DevOps&lt;/a&gt; practices pushing teams to deliver features in weeks, sometimes even days. While this speed boosts innovation, it also creates a major challenge: ensuring quality in every release. Traditional automation frameworks such as Selenium and Appium are powerful but require deep programming knowledge and significant time to implement. Many QA teams end up spending more time writing and maintaining scripts than actually running tests.&lt;/p&gt;

&lt;p&gt;This is where low-code test automation tools like &lt;strong&gt;Katalon Studio&lt;/strong&gt; make a difference. Katalon empowers both technical and non-technical testers to create, manage, and execute automated tests quickly. In this blog, we'll explore Katalon in detail, examine its functionality through code snippets, and discuss its benefits for both testers and businesses.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is Low-Code Test Automation?
&lt;/h2&gt;

&lt;p&gt;Low-code test automation is an approach that minimizes the need for writing complex scripts. Instead, it offers a combination of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Visual interfaces&lt;/li&gt;
&lt;li&gt;Record-and-playback capabilities&lt;/li&gt;
&lt;li&gt;Keyword-driven testing&lt;/li&gt;
&lt;li&gt;Drag-and-drop components&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This doesn't mean advanced users are left out. Tools like Katalon let engineers extend and customize tests using &lt;strong&gt;Groovy&lt;/strong&gt;, a language based on Java. The result is a platform where manual testers, business analysts, and developers can all contribute — making automation more collaborative and scalable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Katalon Studio?
&lt;/h2&gt;

&lt;p&gt;Katalon Studio stands out as a multi-purpose automation platform built on top of Selenium and Appium. It supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Web application testing&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mobile app testing&lt;/strong&gt; (Android &amp;amp; iOS)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API testing&lt;/strong&gt; (REST &amp;amp; SOAP)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Desktop application testing&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Its dual approach — a &lt;strong&gt;Manual View&lt;/strong&gt; for non-technical users and a &lt;strong&gt;Script View&lt;/strong&gt; for coders — makes it suitable for diverse teams. With built-in features like self-healing tests, data-driven testing, and CI/CD integration, Katalon reduces the effort needed to build robust automation frameworks from scratch.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting Started with Katalon: A Simple Example
&lt;/h2&gt;

&lt;p&gt;Imagine testing a basic login functionality. In Katalon's Manual View, you can record user actions like opening a browser, typing a username and password, and clicking the login button. These actions are automatically converted into test steps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;WebUI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;openBrowser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;WebUI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;navigateToUrl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://example.com/login'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;WebUI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setText&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;findTestObject&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Page_Login/input_Username'&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;'testuser'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;WebUI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setEncryptedText&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;findTestObject&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Page_Login/input_Password'&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;'encrypted_password'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;WebUI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;click&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;findTestObject&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Page_Login/button_Login'&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;WebUI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;verifyElementPresent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;findTestObject&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Page_Home/icon_UserProfile'&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;WebUI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;closeBrowser&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clean, readable, and generated without writing a single line manually — this is low-code in action.&lt;/p&gt;




&lt;h2&gt;
  
  
  Scaling Up: Data-Driven Testing
&lt;/h2&gt;

&lt;p&gt;Real-world applications require testing with different sets of data. Katalon simplifies this with its &lt;strong&gt;data-driven testing&lt;/strong&gt; feature, which allows linking test cases with external data sources like Excel, CSV, or databases — enabling you to run the same test across multiple user scenarios without duplicating scripts.&lt;/p&gt;




&lt;h2&gt;
  
  
  API Testing Made Easy
&lt;/h2&gt;

&lt;p&gt;Katalon is not limited to UI automation. It also supports &lt;strong&gt;API testing&lt;/strong&gt;, which is essential in microservices and cloud-native architectures. With just a few steps, you can send requests and validate responses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;ResponseObject&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WS&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sendRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;findTestObject&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'API/GetUser'&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;WS&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;verifyResponseStatusCode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;WS&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;verifyElementPropertyValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'data.id'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'123'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;WS&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;verifyElementPropertyValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'data.name'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'John Doe'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes end-to-end validation — from UI to backend — possible within a single platform.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reducing Maintenance with Object Repository
&lt;/h2&gt;

&lt;p&gt;A common pain in test automation is maintaining scripts when the UI changes. Katalon tackles this with its &lt;strong&gt;Object Repository&lt;/strong&gt;, where locators and properties are stored separately from scripts. If a button ID changes, you update it once in the repository and all linked test cases are fixed instantly.&lt;/p&gt;

&lt;p&gt;Additionally, Katalon offers &lt;strong&gt;self-healing tests&lt;/strong&gt;, where AI automatically detects and substitutes broken locators — drastically reducing flaky tests and saving valuable engineering time.&lt;/p&gt;




&lt;h2&gt;
  
  
  CI/CD Integration for Agile Teams
&lt;/h2&gt;

&lt;p&gt;Automation isn't useful if it's not integrated into the delivery pipeline. Katalon makes this seamless with plugins for &lt;strong&gt;Jenkins&lt;/strong&gt;, &lt;strong&gt;GitLab&lt;/strong&gt;, and &lt;strong&gt;Azure DevOps&lt;/strong&gt;. Tests can be executed automatically with each build, providing instant feedback to developers.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reporting and Analytics
&lt;/h2&gt;

&lt;p&gt;Katalon provides comprehensive reporting out of the box. Test results include execution logs, pass/fail summaries, and screenshots for failed cases. For teams seeking deeper insights, &lt;strong&gt;Katalon TestOps&lt;/strong&gt; offers advanced dashboards and analytics — helping organizations make data-driven decisions about quality.&lt;/p&gt;




&lt;h2&gt;
  
  
  When and Why to Use Katalon
&lt;/h2&gt;

&lt;p&gt;Katalon fits particularly well in these scenarios:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Regression Testing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Automating repetitive checks across builds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Smoke Testing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Quickly validating core functionality&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cross-Browser Testing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Chrome, Firefox, Edge, Safari&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mobile Testing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Android and iOS applications&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;UAT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Business stakeholders validate workflows with minimal coding&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;From a business perspective, the advantages are clear: faster time-to-market, lower automation costs, and improved collaboration between QA, development, and product teams.&lt;/p&gt;




&lt;h2&gt;
  
  
  Limitations to Keep in Mind
&lt;/h2&gt;

&lt;p&gt;No tool is perfect. While Katalon offers broad features, it does have some limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Advanced customizations still require scripting knowledge&lt;/li&gt;
&lt;li&gt;The free edition has limitations compared to the enterprise version&lt;/li&gt;
&lt;li&gt;For highly complex backend systems, custom frameworks may still be a better fit&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Low-code automation is no longer just a trend — it's a necessity for organizations that want to deliver faster without sacrificing quality. &lt;strong&gt;Katalon Studio&lt;/strong&gt; is leading this revolution by providing an all-in-one platform that balances ease of use with advanced flexibility.&lt;/p&gt;

&lt;p&gt;By combining record-and-playback simplicity with powerful scripting, data-driven testing, CI/CD integration, and AI-powered self-healing, Katalon empowers teams to test smarter, collaborate better, and release faster.&lt;/p&gt;

&lt;p&gt;Whether you are a manual tester stepping into automation or a senior QA engineer building scalable solutions, Katalon offers a pathway that grows with your team.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://geekyants.com/blog/katalon-and-the-rise-of-low-code-test-automation" rel="noopener noreferrer"&gt;GeekyAnts Blog&lt;/a&gt;. Written by &lt;a href="https://geekyants.com/sankalp-nihal-pandey" rel="noopener noreferrer"&gt;Sankalp Nihal Pandey&lt;/a&gt;, Software Engineer at GeekyAnts.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devdiscuss</category>
      <category>automation</category>
      <category>qa</category>
    </item>
  </channel>
</rss>
