> ## Documentation Index
> Fetch the complete documentation index at: https://notes.kodekloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Error Handling Resilience Audit

> Technical audit identifying error-handling and resilience gaps in an Express Node.js API with prioritized findings, remediation code snippets, and a roadmap to improve stability and security.

This document is a focused technical audit of error handling and resilience for an API/Node.js (Express) application. It condenses a broader quality review into concrete findings, prioritized remediation, and precise, drop-in code fixes you can apply quickly to reduce risk and improve uptime.

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/Xs8Ruv7b0QCZf7iM/images/Claude-Code-for-Beginners/Code-Review-with-Claude-Code/Error-Handling-Resilience-Audit/error-handling-resilience-audit-demo.jpg?fit=max&auto=format&n=Xs8Ruv7b0QCZf7iM&q=85&s=eec8810c75541cbbd5482a2e5037e85f" alt="A presentation slide titled &#x22;Error Handling & Resilience Audit&#x22; with a large &#x22;Demo&#x22; label on a dark curved background. A small &#x22;© Copyright KodeKloud&#x22; appears in the bottom-left corner." width="1920" height="1080" data-path="images/Claude-Code-for-Beginners/Code-Review-with-Claude-Code/Error-Handling-Resilience-Audit/error-handling-resilience-audit-demo.jpg" />
</Frame>

Purpose: identify gaps in error handling consistency, categorization, async handling, recovery patterns, and error information exposure — then provide targeted code snippets and configuration changes that are minimal to integrate.

Example prompt used to drive the audit (trimmed for clarity):

```text theme={null}
4. ERROR RECOVERY
– Graceful degradation
– Retry mechanisms
– Circuit breakers
– Fallback strategies

5. ERROR INFORMATION
– Development vs production error details
– Stack trace exposure
– Error logging completeness
– User-friendly error messages

Identify error handling gaps and provide improved implementation.

## Provide:

A structured finding report

A scale of 1/10 on how important each finding is

Remediation: precise code-level fix or config change (snippets welcome)

## Constraints & style:

Be concrete and cite exact code locations and identifiers.
Prefer minimal, drop-in fix snippets over prose.
Do not invent files or functions that aren't present; if context is missing, mark as Unable to verify and say what code would prove it.
Write this into a markdown file and place it in the audits/ folder.
```

Evaluation checklist used during the audit:

* ERROR HANDLING CONSISTENCY
  * Centralized error handler?
  * Uniform response formats?
  * Custom error classes vs generic errors?
* ERROR CATEGORIES
  * Validation (400), Authentication (401), Authorization (403), Not found (404), Server (500), Rate limit (429), Conflict (409)
* ASYNC ERROR HANDLING
  * Unhandled promise rejections
  * Async middleware wrapper usage
  * Callback & EventEmitter error handling
* ERROR RECOVERY
  * Graceful degradation
  * Retry mechanisms
  * Circuit breakers
  * Fallback strategies
* ERROR INFORMATION
  * Dev vs Prod error detail
  * Stack trace exposure
  * Error logging completeness
  * User-friendly error messages

***

## Executive summary

Status: ❌ CRITICAL DEFICIENCIES IDENTIFIED\
Overall Score: 3.5 / 10

The Express Login Demo exposes several critical error-handling and resilience gaps. Primary risks include lack of centralized error handling, leakage of sensitive information in logs/responses, missing global handlers for unhandled rejections/uncaught exceptions, and no recovery patterns (retries/circuit breakers/fallbacks).

Most critical security-related fixes are small and can be implemented in \~1 week. Reaching full resilience maturity (observability, circuit breakers, graceful degradation) is estimated at 2–3 months.

Critical findings overview:

