Design & media

API Error Handling

Try it

Error handling patterns across languages and layers — operational vs programmer errors, retry strategies, circuit breakers, error boundaries, HTTP responses, graceful degradation, and structured logging. Use when designing error strategies, building resilient APIs, or reviewing error management.

What it does

Ship resilient software. Handle errors at boundaries, fail fast and loud, never swallow exceptions silently.

The skill document

Error Handling Patterns

Ship resilient software. Handle errors at boundaries, fail fast and loud, never swallow exceptions silently.

Error Handling Philosophy

PrincipleDescription
Fail FastDetect errors early — validate inputs at the boundary, not deep in business logic
Fail LoudErrors must be visible — log them, surface them, alert on them
Handle at BoundariesCatch and translate errors at layer boundaries (controller, middleware, gateway)
Let It CrashFor unrecoverable state, crash and restart (Erlang/OTP philosophy)
Be SpecificCatch specific error types, never bare catch or except
Provide ContextEvery error carries enough context to diagnose without reproducing

Error Types

Operational errors — network timeouts, invalid user input, file not found, DB connection lost. Handle gracefully.

Programmer errorsTypeError, null dereference, assertion failures. Fix the code — don't catch and suppress.

// Operational — handle gracefully
try {
  const data = await fetch('/api/users');
} catch (err) {
  if (err.code === 'ECONNREFUSED') return fallbackData;
  throw err; // re-throw unexpected errors
}

// Programmer — let it crash, fix the bug
const user = null;
user.name; // TypeError — don't try/catch this

Language Patterns

LanguageMechanismAnti-Pattern
JavaScripttry/catch, Promise.catch, Error subclasses.catch(() => {}) swallowing errors
PythonExceptions, context managers (with)Bare except: catching everything
Goerror returns, errors.Is/As, fmt.Errorf wrapping_ = riskyFunction() ignoring error
RustResult, Option, ? operator.unwrap() in production code

JavaScript — Error Subclasses

class AppError extends Error {
  constructor(message, code, statusCode, details = {}) {
    super(message);
    this.name = this.constructor.name;
    this.code = code;
    this.statusCode = statusCode;
    this.details = details;
    this.isOperational = true;
  }
}

class NotFoundError extends AppError {
  constructor(resource, id) {
    super(`${resource} not found`, 'NOT_FOUND', 404, { resource, id });
  }
}

class ValidationError extends AppError {
  constructor(errors) {
    super('Validation failed', 'VALIDATION_ERROR', 422, { errors });
  }
}

Go — Error Wrapping

func GetUser(id string) (*User, error) {
    row := db.QueryRow("SELECT * FROM users WHERE id = $1", id)
    var user User
    if err := row.Scan(&user.ID, &user.Name); err != nil {
        if errors.Is(err, sql.ErrNoRows) {
            return nil, fmt.Errorf("user %s: %w", id, ErrNotFound)
        }
        return nil, fmt.Errorf("querying user %s: %w", id, err)
    }
    return &user, nil
}

Error Boundaries

Express Error Middleware

app.use((err, req, res, next) => {
  const statusCode = err.statusCode || 500;
  const response = {
    error: {
      code: err.code || 'INTERNAL_ERROR',
      message: err.isOperational ? err.message : 'Something went wrong',
      ...(process.env.NODE_ENV === 'development' && { stack: err.stack }),
      requestId: req.id,
    },
  };

  logger.error('Request failed', {
    err, requestId: req.id, method: req.method, path: req.path,
  });

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

React Error Boundary

import { ErrorBoundary } from 'react-error-boundary';

function ErrorFallback({ error, resetErrorBoundary }) {
  return (
    
      Something went wrong
      {error.message}
      Try again
    
  );
}

 queryClient.clear()}>
  


Retry Patterns

PatternWhen to UseConfig
Exponential BackoffTransient failures (network, 503)Base 1s, max 30s, factor 2x
Backoff + JitterMultiple clients retryingRandom ±30% on each delay
Circuit BreakerDownstream service failing repeatedlyOpen after 5 failures, half-open after 30s
BulkheadIsolate failures to prevent cascadeLimit concurrent calls per service
TimeoutPrevent indefinite hangsConnect 5s, read 30s, total 60s

Exponential Backoff with Jitter

async function withRetry(fn, { maxRetries = 3, baseDelay = 1000, maxDelay = 30000 } = {}) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (err) {
      if (attempt === maxRetries || !isRetryable(err)) throw err;
      const delay = Math.min(baseDelay * 2 ** attempt, maxDelay);
      const jitter = delay * (0.7 + Math.random() * 0.6);
      await new Promise((r) => setTimeout(r, jitter));
    }
  }
}

function isRetryable(err) {
  return [408, 429, 500, 502, 503, 504].includes(err.statusCode) || err.code === 'ECONNRESET';
}

Circuit Breaker

class CircuitBreaker {
  constructor({ threshold = 5, resetTimeout = 30000 } = {}) {
    this.state = 'CLOSED';       // CLOSED → OPEN → HALF_OPEN → CLOSED
    this.failureCount = 0;
    this.threshold = threshold;
    this.resetTimeout = resetTimeout;
    this.nextAttempt = 0;
  }

  async call(fn) {
    if (this.state === 'OPEN') {
      if (Date.now() < this.nextAttempt) throw new Error('Circuit is OPEN');
      this.state = 'HALF_OPEN';
    }
    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (err) {
      this.onFailure();
      throw err;
    }
  }

