| name | implement-error-handling |
| description | Implement standardized API error handling |
| shortcut | erro |
Implement Error Handling
Create standardized, production-ready error handling middleware with proper HTTP status codes, consistent error formats, and comprehensive logging. This command generates custom error classes, middleware, and error recovery strategies for Node.js, Python, and other backend frameworks.
Design Decisions
Why standardized error handling matters:
- Client experience: Consistent error formats make client integration predictable
- Debugging: Structured errors with context accelerate troubleshooting
- Security: Proper error handling prevents information leakage in production
- Monitoring: Standardized errors integrate cleanly with observability tools
Alternatives considered:
- HTTP-only errors: Simpler but lacks context for debugging
- Framework defaults: Inconsistent across endpoints, lacks business context
- Exception-based only: Can leak sensitive information, harder to test
This approach balances: Developer experience, security, debugging, and API consistency.
When to Use
Use this command when:
- Starting a new API project that needs error handling
- Refactoring inconsistent error responses across endpoints
- Adding error monitoring and alerting to an existing API
- Migrating from generic framework errors to business-specific errors
- Implementing error handling for microservices that need consistency
Don't use when:
- Your framework's default error handling already meets your needs
- Building proof-of-concept code without production requirements
- Working with legacy systems that can't adopt new error formats
Prerequisites
- Node.js 16+ (for Express/Fastify examples) or Python 3.8+ (for Flask/FastAPI)
- Existing API project structure
- Basic understanding of HTTP status codes (4xx client errors, 5xx server errors)
- (Optional) Logging framework installed (Winston, Pino, Structlog)
- (Optional) Monitoring service (Sentry, DataDog, New Relic)
Process
-
Generate Custom Error Classes
- Create base error class with HTTP status codes
- Define specific error types (ValidationError, NotFoundError, AuthError)
- Add error codes for programmatic handling
-
Build Error Middleware
- Catch all errors in centralized handler
- Format errors consistently (RFC 7807 Problem Details or custom)
- Handle both operational and programmer errors
-
Configure Environment-Specific Behavior
- Development: Include stack traces, detailed messages
- Production: Sanitize errors, log internally, return safe messages
-
Integrate Logging
- Log all errors with context (request ID, user, endpoint)
- Add severity levels (error, warning, critical)
- Include metadata for troubleshooting
-
Add Error Recovery
- Graceful degradation strategies
- Retry logic for transient failures
- Circuit breaker integration
Output Format
Express.js Error Handler
class AppError extends Error {
constructor(message, statusCode, errorCode = null) {
super(message);
this.statusCode = statusCode;
this.errorCode = errorCode;
this.isOperational = true;
this.timestamp = new Date().toISOString();
Error.captureStackTrace(this, this.constructor);
}
}
class ValidationError extends AppError {
constructor(message, errors = []) {
super(message, 400, 'VALIDATION_ERROR');
this.errors = errors;
}
}
class NotFoundError extends AppError {
constructor(resource) {
super(`${resource} not found`, 404, 'NOT_FOUND');
this. = resource;
}
}
{
() {
(message, , );
}
}
{
() {
(message, , );
}
}
. = { , , , , };
logger = ();
= () => {
statusCode = err. || ;
message = err. || ;
logger.({
: err.,
statusCode,
: err.,
: err.,
: req.,
: req.,
: req.,
: req.?.,
: req.
});
(process.. === && !err.) {
message = ;
statusCode = ;
}
res.(statusCode).({
: {
message,
: err.,
statusCode,
: err. || ().(),
: req.,
...(process.. === && { : err. }),
...(err. && { : err. })
}
});
};
. = errorHandler;
FastAPI (Python) Error Handler
from typing import Optional, Any, Dict
from fastapi import HTTPException
from datetime import datetime
class AppError(HTTPException):
def __init__(
self,
status_code: int,
message: str,
error_code: Optional[str] = None,
details: Optional[Dict[str, Any]] = None
):
self.status_code = status_code
self.message = message
self.error_code = error_code
self.details = details or {}
self.timestamp = datetime.utcnow().isoformat()
super().__init__(status_code=status_code, detail=message)
class ValidationError(AppError):
def __init__(self, message: str, errors: list = None):
super().__init__(
status_code=400,
message=message,
error_code="VALIDATION_ERROR",
details={"errors": errors or []}
)
():
():
().__init__(
status_code=,
message=,
error_code=,
details={: resource}
)
():
():
().__init__(
status_code=,
message=message,
error_code=
)
fastapi FastAPI, Request
fastapi.responses JSONResponse
logging
app = FastAPI()
logger = logging.getLogger(__name__)
():
logger.error(
,
extra={
: exc.status_code,
: exc.error_code,
: request.url.path,
: request.method
}
)
JSONResponse(
status_code=exc.status_code,
content={
: {
: exc.message,
: exc.error_code,
: exc.status_code,
: exc.timestamp,
: (request.url.path),
**exc.details
}
}
)
():
logger.exception(, exc_info=exc)
JSONResponse(
status_code=,
content={
: {
: ,
: ,
: ,
: datetime.utcnow().isoformat()
}
}
)
Example Usage
Example 1: Handling Validation Errors
const { ValidationError } = require('./errors/AppError');
const { body, validationResult } = require('express-validator');
router.post('/users',
[
body('email').isEmail().normalizeEmail(),
body('password').isLength({ min: 8 }),
body('name').trim().notEmpty()
],
async (req, res, next) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
throw new ValidationError('Validation failed', errors.array());
}
const user = await createUser(req.body);
res.status(201).json(user);
} catch (error) {
next(error);
}
}
);
Example 2: Resource Not Found
from fastapi import APIRouter
from errors.exceptions import NotFoundError
router = APIRouter()
@router.get("/users/{user_id}")
async def get_user(user_id: int):
user = await db.get_user(user_id)
if not user:
raise NotFoundError("User")
return user
Example 3: Error with Retry Strategy
const { AppError } = require('./errors/AppError');
const retry = require('async-retry');
async function callExternalAPI(endpoint) {
return retry(async (bail, attempt) => {
try {
const response = await fetch(endpoint);
if (!response.ok) {
if (response.status >= 400 && response.status < 500) {
bail(new AppError('External API error', response.status));
return;
}
throw new Error(`API returned ${response.status}`);
}
return response.json();
} catch (error) {
console.log(`Attempt ${attempt} failed: ${error.message}`);
throw error;
}
}, {
retries: 3,
minTimeout: ,
:
});
}
Error Handling
Common issues and solutions:
Problem: Errors logged multiple times
- Cause: Error handlers at multiple middleware layers
- Solution: Log only in the central error handler, not in route handlers
Problem: Stack traces visible in production
- Cause: Environment check not working correctly
- Solution: Verify
NODE_ENV=production is set, check conditional logic
Problem: Lost error context (request ID, user info)
- Cause: Context not attached to request object
- Solution: Use middleware to attach request ID, user before error handler
Problem: Async errors not caught
- Cause: Missing try-catch or next() in async routes
- Solution: Use express-async-errors or wrap all async routes
Problem: Database connection errors crashing app
- Cause: Uncaught promise rejections
- Solution: Add process-level error handlers:
process.on('unhandledRejection', (reason, promise) => {
logger.error('Unhandled Rejection', { reason, promise });
});
process.on('uncaughtException', (error) => {
logger.error('Uncaught Exception', { error });
process.exit(1);
});
Configuration
Error Handler Options
const errorHandlerOptions = {
showStack: process.env.NODE_ENV === 'development',
logLevel: {
operational: 'error',
programmer: 'critical'
},
reportToMonitoring: process.env.NODE_ENV === 'production',
sanitizeFields: ['password', 'ssn', 'creditCard'],
formatters: {
json: (err) => ({ error: err.toJSON() }),
xml: (err) => convertToXML(err)
}
};
Best Practices
DO:
- Log all errors with sufficient context for debugging
- Use specific error classes for different failure scenarios
- Return consistent error format across all endpoints
- Sanitize errors in production (no stack traces, no sensitive data)
- Include correlation IDs for tracing errors across services
- Test error handling as thoroughly as success cases
- Document error codes and responses in API documentation
DON'T:
- Expose internal implementation details in error messages
- Log sensitive data (passwords, tokens, PII)
- Ignore errors or swallow exceptions silently
- Use generic "Error occurred" messages without context
- Return different error formats from different endpoints
- Forget to handle async errors (use try-catch or error middleware)
- Let programmer errors (bugs) be handled the same as operational errors
TIPS:
- Use error codes clients can handle programmatically (
INSUFFICIENT_FUNDS, RATE_LIMIT_EXCEEDED)
- Provide actionable error messages ("Email already registered, try logging in" not "Duplicate entry")
- Group related errors with similar status codes (all validation = 400, all auth = 401/403)
- Consider RFC 7807 Problem Details format for standardization
- Add request IDs to errors for log correlation
- Monitor error rates and alert on anomalies
Related Commands
/validate-api-responses - Validate API responses match schemas
/setup-logging - Configure structured logging for errors
/scan-api-security - Scan for security vulnerabilities in error handling
/create-monitoring - Set up error monitoring dashboards
/generate-rest-api - Generate REST API with built-in error handling
Performance Considerations
- Error creation overhead: Custom error classes add minimal overhead (~1-2ms)
- Stack trace capture: Can be expensive in hot paths, consider disabling in production
- Logging: Use async logging to avoid blocking request threads
- Monitoring: Batch error reports to external services (Sentry, DataDog)
- Memory leaks: Ensure errors don't hold references to large objects
Optimization strategies:
if (process.env.NODE_ENV === 'production') {
Error.stackTraceLimit = 0;
}
const logger = winston.createLogger({
transports: [
new winston.transports.File({
filename: 'error.log',
level: 'error'
})
]
});
Security Considerations
- Information disclosure: Never expose database errors, file paths, or internal IDs
- Error-based enumeration: Avoid revealing if resources exist ("Invalid credentials" not "User not found")
- DoS via errors: Rate limit error-triggering requests
- Log injection: Sanitize user input before logging
- Sensitive data in logs: Redact passwords, tokens, SSNs before logging
Security checklist:
throw new Error(`User ${userId} not found in users table`);
throw new NotFoundError('User');
if (!user) throw new Error('User not found');
if (password !== user.password) throw new Error('Wrong password');
if (!user || password !== user.password) {
throw new UnauthorizedError('Invalid credentials');
}
Troubleshooting
Error handler not catching errors:
- Ensure error handler is registered AFTER all routes
- Check async routes call
next(error) or use express-async-errors
- Verify middleware order: routes → 404 handler → error handler
Errors not logged:
- Check logger configuration and file permissions
- Verify log level settings (error should be logged at all levels)
- Test logger independently before integration
Production errors too verbose:
- Verify NODE_ENV=production environment variable
- Check conditional stack trace logic
- Test with actual production config locally
Error monitoring not working:
- Verify API keys for Sentry/DataDog/New Relic
- Check network connectivity to monitoring service
- Test error reporting independently
Version History
- 1.0.0 (2025-10-11): Initial release with Express and FastAPI examples
- Custom error classes (ValidationError, NotFoundError, AuthError)
- Environment-specific error formatting
- Structured logging integration
- Error recovery patterns