| Category | Score | Status | Key issue |
| -: | :-: | :-: | - |
| Error Handling Consistency | 2/10 | ❌ FAIL | No centralized error middleware; routes return inconsistent shapes |
| Error Categories | 4/10 | ⚠️ PARTIAL | Several HTTP categories (403, 409, 429) missing or used inconsistently |
| Async Error Handling | 3/10 | ❌ FAIL | Missing `unhandledRejection`/`uncaughtException` and async wrapper usage |
| Error Recovery | 1/10 | ❌ FAIL | No retry or circuit breaker patterns; high risk of cascading failures |
| Error Information | 4/10 | ⚠️ PARTIAL | Stack traces and DB errors appear in logs/responses; no structured logging |

***

## 1. ERROR HANDLING CONSISTENCY

Finding

* No centralized error middleware is plugged in after routes.
* Route handlers return heterogeneous response shapes and sometimes directly call `console.error`.
* No standard error object or machine-readable error payload.

Remediation — minimal, drop-in utilities

* Standardized error response object:

```javascript theme={null}
// utils/errorResponse.js
class ErrorResponse {
  constructor(message, statusCode = 500, details = null) {
    this.message = message;
    this.statusCode = statusCode;
    this.details = details;
  }

  toJSON() {
    const payload = { error: this.message };
    if (this.details) payload.details = this.details;
    return payload;
  }
}

module.exports = ErrorResponse;
```

* Central HTTP error class:

```javascript theme={null}
// utils/httpErrors.js
class HTTPError extends Error {
  constructor(message, statusCode = 500, details = null) {
    super(message);
    this.name = this.constructor.name;
    this.statusCode = statusCode;
    this.details = details;
    Error.captureStackTrace(this, this.constructor);
  }
}

module.exports = HTTPError;
```

* Centralized Express error-handling middleware (register after all routes):

```javascript theme={null}
// middleware/errorHandler.js
const HTTPError = require('../utils/httpErrors');
const logger = require('../utils/logger'); // see logger snippet below

const sanitizeError = (err) => {
  // Limit what is returned to the client
  const payload = {
    message: err.message || 'Internal Server Error',
  };
  if (err.code) payload.code = err.code;
  return payload;
};

const errorHandler = (err, req, res, next) => {
  const statusCode = err.statusCode || 500;

  // Server-side logging of full error (structured)
  logger.error('Request error', { error: err });

  // Client response: minimal info in production, expanded in development
  const responseBody = sanitizeError(err);
  if (process.env.NODE_ENV === 'development') {
    responseBody.stack = err.stack;
    responseBody.details = err.details || null;
  }

  res.status(statusCode).json(responseBody);
};

module.exports = errorHandler;
```

Integration tip: Replace ad-hoc responses with `next(new HTTPError('message', 400))`. Ensure `app.use(errorHandler)` is the final middleware.

***

## 2. ERROR CATEGORIES

Finding

* Routes occasionally use 400/401/500, but 403, 404, 409, and 429 are inconsistent or missing.
* Lack of helpers leads to human error and inconsistent client behavior.

Remediation — helper factory for consistent HTTP errors

```javascript theme={null}
// utils/httpHelpers.js
const HTTPError = require('./httpErrors');

const BadRequest = (msg, details) => new HTTPError(msg || 'Bad Request', 400, details);
const Unauthorized = (msg) => new HTTPError(msg || 'Unauthorized', 401);
const Forbidden = (msg) => new HTTPError(msg || 'Forbidden', 403);
const NotFound = (msg) => new HTTPError(msg || 'Not Found', 404);
const Conflict = (msg) => new HTTPError(msg || 'Conflict', 409);
const TooManyRequests = (msg) => new HTTPError(msg || 'Too Many Requests', 429);
const ServiceUnavailable = (msg) => new HTTPError(msg || 'Service Unavailable', 503);

module.exports = { BadRequest, Unauthorized, Forbidden, NotFound, Conflict, TooManyRequests, ServiceUnavailable };
```

Usage example:

