<?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>Forem: Shubham Gupta</title>
    <description>The latest articles on Forem by Shubham Gupta (@shubham_gupta_decf96a6ab2).</description>
    <link>https://forem.com/shubham_gupta_decf96a6ab2</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3726800%2F57cb3ab9-8aa2-4b48-8fd1-27c81332630d.jpg</url>
      <title>Forem: Shubham Gupta</title>
      <link>https://forem.com/shubham_gupta_decf96a6ab2</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/shubham_gupta_decf96a6ab2"/>
    <language>en</language>
    <item>
      <title>Designing a Universal Error Handler for Frontend &amp; Backend (React + Node.js)</title>
      <dc:creator>Shubham Gupta</dc:creator>
      <pubDate>Thu, 22 Jan 2026 18:27:28 +0000</pubDate>
      <link>https://forem.com/shubham_gupta_decf96a6ab2/designing-a-universal-error-handler-for-frontend-backend-react-nodejs-13d1</link>
      <guid>https://forem.com/shubham_gupta_decf96a6ab2/designing-a-universal-error-handler-for-frontend-backend-react-nodejs-13d1</guid>
      <description>&lt;p&gt;Error handling is one of those things we all do — but rarely design properly.&lt;/p&gt;

&lt;p&gt;In most projects, error handling grows organically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a few try/catch blocks&lt;/li&gt;
&lt;li&gt;some HTTP status checks&lt;/li&gt;
&lt;li&gt;random error messages sent from the backend&lt;/li&gt;
&lt;li&gt;UI logic guessing what went wrong
Eventually, the system becomes fragile.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This article walks through how and why I designed a universal error-handling system that works across Backend (Node.js / Express / Next.js APIs) and Frontend (React / Next.js UI) — using a shared contract, not tight coupling.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Problem with Error Handling
&lt;/h2&gt;

&lt;p&gt;Most applications suffer from the same issues:&lt;/p&gt;

&lt;h2&gt;
  
  
  Backend problems
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Errors from DB, validation, or external APIs look different&lt;/li&gt;
&lt;li&gt;Internal stack traces leak to clients&lt;/li&gt;
&lt;li&gt;Error responses change over time&lt;/li&gt;
&lt;li&gt;Hard to debug production issues&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Frontend problems
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Every API returns a different error shape&lt;/li&gt;
&lt;li&gt;UI shows technical or confusing messages&lt;/li&gt;
&lt;li&gt;Error handling logic is duplicated everywhere&lt;/li&gt;
&lt;li&gt;Runtime crashes cause white screens
The core issue isn’t missing try/catch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 The issue is lack of design.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Key Insight: Errors Are a Contract&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Instead of treating errors as exceptions, I started treating them as data.&lt;br&gt;
That led to one fundamental idea:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Frontend and backend should share an error contract, not implementations.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This single decision shaped the entire system.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;Design Principle #1: Shared Contract, Not Tight Coupling&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The frontend and backend do not depend on each other’s code.&lt;br&gt;
They only agree on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;error codes&lt;/li&gt;
&lt;li&gt;error structure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example error contract:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "success": false,
  "message": "User already exists",
  "code": "DUPLICATE_ERROR",
  "details": {
    "field": "email"
  },
  "traceId": "abc-123"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;consistency&lt;/li&gt;
&lt;li&gt;backward compatibility&lt;/li&gt;
&lt;li&gt;freedom to evolve each layer independently&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Design Principle #2: Backend Decides What Happened&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The backend is the source of truth.&lt;/p&gt;

&lt;p&gt;Its responsibility is to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;detect what went wrong&lt;/li&gt;
&lt;li&gt;categorize the error&lt;/li&gt;
&lt;li&gt;attach structured metadata&lt;/li&gt;
&lt;li&gt;assign a stable error code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It does not decide how the error is shown.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Internally, the backend normalizes:&lt;/li&gt;
&lt;li&gt;database errors&lt;/li&gt;
&lt;li&gt;validation failures&lt;/li&gt;
&lt;li&gt;authentication &amp;amp; authorization errors&lt;/li&gt;
&lt;li&gt;third-party API failures&lt;/li&gt;
&lt;li&gt;system-level crashes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything becomes a single, predictable error object.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Design Principle #3: Frontend Decides How to Show It&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The frontend owns user experience.&lt;/p&gt;