  onSuccess() { this.failureCount = 0; this.state = 'CLOSED'; }
  onFailure() {
    this.failureCount++;
    if (this.failureCount >= this.threshold) {
      this.state = 'OPEN';
      this.nextAttempt = Date.now() + this.resetTimeout;
    }
  }
}

HTTP Error Responses

StatusNameWhen to Use
400Bad RequestMalformed syntax, invalid JSON
401UnauthorizedMissing or invalid authentication
403ForbiddenAuthenticated but insufficient permissions
404Not FoundResource does not exist
409ConflictRequest conflicts with current state
422Unprocessable EntityValid syntax but semantic errors
429Too Many RequestsRate limit exceeded (include Retry-After)
500Internal Server ErrorUnexpected server failure
502Bad GatewayUpstream returned invalid response
503Service UnavailableTemporarily overloaded or maintenance

Standard Error Envelope

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The request body contains invalid fields.",
    "details": [
      { "field": "email", "message": "Must be a valid email address" }
    ],
    "requestId": "req_abc123xyz"
  }
}

Graceful Degradation

StrategyExample
Fallback valuesShow cached avatar when image service is down
Feature flagsDisable unstable recommendation engine
Cached responsesServe stale data with X-Cache: STALE header
Partial responseReturn available data with warnings array
async function getProductPage(productId) {
  const product = await productService.get(productId); // critical — propagate errors

  const [reviews, recommendations] = await Promise.allSettled([
    reviewService.getForProduct(productId),
    recommendationService.getForProduct(productId),
  ]);

  return {
    product,
    reviews: reviews.status === 'fulfilled' ? reviews.value : [],
    recommendations: recommendations.status === 'fulfilled' ? recommendations.value : [],
    warnings: [reviews, recommendations]
      .filter((r) => r.status === 'rejected')
      .map((r) => ({ service: 'degraded', reason: r.reason.message })),
  };
}

Logging & Monitoring

PracticeImplementation
Structured loggingJSON: level, message, error, requestId, userId, timestamp
Error trackingSentry, Datadog, Bugsnag — automatic capture with source maps
Alert thresholdsError rate > 1%, P99 latency > 2s, 5xx spike
Correlation IDsPass requestId through all service calls
Log levelserror = needs attention, warn = degraded, info = normal, debug = dev

Anti-Patterns

Anti-PatternFix
Swallowing errors catch (e) {}Log and re-throw, or handle explicitly
Generic catch-all at every levelCatch specific types, let unexpected errors bubble
Error as control flowUse conditionals, return values, or option types
Stringly-typed errors throw "wrong"Throw Error objects with codes and context
Logging and throwingLog at the boundary only, or wrap and re-throw
Catch-and-return-nullReturn Result type, throw, or return error object
Ignoring Promise rejectionsAlways await or attach .catch()
Exposing internalsSanitize responses; log details server-side only

NEVER Do

  1. NEVER swallow errors silentlycatch (e) {} hides bugs and causes silent data corruption
  2. NEVER expose stack traces, SQL errors, or file paths in API responses — log details server-side only
  3. NEVER use string throwsthrow 'error' has no stack trace, no type, no context
  4. NEVER catch and return null without explanation — callers have no idea why the operation failed
  5. NEVER ignore unhandled Promise rejections — always await or attach .catch()
  6. NEVER cache error responses — 5xx and transient errors must not be cached and re-served
  7. NEVER use exceptions for normal control flow — exceptions are for exceptional conditions
  8. NEVER return generic "Something went wrong" without logging the real error — always log the full error server-side with request context

Related skills

Manage Stripe customers, subscriptions, invoices, products, prices, and payments through OAuth-authenticated API calls.

by byungkyu720 installs29 stars

Post videos, photos, text, and documents to 10 social platforms through a single REST API call.

by victorcavero14375 installs50 stars

Search, read, and manage YouTube videos, playlists, channels, subscriptions, and comments via managed OAuth.

by byungkyu880 installs145 stars

Join a video meeting as an AI bot with voice, avatar, and screenshare across four operating modes.

by johnpatternai21 installs8 stars

Generate or update Figma screens from code by composing your published design system's components, variables, and styles.

by OpenAI27.5k stars

More from wpank

Browse all skills

Systematic code review patterns covering security, performance, maintainability, correctness, and testing — with severity levels, structured feedback guidance, review process, and anti-patterns to avoid. Use when reviewing PRs, establishing review standards, or improving review quality.

by wpank554 installs20 stars

Pragmatic coding standards for writing clean, maintainable code — naming, functions, structure, anti-patterns, and pre-edit safety checks. Use when writing new code, refactoring existing code, reviewing code quality, or establishing coding standards.

by wpank198 installs6 stars

Build reliable, fast E2E test suites with Playwright and Cypress. Critical user journey coverage, flaky test elimination, CI/CD integration.

by wpank336 installs6 stars

Build scalable, themable Tailwind CSS component libraries using CVA for variants, compound components, design tokens, dark mode, and responsive grids.

by wpank217 installs9 stars

Create software diagrams using Mermaid syntax. Use when users need to create, visualize, or document software through diagrams including class diagrams, sequence diagrams, flowcharts, ERDs, C4 architecture diagrams, state diagrams, git graphs, and other diagram types. Triggers include requests to diagram, visualize, model, map out, or show the flow of a system.

by wpank250 installs5 stars

Provides backend architecture patterns (Clean Architecture, Hexagonal, DDD) for building maintainable, testable, and scalable systems with clear layering and...

by wpank152 installs7 stars