π¨ Error Handling β
Handling errors gracefully without hiding bugs.
Principles β
- Fail fast β Detect errors as early as possible
- Fail loud β Log errors with context, don't swallow them
- Fail gracefully β Show users a helpful message, not a stack trace
- Fail safely β Don't expose sensitive information in errors
Error handling patterns β
Try-catch (use sparingly) β
typescript
try {
const user = await fetchUser(id)
} catch (error) {
logger.error('Failed to fetch user', { userId: id, error })
throw new AppError('USER_NOT_FOUND', 404)
}Result type (preferred for business logic) β
typescript
type Result<T> = { ok: true; data: T } | { ok: false; error: AppError }Error boundaries (React) β
Catch rendering errors at component level, show fallback UI.
Custom error classes β
typescript
class AppError extends Error {
constructor(
public code: string,
public statusCode: number,
message?: string
) {
super(message || code)
}
}What to log β
- Error message and stack trace
- Request context (URL, method, user ID)
- Input that caused the error
- Timestamp and environment
- Never log: passwords, tokens, PII
Error response to users β
json
{
"error": {
"code": "PAYMENT_FAILED",
"message": "Payment could not be processed. Please try again."
}
}- User-facing messages: helpful, non-technical
- Internal logs: detailed, technical, with context
- Never expose stack traces in production responses
Anti-patterns β
- Empty catch blocks (
catch (e) {}) - Catching generic
Errorwhen you mean a specific type - Using exceptions for control flow
- Returning
nullinstead of throwing on unexpected failures - Logging without enough context to reproduce