Building a Practical Backend Error Mapper for Frontend Applications

Jan 18, 2026

Building a Practical Backend Error Mapper for Frontend Applications

Error handling in frontend applications often becomes a mess of scattered try-catch blocks and inconsistent user messages. After dealing with this problem across multiple projects, I built a simple, reusable error mapping system that transforms backend error codes into user-friendly messages with minimal overhead.

Here’s the straightforward approach that actually works in production.

The Problem

Your backend returns error codes like AUTH_001, PAYMENT_FAILED, or VALIDATION_ERROR. Different parts of your app need different messages for the same error:

  • Login form: “Invalid credentials” for AUTH_001
  • Profile page: “Session expired, please log in” for AUTH_001
  • Checkout: “Payment failed. Please try another card” for PAYMENT_FAILED

Without a system, you end up with duplicated logic, inconsistent messages, and maintenance headaches.

The Solution: Config + Component

The solution is dead simple: one config file that maps errors, one component that renders them. No state management, no hooks drama, no over-engineering.

Step 1: Define Your Types

// types/error.types.ts
export type ErrorType = 'error' | 'warning' | 'info';

export type ErrorContext =
| 'auth'
| 'payment'
| 'form'
| 'api'
| 'network';

export interface ErrorConfig {
title: string;
message: string;
type: ErrorType;
}

export interface BackendError {
code: string;
message?: string;
field?: string;
}

Step 2: Create the Error Config

This is your single source of truth for all error mappings:

// config/errorConfig.ts
import { ErrorConfig, ErrorContext } from '@/types/error.types';

type ErrorMapping = {
codes: Record<string, ErrorConfig>;
default: ErrorConfig;
};

type ErrorConfigMap = Record<ErrorContext, ErrorMapping> & {
default: ErrorConfig;
};

export const ERROR_CONFIG: ErrorConfigMap = {
auth: {
codes: {
AUTH_001: {
title: 'Authentication Failed',
message: 'Invalid email or password. Please try again.',
type: 'error',
},
AUTH_002: {
title: 'Session Expired',
message: 'Your session has expired. Please log in again.',
type: 'warning',
},
// ... more auth errors
},
default: {
title: 'Authentication Error',
message: 'Unable to authenticate. Please try again.',
type: 'error',
},
},

payment: {
codes: {
PAYMENT_FAILED: {
title: 'Payment Failed',
message: 'We couldn\'t process your payment. Please check your card details.',
type: 'error',
},
INSUFFICIENT_FUNDS: {
title: 'Insufficient Funds',
message: 'Your card was declined due to insufficient funds.',
type: 'error',
},
// ... more payment errors
},
default: {
title: 'Payment Error',
message: 'Unable to process payment. Please try again.',
type: 'error',
},
},

// ... other contexts (form, api, network)

default: {
title: 'Unexpected Error',
message: 'Something went wrong. Please try again.',
type: 'error',
},
};

// Helper function with fallback logic
export const getErrorConfig = (
code: string,
source?: ErrorContext
): ErrorConfig => {
if (!source) return ERROR_CONFIG.default;

const contextMapping = ERROR_CONFIG[source];
if (!contextMapping) return ERROR_CONFIG.default;

return contextMapping.codes[code] || contextMapping.default;
};

Step 3: Build the ErrorState Component

A simple, stateless component with two usage patterns:

// components/ErrorState.tsx
import React from 'react';
import { AlertCircle, AlertTriangle, Info, X } from 'lucide-react';
import { ErrorType, ErrorContext, BackendError } from '@/types/error.types';
import { getErrorConfig } from '@/config/errorConfig';

// Two usage patterns supported
type ErrorStateProps =
| {
// Pattern 1: From backend error
error: BackendError;
source?: ErrorContext;
onDismiss?: () => void;
className?: string;
}
| {
// Pattern 2: Direct/hardcoded usage
title: string;
message: string;
type?: ErrorType;
onDismiss?: () => void;
className?: string;
};

