This article was originally published on AI Study Room. For the full version with working code examples and related articles, visit the original post.
Error Handling Best Practices: From Try/Catch to Structured Errors
Random try/catch blocks aren't error handling — they're error hiding. A proper error handling system makes your app debuggable, observable, and resilient. Here's how to move from ad-hoc catches to a structured error system.
Error Types — One Size Doesn't Fit All
| Error Type | HTTP Status | Retry? | Show User? | Notify Dev? |
|---|---|---|---|---|
| Validation error | 400 | No (fix input) | Yes (what to fix) | No |
| Not found | 404 | No | Yes (friendly message) | No |
| Authentication error | 401 | No (log in first) | Yes ("please log in") | No |
| Authorization error | 403 | No | Yes ("you don't have access") | Maybe (possible attack) |
| Rate limit | 429 | Yes (with backoff) | Yes ("too many requests") | No |
| External service failure | 502 | Yes (with backoff) | No (mask it) | Yes (oncall) |
| Internal error (unexpected) | 500 | Maybe | No (mask it) | Yes (immediately) |
Structured Error Handling Pattern
// 1. Define error hierarchy
class AppError extends Error {
constructor(
message: string,
public statusCode: number,
public code: string,
public retryable: boolean = false,
public userMessage?: string
) {
super(message);
this.name = "AppError";
}
}
class ValidationError extends AppError {
constructor(message: string, public fields: Record<string, string>) {
super(message, 400, "VALIDATION_ERROR", false, message);
}
}
class ExternalServiceError extends AppError {
constructor(service: string, cause: Error) {
super(
${service} request failed,
502,
"EXTERNAL_SERVICE_ERROR",
true,
"Something went wrong. Please try again."
);
this.cause = cause;
}
}
// 2. Use in your code
async function chargeCustomer(amount: number, token: string) {
try {
return await stripe.charges.create({ amount, source: token });
} catch (error) {
throw new ExternalServiceError("Stripe", error as Error);
}
}
Global Error Handler (Express/Fastify)
// 3. Global error handler — consistent responses
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
if (err instanceof AppError) {
return res.status(err.statusCode).json({
error: {
code: err.code,
message: err.userMessage || err.message,
fields: err instanceof ValidationError ? err.fields : undefined,
},
});
}
// Unexpected error — log and mask
logger.error({ err, path: req.path, method: req.method });
Sentry.captureException(err);
return res.status(500).json({
error: {
code: "INTERNAL_ERROR",
message: "An unexpected error occurred. We've been notified.",
},
});
});
Async Error Handling in Express
// Express 4 doesn't catch async errors — use a wrapper
const asyncHandler = (fn: Function) =>
(req: Request, res: Response, next: NextFunction) =>
Promise.resolve(fn(req, res, next)).catch(next);
app.get("/users/:id", asyncHandler(async (req, res) => {
const user = await db.users.findById(req.params.id);
if (!user) throw new AppError("User not found", 404, "NOT_FOUND");
res.json(user);
}));
// Express 5 (beta) handles async errors natively
Client-Side Error Handling
// React Error Boundary + toast
function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
return (
<div role="alert">
<h2>Something went wrong</h2>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
);
}
// Wrap sections, not the whole app
<ErrorBoundary FallbackComponent={ErrorFallback}>
<CheckoutForm />
</ErrorBoundary>
Error Handling Checklist
- Define error classes (not just
new Error("something went wrong")). - Validate inputs at the boundary. Return 400, not 500.
- Mask internal errors from users. Log the real error, show a generic message.
- Add a request ID to every error log. Makes debugging across services possible.
- Alert on 5xx spike, not every 5xx. A single 500 might be a blip. 50 in a minute is an incident.
Bottom line: Structured errors + global handler + external service retries + proper logging = an error system that helps you fix bugs instead of hiding them. See also: Testing Strategies and CI/CD Tools.
Read the full article on AI Study Room for complete code examples, comparison tables, and related resources.
Found this useful? Check out more developer guides and tool comparisons on AI Study Room.
Top comments (0)