api-error-handlingError 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.
Install via ClawdBot CLI:
clawdbot install wpank/api-error-handlingShip resilient software. Handle errors at boundaries, fail fast and loud, never swallow exceptions silently.
| Principle | Description |
|-----------|-------------|
| Fail Fast | Detect errors early — validate inputs at the boundary, not deep in business logic |
| Fail Loud | Errors must be visible — log them, surface them, alert on them |
| Handle at Boundaries | Catch and translate errors at layer boundaries (controller, middleware, gateway) |
| Let It Crash | For unrecoverable state, crash and restart (Erlang/OTP philosophy) |
| Be Specific | Catch specific error types, never bare catch or except |
| Provide Context | Every error carries enough context to diagnose without reproducing |
Operational errors — network timeouts, invalid user input, file not found, DB connection lost. Handle gracefully.
Programmer errors — TypeError, 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 | Mechanism | Anti-Pattern |
|----------|-----------|-------------|
| JavaScript | try/catch, Promise.catch, Error subclasses | .catch(() => {}) swallowing errors |
| Python | Exceptions, context managers (with) | Bare except: catching everything |
| Go | error returns, errors.Is/As, fmt.Errorf wrapping | _ = riskyFunction() ignoring error |
| Rust | Result, Option, ? operator | .unwrap() in production code |
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 });
}
}
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
}
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);
});
import { ErrorBoundary } from 'react-error-boundary';
function ErrorFallback({ error, resetErrorBoundary }) {
return (
<div role="alert">
<h2>Something went wrong</h2>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
);
}
<ErrorBoundary FallbackComponent={ErrorFallback} onReset={() => queryClient.clear()}>
<App />
</ErrorBoundary>
| Pattern | When to Use | Config |
|---------|-------------|--------|
| Exponential Backoff | Transient failures (network, 503) | Base 1s, max 30s, factor 2x |
| Backoff + Jitter | Multiple clients retrying | Random ±30% on each delay |
| Circuit Breaker | Downstream service failing repeatedly | Open after 5 failures, half-open after 30s |
| Bulkhead | Isolate failures to prevent cascade | Limit concurrent calls per service |
| Timeout | Prevent indefinite hangs | Connect 5s, read 30s, total 60s |
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';
}
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;
}
}
}
| Status | Name | When to Use |
|--------|------|-------------|
| 400 | Bad Request | Malformed syntax, invalid JSON |
| 401 | Unauthorized | Missing or invalid authentication |
| 403 | Forbidden | Authenticated but insufficient permissions |
| 404 | Not Found | Resource does not exist |
| 409 | Conflict | Request conflicts with current state |
| 422 | Unprocessable Entity | Valid syntax but semantic errors |
| 429 | Too Many Requests | Rate limit exceeded (include Retry-After) |
| 500 | Internal Server Error | Unexpected server failure |
| 502 | Bad Gateway | Upstream returned invalid response |
| 503 | Service Unavailable | Temporarily overloaded or maintenance |
{
"error": {
"code": "VALIDATION_ERROR",
"message": "The request body contains invalid fields.",
"details": [
{ "field": "email", "message": "Must be a valid email address" }
],
"requestId": "req_abc123xyz"
}
}
| Strategy | Example |
|----------|---------|
| Fallback values | Show cached avatar when image service is down |
| Feature flags | Disable unstable recommendation engine |
| Cached responses | Serve stale data with X-Cache: STALE header |
| Partial response | Return 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 })),
};
}
| Practice | Implementation |
|----------|---------------|
| Structured logging | JSON: level, message, error, requestId, userId, timestamp |
| Error tracking | Sentry, Datadog, Bugsnag — automatic capture with source maps |
| Alert thresholds | Error rate > 1%, P99 latency > 2s, 5xx spike |
| Correlation IDs | Pass requestId through all service calls |
| Log levels | error = needs attention, warn = degraded, info = normal, debug = dev |
| Anti-Pattern | Fix |
|-------------|-----|
| Swallowing errors catch (e) {} | Log and re-throw, or handle explicitly |
| Generic catch-all at every level | Catch specific types, let unexpected errors bubble |
| Error as control flow | Use conditionals, return values, or option types |
| Stringly-typed errors throw "wrong" | Throw Error objects with codes and context |
| Logging and throwing | Log at the boundary only, or wrap and re-throw |
| Catch-and-return-null | Return Result type, throw, or return error object |
| Ignoring Promise rejections | Always await or attach .catch() |
| Exposing internals | Sanitize responses; log details server-side only |
catch (e) {} hides bugs and causes silent data corruptionthrow 'error' has no stack trace, no type, no contextawait or attach .catch()Generated Feb 24, 2026
Implement error handling for an e-commerce API to manage payment gateway failures, inventory mismatches, and user input validation. Use retry strategies for transient network errors and structured logging to track operational issues, ensuring orders are processed reliably and failures are surfaced for monitoring.
Apply error boundaries and circuit breakers in a FinTech microservices system to isolate failures in services like transaction processing or fraud detection. Use specific error types and graceful degradation to maintain system stability during partial outages, preventing cascading failures across interconnected services.
Handle errors in a healthcare platform integrating data from multiple sources, such as EHR systems and lab results. Implement validation errors for data inconsistencies, operational error handling for network timeouts, and structured logging to comply with regulatory requirements while ensuring data integrity and patient safety.
Use error handling patterns in a real-time chat app to manage WebSocket disconnections, message delivery failures, and user authentication errors. Apply exponential backoff with jitter for reconnection attempts and error boundaries in the frontend to provide a seamless user experience during intermittent network issues.
Implement error strategies in a SaaS project management tool to handle API rate limiting, database connection losses, and file upload errors. Use circuit breakers for third-party integrations and specific error subclasses to provide clear feedback to users, enhancing reliability and reducing support tickets.
Offer error handling as part of a premium API service tier, providing advanced retry logic, circuit breakers, and detailed error analytics to enterprise clients. Revenue is generated through monthly subscriptions based on usage tiers and additional support services for custom error strategies.
Provide consulting services to help companies design and implement error handling strategies, including code reviews, workshops, and best practices training. Revenue comes from project-based fees, hourly rates, and packaged training programs tailored to specific industries like finance or healthcare.
Develop and maintain open-source error handling libraries or frameworks, monetizing through premium features such as advanced monitoring dashboards, enterprise support, and integration plugins. Revenue is generated via licensing fees for commercial use and paid support contracts.
💬 Integration Tip
Integrate error handling early in the development cycle by setting up middleware for logging and boundaries, and use specific error types to simplify debugging and monitoring across different layers of your application.
Use the mcporter CLI to list, configure, auth, and call MCP servers/tools directly (HTTP or stdio), including ad-hoc servers, config edits, and CLI/type generation.
Connect to 100+ APIs (Google Workspace, Microsoft 365, GitHub, Notion, Slack, Airtable, HubSpot, etc.) with managed OAuth. Use this skill when users want to...
Build, debug, and deploy websites using HTML, CSS, JavaScript, and modern frameworks following production best practices.
YouTube Data API integration with managed OAuth. Search videos, manage playlists, access channel data, and interact with comments. Use this skill when users want to interact with YouTube. For other third party apps, use the api-gateway skill (https://clawhub.ai/byungkyu/api-gateway).
Scaffold, test, document, and debug REST and GraphQL APIs. Use when the user needs to create API endpoints, write integration tests, generate OpenAPI specs, test with curl, mock APIs, or troubleshoot HTTP issues.
Search for jobs across LinkedIn, Indeed, Glassdoor, ZipRecruiter, Google Jobs, Bayt, Naukri, and BDJobs using the JobSpy MCP server.