DEV Community

Young Gao
Young Gao

Posted on

Error Handling Patterns in TypeScript: Beyond Try-Catch

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
}
Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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" } });
});
Enter fullscreen mode Exit fullscreen mode

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

Follow me for more production-ready backend content!


If this helped you, buy me a coffee on Ko-fi!

Top comments (0)