Error Handling Patterns in TypeScript: Beyond Try-Catch
Try-catch blocks scattered everywhere. No idea what errors a function can throw. Callers forget to handle edge cases. Here are better patterns.
The Result Type
Instead of throwing, return a discriminated union:
type Result<T, E = Error> =
| { ok: true; value: T }
| { ok: false; error: E };
function divide(a: number, b: number): Result<number, string> {
if (b === 0) return { ok: false, error: "Division by zero" };
return { ok: true, value: a / b };
}
const result = divide(10, 0);
if (result.ok) {
console.log(result.value); // TypeScript knows this is number
} else {
console.error(result.error); // TypeScript knows this is string
}
Custom Error Classes
class AppError extends Error {
constructor(
message: string,
public code: string,
public statusCode: number = 500,
public isOperational: boolean = true
) {
super(message);
this.name = "AppError";
}
}
class NotFoundError extends AppError {
constructor(resource: string, id: string) {
super(resource + " " + id + " not found", "NOT_FOUND", 404);
}
}
class ValidationError extends AppError {
constructor(public fields: Record<string, string>) {
super("Validation failed", "VALIDATION_ERROR", 400);
}
}
Global Error Handler
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
if (err instanceof AppError && err.isOperational) {
return res.status(err.statusCode).json({
error: { code: err.code, message: err.message },
});
}
// Unexpected error: log and return generic message
console.error("Unexpected error:", err);
res.status(500).json({ error: { code: "INTERNAL", message: "Something went wrong" } });
});
Key Principles
- Operational errors (bad input, not found, rate limited): expected, handle gracefully
- Programming errors (null reference, type error): unexpected, crash and restart
- Never catch and ignore: at minimum, log the error
- Fail fast: validate at boundaries, not deep in business logic
Part of my Production Backend Patterns series. Follow for more practical backend engineering.
You Might Also Like
- API Rate Limiting with Redis: Token Bucket, Sliding Window, and Per-Client Limits
- Environment Variables Done Right: From .env Files to Production Configs
- Docker Compose for Development: The Setup Every Backend Dev Needs
Follow me for more production-ready backend content!
If this helped you, buy me a coffee on Ko-fi!
Top comments (0)