const ErrorState: React.FC<ErrorStateProps> = (props) => {
const { onDismiss, className = '' } = props;

// Determine which pattern is being used and get config
let title: string;
let message: string;
let type: ErrorType;

if ('error' in props) {
// Pattern 1: Map from backend error
const config = getErrorConfig(props.error.code, props.source);
title = config.title;
message = config.message;
type = config.type;
} else {
// Pattern 2: Direct props
title = props.title;
message = props.message;
type = props.type || 'error';
}

const getIcon = () => {
switch (type) {
case 'error':
return <AlertCircle className="w-5 h-5" />;
case 'warning':
return <AlertTriangle className="w-5 h-5" />;
case 'info':
return <Info className="w-5 h-5" />;
}
};

const getColorClasses = () => {
switch (type) {
case 'error':
return 'bg-red-50 border-red-200 text-red-800';
case 'warning':
return 'bg-yellow-50 border-yellow-200 text-yellow-800';
case 'info':
return 'bg-blue-50 border-blue-200 text-blue-800';
}
};

return (
<div
className={`rounded-lg border p-4 ${getColorClasses()} ${className}`}
role="alert"
>
<div className="flex items-start gap-3">
<div className="flex-shrink-0 mt-0.5">
{getIcon()}
</div>

<div className="flex-1 min-w-0">
<h3 className="font-semibold text-sm mb-1">
{title}
</h3>
<p className="text-sm opacity-90">
{message}
</p>
</div>

{onDismiss && (
<button
onClick={onDismiss}
className="flex-shrink-0 hover:opacity-70 transition-opacity"
aria-label="Dismiss error"
>
<X className="w-4 h-4" />
</button>
)}
</div>
</div>
);
};

export default ErrorState;

Usage Examples

Example 1: Login Form

const LoginForm = () => {
const [error, setError] = useState<BackendError | null>(null);

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();

try {
const response = await fetch('/api/auth/login', { /* ... */ });
if (!response.ok) {
setError(await response.json());
return;
}
// Success
} catch {
setError({ code: 'NETWORK_ERROR' });
}
};

return (
<form onSubmit={handleSubmit}>
{error && (
<ErrorState
error={error}
source="auth"
onDismiss={() => setError(null)}
/>
)}
{/* Form inputs */}
</form>
);
};

Example 2: Direct/Hardcoded Usage

<ErrorState
title="Feature Unavailable"
message="This feature is only available to premium users."
type="warning"
onDismiss={() => setShowError(false)}
/>

Example 3: Reusable API Hook

export const useApi = <T,>(source: ErrorContext = 'api') => {
const [error, setError] = useState<BackendError | null>(null);

const execute = async (url: string, config?: RequestInit) => {
try {
const response = await fetch(url, config);
if (!response.ok) {
setError(await response.json());
return null;
}
return await response.json();
} catch {
setError({ code: 'NETWORK_ERROR' });
return null;
}
};

return { error, execute, source };
};

// Usage
const UserProfile = () => {
const { error, execute, source } = useApi('api');

useEffect(() => { execute('/api/user/profile'); }, []);

return (
<div>
{error && <ErrorState error={error} source={source} />}
{/* Render profile */}
</div>
);
};

Extending the Config

Adding new errors is simple - just update the config:

// Add to existing context
auth: {
codes: {
// ... existing codes
AUTH_005: {
title: 'Two-Factor Required',
message: 'Please complete two-factor authentication.',
type: 'info',
},
},
default: { ... }
}

// Or add new context
subscription: {
codes: {
SUB_EXPIRED: {
title: 'Subscription Expired',
message: 'Please renew to continue.',
type: 'warning',
},
},
default: {
title: 'Subscription Error',
message: 'Issue with your subscription.',
type: 'error',
},
}

Why This Works

Simple Fallback Chain:

  1. Look for error.code in source context
  2. Fall back to context default
  3. Fall back to top-level default

Stateless Component:

  • Props in, UI out
  • No unnecessary hooks or state management
  • Easy to test and debug

Flexible API:

  • Use with backend errors: <ErrorState error={err} source="auth" />
  • Use with hardcoded values: <ErrorState title="..." message="..." />

Single Source of Truth:

  • All error mappings in one file
  • Easy to maintain and extend
  • TypeScript catches typos in error codes and sources