```javascript theme={null}
const { NotFound, Forbidden } = require('../utils/httpHelpers');

if (!resource) return next(NotFound('User not found'));
if (!canAccess(user, resource)) return next(Forbidden('Insufficient permissions'));
```

***

## 3. ASYNC ERROR HANDLING

Findings

* No global `process.on('unhandledRejection')` or `process.on('uncaughtException')` handlers.
* Route handlers use manual try/catch or nothing; no async wrapper in place.
* Potential for unhandled promise rejections to crash the process unpredictably.

Remediation

* Add global process handlers (put these near server startup, before `app.listen()`):

```javascript theme={null}
// server.js (or index.js) — add before app.listen(...)
process.on('unhandledRejection', (reason, promise) => {
  console.error('Unhandled Rejection at:', promise, 'reason:', reason);
  // Perform graceful shutdown if you have an http server instance:
  if (server && server.close) {
    server.close(() => process.exit(1));
  } else {
    process.exit(1);
  }
});

process.on('uncaughtException', (error) => {
  console.error('Uncaught Exception:', error);
  // Best practice: log and restart the process (managed by a supervisor like PM2 or k8s)
  process.exit(1);
});
```

* Async wrapper middleware (very small, drop-in):

```javascript theme={null}
// middleware/asyncWrapper.js
module.exports = (fn) => (req, res, next) => {
  Promise.resolve(fn(req, res, next)).catch(next);
};
```

Usage:

```javascript theme={null}
// routes/auth.js
const asyncWrapper = require('../middleware/asyncWrapper');

router.post('/login', asyncWrapper(async (req, res) => {
  const user = await authService.authenticate(req.body);
  res.json({ user });
}));
```

This pattern ensures thrown errors are forwarded to the centralized errorHandler.

***

## 4. ERROR RECOVERY

Findings

* No retry logic for transient failures (database, network).
* No circuit breaker to prevent rapid cascading failures to dependencies.
* No fallback or graceful degradation for non-critical features.

Remediation — small, practical utilities

* Simple retry helper with exponential backoff:

```javascript theme={null}
// utils/retry.js
const delay = (ms) => new Promise((r) => setTimeout(r, ms));

const retry = async (operation, maxAttempts = 3, initialDelay = 500) => {
  let lastError;
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await operation();
    } catch (err) {
      lastError = err;
      if (attempt < maxAttempts) {
        const backoff = initialDelay * 2 ** (attempt - 1);
        await delay(backoff);
      }
    }
  }
  throw lastError;
};

module.exports = retry;
```

Example usage with Mongoose:

```javascript theme={null}
const retry = require('./utils/retry');
await retry(() => mongoose.connect(uri, opts), 5, 1000);
```

* Lightweight circuit breaker to protect fragile external calls:

```javascript theme={null}
// utils/circuitBreaker.js
class CircuitBreaker {
  constructor({ failureThreshold = 5, resetTimeout = 60000 } = {}) {
    this.failureThreshold = failureThreshold;
    this.resetTimeout = resetTimeout;
    this.failureCount = 0;
    this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
    this.nextAttempt = Date.now();
  }

  async fire(action) {
    if (this.state === 'OPEN') {
      if (Date.now() > this.nextAttempt) {
        this.state = 'HALF_OPEN';
      } else {
        throw new Error('CircuitBreakerOpen');
      }
    }

    try {
      const result = await action();
      this.success();
      return result;
    } catch (err) {
      this.fail();
      throw err;
    }
  }

  success() {
    this.failureCount = 0;
    this.state = 'CLOSED';
  }

  fail() {
    this.failureCount += 1;
    if (this.failureCount >= this.failureThreshold) {
      this.state = 'OPEN';
      this.nextAttempt = Date.now() + this.resetTimeout;
    }
  }
}

module.exports = CircuitBreaker;
```

Apply `CircuitBreaker.fire(() => remoteCall())` for DB/third-party services. For production-grade resilience, consider libraries like `opossum` which offer metrics and event hooks.

***

