| name | error-handling-guidelines |
| description | Error handling guidelines for TypeScript including custom error classes, try-catch patterns, error boundaries, API error handling, and recovery patterns. Auto-loaded when working with error handling code. |
| category | guideline |
| user-invocable | false |
Error Handling Guidelines
Core Principles
- Fail fast - Detect errors early, don't let them propagate silently
- Fail gracefully - Provide meaningful feedback, don't crash unnecessarily
- Be specific - Use typed errors with clear messages
- Don't swallow errors - Always log or handle, never ignore
- User-friendly messages - Technical details for logs, human messages for users
Error Types
Custom Error Classes
export class AppError extends Error {
constructor(
message: string,
public readonly code: string,
public readonly statusCode: number = 500,
public readonly isOperational: boolean = true
) {
super(message);
this.name = this.constructor.name;
Error.captureStackTrace(this, this.constructor);
}
}
export class ValidationError extends AppError {
constructor(
message: string,
public readonly fields: Array<{ field: string; message: string }>
) {
super(message, 'VALIDATION_ERROR', 400);
}
}
export class NotFoundError extends AppError {
constructor(resource: string, id: string) {
super(`${resource} with id '${id}' not found`, 'NOT_FOUND', 404);
}
}
export class UnauthorizedError extends AppError {
constructor(message = 'Authentication required') {
super(message, 'UNAUTHORIZED', 401);
}
}
export class ForbiddenError extends AppError {
constructor(message = 'Access denied') {
super(message, 'FORBIDDEN', 403);
}
}
export class ConflictError extends AppError {
constructor(message: string) {
super(message, 'CONFLICT', 409);
}
}
export class RateLimitError extends AppError {
constructor(public readonly retryAfter: number) {
super('Rate limit exceeded', 'RATE_LIMIT', 429);
}
}
Error Code Catalog
export const ErrorCodes = {
VALIDATION_ERROR: 'VALIDATION_ERROR',
INVALID_INPUT: 'INVALID_INPUT',
MISSING_FIELD: 'MISSING_FIELD',
UNAUTHORIZED: 'UNAUTHORIZED',
INVALID_TOKEN: 'INVALID_TOKEN',
TOKEN_EXPIRED: 'TOKEN_EXPIRED',
FORBIDDEN: 'FORBIDDEN',
INSUFFICIENT_PERMISSIONS: 'INSUFFICIENT_PERMISSIONS',
NOT_FOUND: 'NOT_FOUND',
CONFLICT: 'CONFLICT',
ALREADY_EXISTS: 'ALREADY_EXISTS',
EXTERNAL_SERVICE_ERROR: 'EXTERNAL_SERVICE_ERROR',
NETWORK_ERROR: 'NETWORK_ERROR',
TIMEOUT: 'TIMEOUT',
INTERNAL_ERROR: 'INTERNAL_ERROR',
DATABASE_ERROR: 'DATABASE_ERROR',
} as const;
Try-Catch Patterns
Basic Pattern
try {
await riskyOperation();
} catch (error: unknown) {
if (error instanceof AppError) {
logger.warn('Operation failed', { code: error.code, message: error.message });
throw error;
}
if (error instanceof Error) {
logger.error('Unexpected error', { error: error.message, stack: error.stack });
throw new AppError('An unexpected error occurred', 'INTERNAL_ERROR', 500, false);
}
logger.error('Unknown error type', { error });
throw new AppError('An unexpected error occurred', 'INTERNAL_ERROR', 500, false);
}
Never Swallow Errors
try {
await saveData();
} catch (error) {
}
try {
await saveData();
} catch {
return null;
}
try {
await saveData();
} catch (error) {
logger.error('Failed to save data', { error });
throw error;
}
try {
return await fetchFromCache();
} catch (error) {
logger.warn('Cache miss, fetching from source', { error });
return await fetchFromSource();
}
Async Error Handling
fetchData()
.then(processData)
.then(saveData)
.catch(error => {
logger.error('Pipeline failed', { error });
throw error;
});
async function pipeline() {
try {
const data = await fetchData();
const processed = await processData(data);
return await saveData(processed);
} catch (error) {
logger.error('Pipeline failed', { error });
throw error;
}
}
try {
const [users, orders] = await Promise.all([
fetchUsers(),
fetchOrders(),
]);
} catch (error) {
}
const results = await Promise.allSettled([
fetchUsers(),
fetchOrders(),
]);
results.forEach( {
(result. === ) {
logger.(, { : result. });
}
});
Known Gotchas
Error Type in Catch
try {
await operation();
} catch (error) {
if (error instanceof Error) {
console.log(error.message);
}
}
Async Errors in Callbacks
array.forEach(async item => {
await processItem(item);
});
await Promise.all(array.map(async item => {
await processItem(item);
}));
for (const item of array) {
await processItem(item);
}
Error Stack Traces
try {
await operation();
} catch (error) {
const wrappedError = new AppError('Wrapped error', 'WRAPPED');
if (error instanceof Error) {
wrappedError.cause = error;
}
throw wrappedError;
}
Unhandled Promise Rejections
process.on('unhandledRejection', (reason, promise) => {
logger.error('Unhandled promise rejection', { reason });
});
window.addEventListener('unhandledrejection', event => {
logger.error('Unhandled promise rejection', { reason: event.reason });
});
Additional References