Best Practices

  1. Coordinate with Backend: Establish clear error code conventions with your backend team (e.g., AUTH_xxx, PAYMENT_xxx)

  2. Write User-Friendly Messages: Avoid technical jargon. “Session expired, please log in” beats “JWT token validation failed”

  3. Use Appropriate Types: error for critical issues, warning for recoverable problems, info for notifications

  4. Keep Context Specific: Don’t reuse generic codes across contexts. AUTH_001 should mean one thing in auth context

  5. Log Original Errors: Always log the full backend error for debugging, even when showing friendly messages

  6. Test Fallbacks: Verify that unknown error codes gracefully fall back to context/default messages

Conclusion

This approach gives you robust error handling without the complexity. One config file, one component, clean fallbacks. No state management libraries, no over-engineering, just a practical solution that works.

The next time your backend returns ERR_UNKNOWN_XYZ, your users will see a friendly message while you debug the actual error in the console.

Recommendations

HAR Files: Your Secret Weapon for Debugging Production Issues

#debugging

,

#web

,

#har

,

#devtools

,

#performance

How to use HAR files as a practical, zero-setup tool for debugging...

Jan 23, 2026

i18n vs l10n: What Developers Need to Know

#i18n

,

#l10n

,

#internationalization

,

#localization

,

#web development

,

#javascript

,

#react

,

#frontend

A practical guide to internationalization and localization for developers. Learn...

Dec 25, 2025

Things to Do as a Frontend Architect: A Complete Guide

#Frontend

,

#Architecture

,

#JavaScript

,

#Web Development

,

#Best Practices

,

#Leadership

,

#Performance

,

#Developer Experience

A comprehensive guide to the responsibilities, tasks, and best practices for...

Dec 15, 2025

Understanding CRDTs: The Magic Behind Collaborative Editing

#CRDT

,

#Collaborative Editing

,

#Distributed Systems

,

#Real-time

,

#Tech Explained

A friendly deep dive into CRDTs and how they power real-time collaborative...

Oct 20, 2025

React Composition Patterns: Beyond Boolean Props

#React

,

#JavaScript

,

#Web Development

,

#Component Design

,

#Software Architecture

Learn how React compound components and composition patterns can help you escape...

Oct 6, 2025

10 Vue Debugging Tips That Will Transform Your Development Workflow

#Vue.js

,

#Debugging

,

#JavaScript

,

#Frontend

,

#DevTools

,

#Development

,

#Web Development

,

#Vue DevTools

Master Vue.js debugging with 10 battle-tested techniques from real developers....

Aug 26, 2025

Building a Cross-Repository Test Automation Pipeline: From Manual QA Nightmares to Automated Excellence

#automation

,

#testing

,

#CI/CD

,

#GitHub Actions

,

#Playwright

,

#SDK

,

#engineering

,

#DevOps

Learn how to build a cross-repository test automation pipeline that reduced our...

Aug 20, 2025

JavaScript Performance Optimization, 10 Techniques That Actually Move the Needle

#javascript

,

#performance

Discover 10 JavaScript performance optimization techniques that deliver real,...

Aug 18, 2025

Building a Blog Publisher MCP Server to Automate Your Content Workflow with Claude

#MCP

,

#Claude

,

#Automation

,

#TypeScript

,

#GitHub

,

#Blogging

,

#Tutorial

,

#AI Tools

Learn how to build a custom MCP server that lets Claude publish and manage blog...

Aug 7, 2025

20 JavaScript Interview Questions You Should Know in 2025

A practical guide to 20 core JavaScript interview questions — with clear...

Jul 24, 2025

Building a Simple, Scalable Feature Flag System

#nextjs

,

#prisma

,

#feature-flags

,

#fullstack

,

#backend

,

#api-routes

,

#clean-architecture

,

#scalable-design

,

#product-rollout

Built a simple yet scalable feature flag system using Next.js API routes and...

Jul 6, 2025

I Refactored Without Changing a Feature — And It Broke Everything

#HyrumsLaw

,

#Refactoring

,

#LegacyCode

,

#CodeSmells

,

#TechDebt

,

#SoftwareEngineering

,

#CleanCode

Understanding Hyrum’s Law with a Real-World Lesson on Porting vs Refactoring

Jul 5, 2025

How to Publish Your First npm Package: Creating Rainbow Highlight with Utilities

#npm

,

#npm-package

,

#web

,

#javascript

Learn how to create and publish your first npm package. This step-by-step guide...

Sep 22, 2024

Google Dorking: Unlocking Hidden Search Capabilities & Insights

#seach

,

#seo

