| 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();
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' }
};
class ApiError extends Error {
() {
(message);
. = code;
. = statusCode || [code]?. || ;
. = details;
. = ().();
}
}
app.( {
requestId = req. || ;
traceId = req.;
(err, {
requestId,
traceId,
: req.,
: req.,
: req.,
: req.?.
});
(err ) {
res.(err.).((err, requestId, traceId));
}
(err && err) {
apiError = (, , );
res.().((apiError, requestId, traceId));
}
(err. === ) {
details = .(err.).( ({
field,
: err.[field].,
:
}));
apiError = (, , , details);
res.().((apiError, requestId, traceId));
}
(err. === ) {
apiError = (, , );
res.().((apiError, requestId, traceId));
}
internalError = (, , );
res.().((internalError, requestId, traceId));
});
() {
{
: {
: error.,
: error.,
: error.,
requestId,
: error.,
...(error. && { : error. }),
traceId
}
};
}
() {
logData = {
: ().(),
: error.,
: error.,
: error.,
: error.,
context
};
(error. >= ) {
.(, .(logData));
(logData);
} (error. >= ) {
.(, .(logData));
}
}
app.(, (req, res, next) => {
{
{ email, firstName, lastName } = req.;
(!email || !firstName || !lastName) {
(
,
,
,
[
!email && { : , : },
!firstName && { : , : },
!lastName && { : , : }
].()
);
}
existing = .({ email });
(existing) {
(, , );
}
user = .({ email, firstName, lastName });
res.().({ : user });
} (error) {
(error);
}
});
= () => {
.((req, res, next)).(next);
};
app.(, ( (req, res) => {
user = .(req..);
(!user) {
(, , );
}
res.({ : user });
}));
process.(, {
.(, reason);
({ : , 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,
: trace_id,
: error.details error.details
}
}
():
request_id = request.headers.get(, )
trace_id = request.headers.get()
log_error(error, {
: request_id,
: trace_id,
: request.method,
: request.path
})
response = jsonify(format_error(error, request_id, trace_id))
response, error.status_code
():
request_id =
api_error = APIError(, , )
jsonify(format_error(api_error, request_id, )),
():
request_id =
api_error = APIError(, , )
jsonify(format_error(api_error, request_id, )),
():
request_id =
logger.error(, exc_info=)
api_error = APIError(, , )
jsonify(format_error(api_error, request_id, )),
():
log_entry = {
: datetime.utcnow().isoformat(),
: error.code,
: error.message,
: error.status_code,
: context
}
error.status_code >= :
logger.error(log_entry)
error.status_code >= :
logger.warning(log_entry)
():
data = request.get_json()
data:
APIError(, , )
errors = []
data.get():
errors.append({: , : })
data.get():
errors.append({: , : })
errors:
APIError(, , , errors)
:
user = User.create(**data)
jsonify({: user.to_dict()}),
IntegrityError:
APIError(, , )
():
user = User.query.get(user_id)
user:
APIError(, , )
jsonify({: user.to_dict()})
4. Error Recovery Strategies
class CircuitBreaker {
constructor(failureThreshold = 5, timeout = 60000) {
this.failureCount = 0;
this.failureThreshold = failureThreshold;
this.timeout = timeout;
this.state = 'CLOSED';
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;
} (error) {
.();
error;
}
}
() {
. = ;
. = ;
}
() {
.++;
(. >= .) {
. = ;
. = .() + .;
}
}
}
() {
( attempt = ; attempt < maxRetries; attempt++) {
{
();
} (error) {
(attempt === maxRetries - ) error;
delay = .(, attempt) * ;
( (resolve, delay));
}
}
}
5. Error Monitoring
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
});
}
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.(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