Error Handling

Learn more about how to handle errors in CallApi

It's prevalent knowledge that making network requests is inherently risky. Things can go wrong for many reasons:

  • The server might be down.
  • The server might respond with an error status like 404 Not Found (the resource doesn't exist) or 500 Internal Server Error (something broke on the server).
  • There might be a network issue.
  • The request might timeout.
  • The response data might not be in the format you expected (e.g., not valid JSON).

When using the standard browser fetch API, handling these failures can sometimes be a bit clunky due to the following reasons:

  • Network errors throw one type of error
  • Non-2xx HTTP responses don't throw errors by default (you have to check response.ok)
  • Parsing errors might throw yet another type

This can lead to complex if/else chains and unwieldy try...catch blocks just to figure out what went wrong.

CallApi aims to make dealing with these failures much more predictable and convenient.

Structure of the error property

As introduced in the Getting Started guide, CallApi wraps responses in a result object with three key properties: data, error, and response.

When something goes wrong, the error property contains a structured object with:

  1. name: A string identifying the type of error (e.g., 'HTTPError', 'ValidationError', 'TypeError', 'TimeoutError', ...etc).

  2. message: A brief description of what went wrong:

    • For HTTP errors: The error message from the server, or if not provided, falls back to the defaultHTTPErrorMessage option
    • For validation errors: A formatted error message derived from the validation issues array
    • For non-HTTP errors: The error message from the JavaScript error object that caused the error
  3. errorData: The detailed error information:

    • For HTTP errors: It is set to the parsed error response from the API
    • For validation errors: It is set to the validation issues array
    • For non-HTTP errors: It is set to false
  4. originalError: The original error object that caused the error:

    • For HTTP errors: HTTPError
    • For validation errors: ValidationError
    • For non-HTTP errors: The underlying javascript error object (e.g., TypeError, DOMException, etc.)
api.ts
import {  } from "@zayne-labs/callapi";

const {  } = await ("https://my-api.com/api/v1/session");
Hover over the error object to see the type

Handling HTTP Errors

One of the most common types of errors you'll encounter is when the server responds with a status code outside the 200-299 range (like 400, 401, 403, 404, 500, 503, etc.). Standard fetch doesn't throw an error for these responses.

CallApi, by default, wraps these responses in an HTTPError. You can customize the error response data type by providing a second generic type argument to callApi.

api.ts
import {  } from "@zayne-labs/callapi";

type  = {
	: <string | string[]>;
	: string;
};

const {  } = await <unknown, >("/api/endpoint");

if () {
	.(.);
}

Since the error property is a discriminated union, you can use the isHTTPError utility from @zayne-labs/callapi/utils to check if it's an HTTP error:

api.ts
import {  } from "@zayne-labs/callapi";
import {  } from "@zayne-labs/callapi/utils";

type  = {
	: boolean;
	: number;
	: string;
	: string;
};

type  = {
	?: <string | string[]>;
	?: string;
};

const { ,  } = await <, >("https://my-api.com/api/v1/session");

if (()) {
	.();
	.(.); // 'HTTPError'
	.(.);
	.(.); // Will be set to the error response data
}

Handling Validation Errors

When schema validation fails, CallApi wraps the failure in a ValidationError. See the Validation section for details.

You can use the isValidationError utility to check specifically for this error type:

api.ts
import {  } from "@zayne-labs/callapi";
import {  } from "@zayne-labs/callapi/utils";
import {  } from "zod";

const  = .({
	: .(),
	: .(),
	: .(),
	: .(),
});

const { ,  } = await ("https://my-api.com/api/v1/session", {
	: {
		: ,
	},
});

if (()) {
	.(.); // 'ValidationError'
	.(.); // Validation issues array
}

Throwing Errors

Set throwOnError: true to throw errors instead of returning them—useful for libraries expecting promise rejection:

api.ts
import {  } from "@zayne-labs/callapi";
import { ,  } from "@zayne-labs/callapi/utils";

type  = {
	: boolean;
	: number;
	: string;
	: string;
};

type  = {
	?: <string | string[]>;
	?: string;
};

try {
	const {  } = await <>("https://my-api.com/api/v1/session", {
		: true,
	});
} catch () {
	if (<>()) {
		.(.);
	}

	if (()) {
		.(.);
	}
}

Conditional throwing:

You can also pass a function to throwOnError for conditional throwing based on the error context:

api.ts
import {  } from "@zayne-labs/callapi";
import { ,  } from "@zayne-labs/callapi/utils";

// Only throw for authentication errors
const  = await ("https://my-api.com/api/v1/session", {
	: ({  }) => ?. === 401,
});

// Throw for client errors (user mistakes) but not server errors (temporary issues)
const  = await ("https://my-api.com/api/users", {
	: ({  }) => {
		if (!) {
			return false;
		}

		return . >= 400 && . < 500;
	},
});

// Complex conditional logic based on error type and context
const  = await ("https://my-api.com/api/sensitive", {
	: ({ , ,  }) => {
		// Always throw validation errors - data integrity is critical
		if (()) {
			return true;
		}

		// Throw HTTP errors for sensitive endpoints
		if (() && ?. === 403 && .?.("/sensitive")) {
			return true;
		}

		// Throw rate limiting errors during business hours (handle differently off-hours)
		if (?. === 429) {
			const  = new ().();
			return  >= 9 &&  <= 17;
		}

		// Return other errors in result object
		return false;
	},
});

Type Narrowing

The data and error properties form a discriminated union—if one is present, the other is null. TypeScript automatically narrows types after error checks:

api.ts
import {  } from "@zayne-labs/callapi";
import {  } from "@zayne-labs/callapi/utils";

type  = {
	: boolean;
	: number;
	: string;
	: string;
};

type  = {
	?: <string | string[]>;
	?: string;
};

const { ,  } = await <, >("https://my-api.com/api/v1/session");

if (()) {
	.();
} else if () {
	.();
} else {
	.(); // TypeScript knows data is not null
}

Error Recovery with refetch

Sometimes you want to silently recover from an error and retry the request (e.g., refreshing an expired auth token). CallApi provides a refetch() function within the options object of your error hooks for this purpose.

For common recovery patterns like token refreshing, see the Authorization guide.

Edit on GitHub

Last updated on

On this page