,

#research

Explore 16 advanced Google Dorking techniques to uncover valuable data, security...

Aug 8, 2024

This One HTML Attribute Could Save Your Web App from a Security Nightmare

#web-security

,

#cdn

,

#web

Web security is a critical concern for developers, yet some of the most...

Jun 29, 2024

Are You Still Using Basic CSS? Here Are 7 Tricks to Get Ahead of the Curve

#css

Bored of the same old CSS? Unleash 7 hidden gems to take your designs to the...

Dec 27, 2023

Easiest way to store your logs in a file WITHOUT chaging the source file(node)

#productivity

Often, developers face challenges when dealing with a flood of logs in the...

Dec 21, 2023

Build Your Own Pinterest-Style Masonry Grid: A Step-by-Step Guide

#css

,

#web

,

#layout

Create a masonary grid layout with left to right content flow, supporting...

Dec 10, 2023

Using git diff and git apply to Share Local Changes with Peers

#git

,

#productivity

,

#software_engeneering

,

#dev

git diff and git apply are two powerful Git commands that can be used to share...

Nov 12, 2023

React Portals: Render Components Outside the current DOM Hierarchy

#react

,

#web

The createPortal API in React allows you to render child elements into a...

Jul 27, 2023

Cloning Made Easy: Try degit and Clone Directories within Repos.

#git

,

#productivit

Have you ever faced the dilemma of wanting just a small portion of a repository,...

Jul 19, 2023

Debugging Web Apps with Browser Dev Tools: 6 Amazing Tricks

#browser

,

#debugging

,

#web

Debugging web applications can be a challenging task, with errors like...

Jul 13, 2023

Controlled Versus Uncontrolled Components in React

#react

,

#forms

Understanding State Management Within Forms Comparing controlled and...

Nov 5, 2022

Format Numbers, Dates and Currencies with the Intl Object in Javascript

#javascript

,

#html

,

#web

Intl object can be used to format data into commonly used formats of dates,...

Sep 13, 2022

Image Masking on Hover Using CSS Clip Path and Javascript

#javscript

,

#css

,

#html

Image Masking can be used to add fancy hover highlight effects to images for...

Jul 23, 2022

Recreating CSS Tricks Fancy Grid Hover Effect

#html

,

#css

,

#UI

,

#recreation

CSS Trick had a simple yet cool grid layout which I found dope. So lets try to...

May 21, 2022

File Explorer Recursive React Component

#react

,

#javascript

,

#web

How to create a recursive folder Component using react.

Apr 16, 2022

Add Google Fonts to Your React & NextJS + TailwindCSS Project (Next 14)

#css

,

#tailwindcss

,

#react

,

#nextjs

,

#tailwind

,

#design

Use Google Fonts in Your TailwindCSS Projects

Apr 6, 2022

Event Delegation in Javascript

#javscript

,

#css

,

#html

,

#web

,

#performance

Handling multiple Events in Javascript with minimal CPU Usage

Mar 6, 2022

A Simple Web Accessibility Trick that you most probably missed!

#html

,

#css

,

#web-accessibility

,

#user-experience

Imagine that you cannot use the mouse and have to Navigate a Website with the...

Dec 23, 2021

Top Terminal Commands I Use For Productivity

#linux

,

#cli

,

#terminal

The whole point of development is solving problems. But very often we Developers...

Nov 3, 2021

CSS Logical Properties

#css

,

#html

CSS logical properties are properties which are used to design element on the...

Oct 5, 2021

Fluid Typography in CSS 💧

#css

,

#html

,

#typography

CSS Best Practices in Fluid Typography

Aug 15, 2021

CSS Units in a Nutshell 🐚

#css

,

#html

Are you still writing your css units in pixels and percentages? if you are then...

Aug 8, 2021

Master Markdown in 5minutes ⌚

#markdown

,

#documentation

Markdown is a lightweight markup language for creating formatted text using a...

Aug 1, 2021

What is JAMStack ✨

#jamstack

Jamstack stands for Javascript APIS and Markup and it is based on this idea of...

Jul 31, 2021

+

Check my latest Blog Post

HAR Files: Your Secret Weapon for Debugging Production Issues

Read Now
Oh My Gawwdd!!!!!!!

Wow you have been viewing my site since 20 seconds!

+
+