&lt;p&gt;It receives structured error data and decides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the message shown to users&lt;/li&gt;
&lt;li&gt;localization (i18n)&lt;/li&gt;
&lt;li&gt;UI presentation (toast, banner, modal)&lt;/li&gt;
&lt;li&gt;retry or recovery behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allows the same backend error to be shown differently in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;admin dashboards&lt;/li&gt;
&lt;li&gt;consumer apps&lt;/li&gt;
&lt;li&gt;mobile vs desktop UIs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Technical message ≠ UI message — and that’s intentional.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Design Principle #4: Progressive Adoption&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This system is not all-or-nothing.&lt;/p&gt;

&lt;p&gt;You can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use only backend utilities&lt;/li&gt;
&lt;li&gt;use only frontend hooks&lt;/li&gt;
&lt;li&gt;use both together for best results&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even if the backend doesn’t follow the contract perfectly, the frontend falls back safely.&lt;/p&gt;

&lt;p&gt;This makes the library practical for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;legacy systems&lt;/li&gt;
&lt;li&gt;gradual refactors&lt;/li&gt;
&lt;li&gt;monorepos with mixed stacks&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Design Principle #5: Hooks-Only, Modern React&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;All frontend APIs are built with hooks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no class components&lt;/li&gt;
&lt;li&gt;no outdated patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;useAPIError()&lt;/code&gt; – handle API &amp;amp; network errors&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;useAsyncData()&lt;/code&gt; – async operations with built-in error state&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;useGlobalError()&lt;/code&gt; – app-wide error state&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;useNetworkStatus()&lt;/code&gt; – offline/online detection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Runtime UI crashes are handled via a React error boundary, wrapped once at the app root.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Design Principle #6: TypeScript-First&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;TypeScript isn’t an add-on — it’s the foundation.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;strict typing&lt;/li&gt;
&lt;li&gt;shared types between FE &amp;amp; BE&lt;/li&gt;
&lt;li&gt;discriminated unions for error types&lt;/li&gt;
&lt;li&gt;strong inference for consumers&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;fewer runtime surprises&lt;/li&gt;
&lt;li&gt;safer refactors&lt;/li&gt;
&lt;li&gt;better IDE experience&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Types are the documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;End-to-End Flow (How Everything Connects)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Here’s the full journey of an error:&lt;/p&gt;

&lt;p&gt;Backend detects a failure&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Error is normalized into a standard structure&lt;/li&gt;
&lt;li&gt;API returns a predictable error response&lt;/li&gt;
&lt;li&gt;Frontend parses the error&lt;/li&gt;
&lt;li&gt;Error code is mapped to a user-friendly message&lt;/li&gt;
&lt;li&gt;UI displays a safe, meaningful response&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At no point does the UI guess what went wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why This Matters in Production&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This approach gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cleaner APIs&lt;/li&gt;
&lt;li&gt;better UX&lt;/li&gt;
&lt;li&gt;safer production behavior&lt;/li&gt;
&lt;li&gt;easier debugging (via trace IDs)&lt;/li&gt;
&lt;li&gt;a shared mental model for teams&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Error handling stops being “glue code” and becomes infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Result: A Universal Error Handler&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I packaged this system as an open-source npm library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install @shubhamgupta-oss/universal-error-handler

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React&lt;/li&gt;
&lt;li&gt;Next.js&lt;/li&gt;
&lt;li&gt;Node.js&lt;/li&gt;
&lt;li&gt;Express&lt;/li&gt;
&lt;li&gt;TypeScript&lt;/li&gt;
&lt;li&gt;React 18 &amp;amp; 19&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GitHub repo: &lt;a href="https://github.com/shubhamgupta-oss/universal-error-handler" rel="noopener noreferrer"&gt;https://github.com/shubhamgupta-oss/universal-error-handler&lt;/a&gt;&lt;br&gt;
NPM package: &lt;a href="https://www.npmjs.com/package/@shubhamgupta-oss/universal-error-handler" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/@shubhamgupta-oss/universal-error-handler&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Good error handling isn’t about catching errors.&lt;/p&gt;

&lt;p&gt;It’s about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;defining responsibility&lt;/li&gt;
&lt;li&gt;designing contracts&lt;/li&gt;
&lt;li&gt;respecting separation of concerns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you treat errors as a first-class system, everything else becomes simpler.&lt;/p&gt;

&lt;p&gt;If you’re building full-stack applications and struggling with inconsistent error handling, I hope this approach helps.&lt;/p&gt;

&lt;p&gt;Feedback, suggestions, and contributions are welcome. 🙌&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>javascript</category>
      <category>node</category>
      <category>react</category>
    </item>
  </channel>
</rss>
