api-error-handling
Apply when designing error responses, implementing error handlers, and ensuring consistent error format across APIs.
When & Why to Use This Skill
This Claude skill provides a comprehensive framework for designing and implementing standardized API error responses. It leverages industry best practices and standards like RFC 9457 (Problem Details) to ensure consistent error formats across backend services. By providing structured patterns for error class hierarchies, global middleware handlers, and frontend consumption, it helps developers improve API reliability, security, and the overall developer experience (DX).
Use Cases
- Designing a standardized error response structure for a new RESTful API to ensure consistency across multiple microservices.
- Implementing global error handling middleware in Express.js or Next.js to catch exceptions and return machine-readable responses without leaking sensitive stack traces.
- Creating a reusable TypeScript error class hierarchy to manage specific application failures such as validation errors, 404 Not Found, and unauthorized access.
- Developing frontend API clients that can gracefully parse structured error codes to provide actionable feedback to end-users.
- Refactoring legacy backend code to replace inconsistent '200 OK' error responses with appropriate HTTP status codes and standardized error objects.
| name | api-error-handling |
|---|---|
| description | Apply when designing error responses, implementing error handlers, and ensuring consistent error format across APIs. |
| version | 1.1.0 |
| tokens | ~650 |
| confidence | high |
| - https | //datatracker.ietf.org/doc/html/rfc9110#section-6 |
| last_validated | 2025-12-10 |
| next_review | 2025-12-24 |
| tags | [api, error-handling, backend, rest] |
When to Use
Apply when designing error responses, implementing error handlers, and ensuring consistent error format across APIs.
Patterns
Pattern 1: Standard Error Response Format
// Source: https://www.rfc-editor.org/rfc/rfc9457 (Problem Details)
interface ApiError {
error: {
code: string; // Machine-readable code
message: string; // Human-readable message
details?: ErrorDetail[];// Field-level errors
requestId?: string; // For debugging
};
}
interface ErrorDetail {
field: string;
message: string;
code?: string;
}
// Example response
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{ "field": "email", "message": "Invalid email format", "code": "INVALID_FORMAT" },
{ "field": "age", "message": "Must be positive", "code": "INVALID_RANGE" }
],
"requestId": "req_abc123"
}
}
Pattern 2: Error Class Hierarchy
// Source: Best practice pattern
class AppError extends Error {
constructor(
public code: string,
message: string,
public statusCode: number,
public details?: ErrorDetail[]
) {
super(message);
this.name = 'AppError';
}
}
class ValidationError extends AppError {
constructor(details: ErrorDetail[]) {
super('VALIDATION_ERROR', 'Validation failed', 400, details);
}
}
class NotFoundError extends AppError {
constructor(resource: string) {
super('NOT_FOUND', `${resource} not found`, 404);
}
}
class UnauthorizedError extends AppError {
constructor() {
super('UNAUTHORIZED', 'Authentication required', 401);
}
}
Pattern 3: Global Error Handler (Express/Next.js)
// Source: Best practice pattern
function errorHandler(
err: Error,
req: Request,
res: Response,
next: NextFunction
) {
// Log for debugging
console.error('Error:', {
message: err.message,
stack: err.stack,
requestId: req.headers['x-request-id'],
});
if (err instanceof AppError) {
return res.status(err.statusCode).json({
error: {
code: err.code,
message: err.message,
details: err.details,
requestId: req.headers['x-request-id'],
},
});
}
// Unknown error - don't leak details
return res.status(500).json({
error: {
code: 'INTERNAL_ERROR',
message: 'An unexpected error occurred',
requestId: req.headers['x-request-id'],
},
});
}
Pattern 4: Frontend Error Handling
// Source: Best practice pattern
async function apiCall<T>(url: string, options?: RequestInit): Promise<T> {
const response = await fetch(url, options);
if (!response.ok) {
const error = await response.json();
throw new ApiError(error.error.code, error.error.message, error.error.details);
}
return response.json();
}
// Usage with error handling
try {
const user = await apiCall<User>('/api/users/123');
} catch (error) {
if (error instanceof ApiError) {
if (error.code === 'NOT_FOUND') {
showNotification('User not found');
} else if (error.code === 'VALIDATION_ERROR') {
setFormErrors(error.details);
}
}
}
Pattern 5: Error Code Constants
// Source: Best practice pattern
export const ErrorCodes = {
VALIDATION_ERROR: 'VALIDATION_ERROR',
NOT_FOUND: 'NOT_FOUND',
UNAUTHORIZED: 'UNAUTHORIZED',
FORBIDDEN: 'FORBIDDEN',
CONFLICT: 'CONFLICT',
RATE_LIMITED: 'RATE_LIMITED',
INTERNAL_ERROR: 'INTERNAL_ERROR',
} as const;
type ErrorCode = typeof ErrorCodes[keyof typeof ErrorCodes];
Anti-Patterns
- Exposing stack traces - Never in production
- Generic "Error occurred" - Provide actionable messages
- 200 for errors - Use appropriate HTTP status codes
- Inconsistent format - Same structure for all errors
Verification Checklist
- All errors have code + message
- Status codes match error type
- Validation errors include field details
- Stack traces hidden in production
- Request ID for debugging correlation