# Errors and How to Handle them. 

A backend engineer is bound to face some errors sooner or later so it is necessary to create a fault tolerant backend system and handle the Errors.

## Types of Errors:

### 1\. Logical Errors

**Cause:** Flaws in reasoning, misunderstood requirements, or incorrect algorithm design.

*   Misinterpreting requirements
    
*   Incorrect algorithm / condition logic
    
*   Ignoring edge cases
    

**Example:**

```cpp
if (age > 18) allowAccess();
```

Bug: misses `age == 18` → should be `>= 18`

* * *

### 2\. Database Errors

**Cause:** Issues interacting with the database layer.

*   **Connection errors:** DB not reachable
    
*   **Constraint violations:** unique/foreign key failure
    
*   **Query errors:** malformed SQL or wrong assumptions
    

* * *

### 3\. External Service Errors

**Cause:** Dependency on third-party services (you’re effectively a client).

*   Network failures
    
*   Connection timeouts
    
*   DNS resolution failures
    
*   Network partitions
    
*   Rate limiting (e.g., Clerk, Auth0)
    

**Example:**

```js
await axios.get("https://api.auth0.com/user");
```

Fails with `429 Too Many Requests`

**Mitigation:**

*   Retry with **exponential backoff (trying after 1min then 2 then 4 then 8 ...)**
    
*   Fallback mechanisms
    

* * *

### 4\. Input Validation Errors

**Cause:** Invalid or malformed user input (most controllable category).

*   Format validation (email, phone)
    
*   Range validation (age < 0)
    
*   Missing required fields
    

**Example:**

```json
{ "email": "not-an-email" }
```

Rejected due to invalid format

**Note:** Easiest to prevent via schema validation (e.g., Joi, Zod)

* * *

### 5\. Configuration Errors

**Cause:** Incorrect environment or system configuration.

*   Missing environment variables
    
*   Wrong API keys / DB URLs
    
*   Misconfigured ports/services
    

**Example:**

```plaintext
DATABASE_URL=undefined
```

App crashes on startup

**Best Practice:**

*   **Fail fast at startup**, not during runtime
    
*   Validate config using schemas before boot
    

* * *

## Proactive Error Handling (Before Errors Happen)

The most effective error handling isn’t reactive — it’s **preventive**. Strong backend systems are designed to **detect, isolate, and surface issues before they impact users**.

### 1\. Health Checks (Service-Level Monitoring)

Expose endpoints like:

*   `/health`
    
*   `/status`
    

These are continuously pinged by load balancers or monitoring tools to verify:

*   Service is running
    
*   Dependencies are reachable
    
*   System is responsive
    

* * *

### 2\. Database Health Checks

Monitor database behaviour, not just availability:

*   Query latency (slow queries act like an early warning)
    
*   Connection pool saturation (long job queues).
    
*   Failed/blocked queries
    

**Purpose:** Catch degradation before full outages.

* * *

### 3\. External Service Health Checks

Since third-party services are outside your control, actively validate them:

*   Dummy API calls to auth providers
    
*   Sending test emails via email services
    
*   Simulating lightweight transactions
    

**Purpose:** Ensure integrations (Auth, Payments, Notifications) are operational.

* * *

### 4\. Configuration Validation (Fail Fast)

Misconfiguration should **crash the app at startup**, not during runtime.

*   Validate environment variables
    
*   Check API keys, DB URLs, ports
    
*   Schema-based config validation
    

**Purpose:** *If the system can’t run correctly, don’t let it start.*

* * *

### 5\. Core Functionality Checks

Before deployment or during startup:

*   Ensure critical flows are functional
    
*   Verify dependencies are wired correctly
    
*   Run smoke tests on key features
    

* * *

### 6\. Logging & Monitoring (Early Detection)

Proactive systems don’t just prevent errors — they **detect and diagnose instantly**:

*   Centralised logging (trace failures quickly)
    
*   Real-time monitoring & alerts
    
*   Track performance metrics (latency, throughput, not just errors)
    

> Note: Performance degradation is often the **first signal** of an upcoming failure.

*(We’ll cover logging, monitoring, and performance tracking in more depth afterwards.)*

* * *

## Handling Errors Gracefully (When They Do Happen)

Even with proactive systems, failures are inevitable. The goal is to **minimise user impact, recover where possible, and fail intelligently where not**.

* * *

### 1\. Immediate Responses (Recoverable vs Non-Recoverable)

**Recoverable Errors:** System can retry or provide fallback.

*   Example: Email service down → Queue the email + retry later → Respond: *“Action successful, email will be sent shortly”*
    

**Non-Recoverable Errors:** Cannot complete the operation → contain damage.

*   Example: Payment processing failure → Abort transaction, rollback state → Respond: *“Payment failed, please try again”*
    

**Key Idea:**

*   Recover silently when possible
    
*   Fail transparently when necessary
    

* * *

### 2\. Graceful Degradation

Instead of full failure, **reduce functionality but keep system usable**.

*   Recommendation service down → show generic content
    
*   Analytics failure → skip tracking, don’t block request
    

**Goal:** Maintain core user experience even under partial failure.

* * *

### 3\. Error Recovery Strategies

Structured approaches to handle transient failures:

*   **Retries with exponential backoff**
    
*   **Fallback mechanisms** (cached data, secondary service)
    
*   **Queue-based recovery** (async jobs for later retry)
    

**Example:**

```js
retry(fn, { retries: 3, backoff: exponential });
```

* * *

### 4\. Error Propagation Control (Bubbling Up)

Errors should **bubble up cleanly**, not leak or get swallowed.

*   Lower layers → throw structured errors
    
*   Upper layers → decide response/action
    

**Example:**

```js
// service layer
if (!user) throw new NotFoundError("User not found");

// controller layer
catch (err) {
  next(err); // pass to global handler
}
```

**Principle:**

*   Don’t handle errors where you **can’t act meaningfully**
    

* * *

### 5\. Global Error Handling (Final Safety Net)

A centralised middleware that **catches all unhandled errors**.

It ensures to:

*   Standardise error responses
    
*   Hide internal details (no stack leaks)
    
*   Log errors for debugging
    

**Example :**

```js
app.use((err, req, res, next) => {
  console.error(err);

  res.status(err.status || 500).json({
    message: err.message || "Internal Server Error",
  });
});
```

This layer ensures that: no request crashes silently, consistent API responses are sent and to centralisation of Logging and increasing the observability

* * *

## Conclusion

Building resilient backend systems isn’t about eliminating errors—it’s about **anticipating, detecting, and handling them with control and clarity**. From proactive health checks to graceful degradation and centralised error handling, robustness comes from designing for failure at every layer.

In the next section, we’ll take this further by diving into **Production-Grade Configuration Management**—because many failures don’t start in code, they start in config.
