⚠ Error Handling Guide

TypeScript Error Handling Patterns That Actually Work

7 min read · TypeScript · API Design · React · tRPC

Most frontend apps handle errors as afterthoughts. This guide shows you how to use Result types, Either patterns, and discriminated union error codes to make error handling explicit, type-safe, and exhaustive.

Why try/catch is Not Enough

The traditional JavaScript pattern for error handling is try/catch. The problem? TypeScript has no way to type what errors are thrown. The caught error is always typed as unknown, meaning you lose type safety the moment something goes wrong.

❌ Untyped

try/catch approach

The error could be anything — a string, an Error object, a network failure. No type information means no autocompletion, no exhaustive checks.

✓ Type-safe

Result / Either pattern

Errors become first-class return values. TypeScript can verify you've handled every possible failure case at compile time.

Pattern 1: The Result Type

The Result pattern represents the outcome of an operation that can either succeed or fail. It eliminates the need for try/catch in most cases:

export type ApiErrorCode =
  | 'NOT_FOUND'
  | 'UNAUTHORIZED'
  | 'FORBIDDEN'
  | 'INTERNAL_ERROR';

export type ActionResult<T> =
  | { success: true; data: T }
  | { success: false; error: {
      code: ApiErrorCode;
      message: string;
    }};

// Usage — TypeScript enforces you handle both branches:
const result = await fetchUser(id);

if (!result.success) {
  // result.error.code is strongly typed → 'NOT_FOUND' | 'UNAUTHORIZED' | ...
  switch (result.error.code) {
    case 'NOT_FOUND': return show404();
    case 'UNAUTHORIZED': return redirectToLogin();
    default: return showGenericError();
  }
}

// result.data is guaranteed to exist here
renderUser(result.data);

Pattern 2: The Either Type (Functional Style)

Inspired by functional programming languages like Haskell and Scala, Either represents a value that is one of two possible types — conventionally Left (failure) and Right (success):

export type Either<E, T> =
  | { _tag: 'Left'; error: E }
  | { _tag: 'Right'; value: T };

// Type guard helper (auto-generated by the tool)
export const isLeft = <E, T>(e: Either<E, T>): e is { _tag: 'Left'; error: E } =>
  e._tag === 'Left';

// Usage:
const result: Either<ApiError, User> = await getUser(id);

if (isLeft(result)) {
  console.error(result.error); // typed as ApiError
} else {
  console.log(result.value);  // typed as User
}

Pattern 3: Validation Error Types

Form validation errors need a different structure — a field-keyed map of error messages that your UI components can consume directly:

export type ValidationErrorCode = 'VALIDATION_FAILED' | 'INVALID_INPUT';

export interface ValidationError {
  code: ValidationErrorCode;
  message: string;
  errors: Record<string, string[]>; // { "email": ["Invalid format", "Required"] }
}

// React usage:
if (!result.success && result.error.code === 'VALIDATION_FAILED') {
  const { errors } = result.error;
  setFieldError('email', errors.email?.[0]);
  setFieldError('password', errors.password?.[0]);
}
✓ Works with React Hook Form + Zod Combine this pattern with @hookform/resolvers/zod and the Zod Schema Generator to get end-to-end type safety from form input to API response and back.

When to Use Which Pattern

Frequently Asked Questions

Is this compatible with tRPC?

Yes. tRPC uses Zod for input validation and throws TRPCError for server-side errors. You can wrap tRPC calls in a Result type on the client side using a thin adapter function.

How do I integrate with React Query?

Use useQuery's onError callback or the error property. Cast the error to your typed error union using a type guard generated by this tool.

Can I generate error types from an OpenAPI spec?

Not directly, but you can copy the error response schemas from your OpenAPI JSON into the JSON-to-TS converter to bootstrap your type definitions quickly.

What is a discriminated union?

A discriminated union is a TypeScript union type where each member has a common property (the "discriminant") with a unique literal value — like _tag: 'Left' | 'Right' or success: true | false. This lets TypeScript narrow the type automatically based on that field.

Generate Your Error Types Now

Select a preset, customize your error codes, and copy production-ready TypeScript in seconds.

Open Error Response Types →