Claude Code Plugins

Community-maintained marketplace

Feedback
4
0

Implement comprehensive API error handling with standardized error responses, logging, monitoring, and user-friendly messages. Use when building resilient APIs, debugging issues, or improving error reporting.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name api-error-handling
description Implement comprehensive API error handling with standardized error responses, logging, monitoring, and user-friendly messages. Use when building resilient APIs, debugging issues, or improving error reporting.

API Error Handling

Overview

Build robust error handling systems with standardized error responses, detailed logging, error categorization, and user-friendly error messages.

When to Use

  • Handling API errors consistently
  • Debugging production issues
  • Implementing error recovery strategies
  • Monitoring error rates
  • Providing meaningful error messages to clients
  • Tracking error patterns

Instructions

1. Standardized Error Response Format

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Input validation failed",
    "statusCode": 422,
    "requestId": "req_abc123xyz789",
    "timestamp": "2025-01-15T10:30:00Z",
    "details": [
      {
        "field": "email",
        "message": "Invalid email format",
        "code": "INVALID_EMAIL"
      },
      {
        "field": "age",
        "message": "Must be at least 18",
        "code": "VALUE_OUT_OF_RANGE"
      }
    ],
    "path": "/api/users",
    "method": "POST",
    "traceId": "trace_001"
  }
}

2. Node.js Error Handling

const express = require('express');
const app = express();

// Error codes and mappings
const ERROR_CODES = {
  VALIDATION_ERROR: { status: 422, message: 'Validation failed' },
  NOT_FOUND: { status: 404, message: 'Resource not found' },
  UNAUTHORIZED: { status: 401, message: 'Authentication required' },
  FORBIDDEN: { status: 403, message: 'Access denied' },
  CONFLICT: { status: 409, message: 'Resource conflict' },
  RATE_LIMITED: { status: 429, message: 'Too many requests' },
  INTERNAL_ERROR: { status: 500, message: 'Internal server error' },
  SERVICE_UNAVAILABLE: { status: 503, message: 'Service unavailable' }
};

// Custom error class
class ApiError extends Error {
  constructor(code, message, statusCode = null, details = null) {
    super(message);
    this.code = code;
    this.statusCode = statusCode || ERROR_CODES[code]?.status || 500;
    this.details = details;
    this.timestamp = new Date().toISOString();
  }
}

// Global error handler middleware
app.use((err, req, res, next) => {
  const requestId = req.id || `req_${Date.now()}`;
  const traceId = req.traceId;

  // Log error
  logError(err, {
    requestId,
    traceId,
    method: req.method,
    path: req.path,
    query: req.query,
    userId: req.user?.id
  });

  // Handle different error types
  if (err instanceof ApiError) {
    return res.status(err.statusCode).json(formatErrorResponse(err, requestId, traceId));
  }

  if (err instanceof SyntaxError && 'body' in err) {
    const apiError = new ApiError('VALIDATION_ERROR', 'Invalid JSON', 400);
    return res.status(400).json(formatErrorResponse(apiError, requestId, traceId));
  }

  if (err.name === 'ValidationError') {
    const details = Object.keys(err.errors).map(field => ({
      field,
      message: err.errors[field].message,
      code: 'VALIDATION_FAILED'
    }));
    const apiError = new ApiError('VALIDATION_ERROR', 'Validation failed', 422, details);
    return res.status(422).json(formatErrorResponse(apiError, requestId, traceId));
  }

  if (err.name === 'CastError') {
    const apiError = new ApiError('NOT_FOUND', 'Invalid resource ID', 404);
    return res.status(404).json(formatErrorResponse(apiError, requestId, traceId));
  }

  // Unknown error
  const internalError = new ApiError('INTERNAL_ERROR', 'An unexpected error occurred', 500);
  res.status(500).json(formatErrorResponse(internalError, requestId, traceId));
});

// Error response formatter
function formatErrorResponse(error, requestId, traceId) {
  return {
    error: {
      code: error.code,
      message: error.message,
      statusCode: error.statusCode,
      requestId,
      timestamp: error.timestamp,
      ...(error.details && { details: error.details }),
      traceId
    }
  };
}

