Error Types Overview

Avatar of Hemanta SundarayHemanta Sundaray

When working with HttpClient, you’ll encounter a small set of well-defined error types. Understanding them is essential for writing robust error handling code.

HttpClientError

HttpClientError is the single error class for all HTTP client errors. Every error from the client is an HttpClientError with _tag: "HttpClientError". What varies is the reason, which is a union of six tagged types, split into two categories: request errors (failures before the server responds) and response errors (failures after the server responds).

You can check whether an unknown value is an HttpClientError using the isHttpClientError guard:

import { HttpClientError } from "effect/unstable/http";
HttpClientError.isHttpClientError(someValue); // true or false

Request Errors

Request errors occur before the server sends a response. The request either couldn’t be built or couldn’t be sent.

  • TransportError: A network-level failure. The connection timed out, DNS resolution failed, the server refused the connection, or the network is unreachable.
  • EncodeError: The request body couldn’t be serialized. This typically happens if bodyJson encounters data that JSON.stringify can’t handle.
  • InvalidUrlError: The URL couldn’t be parsed into a valid URL object.

Every request error includes the original request object, so you always know which request failed.

http.ts
import {
FetchHttpClient,
HttpClient,
HttpClientRequest,
} from "effect/unstable/http";
import { Effect } from "effect";
const program = Effect.gen(function* () {
const client = yield* HttpClient.HttpClient;
// This will fail with a TransportError (invalid host)
const request = HttpClientRequest.get("https://this-does-not-exist.invalid");
const response = yield* client.execute(request);
return yield* response.json;
}).pipe(
Effect.catch((error) => {
console.log("Tag:", error._tag);
console.log("Reason:", error.reason._tag);
console.log("Request:", error.reason.methodAndUrl);
console.log("Message:", error.reason.message);
return Effect.void;
}),
Effect.provide(FetchHttpClient.layer),
);
// Test it
Effect.runPromise(program);

Output:

Terminal
Tag: HttpClientError
Reason: TransportError
Request: GET https://this-does-not-exist.invalid
Message: Transport error (GET https://this-does-not-exist.invalid)

Notice the structure. The outer error._tag is always "HttpClientError". The error.reason._tag tells you the specific failure type. Properties like methodAndUrl and message live on the reason object.

Response Errors

Response errors occur after the server sends a response but something went wrong when processing it.

It has a reason field with these possible values:

  • StatusCodeError: The response came back with a status code your code considers unacceptable. For example, a 404 or 500. This error is produced when you explicitly opt in to status code checking using filterStatusOk, filterStatus, or similar filters, which are covered in a later chapter.
  • DecodeError: The response body couldn’t be parsed. For example, calling response.json on a response that isn’t valid JSON.
  • EmptyBodyError: You tried to read a body that doesn’t exist (e.g., a 204 No Content response).

Every response error includes both the original request and the response, giving you full context.

http.ts
import {
FetchHttpClient,
HttpClient,
HttpClientRequest,
} from "effect/unstable/http";
import { Effect } from "effect";
const program = Effect.gen(function* () {
const client = (yield* HttpClient.HttpClient).pipe(HttpClient.filterStatusOk);
// This returns a 404, which filterStatusOk turns into a StatusCodeError
const request = HttpClientRequest.get("https://dummyjson.com/users/9999");
const response = yield* client.execute(request);
return yield* response.json;
}).pipe(
Effect.catch((error) => {
console.log("Tag:", error._tag);
console.log("Reason:", error.reason._tag);
if (error.response) {
console.log("Status:", error.response.status);
}
console.log("Request:", error.reason.methodAndUrl);
console.log("Message:", error.reason.message);
return Effect.void;
}),
Effect.provide(FetchHttpClient.layer),
);
// Test it
Effect.runPromise(program);

Output:

Terminal
Tag: HttpClientError
Reason: StatusCodeError
Status: 404
Request: GET https://dummyjson.com/users/9999
Message: StatusCode: non 2xx status code (404 GET https://dummyjson.com/users/9999)

SchemaError

SchemaError comes from Effect’s Schema module, not from HttpClient itself. You’ll encounter it when using schema-based validation like HttpClientResponse.schemaBodyJson() or HttpClientResponse.schemaHeaders().

When the response body or headers don’t match your schema, the validation step fails with a SchemaError. This error is separate from HttpClientError. It’s a schema validation failure, not an HTTP failure.

http.ts
import {
FetchHttpClient,
HttpClient,
HttpClientRequest,
HttpClientResponse,
} from "effect/unstable/http";
import { Effect, Schema } from "effect";
// A schema that expects fields the API doesn't return
const WrongSchema = Schema.Struct({
nonExistentField: Schema.String,
});
const program = Effect.gen(function* () {
const client = yield* HttpClient.HttpClient;
const request = HttpClientRequest.get("https://dummyjson.com/users/1");
const response = yield* client.execute(request);
// This will fail because the response doesn't match our schema
const data = yield* HttpClientResponse.schemaBodyJson(WrongSchema)(response);
return data;
}).pipe(
Effect.catch((error) => {
console.log("Tag:", error._tag);
console.log("Message:", error.message);
return Effect.void;
}),
Effect.provide(FetchHttpClient.layer),
);
// Test it
Effect.runPromise(program);

Output:

Terminal
Tag: SchemaError
Message: Missing key
at ["nonExistentField"]

Sign in to save progress

Stay in the loop

Get notified when new chapters are added and when this course is complete.