Error Handling
Custom Error Classes (TypeScript)
class AppError extends Error {
constructor(
message: string,
public readonly statusCode: number = 500,
public readonly code: string = 'INTERNAL_ERROR',
public readonly details?: unknown,
) {
super(message);
this.name = this.constructor.name;
}
}
class NotFoundError extends AppError {
constructor(resource: string, id: string) {
super(`${resource} ${id} not found`, 404, 'NOT_FOUND');
}
}
class ValidationError extends AppError {
constructor(errors: { field: string; message: string }[]) {
super('Validation failed', 400, 'VALIDATION_ERROR', errors);
}
}
Global Error Handler (Express)
// Must have 4 parameters for Express to recognize as error middleware
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
if (err instanceof AppError) {
return res.status(err.statusCode).json({
type: `https://api.example.com/errors/${err.code}`,
title: err.message,
status: err.statusCode,
detail: err.details,
instance: req.originalUrl,
});
}
// Unexpected errors
logger.error('Unhandled error', { error: err, path: req.originalUrl });
res.status(500).json({
type: 'https://api.example.com/errors/INTERNAL_ERROR',
title: 'Internal server error',
status: 500,
});
});
Problem Details (RFC 9457)
{
"type": "https://api.example.com/errors/INSUFFICIENT_FUNDS",
"title": "Insufficient funds",
"status": 422,
"detail": "Account balance is $10.00, but transfer requires $25.00",
"instance": "/transfers/abc123"
}
React Error Boundaries
import { Component, ErrorInfo, ReactNode } from 'react';
class ErrorBoundary extends Component<
{ fallback: ReactNode; children: ReactNode },
{ hasError: boolean }
> {
state = { hasError: false };
static getDerivedStateFromError() { return { hasError: true }; }
componentDidCatch(error: Error, info: ErrorInfo) {
reportError(error, info.componentStack);
}
render() {
return this.state.hasError ? this.props.fallback : this.props.children;
}
}
// Usage
<ErrorBoundary fallback={<ErrorPage />}>
<App />
</ErrorBoundary>
Spring Boot (@ControllerAdvice)
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ProblemDetail handleNotFound(ResourceNotFoundException ex) {
ProblemDetail pd = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getMessage());
pd.setType(URI.create("https://api.example.com/errors/NOT_FOUND"));
pd.setTitle("Resource not found");
return pd;
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ProblemDetail handleValidation(MethodArgumentNotValidException ex) {
ProblemDetail pd = ProblemDetail.forStatus(HttpStatus.BAD_REQUEST);
pd.setTitle("Validation failed");
Map<String, String> errors = new HashMap<>();
ex.getFieldErrors().forEach(e -> errors.put(e.getField(), e.getDefaultMessage()));
pd.setProperty("errors", errors);
return pd;
}
}
FastAPI Exception Handlers
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse
@app.exception_handler(AppError)
async def app_error_handler(request: Request, exc: AppError):
return JSONResponse(status_code=exc.status_code, content={
"type": f"https://api.example.com/errors/{exc.code}",
"title": exc.message,
"status": exc.status_code,
})
Anti-Patterns
| Anti-Pattern | Fix |
|---|
| Catching all exceptions silently | Log and re-throw or return structured error |
| Exposing stack traces to clients | Return generic message, log details server-side |
| String-based error checking | Use typed error classes or error codes |
| No global error handler | Add framework-level catch-all middleware |
| Inconsistent error response format | Use Problem Details (RFC 9457) everywhere |
Production Checklist
- Custom error classes with status codes
- Global error handler catches all unhandled errors
- Problem Details format for API errors
- Stack traces never exposed to clients
- React Error Boundaries around route-level components
- Errors logged with context (request ID, user, path)