// Error logger
function logError(error, context) {
  const logData = {
    timestamp: new Date().toISOString(),
    errorCode: error.code,
    errorMessage: error.message,
    statusCode: error.statusCode,
    stack: error.stack,
    context
  };

  // Log to different levels based on severity
  if (error.statusCode >= 500) {
    console.error('[ERROR]', JSON.stringify(logData));
    // Send to error tracking service (Sentry, etc)
    trackError(logData);
  } else if (error.statusCode >= 400) {
    console.warn('[WARN]', JSON.stringify(logData));
  }
}

// Route with error handling
app.post('/api/users', async (req, res, next) => {
  try {
    const { email, firstName, lastName } = req.body;

    // Validation
    if (!email || !firstName || !lastName) {
      throw new ApiError(
        'VALIDATION_ERROR',
        'Missing required fields',
        422,
        [
          !email && { field: 'email', message: 'Email is required' },
          !firstName && { field: 'firstName', message: 'First name is required' },
          !lastName && { field: 'lastName', message: 'Last name is required' }
        ].filter(Boolean)
      );
    }

    // Check for conflicts
    const existing = await User.findOne({ email });
    if (existing) {
      throw new ApiError('CONFLICT', 'Email already exists', 409);
    }

    const user = await User.create({ email, firstName, lastName });
    res.status(201).json({ data: user });
  } catch (error) {
    next(error);
  }
});

// Async route wrapper
const asyncHandler = (fn) => (req, res, next) => {
  Promise.resolve(fn(req, res, next)).catch(next);
};

app.get('/api/users/:id', asyncHandler(async (req, res) => {
  const user = await User.findById(req.params.id);

  if (!user) {
    throw new ApiError('NOT_FOUND', 'User not found', 404);
  }

  res.json({ data: user });
}));

// Handle unhandled rejections
process.on('unhandledRejection', (reason, promise) => {
  console.error('Unhandled Rejection:', reason);
  trackError({ type: 'unhandledRejection', reason });
});

3. Python Error Handling (Flask)

from flask import Flask, jsonify, request
from datetime import datetime
import logging
import traceback
from functools import wraps

app = Flask(__name__)
logger = logging.getLogger(__name__)

class APIError(Exception):
    def __init__(self, code, message, status_code=500, details=None):
        super().__init__()
        self.code = code
        self.message = message
        self.status_code = status_code
        self.details = details or []
        self.timestamp = datetime.utcnow().isoformat()

ERROR_CODES = {
    'VALIDATION_ERROR': 422,
    'NOT_FOUND': 404,
    'UNAUTHORIZED': 401,
    'FORBIDDEN': 403,
    'CONFLICT': 409,
    'INTERNAL_ERROR': 500
}

def format_error(error, request_id, trace_id):
    return {
        'error': {
            'code': error.code,
            'message': error.message,
            'statusCode': error.status_code,
            'requestId': request_id,
            'timestamp': error.timestamp,
            'traceId': trace_id,
            'details': error.details if error.details else None
        }
    }

@app.errorhandler(APIError)
def handle_api_error(error):
    request_id = request.headers.get('X-Request-ID', f'req_{int(datetime.utcnow().timestamp())}')
    trace_id = request.headers.get('X-Trace-ID')

    log_error(error, {
        'request_id': request_id,
        'trace_id': trace_id,
        'method': request.method,
        'path': request.path
    })

    response = jsonify(format_error(error, request_id, trace_id))
    return response, error.status_code

@app.errorhandler(400)
def handle_bad_request(error):
    request_id = f'req_{int(datetime.utcnow().timestamp())}'
    api_error = APIError('VALIDATION_ERROR', 'Invalid request', 400)
    return jsonify(format_error(api_error, request_id, None)), 400

@app.errorhandler(404)
def handle_not_found(error):
    request_id = f'req_{int(datetime.utcnow().timestamp())}'
    api_error = APIError('NOT_FOUND', 'Resource not found', 404)
    return jsonify(format_error(api_error, request_id, None)), 404

