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.
Level up your Claude Code workflow
50 battle-tested prompt templates you can copy-paste into Claude Code right now.
Free PDF: Claude Code Cheatsheet in 5 Minutes
Key commands, shortcuts, and prompt examples on a single printable page.
About the Author
Masa
Engineer obsessed with Claude Code. Runs claudecode-lab.com, a 10-language tech media with 2,000+ pages.
Related Posts
Getting Started with Claude Code Agent SDK — Build Autonomous Agents Fast
Learn how to build autonomous AI agents with Claude Code Agent SDK. Covers setup, tool definitions, and multi-step execution with practical code examples.
The Complete Guide to Context Management in Claude Code
Learn practical techniques to maximize Claude Code's context window. Covers token optimization, conversation splitting, and CLAUDE.md usage.
Mastering Claude Code Hooks: Auto-Format, Auto-Test, and More
Mastering Claude Code Hooks: Auto-Format, Auto-Test, and More. A practical guide with code examples.