## 5. ERROR INFORMATION (LOGGING & EXPOSURE)

Findings

* Console logging of full error objects and DB details leaks secrets.
* No structured logging; logs are inconsistent and hard to search.
* Stack traces are returned to clients in production in some cases.

Remediation — structured logger + sanitization

* Replace `console.log`/`console.error` with a structured logger like winston:

```javascript theme={null}
// utils/logger.js
const { createLogger, format, transports } = require('winston');

const logger = createLogger({
  level: process.env.LOG_LEVEL || 'info',
  format: format.combine(
    format.timestamp(),
    format.errors({ stack: true }),
    format.splat(),
    format.json()
  ),
  transports: [
    new transports.Console(),
    // Add file or centralized transports for production:
    // new transports.File({ filename: 'logs/error.log', level: 'error' })
  ],
});

module.exports = logger;
```

* Sanitize logs and responses: ensure `sanitizeError` strips fields like `password`, `connectionString`, `ssn` before logging or returning to clients.

* Environment-specific responses: only include stack traces in development. In production, return minimal messages and include a reference ID to correlate logs:

```javascript theme={null}
// Example production response
{
  "error": "Service Unavailable",
  "reference": "err-20261020-abcdef"
}
```

Record the reference in structured logs for post-mortem.

***

## PRIORITY REMEDIATION PLAN

Immediate (Week 1) — Critical

1. Add and register centralized `errorHandler` middleware; replace ad-hoc responses.
2. Add global `unhandledRejection` / `uncaughtException` handlers and ensure `server.close()` is used for graceful shutdown.
3. Replace `console` with structured logger and add basic sanitization rules.

Short-term (Month 1)
4\. Add `asyncWrapper` for routes and refactor handlers to use it.
5\. Adopt standardized HTTP error helpers and ensure all routes use them.
6\. Use `retry` helper for transient DB/network operations.

Medium-term (Month 2–3)
7\. Integrate circuit breakers around external dependencies.
8\. Implement graceful degradation and fallback strategies for non-critical features.
9\. Add rate limiting (return 429) and monitoring/alerts tied to error thresholds and circuit breaker events.

<Callout icon="lightbulb" color="#1CB2FE">
  Prioritize fixes that remove secret leakage from logs and add global rejection handlers — these reduce attack surface and prevent abrupt crashes.
</Callout>

***

## SECURITY IMPACT & READINESS

Production Readiness: NOT READY

After applying prioritized fixes:

* Error information disclosure: LOW RISK
* Application stability: MEDIUM → HIGH (once retries/circuit breakers are in place)
* Debugging & observability: SIGNIFICANTLY IMPROVED (with structured logging and reference IDs)

Estimated time:

* Critical fixes: 2–3 weeks (depending on testing and rollout strategy)
* Full resilience (monitoring, circuit breakers, graceful degradation): 2–3 months

***

## Notes on verification

* If repository snapshot lacks specific files or code locations, mark the finding as "Unable to verify" and list the exact evidence that would validate the finding (e.g., "routes/auth.js contains `console.error(err)` with full `err` object logged" or "no `process.on('unhandledRejection')` handler present in server startup file").
* Prefer minimal, drop-in snippets to make integration easier.
* Keep a checklist of files changed and tests added to verify behavior (unit tests for `asyncWrapper`, integration tests for `retry` and `circuitBreaker`).

***

The full technical audit (structured findings, importance scores, and all suggested code snippets) was written to audits/ERROR\_HANDLING\_COMPREHENSIVE\_REVIEW\.md per the audit prompt. Implementing the above will materially improve resilience, security, and production readiness.

<CardGroup>
  <Card title="Watch Video" icon="video" cta="Learn more" href="https://learn.kodekloud.com/user/courses/claude-code-for-beginners/module/dd033355-c556-4619-993c-ac717c0f5741/lesson/60daca1c-a66f-4c9c-8087-8823972a6cce" />
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.