@app.errorhandler(500)
def handle_internal_error(error):
    request_id = f'req_{int(datetime.utcnow().timestamp())}'
    logger.error(f'Internal error: {error}', exc_info=True)
    api_error = APIError('INTERNAL_ERROR', 'Internal server error', 500)
    return jsonify(format_error(api_error, request_id, None)), 500

def log_error(error, context):
    log_entry = {
        'timestamp': datetime.utcnow().isoformat(),
        'code': error.code,
        'message': error.message,
        'status': error.status_code,
        'context': context
    }

    if error.status_code >= 500:
        logger.error(log_entry)
    elif error.status_code >= 400:
        logger.warning(log_entry)

@app.route('/api/users', methods=['POST'])
def create_user():
    data = request.get_json()

    if not data:
        raise APIError('VALIDATION_ERROR', 'Request body required', 400)

    errors = []
    if not data.get('email'):
        errors.append({'field': 'email', 'message': 'Email is required'})
    if not data.get('firstName'):
        errors.append({'field': 'firstName', 'message': 'First name is required'})

    if errors:
        raise APIError('VALIDATION_ERROR', 'Validation failed', 422, errors)

    try:
        user = User.create(**data)
        return jsonify({'data': user.to_dict()}), 201
    except IntegrityError:
        raise APIError('CONFLICT', 'Email already exists', 409)

@app.route('/api/users/<user_id>')
def get_user(user_id):
    user = User.query.get(user_id)
    if not user:
        raise APIError('NOT_FOUND', 'User not found', 404)
    return jsonify({'data': user.to_dict()})

4. Error Recovery Strategies

// Circuit breaker pattern
class CircuitBreaker {
  constructor(failureThreshold = 5, timeout = 60000) {
    this.failureCount = 0;
    this.failureThreshold = failureThreshold;
    this.timeout = timeout;
    this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
    this.nextAttempt = Date.now();
  }

  async execute(fn) {
    if (this.state === 'OPEN') {
      if (Date.now() < this.nextAttempt) {
        throw new ApiError('SERVICE_UNAVAILABLE', 'Circuit breaker is open', 503);
      }
      this.state = 'HALF_OPEN';
    }

    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

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

  onFailure() {
    this.failureCount++;
    if (this.failureCount >= this.failureThreshold) {
      this.state = 'OPEN';
      this.nextAttempt = Date.now() + this.timeout;
    }
  }
}

// Retry with exponential backoff
async function retryWithBackoff(fn, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (attempt === maxRetries - 1) throw error;

      const delay = Math.pow(2, attempt) * 1000;
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

5. Error Monitoring

// Sentry integration
const Sentry = require('@sentry/node');

Sentry.init({ dsn: process.env.SENTRY_DSN });

function trackError(errorData) {
  Sentry.captureException(new Error(errorData.errorMessage), {
    tags: {
      code: errorData.errorCode,
      status: errorData.statusCode
    },
    extra: errorData.context
  });
}

// Error rate monitoring
const errorMetrics = {
  total: 0,
  byCode: {},
  byStatus: {}
};

function recordError(error) {
  errorMetrics.total++;
  errorMetrics.byCode[error.code] = (errorMetrics.byCode[error.code] || 0) + 1;
  errorMetrics.byStatus[error.statusCode] = (errorMetrics.byStatus[error.statusCode] || 0) + 1;
}

app.get('/metrics/errors', (req, res) => {
  res.json(errorMetrics);
});

Best Practices

✅ DO

  • Use consistent error response format
  • Include request ID for tracing
  • Log with appropriate severity levels
  • Provide actionable error messages
  • Include error details for debugging
  • Use standard HTTP status codes
  • Implement error recovery strategies
  • Monitor error rates
  • Distinguish user vs server errors
  • Handle all error types

❌ DON'T

  • Expose stack traces to clients
  • Return 200 for errors
  • Ignore errors silently
  • Log sensitive data
  • Use vague error messages
  • Mix error handling with business logic
  • Retry all errors indefinitely
  • Expose internal implementation details
  • Return different formats for errors