<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Ankit Bhalla on Medium]]></title>
        <description><![CDATA[Stories by Ankit Bhalla on Medium]]></description>
        <link>https://medium.com/@ankit.devninja?source=rss-e77024d7fd90------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*cH1xFkj0QHqOk5yY6hYzwQ.jpeg</url>
            <title>Stories by Ankit Bhalla on Medium</title>
            <link>https://medium.com/@ankit.devninja?source=rss-e77024d7fd90------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 11:13:19 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@ankit.devninja/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Claude Code for React & React Native: Workflows That Actually Move the Needle]]></title>
            <link>https://medium.com/cars24/claude-code-for-react-react-native-workflows-that-actually-move-the-needle-33b8bb410b14?source=rss-e77024d7fd90------2</link>
            <guid isPermaLink="false">https://medium.com/p/33b8bb410b14</guid>
            <category><![CDATA[claude-code]]></category>
            <category><![CDATA[ai-agent]]></category>
            <category><![CDATA[react-native]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[developer-productivity]]></category>
            <dc:creator><![CDATA[Ankit Bhalla]]></dc:creator>
            <pubDate>Mon, 09 Feb 2026 10:20:30 GMT</pubDate>
            <atom:updated>2026-02-09T10:46:01.945Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1VOaWh4s0Y7_jBoHrFozzg.png" /></figure><p><strong><em>Frontend is forgiving. Mobile is not.</em></strong></p><p>I’ve spent over a decade building mobile experiences, eventually landing deep in React Native where JavaScript collides with gestures, memory limits, native bridges, and two operating systems that rarely agree with each other. I’ve rewritten apps through the React era, from class components to hooks, often under App Store and Play Store review cycles where “almost correct” still means rejection.</p><p>Mobile doesn’t forgive abstractions that leak. It charges interest on every shortcut.</p><p>Despite all that churn, nothing boosted my productivity more than mastering <strong>Claude Code</strong> as a serious mobile/web development tool.</p><p>But here’s what most articles get wrong. They list every slash command like a man page and call it a guide. That’s not how a principal engineer thinks.</p><p>We don’t think in commands. We think in <strong>systems</strong>. We think in workflows. We think about which combinations of tools create leverage — where the whole becomes greater than the sum of its parts.</p><p>This article is my opinionated, battle-tested guide to the Claude Code commands and features that actually move the needle when building <strong>React and React Native</strong> applications. Not everything Claude can do but only the things that consistently deliver maximum output in real-world mobile and web codebases.</p><blockquote>💡 Principal Insight</blockquote><blockquote>The goal isn’t to use every feature Claude offers. It’s to build a workflow where context stays clean, intent stays clear, and Claude operates like a senior engineer on your team &amp; not a fancy autocomplete.</blockquote><h3>01. The Foundation Layer — CLAUDE.md</h3><p>Before you type a single command, you need to give Claude a brain for your project. The CLAUDE.md file is the single most impactful thing you can do. Period. It&#39;s not optional — it&#39;s the difference between Claude generating generic React boilerplate and Claude generating code that fits your architecture like a glove.</p><p>Your First Command — Always</p><pre>claude<br>/init</pre><p>The /init command scans your project and generates a starter CLAUDE.md. But don&#39;t stop there. A principal-level CLAUDE.md for a React Native project should encode your architectural decisions:</p><p>CLAUDE.md — React Native Project (Example)</p><pre>## Architecture<br>- React Native 0.76+ with New Architecture enabled<br>- Navigation: React Navigation v7 (stack + bottom tabs)<br>- State: Zustand for client state, TanStack Query for server state<br>- Styling: StyleSheet.create() — no inline styles, no Tailwind<br><br>## Code Standards<br>- Functional components only. No class components.<br>- Custom hooks for ALL business logic (useAuth, useCart, etc.)<br>- Barrel exports via index.ts in every feature folder<br>- Error boundaries at route level minimum<br><br>## File Structure<br>src/<br>  features/     # Feature-based modules<br>  shared/       # Shared components, hooks, utils<br>  navigation/   # Navigation config<br>  services/     # API layer (Axios + interceptors)<br>  types/        # Global TypeScript types<br><br>## Testing<br>- Jest + React Native Testing Library<br>- Test files colocated: Component.test.tsx<br>- E2E: Detox for critical user flows<br><br>## Commands<br>- Dev: npx react-native start<br>- Test: jest --watchAll<br>- Lint: eslint . --fix<br>- Type check: tsc --noEmit</pre><p>📊 Impact Metric</p><p>In my experience, a well-crafted CLAUDE.md reduces “wrong architecture” suggestions by roughly 70%. Claude stops suggesting Redux when you’ve specified Zustand. It stops creating class components. It follows <em>your</em> conventions. The 30-minute investment compounds across every session.</p><p>🎯 Pro Tip</p><p>Use /memory during any session to quickly edit your CLAUDE.md without leaving the terminal. Treat it like a living document — update it every time you make an architectural decision.</p><h3>02. The Command Arsenal: What Actually Matters</h3><p>Claude Code has dozens of commands. Most articles list all of them. Here’s my tiered ranking based on actual frontend development impact:</p><h3>Tier 1 — Use These Every Single Session</h3><p><strong>/initCritical</strong></p><p>Generates CLAUDE.md by scanning your codebase. Run it once, then refine manually. This is your project’s DNA for Claude.</p><p><strong>/compactCritical</strong></p><p>Summarizes conversation history, keeping key decisions while freeing context window. In long React refactoring sessions, this is a lifesaver. Use /compact Focus on the component migration to guide what gets preserved.</p><p><strong>Shift+Tab (Plan Mode)Critical</strong></p><p>Toggles between plan mode and execution mode. Plan mode means Claude analyzes and strategizes without writing code. I use this 90% of the time before any implementation.</p><p><strong>@ (File Reference)Critical</strong></p><p>Point Claude at specific files: @src/components/Button.tsx — review this for accessibility. Dramatically reduces hallucination because Claude reads the actual file instead of guessing.</p><h3>Tier 2 — Use These Weekly</h3><p><strong>/reviewHigh</strong></p><p>Runs a code review against your staged changes. Catches performance anti-patterns like unnecessary re-renders, missing React.memo, and inline object creation in JSX.</p><p><strong>/clearHigh</strong></p><p>Nuclear reset. Use between completely unrelated tasks. Switching from API work to CSS layout? /clear prevents context pollution.</p><p><strong>/contextHigh</strong></p><p>Shows how much of the context window you’ve consumed. When this gets above 60%, Claude starts forgetting your earlier architectural decisions. Time to /compact.</p><p><strong>Esc + Esc (/rewind)High</strong></p><p>Undo Claude’s last action. Invaluable when Claude takes a wrong architectural turn during a React Native navigation refactor.</p><h3>Tier 3 — Situational But Powerful</h3><p><strong>/costMedium</strong></p><p>Check token usage against plan limits. Useful for staying aware during heavy refactoring sessions.</p><p><strong>/agentsMedium</strong></p><p>Manage your custom subagents. Worth setting up for recurring FE tasks like design system audits.</p><p><strong>! (Bang Command)Medium</strong></p><p>Run bash directly: !npx tsc — noEmit to type-check without leaving Claude. Great for quick validation loops.</p><blockquote>⚠️ Watch Out</blockquote><blockquote>The biggest mistake I see: developers never use /compact or /clear. They have 45-minute sessions where Claude progressively forgets the CLAUDE.md conventions and starts generating garbage. Context management isn&#39;t optional it&#39;s the skill that separates productive sessions from frustrating ones.</blockquote><h3>03. Skills &amp; Plugins — Where the Real Leverage Is</h3><p>Skills are where Claude Code goes from “helpful assistant” to “team member who knows our codebase.” A skill is a SKILL.md file with instructions that Claude automatically applies when the context matches. Think of them as reusable architectural guardrails.</p><h3>The Must-Install Skills for React/RN Developers</h3><p>🔥 frontend-design (Built-in Plugin)</p><p>Ships with Claude Code. Automatically activates when building UI components. Pushes Claude toward distinctive, production-grade interfaces instead of generic “AI-looking” output. It’s the difference between a Bootstrap clone and something that looks intentionally designed.</p><pre>/plugins install frontend-design</pre><p>📱 React Native Best Practices (Callstack)</p><p>Just released by Callstack — the team behind React Native’s core tooling. Covers JS performance patterns, native optimization, and bundle size reduction. Claude automatically references these patterns when working in RN codebases.</p><pre>/plugin marketplace add callstackincubator/agent-skills<br>/plugin install react-native-best-practices@callstack-agent-skills</pre><p>⚛️ Vercel React Best Practices</p><p>45 actionable rules from Vercel Engineering organized by impact level. Covers waterfalls, bundle issues, server-side patterns, and rendering optimizations. Claude categorizes findings as Critical, High, and Medium during reviews.</p><pre>pip install skilz<br>skilz install vercel-labs/agent-skills/vercel-react-best-practices</pre><h3>Building Your Own Custom Skills</h3><p>The pre-built skills get you started. The custom skills are where you encode <em>your</em> team’s tribal knowledge. Here’s a skill I use for every React Native feature:</p><p>.claude/skills/rn-feature/SKILL.md</p><pre>---<br>name: rn-feature<br>description: Scaffold a new React Native feature module with <br>  screens, navigation, hooks, types, and tests following our <br>  architecture.<br>---<br><br>When creating a new feature module:<br>1. Create the directory structure:<br>   src/features/&lt;name&gt;/<br>   ├── screens/        # Screen components<br>   ├── components/     # Feature-specific components<br>   ├── hooks/          # Custom hooks for business logic<br>   ├── types/          # TypeScript types/interfaces<br>   ├── services/       # API calls (TanStack Query)<br>   ├── __tests__/      # Colocated tests<br>   └── index.ts        # Barrel export<br>2. Every screen gets an Error Boundary wrapper<br>3. Every API call goes through a custom hook with TanStack Query<br>4. Navigation types must extend RootStackParamList<br>5. Run tsc --noEmit after scaffolding to verify types</pre><p>Now when I type /rn-feature checkout-flow, Claude scaffolds the entire module following our exact architecture. No more explaining the folder structure every session.</p><blockquote>💡 Principal Insight</blockquote><blockquote>Skills activate automatically when their description matches the task context. This is the key mental model: you’re not running skills manually most of the time. You describe a task, and Claude pulls in the right skill. This only works if your descriptions are specific enough.</blockquote><h3>04. Subagents — The Principal Engineer’s Secret Weapon</h3><p>This is where most developers stop exploring, and where the real leverage begins. Subagents are isolated Claude instances that run in separate context windows. They do the heavy investigative work and return concise summaries — without polluting your main session.</p><p>Here’s the math that should convince you. In a typical debugging session, Claude reads log files, parses error traces, checks dozens of files. That might consume 170K tokens. A subagent does the same work in its own context and returns a 5K-token summary. Your main context stays clean for actual implementation.</p><p>Built-in Subagents</p><pre># Explore — Read-only codebase investigation<br>&quot;Use a subagent to investigate how our auth flow <br>handles token refresh across the app&quot;<br><br># Plan - Architecture planning without touching code<br>&quot;Use a Plan subagent to design the migration <br>strategy from Redux to Zustand for our cart module&quot;<br># General-purpose - Your Swiss army knife<br>&quot;Spin up a subagent to audit all our custom hooks <br>for missing cleanup in useEffect&quot;</pre><h3>Custom Subagent: The React Native Performance Auditor</h3><p>.claude/agents/rn-perf-auditor.md</p><pre>---<br>name: rn-perf-auditor<br>description: Audit React Native components for performance issues.<br>  Use when reviewing FlatList implementations, heavy screens, <br>  or before release.<br>allowed-tools: Read, Grep, Glob<br>---<br>You are a React Native performance specialist. Audit the <br>codebase for:<br>1. FlatList without keyExtractor or getItemLayout<br>2. Inline arrow functions in renderItem causing re-renders<br>3. Missing React.memo on list item components<br>4. Heavy computation in render path (move to useMemo)<br>5. Image components without width/height (causes layout thrash)<br>6. Animated.Value created inside component body (should be useRef)<br>7. InteractionManager.runAfterInteractions not used for <br>   post-navigation heavy work<br>Return findings grouped by severity: Critical, Warning, Info.<br>Include file paths and line numbers.<br></pre><p>🎯 Pro Tip</p><p>The golden rule of subagents: <strong>reads are parallel, writes are serial</strong>. You can have multiple subagents reading and analyzing simultaneously. But keep all actual code changes in the main thread to avoid merge conflicts. Think of subagents as your research team — they investigate, you implement.</p><p>You can also run background tasks with &amp; (ampersand). Kick off a heavy refactor, switch to another task, and check back:</p><p>Background Tasks</p><pre># Start a background task<br>&amp; Refactor the authentication module<br><br># Check running tasks<br>/tasks<br># Teleport into a background session<br># Press &#39;t&#39; to jump in</pre><h3>05. Hooks — Deterministic Automation</h3><p>Skills and subagents rely on Claude’s judgment. Hooks don’t. Hooks are deterministic scripts that fire at specific moments before a tool runs, after a tool runs, when permissions are requested. They always execute the same way, every time.</p><p>For frontend development, here are the hooks that have saved me the most time:</p><pre>#.claude/settings.json - Useful Hooks<br><br>{<br>  &quot;hooks&quot;: {<br>    &quot;PreToolUse&quot;: [<br>      {<br>        &quot;matcher&quot;: &quot;Write&quot;,<br>        &quot;command&quot;: &quot;echo &#39;Remember: Run tsc --noEmit after writing .tsx files&#39;&quot;<br>      }<br>    ],<br>    &quot;PostToolUse&quot;: [<br>      {<br>        &quot;matcher&quot;: &quot;Bash(npm install*)&quot;,<br>        &quot;command&quot;: &quot;echo &#39;Verify package.json for unexpected dependency additions&#39;&quot;<br>      }<br>    ],<br>    &quot;Stop&quot;: [<br>      {<br>        &quot;command&quot;: &quot;terminal-notifier -message &#39;Claude finished the task&#39; -title &#39;Claude Code&#39;&quot;<br>      }<br>    ]<br>  }<br>}</pre><blockquote>💡 Principal Insight</blockquote><blockquote>The Stop hook for desktop notifications is a game-changer. I kick off a component scaffolding task, go review a PR, and get a native macOS notification when Claude finishes. No more staring at the terminal.</blockquote><h3>06. The 10x Workflow — Putting It All Together</h3><p>Here’s my actual daily workflow for building React Native features. This is where the individual commands compound into something transformative:</p><p>The Daily Flow</p><ol><li>Open Terminal — Split Into 3 Panes <br>Pane 1: Main Claude session. Pane 2: Metro bundler + device logs. Pane 3: A second Claude session for investigation/research.</li><li>Shift+Tab → Plan Mode <br>Describe the feature. Let Claude analyze the codebase and propose architecture. Don’t let it write code yet.</li><li>Subagent: Explore<br>“Use a subagent to investigate how similar features are implemented in our codebase.” This gives Claude (and you) real patterns to follow.</li><li>Review Plan → Shift+Tab → Execute<br>Once the plan looks right, switch to execution mode. Claude writes code following your CLAUDE.md conventions and the architecture plan.</li><li>/review → Iterate<br>After implementation, /review catches things like missing error boundaries, inline styles, or untested edge cases.</li><li>/compact After Each Feature<br>Don’t carry feature A’s context into feature B. Compact with specific instructions: /compact Keep the navigation changes, drop the API discussion.</li></ol><h3>07. The TDD Loop with Claude</h3><p>TDD is one thing that everyone talks about but are rarely consumed in development cycles. With claude this is the workflow that gives me the highest confidence in production code:</p><pre># Step 1: Write failing tests first<br>&quot;Write failing tests for a PaymentCard component using <br>React Native Testing Library:<br>- Renders masked card number (•••• •••• •••• 4242)<br>- Shows expiry date in MM/YY format<br>- onPress fires with card ID<br>- Disabled state greys out and prevents interaction<br>- Accessibility: card type announced to screen reader<br>Save as PaymentCard.test.tsx. Do NOT implement yet.&quot;<br><br># Step 2: Review the tests (Plan Mode)<br>Shift+Tab → Review test coverage, add edge cases<br># Step 3: Implement to pass<br>Shift+Tab → &quot;Now implement PaymentCard.tsx to pass all tests&quot;<br># Step 4: Verify<br>!npx jest PaymentCard --coverage</pre><blockquote>📊 Impact Metric</blockquote><blockquote>When I adopted this TDD loop with Claude, bug reports from QA for new components dropped by about 60%. The tests Claude writes catch edge cases I wouldn’t think of — accessibility states, empty data scenarios, race conditions in async operations.</blockquote><h3>08. The Mindset Shift</h3><p>After 10+ years of building frontend at scale, here’s what I’ve learned about using Claude Code effectively: it’s not about the commands. It’s about treating Claude as a system you architect, not a chatbot you prompt.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5SRRcZxgoEs-VR6aRbTMdg.png" /></figure><p>The principal engineer’s job isn’t to write more code. It’s to make the right trade-offs, encode standards that scale, and build systems where the team (including Claude) produces consistently excellent output.</p><p>Claude Code is the first AI tool I’ve used where the investment in configuration actually pays compound returns. The CLAUDE.md you write today saves you from wrong patterns tomorrow. The skills you create this week save your team from inconsistency next month. The subagents you configure once handle the investigative work that used to eat your afternoons.</p><p>Start with /init. Refine your CLAUDE.md. Install the Callstack and Vercel skills. Set up one custom subagent. That&#39;s your first week. Everything else compounds from there.</p><p><em>Ship well. Ship fast. Ship with intent. ✌️</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=33b8bb410b14" width="1" height="1" alt=""><hr><p><a href="https://medium.com/cars24/claude-code-for-react-react-native-workflows-that-actually-move-the-needle-33b8bb410b14">Claude Code for React &amp; React Native: Workflows That Actually Move the Needle</a> was originally published in <a href="https://medium.com/cars24">Cars24 Engineering Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building Resilient Node.js BFFs at Scale: Hard-Earned Lessons from Production]]></title>
            <link>https://medium.com/cars24/building-resilient-node-js-bffs-at-scale-hard-earned-lessons-from-production-995951d1c96e?source=rss-e77024d7fd90------2</link>
            <guid isPermaLink="false">https://medium.com/p/995951d1c96e</guid>
            <category><![CDATA[bff]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <category><![CDATA[backend]]></category>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[backend-development]]></category>
            <dc:creator><![CDATA[Ankit Bhalla]]></dc:creator>
            <pubDate>Tue, 27 Jan 2026 17:45:00 GMT</pubDate>
            <atom:updated>2026-01-28T09:06:57.599Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lTularxWvQLQ1GEmZyxOJQ.png" /></figure><h3>Introduction</h3><p>CARS24&#39;s BFF layer now handles millions of requests across multiple countries, orchestrating data from multiple microservices. It powers superapp, home pages, car listings, search, checkout, and everything in between. When it works, nobody notices. When it fails, the business notices immediately.</p><p>These aren’t theoretical patterns from a side project. They’re solutions that survived the test of real traffic, real failures, and real business consequences. They’re the hard-won lessons from outages, incidents, and countless hours of debugging.</p><p>I won’t promise you’ll never face another production issue. But maybe, just maybe, you’ll recognise the warning signs a little earlier. You’ll avoid some of the mistakes we made. You’ll have a better mental model for why things fail in ways you never expected.</p><p>So grab your coffee (or tea, I don’t judge), and let’s dive into the messy, beautiful reality of building resilient systems in production. This is the story of what actually works, warts and all.</p><p><em>Welcome to the real world of BFF architecture. I’m glad you’re here</em></p><h3>Problem #1: Promise.all() is a Footgun for Production BFFs</h3><h4>The Trap Everyone Falls Into</h4><p>You learn about Promise.all() and think you&#39;re done with parallel execution:</p><pre>const [users, orders, inventory] = await Promise.all([<br>  getUserData(userId),<br>  getOrders(userId),<br>  getInventory(userId)<br>]);</pre><p><strong>This works great until one service throws an error. Then everything fails.</strong></p><p>In production, this means:</p><ul><li>Inventory service has a hiccup → entire user dashboard crashes</li><li>One timeout → user sees error page</li><li>Partial failures become total failures</li></ul><h4>The Real Solution: Layered Failure Handling</h4><pre>// Layer 1: Critical dependencies that MUST succeed<br>async function getCriticalData(userId) {<br>  try {<br>    return await Promise.all([<br>      getAuthContext(userId),<br>      getUserProfile(userId)<br>    ]);<br>  } catch (error) {<br>    // If critical fails, the request should fail<br>    throw new Error(&#39;Critical dependency failure&#39;);<br>  }<br>}</pre><pre>// Layer 2: Important but degradable dependencies<br>async function getImportantData(userId) {<br>  const results = await Promise.allSettled([<br>    getOrders(userId),<br>    getPaymentStatus(userId)<br>  ]);<br>  <br>  // Return what succeeded, log what failed<br>  return results.map((result, idx) =&gt; {<br>    if (result.status === &#39;fulfilled&#39;) {<br>      return result.value;<br>    }<br>    <br>    logger.warn({ <br>      service: [&#39;orders&#39;, &#39;payment&#39;][idx],<br>      reason: result.reason.message <br>    });<br>    return null;<br>  });<br>}</pre><pre>// Layer 3: Optional enrichments (fire and don&#39;t wait)<br>async function startOptionalEnrichments(userId, responseId) {<br>  // Don&#39;t await - let these run in background<br>  Promise.allSettled([<br>    getRecommendations(userId),<br>    trackAnalytics(userId),<br>    prefetchRelatedData(userId)<br>  ]).then(results =&gt; {<br>    // Push updates via WebSocket or cache for next request<br>    notifyClient(responseId, results.filter(r =&gt; r.status === &#39;fulfilled&#39;));<br>  });<br>}</pre><p><strong>The key insight:</strong> Not all data needs to be in the initial response. Split your dependencies into:</p><ol><li><strong>Blocking critical</strong> (must have)</li><li><strong>Blocking degradable</strong> (nice to have in response, handle failures)</li><li><strong>Non-blocking</strong> (can arrive later via WebSocket/polling)</li></ol><blockquote>This pattern <strong>reduced</strong> our <strong>error rate from 15% to &lt;1%</strong> during downstream instability.</blockquote><h3>Problem #2: Axios Timeout Doesn’t Actually Cancel Requests</h3><h3>The Misconception</h3><pre>await axios.get(url, { timeout: 1000 });</pre><p><strong>What developers think:</strong> “Request will be cancelled after 1 second”</p><p><strong>What actually happens:</strong></p><ul><li>Promise rejects after 1 second ✓</li><li>Socket connection stays open ✗</li><li>Memory isn’t freed ✗</li><li>Database connection on the downstream service keeps running ✗</li></ul><p>Under load, this causes:</p><ul><li>Socket pool exhaustion</li><li>Memory leaks</li><li>Cascading failures on downstream services</li><li>“Connection reset by peer” errors</li></ul><h4>The Actual Solution: AbortController + Proper Cleanup</h4><pre>class ServiceClient {<br>  constructor(baseURL, defaultTimeout = 1000) {<br>    this.axios = axios.create({<br>      baseURL,<br>      httpAgent: new http.Agent({<br>        keepAlive: true,<br>        keepAliveMsecs: 30000,<br>        maxSockets: 50,<br>        maxFreeSockets: 10,<br>        timeout: 60000  // Socket timeout (different from request timeout)<br>      })<br>    });<br>  }<br>  <br>  async request(url, options = {}) {<br>    const timeout = options.timeout || this.defaultTimeout;<br>    const controller = new AbortController();<br>    <br>    const timeoutId = setTimeout(() =&gt; {<br>      controller.abort();<br>      logger.warn({<br>        url,<br>        timeout,<br>        message: &#39;Request aborted due to timeout&#39;<br>      });<br>    }, timeout);<br>    <br>    try {<br>      const response = await this.axios.get(url, {<br>        ...options,<br>        signal: controller.signal,<br>        timeout: timeout  // Still set axios timeout as fallback<br>      });<br>      <br>      return response.data;<br>    } catch (error) {<br>      // Differentiate timeout vs other errors<br>      if (axios.isCancel(error) || error.code === &#39;ECONNABORTED&#39;) {<br>        throw new TimeoutError(`Request to ${url} timed out after ${timeout}ms`);<br>      }<br>      throw error;<br>    } finally {<br>      clearTimeout(timeoutId);<br>      // AbortController cleanup is automatic, but clearing timeout prevents memory leaks<br>    }<br>  }<br>}</pre><p><strong>Critical details that must be remembered:</strong></p><ol><li><strong>httpAgent.timeout vs axios.timeout:</strong></li></ol><ul><li>httpAgent.timeout: How long a socket can be idle before closure</li><li>axios.timeout: Maximum time for the entire request</li><li>You need both for different failure modes</li></ul><p><strong>2. AbortSignal actually cancels the underlying request:</strong></p><ul><li>Closes the socket immediately</li><li>Frees memory</li><li>Stops waiting for response</li></ul><p><strong>3. Connection pooling matters:</strong></p><ul><li>maxSockets: Limit concurrent connections per host</li><li>keepAlive: Reuse TCP connections (massive perf win)</li><li>Without pooling, you’ll hit EMFILE (too many open files) errors</li></ul><p>This reduced our socket exhaustion incidents from daily to never.</p><h3>Problem #3: The Event Loop Lag You Can’t See</h3><h4>The Silent Killer</h4><p>Your BFF is serving requests fine. Latency looks good in metrics. Then suddenly during peak traffic:</p><ul><li>Timeouts spike</li><li>Requests queue up</li><li>Nothing appears to be CPU-bound in profiling</li></ul><p><strong>The culprit: Event loop blocking from unexpected sources</strong></p><p>Common blockers we found:</p><pre>// 1. Synchronous JSON parsing of large payloads<br>app.post(&#39;/upload&#39;, (req, res) =&gt; {<br>  const data = JSON.parse(req.body);  // Blocks if body is 10MB+<br>});</pre><pre>// 2. Large object stringification in logging<br>logger.info(&#39;Response:&#39;, JSON.stringify(hugeObject));  // Blocks event loop</pre><pre>// 3. Regex on untrusted input<br>const matches = userInput.match(/complex.*regex/gi);  // Can be O(2^n)</pre><pre>// 4. Synchronous crypto operations<br>const hash = crypto.createHash(&#39;sha256&#39;).update(data).digest(&#39;hex&#39;);  // Blocks</pre><h4>The Solution: Measure and Offload</h4><p><strong>First, make event loop lag visible:</strong></p><pre>const { performance } = require(&#39;perf_hooks&#39;);</pre><pre>class EventLoopMonitor {<br>  constructor(threshold = 50) {<br>    this.threshold = threshold;<br>    this.lastCheck = performance.now();<br>    <br>    setInterval(() =&gt; {<br>      const now = performance.now();<br>      const lag = now - this.lastCheck - 100;  // Expected interval is 100ms<br>      <br>      if (lag &gt; this.threshold) {<br>        logger.warn({<br>          eventLoopLag: Math.round(lag),<br>          message: &#39;Event loop blocked&#39;<br>        });<br>      }<br>      <br>      this.lastCheck = now;<br>    }, 100);<br>  }<br>}</pre><pre>// Start monitoring<br>new EventLoopMonitor(50);</pre><p><strong>Then offload blocking operations:</strong></p><pre>const { Worker } = require(&#39;worker_threads&#39;);</pre><pre>// worker.js<br>const { parentPort } = require(&#39;worker_threads&#39;);</pre><pre>parentPort.on(&#39;message&#39;, ({ action, data }) =&gt; {<br>  switch(action) {<br>    case &#39;parse-json&#39;:<br>      try {<br>        const parsed = JSON.parse(data);<br>        parentPort.postMessage({ success: true, result: parsed });<br>      } catch (error) {<br>        parentPort.postMessage({ success: false, error: error.message });<br>      }<br>      break;<br>      <br>    case &#39;hash-data&#39;:<br>      const hash = crypto.createHash(&#39;sha256&#39;).update(data).digest(&#39;hex&#39;);<br>      parentPort.postMessage({ success: true, result: hash });<br>      break;<br>  }<br>});</pre><pre>// main.js<br>class WorkerPool {<br>  constructor(workerPath, size = 4) {<br>    this.workers = Array.from({ length: size }, () =&gt; ({<br>      worker: new Worker(workerPath),<br>      busy: false<br>    }));<br>    this.queue = [];<br>  }<br>  <br>  async execute(action, data, timeout = 5000) {<br>    return new Promise((resolve, reject) =&gt; {<br>      const worker = this.workers.find(w =&gt; !w.busy);<br>      <br>      if (!worker) {<br>        // Queue if all workers busy<br>        this.queue.push({ action, data, resolve, reject });<br>        return;<br>      }<br>      <br>      worker.busy = true;<br>      <br>      const timeoutId = setTimeout(() =&gt; {<br>        worker.worker.terminate();  // Kill stuck worker<br>        worker.worker = new Worker(workerPath);  // Spawn new one<br>        worker.busy = false;<br>        reject(new Error(&#39;Worker timeout&#39;));<br>        this.processQueue();<br>      }, timeout);<br>      <br>      worker.worker.once(&#39;message&#39;, (result) =&gt; {<br>        clearTimeout(timeoutId);<br>        worker.busy = false;<br>        <br>        if (result.success) {<br>          resolve(result.result);<br>        } else {<br>          reject(new Error(result.error));<br>        }<br>        <br>        this.processQueue();<br>      });<br>      <br>      worker.worker.postMessage({ action, data });<br>    });<br>  }<br>  <br>  processQueue() {<br>    if (this.queue.length === 0) return;<br>    <br>    const worker = this.workers.find(w =&gt; !w.busy);<br>    if (worker) {<br>      const task = this.queue.shift();<br>      this.execute(task.action, task.data)<br>        .then(task.resolve)<br>        .catch(task.reject);<br>    }<br>  }<br>}</pre><pre>// Usage<br>const workerPool = new WorkerPool(&#39;./worker.js&#39;);</pre><pre>app.post(&#39;/upload&#39;, async (req, res) =&gt; {<br>  try {<br>    const parsed = await workerPool.execute(&#39;parse-json&#39;, req.body);<br>    res.json({ success: true, data: parsed });<br>  } catch (error) {<br>    res.status(400).json({ error: error.message });<br>  }<br>});</pre><p><strong>When to use worker threads (our rule of thumb):</strong></p><ul><li>JSON parsing/stringifying of objects &gt;100KB</li><li>Crypto operations on data &gt;10KB</li><li>Any synchronous operation taking &gt;10ms in profiling</li><li>Regex on untrusted user input</li></ul><p><strong>This reduced our P99 event loop lag from 300ms to &lt;5ms.</strong></p><blockquote>You may use some 3rd party libs for managing the worker thread if its adding overhead to your application eg: <a href="https://www.npmjs.com/package/piscina">Piscina</a>, <a href="https://www.npmjs.com/package/workerpool">Workerpool</a></blockquote><h3>Problem #4: Cache Stampede During Downstream Outages</h3><h4>The Scenario</h4><p>Your cache has a 5-minute TTL. Downstream service goes down. What happens?</p><pre>async function getData(key) {<br>  const cached = await cache.get(key);<br>  if (cached) return cached;<br>  <br>  const fresh = await downstreamService.fetch(key);  // This fails<br>  await cache.set(key, fresh, 300);<br>  return fresh;<br>}</pre><p><strong>At scale:</strong></p><ol><li>Cache expires for popular key</li><li>100 requests try to fetch simultaneously</li><li>All 100 requests hit the failing downstream</li><li>Downstream gets overwhelmed further</li><li>None of the requests cache the result (because fetch failed)</li><li>Next 100 requests repeat the cycle</li></ol><p><strong>This is a cache stampede amplified by downstream failure.</strong></p><h4>The Solution: Request Coalescing + Stale-While-Revalidate</h4><pre>class ResilientCache {<br>  constructor(redisClient) {<br>    this.cache = redisClient;<br>    this.inflightRequests = new Map();<br>  }<br>  <br>  async get(key, fetchFn, options = {}) {<br>    const { ttl = 300, staleWhileRevalidate = 600 } = options;<br>    <br>    // 1. Check cache<br>    const cached = await this.cache.get(key);<br>    <br>    if (cached) {<br>      const { data, cachedAt, staleAt } = JSON.parse(cached);<br>      const now = Date.now();<br>      <br>      // Fresh data - return immediately<br>      if (now &lt; staleAt) {<br>        return data;<br>      }<br>      <br>      // Stale but within grace period - return stale, refresh in background<br>      if (now &lt; cachedAt + staleWhileRevalidate * 1000) {<br>        // Fire and forget background refresh<br>        this.backgroundRefresh(key, fetchFn, ttl, staleWhileRevalidate)<br>          .catch(err =&gt; logger.warn({ key, error: err.message }));<br>        <br>        return data;  // Return stale data immediately<br>      }<br>    }<br>    <br>    // 2. Check if another request is already fetching this key<br>    if (this.inflightRequests.has(key)) {<br>      return this.inflightRequests.get(key);<br>    }<br>    <br>    // 3. Fetch fresh data (only one request does this)<br>    const fetchPromise = this.fetchAndCache(key, fetchFn, ttl, staleWhileRevalidate);<br>    this.inflightRequests.set(key, fetchPromise);<br>    <br>    try {<br>      const result = await fetchPromise;<br>      return result;<br>    } finally {<br>      this.inflightRequests.delete(key);<br>    }<br>  }<br>  <br>  async fetchAndCache(key, fetchFn, ttl, staleWhileRevalidate) {<br>    try {<br>      const data = await fetchFn();<br>      const now = Date.now();<br>      <br>      await this.cache.set(<br>        key,<br>        JSON.stringify({<br>          data,<br>          cachedAt: now,<br>          staleAt: now + ttl * 1000<br>        }),<br>        &#39;EX&#39;,<br>        ttl + staleWhileRevalidate<br>      );<br>      <br>      return data;<br>    } catch (error) {<br>      // On fetch failure, try to return stale data even if expired<br>      // Note: For repeatedly failing services, consider adding a circuit breaker<br>      // to stop retry attempts temporarily and fail fast<br>      const stale = await this.cache.get(key);<br>      <br>      if (stale) {<br>        const { data } = JSON.parse(stale);<br>        logger.warn({<br>          key,<br>          message: &#39;Returning stale data due to fetch failure&#39;,<br>          error: error.message<br>        });<br>        return data;<br>      }<br>      <br>      throw error;<br>    }<br>  }<br>  <br>  async backgroundRefresh(key, fetchFn, ttl, staleWhileRevalidate) {<br>    try {<br>      await this.fetchAndCache(key, fetchFn, ttl, staleWhileRevalidate);<br>    } catch (error) {<br>      // Silently fail background refreshes<br>      logger.debug({ key, message: &#39;Background refresh failed&#39; });<br>    }<br>  }<br>}</pre><pre>// Usage<br>const cache = new ResilientCache(redisClient);</pre><pre>async function getUserData(userId) {<br>  return cache.get(<br>    `user:${userId}`,<br>    () =&gt; userService.fetch(userId),<br>    { <br>      ttl: 300,              // Fresh for 5 minutes<br>      staleWhileRevalidate: 600  // Serve stale for up to 10 more minutes<br>    }<br>  );<br>}</pre><p><strong>Why this works:</strong></p><ol><li><strong>Request coalescing:</strong> Only one request fetches data for a key, others wait for that result</li><li><strong>Stale-while-revalidate:</strong> During outages, serve stale data instead of failing</li><li><strong>Background refresh:</strong> Users get instant responses with stale data while fresh data loads</li><li><strong>Graceful degradation:</strong> System stays up even when downstream is completely down</li></ol><blockquote>This pattern <strong>eliminated our cache stampede </strong>incidents entirely and kept <strong>availability &gt;99.9% </strong>even during downstream outages.</blockquote><h3>Problem #5: Memory Leaks from Forgotten Cleanup</h3><h4>The Subtle Leak</h4><pre>async function processWithTimeout(fn, timeout) {<br>  return Promise.race([<br>    fn(),<br>    new Promise((_, reject) =&gt; <br>      setTimeout(() =&gt; reject(new Error(&#39;Timeout&#39;)), timeout)<br>    )<br>  ]);<br>}</pre><p><strong>This leaks memory.</strong> If fn() resolves after timeout, the setTimeout timer still exists. Over thousands of requests, you accumulate thousands of timers.</p><h4>The Proper Pattern</h4><pre>async function processWithTimeout(fn, timeout) {<br>  let timeoutId;<br>  <br>  const timeoutPromise = new Promise((_, reject) =&gt; {<br>    timeoutId = setTimeout(() =&gt; reject(new Error(&#39;Timeout&#39;)), timeout);<br>  });<br>  <br>  try {<br>    return await Promise.race([fn(), timeoutPromise]);<br>  } finally {<br>    clearTimeout(timeoutId);  // Always cleanup<br>  }<br>}</pre><p><strong>Other common leak sources:</strong></p><pre>// Event listeners that aren&#39;t removed<br>class RequestHandler {<br>  constructor() {<br>    this.controller = new AbortController();<br>    <br>    // BAD: Listener never removed<br>    this.controller.signal.addEventListener(&#39;abort&#39;, () =&gt; {<br>      this.cleanup();<br>    });<br>  }<br>}</pre><pre>// GOOD: Remove listeners<br>class RequestHandler {<br>  constructor() {<br>    this.controller = new AbortController();<br>    this.abortHandler = () =&gt; this.cleanup();<br>    this.controller.signal.addEventListener(&#39;abort&#39;, this.abortHandler);<br>  }<br>  <br>  destroy() {<br>    this.controller.signal.removeEventListener(&#39;abort&#39;, this.abortHandler);<br>  }<br>}</pre><pre>// Axios interceptors that accumulate<br>// BAD: Adds a new interceptor on every request<br>app.use((req, res, next) =&gt; {<br>  axios.interceptors.request.use(config =&gt; {<br>    config.headers[&#39;X-Request-ID&#39;] = req.id;<br>    return config;<br>  });<br>  next();<br>});</pre><pre>// GOOD: Add interceptor once at startup<br>const requestInterceptor = axios.interceptors.request.use(config =&gt; {<br>  config.headers[&#39;X-Request-ID&#39;] = asyncLocalStorage.getStore()?.requestId;<br>  return config;<br>});</pre><p>We found these leaks by running heap snapshots at 1-hour intervals and comparing:</p><pre>// Memory profiling in staging<br>if (process.env.NODE_ENV === &#39;staging&#39;) {<br>  const v8 = require(&#39;v8&#39;);<br>  const fs = require(&#39;fs&#39;);<br>  <br>  setInterval(() =&gt; {<br>    const snapshot = v8.writeHeapSnapshot();<br>    logger.info({ heapSnapshot: snapshot });<br>  }, 3600000);  // Every hour<br>}</pre><h3>Problem #6: Logging at Scale Kills Performance</h3><h4>The Innocent Looking Code</h4><pre>logger.info(&#39;Request received&#39;, {<br>  userId: req.user.id,<br>  body: req.body,<br>  timestamp: new Date().toISOString()<br>});</pre><p>At 1000 RPS:</p><ul><li>JSON.stringify(req.body) runs 1000 times/second</li><li>If body is 50KB, that’s 50MB/s of serialization</li><li>Blocks event loop 1–5ms per call</li><li>Cumulative blocking: 1–5 seconds of event loop time per second</li></ul><blockquote><strong>Your logs are causing the latency you’re logging about.</strong></blockquote><h4>The Solution: Sampling + Async Logging</h4><pre>class SmartLogger {<br>  constructor(options = {}) {<br>    this.sampleRate = options.sampleRate || 0.1;  // Log 10% of requests<br>    this.alwaysLogErrors = options.alwaysLogErrors !== false;<br>    this.queue = [];<br>    this.flushInterval = options.flushInterval || 1000;<br>    <br>    // Async flush to transport<br>    setInterval(() =&gt; this.flush(), this.flushInterval);<br>  }<br>  <br>  shouldSample() {<br>    return Math.random() &lt; this.sampleRate;<br>  }<br>  <br>  info(message, meta = {}) {<br>    // Sample normal requests<br>    if (!this.shouldSample()) return;<br>    <br>    this.queue.push({<br>      level: &#39;info&#39;,<br>      message,<br>      ...this.sanitizeMeta(meta),<br>      timestamp: Date.now()<br>    });<br>  }<br>  <br>  error(message, meta = {}) {<br>    // Always log errors<br>    this.queue.push({<br>      level: &#39;error&#39;,<br>      message,<br>      ...this.sanitizeMeta(meta),<br>      timestamp: Date.now()<br>    });<br>  }<br>  <br>  sanitizeMeta(meta) {<br>    // Don&#39;t log large objects - log their size instead<br>    return Object.entries(meta).reduce((acc, [key, value]) =&gt; {<br>      if (typeof value === &#39;object&#39; &amp;&amp; value !== null) {<br>        const size = Buffer.byteLength(JSON.stringify(value));<br>        acc[`${key}Size`] = size;<br>        <br>        // Only include full object if small<br>        if (size &lt; 1024) {<br>          acc[key] = value;<br>        }<br>      } else {<br>        acc[key] = value;<br>      }<br>      return acc;<br>    }, {});<br>  }<br>  <br>  async flush() {<br>    if (this.queue.length === 0) return;<br>    <br>    const batch = this.queue.splice(0, this.queue.length);<br>    <br>    // Async write - doesn&#39;t block event loop<br>    setImmediate(() =&gt; {<br>      // Send to logging service<br>      this.transport.write(batch).catch(err =&gt; {<br>        console.error(&#39;Failed to flush logs:&#39;, err);<br>      });<br>    });<br>  }<br>}</pre><pre>// Usage with adaptive sampling<br>class AdaptiveLogger extends SmartLogger {<br>  constructor(options) {<br>    super(options);<br>    this.errorRate = 0;<br>    this.lastMinuteErrors = [];<br>  }<br>  <br>  error(message, meta) {<br>    super.error(message, meta);<br>    <br>    // Track error rate<br>    this.lastMinuteErrors.push(Date.now());<br>    this.lastMinuteErrors = this.lastMinuteErrors.filter(<br>      t =&gt; t &gt; Date.now() - 60000<br>    );<br>    <br>    this.errorRate = this.lastMinuteErrors.length / 60;<br>    <br>    // Increase sampling when error rate is high<br>    if (this.errorRate &gt; 10) {<br>      this.sampleRate = 1.0;  // Log everything<br>    } else if (this.errorRate &gt; 5) {<br>      this.sampleRate = 0.5;  // Log 50%<br>    } else {<br>      this.sampleRate = 0.1;  // Log 10%<br>    }<br>  }<br>}</pre><p>This reduced our logging overhead from 15% CPU to &lt;2% CPU while maintaining debuggability.</p><h3>The Complete BFF Request Pattern</h3><pre>const { AsyncLocalStorage } = require(&#39;async_hooks&#39;);<br>const asyncLocalStorage = new AsyncLocalStorage();</pre><pre>class BFFHandler {<br>  constructor() {<br>    this.cache = new ResilientCache(redis);<br>    this.logger = new AdaptiveLogger();<br>    this.workerPool = new WorkerPool(&#39;./worker.js&#39;);<br>  }<br>  <br>  async handleRequest(req, res) {<br>    const requestId = req.headers[&#39;x-request-id&#39;] || generateId();<br>    const startTime = Date.now();<br>    <br>    // Store request context for access anywhere<br>    return asyncLocalStorage.run({ requestId }, async () =&gt; {<br>      try {<br>        const userId = req.user.id;<br>        <br>        // Phase 1: Critical data (must succeed)<br>        const critical = await this.getCriticalData(userId);<br>        <br>        // Phase 2: Important data (degrade gracefully)<br>        const important = await this.getImportantData(userId);<br>        <br>        // Phase 3: Optional data (fire and forget)<br>        this.startOptionalEnrichments(userId, requestId);<br>        <br>        // Phase 4: Transform (offload if needed)<br>        const response = await this.transformResponse(critical, important);<br>        <br>        // Log success<br>        this.logger.info(&#39;request_completed&#39;, {<br>          userId,<br>          latency: Date.now() - startTime,<br>          path: req.path<br>        });<br>        <br>        res.json(response);<br>        <br>      } catch (error) {<br>        this.logger.error(&#39;request_failed&#39;, {<br>          error: error.message,<br>          stack: error.stack,<br>          userId: req.user?.id,<br>          latency: Date.now() - startTime<br>        });<br>        <br>        res.status(500).json({ error: &#39;Internal server error&#39; });<br>      }<br>    });<br>  }<br>  <br>  async getCriticalData(userId) {<br>    const timeout = 800;<br>    const controller = new AbortController();<br>    const timeoutId = setTimeout(() =&gt; controller.abort(), timeout);<br>    <br>    try {<br>      return await Promise.all([<br>        serviceClient.request(&#39;/auth&#39;, { userId, signal: controller.signal }),<br>        serviceClient.request(&#39;/profile&#39;, { userId, signal: controller.signal })<br>      ]);<br>    } finally {<br>      clearTimeout(timeoutId);<br>    }<br>  }<br>  <br>  async getImportantData(userId) {<br>    const results = await Promise.allSettled([<br>      this.cache.get(`orders:${userId}`, () =&gt; <br>        serviceClient.request(&#39;/orders&#39;, { userId }), <br>        { ttl: 60, staleWhileRevalidate: 300 }<br>      ),<br>      this.cache.get(`payment:${userId}`, () =&gt;<br>        serviceClient.request(&#39;/payment&#39;, { userId }),<br>        { ttl: 30, staleWhileRevalidate: 120 }<br>      )<br>    ]);<br>    <br>    return results.map(r =&gt; r.status === &#39;fulfilled&#39; ? r.value : null);<br>  }<br>  <br>  startOptionalEnrichments(userId, requestId) {<br>    Promise.allSettled([<br>      serviceClient.request(&#39;/recommendations&#39;, { userId }),<br>      serviceClient.request(&#39;/analytics&#39;, { userId })<br>    ]).then(results =&gt; {<br>      // Send via WebSocket or cache for next request<br>      this.notifyClient(requestId, results);<br>    });<br>  }<br>  <br>  async transformResponse(critical, important) {<br>    const combined = { ...critical, ...important };<br>    <br>    // Check if transformation is CPU-intensive<br>    const dataSize = Buffer.byteLength(JSON.stringify(combined));<br>    <br>    if (dataSize &gt; 100000) {<br>      // Offload to worker thread<br>      return this.workerPool.execute(&#39;transform&#39;, combined);<br>    }<br>    <br>    // Simple transformation on main thread<br>    return this.transform(combined);<br>  }<br>}</pre><h3>Key Takeaways</h3><ol><li><strong>Promise.all() vs Promise.allSettled() vs fire-and-forget</strong> — Know when to use each based on criticality</li><li><strong>Timeouts need AbortController</strong> — Otherwise you’re not actually cancelling requests</li><li><strong>Event loop lag is invisible</strong> — Instrument it explicitly or you’ll never know what’s blocking</li><li><strong>Cache stampedes amplify outages</strong> — Use request coalescing + stale-while-revalidate</li><li><strong>Memory leaks are cumulative</strong> — Always cleanup timers, listeners, and interceptors</li><li><strong>Logging at scale requires sampling</strong> — Adaptive sampling based on error rate works well</li></ol><h3>Conclusion</h3><p>These aren’t beginner problems. They’re the issues that emerge when “basic” Node.js patterns meet production traffic.</p><p>The difference between a BFF that handles 100rps and one that handles 10,000rps isn’t some magical framework; it’s understanding these failure modes and designing around them.</p><p><strong>Most importantly: These patterns compound.</strong> Each one individually gives modest improvements. Together, they’re the difference between a system that falls over and one that stays up.</p><p><em>If this helped you pause, reflect, and look at your systems with a little more care, do give it a clap.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=995951d1c96e" width="1" height="1" alt=""><hr><p><a href="https://medium.com/cars24/building-resilient-node-js-bffs-at-scale-hard-earned-lessons-from-production-995951d1c96e">Building Resilient Node.js BFFs at Scale: Hard-Earned Lessons from Production</a> was originally published in <a href="https://medium.com/cars24">Cars24 Engineering Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Sharing Business Logic Across React and React Native Using Domain-Driven Design]]></title>
            <link>https://medium.com/cars24/sharing-business-logic-across-react-and-react-native-using-domain-driven-design-5e63bf960e3e?source=rss-e77024d7fd90------2</link>
            <guid isPermaLink="false">https://medium.com/p/5e63bf960e3e</guid>
            <category><![CDATA[react]]></category>
            <category><![CDATA[system-architecture]]></category>
            <category><![CDATA[design-systems]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[react-native]]></category>
            <dc:creator><![CDATA[Ankit Bhalla]]></dc:creator>
            <pubDate>Wed, 21 Jan 2026 08:57:49 GMT</pubDate>
            <atom:updated>2026-01-21T13:27:11.856Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*5yfhb5mq9TtWB6ek.png" /></figure><p>As the <strong>CARS24</strong> product scaled across web and mobile, a familiar problem emerged not performance, not tooling, but <strong>logic drift</strong>.</p><p>The same business rules were being re-implemented in React and React Native. Edge cases behaved slightly differently. Fixes landed on one platform and lagged on the other. Engineers spent more time coordinating correctness than delivering features.</p><p>This wasn’t a people problem. It was a <strong>structural problem</strong>.</p><p>React and React Native are excellent delivery layers. But once validation, orchestration, and decision-making logic starts living inside UI code, platforms slowly become owners of business correctness. Duplication becomes inevitable, and consistency becomes a coordination exercise.</p><p>This post describes how we moved business logic out of platforms entirely and what changed once the <strong>domain became the single source of truth</strong>.</p><h3>Architectural Goal</h3><p>The architectural goal was deliberately simple:</p><p><strong>Define business rules once. Execute them everywhere.</strong></p><p>Instead of asking “how do we keep web and mobile in sync?”, we asked a different question:</p><blockquote>What if platforms were no longer responsible for correctness at all?</blockquote><p>To achieve this, we introduced a shared, platform-agnostic domain layer that:</p><ul><li>Explicitly models business concepts</li><li>Exposes behavior through use cases</li><li>Is consumed uniformly by web and mobile</li></ul><p>Once in place, platform teams could move independently — without fear of drifting business behavior. <strong>Core Design Principle: Domain Ownership Over Platform Ownership</strong></p><p>Traditional frontend architectures often split responsibility like this:</p><pre>web/<br>mobile/</pre><p>In practice, this means:</p><ul><li>Validation logic lives inside components</li><li>Edge cases are handled locally</li><li>Business correctness is enforced socially, not structurally</li></ul><p>Over time, platforms stop being delivery layers and start becoming <strong>business rule engines</strong>.</p><p>We intentionally broke this model.</p><p>Instead, we introduced explicit domain ownership:</p><pre>shared-domain/<br>  domain/<br>  application/<br>  ports/</pre><p>In this structure:</p><ul><li>The <strong>domain owns correctness</strong></li><li>Platforms own rendering and interaction</li><li>Dependencies flow inward and never outward</li></ul><p>This single decision removed an entire category of coordination overhead.</p><h3>Domain Layer: Explicit Business Concepts</h3><p>Domain entities represent <strong>what the business is</strong>, not how it is displayed.</p><pre>export class Vehicle {<br>  constructor(private readonly props: VehicleProps) {<br>    if (!props.id) throw new Error(&#39;Vehicle id is required&#39;);<br>    if (!props.title) throw new Error(&#39;Vehicle title is required&#39;);<br>    Object.freeze(this.props);<br>  }<br>  get id() { return this.props.id; }<br>  get title() { return this.props.title; }<br>}</pre><h3>Why this matters once implemented</h3><ul><li>Certain classes of invalid states are prevented at construction time.</li><li>Business rules are enforced at creation boundaries</li><li>UI layers cannot accidentally corrupt domain data</li><li>The same conceptual model can be reused, even if implementations differ.</li></ul><p>Many <strong>mutation and state-corruption bugs are eliminated</strong>.</p><h3>Application Layer: Use Cases as the System API</h3><p>Use cases define <strong>what the system can do</strong>.</p><pre>export class GetVehicleDetailsUseCase {<br>  constructor(private repo: VehicleRepoPort) {}<br>  async execute(ctx: PlatformContextPort, input: Input) {<br>    if (!input.vehicleId) {<br>      throw new Error(&#39;vehicleId is required&#39;);<br>    }<br>    return {<br>      vehicle: await this.repo.getVehicleDetails(input.vehicleId, ctx),<br>    };<br>  }<br>}</pre><h3>What this gives you structurally</h3><ul><li>A clear, discoverable application API</li><li>No business logic inside UI components</li><li>Consistent business outcomes across platforms.</li><li>Testable logic without platform dependencies</li></ul><p>Once implemented, <strong>feature discussions shift from “where do we code this?” to “what is the correct use case?”</strong>.</p><h3>Ports: Stable Contracts at the Boundary</h3><p>Ports define <strong>what the application needs</strong>, not how it is fulfilled.</p><pre>export interface VehicleRepoPort {<br>  getVehicleDetails(id: string, ctx: PlatformContextPort): Promise&lt;Vehicle&gt;;<br>}</pre><p>Platform-specific adapters implement these contracts.</p><h3>Web Adapter</h3><pre>class VehicleRepoWeb implements VehicleRepoPort {<br>  async getVehicleDetails(id) {<br>    const res = await fetch(`/api/vehicles/${id}`);<br>    return new Vehicle(await res.json());<br>  }<br>}</pre><h3>Mobile Adapter</h3><pre>class VehicleRepoMobile implements VehicleRepoPort {<br>  async getVehicleDetails(id) {<br>    const cached = await AsyncStorage.getItem(id);<br>    if (cached) return new Vehicle(JSON.parse(cached));<br>    // fallback to API<br>  }<br>}</pre><h3>Why this works long-term</h3><ul><li>Infrastructure decisions remain local to platforms</li><li>Business logic never changes when transport changes</li><li>Offline, caching, and performance strategies evolve independently</li></ul><p>Once ports are in place, <strong>platform evolution stops leaking into business logic</strong>.</p><h3>Platform Context: Explicit and Controlled Variance</h3><p>When platform-specific behavior is genuinely required, we pass it explicitly.</p><pre>export interface PlatformContextPort {<br>  platform: &#39;web&#39; | &#39;ios&#39; | &#39;android&#39;;<br>  dimensions?: { width: number; height: number };<br>}</pre><h3>Structural benefits</h3><ul><li>Platform branching is intentional, not accidental</li><li>Use cases remain deterministic and reviewable</li><li>Platform-specific logic is isolated and auditable</li></ul><p>Most use cases never need this — when they do, the dependency is visible.</p><h3>Shared Logic Example: Filtering System</h3><p>We implemented complex filter logic entirely within the domain:</p><ul><li>Operator resolution</li><li>Query composition</li><li>Grouped filter interpretation</li></ul><p>Both platforms consume this logic and render it differently.</p><h3>What this guarantees</h3><ul><li>Identical filtering behavior across platforms</li><li>Single-point fixes for edge cases</li><li>UI teams focus purely on interaction and UX</li></ul><p>Once implemented, <strong>platform-specific filter bugs stop occurring by design</strong>.</p><h3>Consumption Model: Thin UI Layers</h3><p><strong>React</strong></p><pre>useCase.execute(ctx, { vehicleId }).then(setVehicle);</pre><p><strong>React Native</strong></p><pre>useCase.execute(ctx, { vehicleId }).then(setVehicle);</pre><h3>Why this scales</h3><ul><li>UI components remain declarative</li><li>State transitions are predictable</li><li>Business logic changes do not ripple through UI code</li></ul><p>The UI becomes a true presentation layer.</p><h3>Dependency Injection at the Boundary</h3><pre>export function createContainer(repo: VehicleRepoPort) {<br>  return {<br>    getVehicleDetails: new GetVehicleDetailsUseCase(repo),<br>  };<br>}</pre><p>Dependencies are resolved once, at app startup.</p><h3>Structural advantages</h3><ul><li>Easy test substitution</li><li>No hidden singletons</li><li>Explicit dependency graphs</li></ul><h3>Testing: Business Logic as a First-Class Citizen</h3><pre>class MockVehicleRepo implements VehicleRepoPort {<br>  async getVehicleDetails() {<br>    return new Vehicle({ id: &#39;1&#39;, title: &#39;Test Car&#39;, make: &#39;Toyota&#39;, model: &#39;Camry&#39;, year: 2023 });<br>  }<br>}</pre><h3>What this enables</h3><ul><li>Fast, deterministic tests</li><li>No network or platform mocking</li><li>High confidence in business rules</li></ul><p>Once implemented, <strong>testing shifts from integration-heavy to logic-first</strong>.</p><h3>What Teams Get Once This Is in Place</h3><p>This architecture guarantees:</p><ul><li><strong>Single source of truth</strong> for business rules</li><li><strong>Predictable cross-platform behavior</strong></li><li><strong>Lower cognitive load</strong> for engineers</li><li><strong>Safer refactors</strong> with localized impact</li><li><strong>Faster onboarding</strong> through clear boundaries</li></ul><p>Velocity improves not by pushing harder but by <strong>removing structural friction</strong>.</p><h3>Adoption Strategy</h3><p>This architecture is designed to be introduced:</p><ul><li>Incrementally</li><li>Feature-by-feature</li><li>Without pausing delivery</li></ul><p>Teams naturally adopt it once the benefits become apparent.</p><h3>Conclusion</h3><p>This domain-driven frontend architecture is not about abstraction for its own sake.<br> It is about <strong>clear ownership, explicit boundaries, and predictable behavior at scale</strong>.</p><p>By treating React and React Native as delivery layers and the domain as the source of truth, teams achieve:</p><ul><li>Alignment without constant coordination</li><li>Quality without duplication</li><li>Velocity without shortcuts</li></ul><p>Once adopted, this pattern quietly becomes foundational.</p><p>Not because it’s clever but because it removes friction engineers didn’t realise they were carrying.</p><p><em>If you believe business rules shouldn’t depend on UI frameworks, share this with your team and leave a 👏 to keep the conversation going.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5e63bf960e3e" width="1" height="1" alt=""><hr><p><a href="https://medium.com/cars24/sharing-business-logic-across-react-and-react-native-using-domain-driven-design-5e63bf960e3e">Sharing Business Logic Across React and React Native Using Domain-Driven Design</a> was originally published in <a href="https://medium.com/cars24">Cars24 Engineering Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Caching in ReactNative apps — Boon or Bane?]]></title>
            <link>https://medium.com/cars24/caching-in-reactnative-apps-boon-or-bane-ef4964048315?source=rss-e77024d7fd90------2</link>
            <guid isPermaLink="false">https://medium.com/p/ef4964048315</guid>
            <dc:creator><![CDATA[Ankit Bhalla]]></dc:creator>
            <pubDate>Mon, 03 Mar 2025 12:54:24 GMT</pubDate>
            <atom:updated>2025-03-03T12:55:26.405Z</atom:updated>
            <content:encoded><![CDATA[<h3>Caching in ReactNative apps — Boon or Bane?</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6JbpPgq0avfgaP-vKG856w.png" /></figure><p><strong>Nobody likes a sluggish app.</strong> Users expect <strong>instant</strong> responses — whether they’re scrolling, tapping, or buying their next dream car. A slow app? That’s a one-way ticket to frustration and the uninstall button. But there’s a secret weapon to keep your app <strong>fast, efficient, and buttery smooth</strong>: <strong>Caching!</strong></p><blockquote>55% of the users will blame the mobile app for the performance issues, no matter the actual cause; while 37% of the users who experience a mobile app that crashed or froze will think less of the company that made the app</blockquote><h3>What are the key challenges for app performance</h3><p>Not all users have access to high-speed WiFi or unlimited 5G. Many depend on unstable connections and data plans with strict limits <em>(specially in developing countries)</em>. Additionally, mobile devices have limited processing power, making every optimisation crucial. Here’s what you’re dealing with</p><ul><li><strong>Network Latency: </strong>Fetching data from servers takes time, especially on slow networks. In turn making the application slow and thus impacting user experience.</li><li><strong>Battery Drain: </strong>Excessive API calls and processing can be a battery killer.</li><li><strong>Data Costs</strong>: A data-hungry app might send users running for cheaper alternatives. <em>You certainly do not want that!</em></li><li><strong>App Freezes &amp; Crashes</strong>: Poor performance equals bad user experience (and bad reviews!).</li></ul><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Ftenor.com%2Fembed%2F22611421&amp;display_name=Tenor&amp;url=https%3A%2F%2Ftenor.com%2Fview%2Fjosh-ruben-collegehumor-dropout-game-changer-explosion-gif-22611421&amp;image=https%3A%2F%2Fmedia.tenor.com%2FKnPIDM4MDvkAAAAN%2Fjosh-ruben-collegehumor.png&amp;type=text%2Fhtml&amp;schema=tenor" width="600" height="400" frameborder="0" scrolling="no"><a href="https://medium.com/media/8a1015c537d5f212bbca740d0b356d26/href">https://medium.com/media/8a1015c537d5f212bbca740d0b356d26/href</a></iframe><h3>Caching to the rescue</h3><h4>What is Caching?</h4><p>Imagine you’re ordering a pizza from your favourite restaurant. The first time you call, you provide your address, favourite toppings, and crust preference. It takes <strong>30 minutes</strong> to get your pizza. Now, what if you call again tomorrow and order the same pizza? Instead of asking for all your details again, the restaurant <strong>remembers</strong> (caches) your information, making the process faster. This is <strong>caching</strong> — storing frequently used data so that it can be retrieved quickly without reprocessing the request.</p><h3>Why Caching is a Game-changer</h3><ul><li><strong>Lower data consumption: </strong>Caching allows apps to function better in lower network or unstable network conditions by serving locally stored data instead of making repeated API calls.</li><li><strong>Faster loading time &amp; Smoother, more responsive UI: </strong>Apps that load instantly and provide smooth navigation retain users better. Studies show that a 1-second delay in load time can lead to a 7% drop in conversions <em>[A metric that is used for web but we can assume the same here as user responsiveness is similar]</em>.</li><li><strong>Boosts battery efficiency:</strong> Frequent API requests and image loads can drain a device’s battery quickly. Caching reduces network calls and CPU usage, leading to a more energy-efficient application.</li></ul><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Ftenor.com%2Fembed%2F27229075&amp;display_name=Tenor&amp;url=https%3A%2F%2Ftenor.com%2Fview%2Fgame-changer-dropout-tv-josh-ruben-yes-nailed-it-gif-27229075&amp;image=https%3A%2F%2Fmedia.tenor.com%2FUf7X7Fqm-NQAAAAN%2Fgame-changer-dropout-tv.png&amp;type=text%2Fhtml&amp;schema=tenor" width="600" height="400" frameborder="0" scrolling="no"><a href="https://medium.com/media/11895d8d17609cf20c32ed04336d44ac/href">https://medium.com/media/11895d8d17609cf20c32ed04336d44ac/href</a></iframe><p>Determining what to cache depends on your app’s needs. There is no universal answer; it must be tailored to your specific use case. If your app is image-heavy, prioritising an image caching strategy is crucial. On the other hand, if your app relies on multiple API calls at launch (5 to 10 or more), you should strongly consider caching API responses using libraries like react-query or WatermelonDB. These tools ensure efficient data handling, reducing load times and enhancing user experience while avoiding stale data issues. Touching point on few strategies that I commonly use as a practice.</p><h4>1. Caching Images</h4><p>Images are one of the most commonly cached assets in apps. Imagine you’re browsing a car showroom’s online catalog. If you had to download high-quality car images every time you opened the app, it would be slow and waste internet data. Instead, your phone caches (saves) these images so that the next time you visit, they load instantly.</p><p>Using an efficient caching library like react-native-fast-image ensures that car images load instantly without repeated downloads, giving you a smooth user experience.</p><pre>// ForDevelopers<br>import FastImage from &#39;react-native-fast-image&#39;;<br><br>const CachedImage = ({ uri }) =&gt; {<br>  return (<br>    &lt;FastImage<br>      style={{ width: 100, height: 100 }}<br>      source={{ uri }}<br>      resizeMode={FastImage.resizeMode.cover}<br>    /&gt;<br>  );<br>};</pre><p>FastImage provides different caching modes such as immutable, web, and cacheOnly, allowing developers to optimise caching behavior based on their needs. Choosing the right cache control ensures minimal redundant network requests while keeping images up-to-date.<br>In conjunction with this, invest in a good CDN that dynamically serves optimised images tailored to the device’s resolution and screen density, reducing load times and bandwidth usage eg: Akamai, CacheFly, cloudflare, fastly etc.</p><h4>2. Caching API Responses</h4><p>Imagine a pizza delivery app where the server acts like an overly enthusiastic waiter who keeps running to the kitchen every five seconds yelling, “Any new orders?!” The chefs are overwhelmed, orders are delayed, and chaos ensues. Now, introduce caching — it’s like giving the waiter a notepad where they jot down the last known orders, so they can just glance at it instead of sprinting back and forth. Meanwhile, in the background, the app quietly checks for updates without disturbing the already stressed-out digital kitchen.</p><p>Latest libs like react-query make this process effortless, ensuring fast load times and keeping the users (and the kitchen) happy.</p><pre>// ForDevelopers<br>import { QueryClient, QueryClientProvider, useQuery } from &#39;react-query&#39;;<br>import axios from &#39;axios&#39;;<br><br>const queryClient = new QueryClient();<br>const fetchPosts = async () =&gt; {<br>  const { data } = await axios.get(&#39;https://jsonplaceholder.typicode.com/posts&#39;);<br>  return data;<br>};<br>const Posts = () =&gt; {<br>  const { data, error, isLoading } = useQuery(&#39;posts&#39;, fetchPosts, {<br>    staleTime: 1000 * 60 * 5, // Cache API data for 5 minutes<br>    cacheTime: 1000 * 60 * 10, // Data stays in cache for 10 minutes<br>  });<br>  if (isLoading) return &lt;Text&gt;Loading...&lt;/Text&gt;;<br>  if (error) return &lt;Text&gt;Error fetching posts&lt;/Text&gt;;<br>  return data.map(post =&gt; &lt;Text key={post.id}&gt;{post.title}&lt;/Text&gt;);<br>};<br>const App = () =&gt; (<br>  &lt;QueryClientProvider client={queryClient}&gt;<br>    &lt;Posts /&gt;<br>  &lt;/QueryClientProvider&gt;<br>);</pre><p>You can also try out SWR(Stale-While-Revalidate) which is a data-fetching library that provides a simple and efficient caching mechanism. It follows a strategy where it first returns cached (stale) data, then fetches the latest data in the background (revalidate), and finally updates the UI when fresh data arrives.</p><pre>// ForDevelopers<br>import useSWR, { mutate } from &#39;swr&#39;;<br><br>const { data, error } = useSWR(&#39;https://api.example.com/cars&#39;, fetcher, {<br>  refreshInterval: 5000, // Fetch new data every 5 seconds<br>});<br><br>// Update cache without re-fetching<br>mutate(&#39;https://api.example.com/cars&#39;, [...data, newCar], false);<br><br>// Offline first approach - Show last available data if offline<br>const { data, error } = useSWR(&#39;https://api.example.com/cars&#39;, fetcher, {<br>  fallbackData: cachedData, <br>});</pre><h4>3. Caching Database Queries</h4><p>React Native apps using local databases (e.g., SQLite, WatermelonDB, or MMKV) can optimize query performance by leveraging in-memory caching, indexing, and background synchronisation. WatermelonDB, for example, ensures offline-first functionality with real-time sync capabilities, while MMKV provides ultra-fast key-value storage for frequently accessed data, enhancing app responsiveness.</p><pre>// ForDevelopers<br>import { Database } from &#39;@nozbe/watermelondb&#39;;<br>import SQLiteAdapter from &#39;@nozbe/watermelondb/adapters/sqlite&#39;;<br>import schema from &#39;./model/schema&#39;;<br>import Post from &#39;./model/Post&#39;;<br><br>const adapter = new SQLiteAdapter({ schema });<br>const database = new Database({ adapter, modelClasses: [Post] });<br><br>const fetchPosts = async () =&gt; {<br>  const posts = await database.get(&#39;posts&#39;).query().fetch();<br>  return posts;<br>};</pre><pre>// ForDevelopers<br>import SQLite from &#39;react-native-sqlite-storage&#39;;<br><br>const db = SQLite.openDatabase({ name: &#39;mydb.db&#39;, location: &#39;default&#39; });<br><br>const getCachedData = (query, params = []) =&gt; {<br>  return new Promise((resolve, reject) =&gt; {<br>    db.transaction((tx) =&gt; {<br>      tx.executeSql(query, params, (_, results) =&gt; resolve(results.rows.raw()), reject);<br>    });<br>  });<br>};</pre><h4>4. Caching UI Elements</h4><p>Dynamic UI components such as lists and grids should use virtualisation techniques to minimise rendering overhead. React Native provides hooks like useMemo useCallback and React.memo which assists in setting up caching of UI.</p><pre>// ForDevelopers<br>const renderItem = useCallback(({item}) =&gt; (<br>   &lt;View key={item.key}&gt;<br>      &lt;Text&gt;{item.title}&lt;/Text&gt;<br>   &lt;/View&gt;<br> ), []);<br><br>return (<br>  &lt;FlatList data={items} renderItem={renderItem} /&gt;;<br>);</pre><pre>// ForDevelopers<br>import React, {memo} from &#39;react&#39;;<br>import {View, Text} from &#39;react-native&#39;;<br><br>const MyListItem = memo(<br>  ({title}: {title: string}) =&gt; (<br>    &lt;View&gt;<br>      &lt;Text&gt;{title}&lt;/Text&gt;<br>    &lt;/View&gt;<br>  ),<br>  (prevProps, nextProps) =&gt; {<br>    return prevProps.title === nextProps.title;<br>  },<br>);<br><br>export default MyListItem;</pre><h4>Best Practices for Effective Caching</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LICY7KjpMWx52TfAtrRAQg.png" /><figcaption>Something that worked for me in these past years</figcaption></figure><ol><li><strong>Define a Caching Strategy</strong> — Identify critical resources and define clear expiration rules. [<strong><em>Some samples shared above]</em></strong></li><li><strong>Use Appropriate Storage Mechanisms</strong> — Choose between in-memory caching, local databases, or disk storage based on data type and usage</li><li><strong>Implement Cache Invalidation</strong> — Regularly refresh stale data using proper eviction policies such as LRU (Least Recently Used)</li><li><strong>Monitor </strong>— Use analytics to track cache hit/miss ratios and adjust cache policies accordingly. You can use tools like Sentry / bugsnag / rollbar / embrace etc.</li><li><strong>Ensure Security</strong> — Avoid caching sensitive user data or encrypt cache storage to prevent leaks. Use keychain / keystore / EncryptedSharedPreferences to store sensitive data.</li></ol><h3>Conclusion</h3><p>Caching is both a boon and a bane. When implemented wisely, it can be a game-changer, significantly enhancing app performance and user experience. However, if mismanaged, it can lead to outdated data, memory bloat, and unexpected bugs, ultimately degrading the app’s reliability.</p><p>By following best practices and continuously optimising caching mechanisms, you can strike the right balance between performance and reliability, delivering a seamless experience to users.</p><p>So, is caching a boon or a bane? The answer lies in how you use it!</p><p><strong><em>If you like this please subscribe for more such content. Happy Coding!</em></strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ef4964048315" width="1" height="1" alt=""><hr><p><a href="https://medium.com/cars24/caching-in-reactnative-apps-boon-or-bane-ef4964048315">Caching in ReactNative apps — Boon or Bane?</a> was originally published in <a href="https://medium.com/cars24">Cars24 Engineering Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[React Native Vs. Native Development]]></title>
            <link>https://medium.com/@ankit.devninja/react-native-vs-native-development-7667b132822e?source=rss-e77024d7fd90------2</link>
            <guid isPermaLink="false">https://medium.com/p/7667b132822e</guid>
            <category><![CDATA[mobile-app-development]]></category>
            <category><![CDATA[startup]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[react-native]]></category>
            <dc:creator><![CDATA[Ankit Bhalla]]></dc:creator>
            <pubDate>Tue, 05 Oct 2021 04:47:40 GMT</pubDate>
            <atom:updated>2021-10-05T04:47:40.384Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_bTWD5bPMedtHtk1NnYJgA.jpeg" /></figure><p><strong>What is React Native?</strong></p><p>A framework developed and promoted by Facebook contains all the goods of web and React and bundles it for cross-platform mobile and web application development.</p><p><strong>How does it affect your application development?</strong></p><p>Unlike Native Development, where you write code individually for iOS (swift/Obj C) and Android (Kotlin/Java), RN gets powered by JS and uses a single code base to render the UI. In addition to this, it also gives you the discretion to write code on the native side (iOS and Android) if need be.</p><p><strong>BENEFITS</strong></p><ol><li><strong>Faster time to market and high execution speed: </strong>Building applications with a single code base gives you more power to develop features in a centralized place. So launching to market is done faster.</li><li><strong>Fewer resources required for the same amount of work: </strong>It cut short the dependencies of having multiple teams doing redundant development for both platforms.</li><li><strong>Mingle with website codebase: </strong>You get the power to reuse the code written in a React Website. It helps you to reduce the time of development and easy rollout of sprints.</li><li><strong>Growing community: </strong>The day by day increasing RN community gives us a cutting edge over other platforms’ application development tools. The latest updates in RN have increased the build creation speed and added better debugging tools.</li><li><strong>Power of Native is still there: </strong>In need of an hour, you can use the native side of mobile development. For some heavy lifting operations, it does come in handy.</li><li><strong>On the fly changes: </strong>The most powerful tool that comes along with RN is appcenter code push that enables you to push your bundle/changes live without deploying your application again and going through that exhausting review process, primarily from Apple.</li></ol><p>Also, ReactNative now provides ReactNative web, and by switching to RN web, we can use the same codebase to create websites, thus gives us more freedom to explore web options and be fast in execution if our goal is to build web apps. I will not stress it as this blog is more in comparison with Native app development.</p><p>It is hard to coin everything or every aspect of the actual requirement at hand. Selecting the right tech stack is always a challenge; mobile developers or managers have the above-stated points as their primary objective while picking up any tech stack.</p><p><strong>Why go with React Native</strong></p><ol><li>Faster execution and time to market</li><li>Fewer resources at the disposal</li><li>Have the same scope defining for both platforms (iOS and Android)</li><li>Want to have more focus on more immediate maintenance</li></ol><p><strong>Why go with Native Application</strong></p><ol><li>When you already have a team of experts in both areas (iOS and Android). More resources in hand.</li><li>Have time in hand to execute things</li><li>Willing to maintain multiple code base for the exact scope of work</li><li>Want to perform some heavy lifting jobs like running AR/VR induced screens or rendering 3D spaces in the app.</li></ol><p><strong>CONCLUSION:</strong></p><p>With growing technologies, companies have started following the mobile-first approach, and everyone is aiming to launch apps first to market. I feel that it’s inefficient to maintain three different code bases of the web, iOS, and Android with this intent in mind. With this growing tech, we need better solutions to handle this in one centralized place and give the same or better results. React Native is one such approach that can surely provide an edge in terms of <strong>productivity</strong> (faster time to market as execution speed increases with one centralized codebase), <strong>quality</strong> (maintenance of the code becomes easy), and <strong>scope</strong> (not to worry about redundant development in multiple platforms)</p><p><em>If you like this article please don’t forget to press on </em><strong><em>👏.</em></strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7667b132822e" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>