Advanced

Designing Error Boundaries with Claude Code: From Frontend to API

Designing error boundaries using Claude Code. From frontend to API. Includes practical code examples.

What Is an Error Boundary?

An error boundary is a defensive design pattern that prevents the entire application from crashing when part of it throws an error. With Claude Code, you can design consistent error handling across both the frontend and the backend.

React Error Boundary

import { Component, ErrorInfo, ReactNode } from "react";

interface ErrorBoundaryProps {
  children: ReactNode;
  fallback?: ReactNode;
  onError?: (error: Error, errorInfo: ErrorInfo) => void;
}

interface ErrorBoundaryState {
  hasError: boolean;
  error: Error | null;
}

class ErrorBoundary extends Component<
  ErrorBoundaryProps,
  ErrorBoundaryState
> {
  constructor(props: ErrorBoundaryProps) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error: Error): ErrorBoundaryState {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    console.error("Error caught by boundary:", error, errorInfo);
    this.props.onError?.(error, errorInfo);

    // Send to error monitoring service
    reportError(error, {
      componentStack: errorInfo.componentStack,
    });
  }

  render() {
    if (this.state.hasError) {
      return (
        this.props.fallback || (
          <div className="error-fallback">
            <h2>Something went wrong</h2>
            <p>{this.state.error?.message}</p>
            <button onClick={() => this.setState({ hasError: false, error: null })}>
              Try again
            </button>
          </div>
        )
      );
    }

    return this.props.children;
  }
}

Hook for Function Components

import { useCallback, useState } from "react";

function useErrorHandler() {
  const [error, setError] = useState<Error | null>(null);

  if (error) {
    throw error; // Caught by the parent Error Boundary
  }

  const handleError = useCallback((err: unknown) => {
    if (err instanceof Error) {
      setError(err);
    } else {
      setError(new Error(String(err)));
    }
  }, []);

  const withErrorHandling = useCallback(
    <T,>(fn: () => Promise<T>) => {
      return async () => {
        try {
          return await fn();
        } catch (err) {
          handleError(err);
          return undefined;
        }
      };
    },
    [handleError]
  );

  return { handleError, withErrorHandling };
}

Standardizing API Error Responses

// Error code definitions
enum ErrorCode {
  VALIDATION_ERROR = "VALIDATION_ERROR",
  NOT_FOUND = "NOT_FOUND",
  UNAUTHORIZED = "UNAUTHORIZED",
  FORBIDDEN = "FORBIDDEN",
  CONFLICT = "CONFLICT",
  INTERNAL_ERROR = "INTERNAL_ERROR",
  RATE_LIMITED = "RATE_LIMITED",
}

interface ApiError {
  code: ErrorCode;
  message: string;
  details?: Record<string, unknown>;
  requestId: string;
  timestamp: string;
}

class AppError extends Error {
  constructor(
    public code: ErrorCode,
    message: string,
    public statusCode: number,
    public details?: Record<string, unknown>
  ) {
    super(message);
    this.name = "AppError";
  }

  static notFound(resource: string) {
    return new AppError(
      ErrorCode.NOT_FOUND,
      `${resource} not found`,
      404
    );
  }

  static validation(details: Record<string, string>) {
    return new AppError(
      ErrorCode.VALIDATION_ERROR,
      "Validation failed",
      400,
      details
    );
  }

  static unauthorized(message = "Authentication required") {
    return new AppError(ErrorCode.UNAUTHORIZED, message, 401);
  }
}

Global Error Handler

import { v4 as uuidv4 } from "uuid";

function globalErrorHandler(
  err: Error,
  req: express.Request,
  res: express.Response,
  next: express.NextFunction
) {
  const requestId = uuidv4();

  if (err instanceof AppError) {
    // Business error
    return res.status(err.statusCode).json({
      code: err.code,
      message: err.message,
      details: err.details,
      requestId,
      timestamp: new Date().toISOString(),
    });
  }

  // Unexpected error
  console.error(`[${requestId}] Unexpected error:`, err);

  res.status(500).json({
    code: ErrorCode.INTERNAL_ERROR,
    message: "An unexpected error occurred",
    requestId,
    timestamp: new Date().toISOString(),
  });
}

// Registration order matters: place after your routers
app.use(globalErrorHandler);

Catching Async Errors

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

// Usage example
router.get(
  "/users/:id",
  asyncHandler(async (req, res) => {
    const user = await prisma.user.findUnique({
      where: { id: req.params.id },
    });

    if (!user) {
      throw AppError.notFound("User");
    }

    res.json({ data: user });
  })
);

Hierarchical Error Boundaries

On the React side, you place multiple error boundaries hierarchically. Use this as a reference when asking Claude Code to design error handling. For writing prompts, see 5 tips for better prompts, and for improving code quality, see automating refactoring.

function App() {
  return (
    <ErrorBoundary fallback={<FullPageError />}>
      <Header />
      <ErrorBoundary fallback={<SidebarFallback />}>
        <Sidebar />
      </ErrorBoundary>
      <ErrorBoundary fallback={<ContentFallback />}>
        <MainContent />
      </ErrorBoundary>
    </ErrorBoundary>
  );
}

For error handling design principles, see MDN Web Docs: Error handling. For Claude Code details, see the official documentation.

Summary

Error boundaries are an important design pattern for preserving user experience. With Claude Code, you can efficiently implement consistent error handling from the frontend all the way through the API.

#Claude Code #error handling #React #API design #TypeScript

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.