Advanced

Error Handling Design Patterns with Claude Code

Learn about error handling design patterns using Claude Code. Practical tips and code examples included.

Improve Your Error Handling Design With Claude Code

Proper error handling is directly tied to application robustness. With Claude Code, you can efficiently implement type-safe and consistent error-handling patterns.

Designing Custom Error Classes

> Design custom error classes to use across the entire application.
> Include HTTP status code mapping, error codes,
> and user-facing messages.
// errors/base.ts
export abstract class AppError extends Error {
  abstract readonly statusCode: number;
  abstract readonly code: string;
  readonly isOperational: boolean;

  constructor(message: string, isOperational = true) {
    super(message);
    this.name = this.constructor.name;
    this.isOperational = isOperational;
    Error.captureStackTrace(this, this.constructor);
  }

  toJSON() {
    return {
      error: {
        code: this.code,
        message: this.message,
        ...(process.env.NODE_ENV === "development" && { stack: this.stack }),
      },
    };
  }
}

// errors/http.ts
export class NotFoundError extends AppError {
  readonly statusCode = 404;
  readonly code = "NOT_FOUND";

  constructor(resource: string, id?: string) {
    super(id ? `${resource}(${id}) not found` : `${resource} not found`);
  }
}

export class ValidationError extends AppError {
  readonly statusCode = 400;
  readonly code = "VALIDATION_ERROR";
  readonly fields: Record<string, string[]>;

  constructor(fields: Record<string, string[]>) {
    super("Validation errors");
    this.fields = fields;
  }

  toJSON() {
    return {
      error: {
        code: this.code,
        message: this.message,
        fields: this.fields,
      },
    };
  }
}

export class UnauthorizedError extends AppError {
  readonly statusCode = 401;
  readonly code = "UNAUTHORIZED";
  constructor(message = "Authentication required") {
    super(message);
  }
}

export class ForbiddenError extends AppError {
  readonly statusCode = 403;
  readonly code = "FORBIDDEN";
  constructor(message = "Permission denied") {
    super(message);
  }
}

The Result Type Pattern

A Result type pattern that handles errors in a type-safe way without exceptions.

> Implement the Result type pattern.
> Distinguish success and failure at the type level.
// types/result.ts
type Result<T, E = Error> =
  | { success: true; data: T }
  | { success: false; error: E };

function ok<T>(data: T): Result<T, never> {
  return { success: true, data };
}

function err<E>(error: E): Result<never, E> {
  return { success: false, error };
}

// Usage example
async function findUser(id: string): Promise<Result<User, NotFoundError>> {
  const user = await db.user.findUnique({ where: { id } });
  if (!user) {
    return err(new NotFoundError("User", id));
  }
  return ok(user);
}

// Caller
const result = await findUser("123");
if (result.success) {
  console.log(result.data.name); // type-safe access
} else {
  console.log(result.error.message); // error type is also type-safe
}

Global Error Handler

Implement a global error handler for Express.

> Create a global error handler middleware for Express.
> Convert AppError into an appropriate response; unexpected errors should produce 500.
> Hide stack traces in production.
import { Request, Response, NextFunction } from "express";
import { AppError } from "../errors/base";

export function globalErrorHandler(
  err: Error,
  req: Request,
  res: Response,
  _next: NextFunction
) {
  // Logging
  if (err instanceof AppError && err.isOperational) {
    console.warn(`[${err.code}] ${err.message}`);
  } else {
    console.error("Unexpected error:", err);
  }

  // Return the defined response for AppError
  if (err instanceof AppError) {
    return res.status(err.statusCode).json(err.toJSON());
  }

  // Unexpected error
  res.status(500).json({
    error: {
      code: "INTERNAL_ERROR",
      message: process.env.NODE_ENV === "production"
        ? "A server error occurred"
        : err.message,
    },
  });
}

Catching Async Errors

> Create a wrapper that automatically catches errors from
> Express async route handlers.
import { Request, Response, NextFunction, RequestHandler } from "express";

function asyncHandler(
  fn: (req: Request, res: Response, next: NextFunction) => Promise<void>
): RequestHandler {
  return (req, res, next) => {
    fn(req, res, next).catch(next);
  };
}

// Usage example: no more try/catch
router.get("/users/:id", asyncHandler(async (req, res) => {
  const user = await userService.findById(req.params.id);
  if (!user) {
    throw new NotFoundError("User", req.params.id);
  }
  res.json({ data: user });
}));

Frontend Error Boundaries

> Create a React Error Boundary.
> Display different UI based on the type of error.
import { Component, ReactNode } from "react";

interface Props {
  children: ReactNode;
  fallback?: (error: Error, retry: () => void) => ReactNode;
}

interface State {
  error: Error | null;
}

class ErrorBoundary extends Component<Props, State> {
  state: State = { error: null };

  static getDerivedStateFromError(error: Error) {
    return { error };
  }

  handleRetry = () => {
    this.setState({ error: null });
  };

  render() {
    if (this.state.error) {
      if (this.props.fallback) {
        return this.props.fallback(this.state.error, this.handleRetry);
      }
      return (
        <div className="p-8 text-center">
          <h2 className="text-xl font-bold text-red-600">Something went wrong</h2>
          <p className="mt-2 text-gray-600">{this.state.error.message}</p>
          <button
            onClick={this.handleRetry}
            className="mt-4 rounded bg-blue-500 px-4 py-2 text-white"
          >
            Try again
          </button>
        </div>
      );
    }
    return this.props.children;
  }
}

For concrete debugging and error investigation techniques, see the complete debugging techniques guide, and for TypeScript type design, see TypeScript development tips. For handling error information from a security perspective, also see automating security audits.

Summary

Consistent error-handling design significantly improves the robustness and maintainability of your application. With Claude Code, you can efficiently implement custom error classes, Result types, and a global error handler.

For error handling best practices, see the official Node.js guide. For Claude Code, see the official Anthropic documentation.

#Claude Code #error handling #design patterns #TypeScript #robustness

Level up your Claude Code workflow

50 battle-tested prompt templates you can copy-paste into Claude Code right now.

Free

Free PDF: Claude Code Cheatsheet in 5 Minutes

Key commands, shortcuts, and prompt examples on a single printable page.

Download PDF
M

About the Author

Masa

Engineer obsessed with Claude Code. Runs claudecode-lab.com, a 10-language tech media with 2,000+ pages.