HTTP Client
The HTTP client lets you talk to external APIs as Effects. Instead of a one-shot
fetch call, you get a HttpClient service that you can configure once — base
URL, default headers, retries, rate limiting, tracing — and then reuse across
your application. Requests are values you build up with HttpClientRequest, and
responses are decoded with schema-aware helpers from HttpClientResponse, so
both the success and failure channels are fully typed.
The modules live under effect/unstable/http:
import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse} from "effect/unstable/http"Here is the shape of a typical client service. It wraps the HttpClient service
with some common middleware, then exposes a few domain methods that decode their
responses into schemas:
import { Context, Effect, flow, Layer, Schedule, Schema } from "effect"import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse} from "effect/unstable/http"
// The domain model we expect the API to return.class Todo extends Schema.Class<Todo>("Todo")({ userId: Schema.Number, id: Schema.Number, title: Schema.String, completed: Schema.Boolean}) {}
export class JsonPlaceholder extends Context.Service<JsonPlaceholder, { getTodo(id: number): Effect.Effect<Todo, JsonPlaceholderError>}>()("app/JsonPlaceholder") { static readonly layer = Layer.effect( JsonPlaceholder, Effect.gen(function*() { // Take the base HttpClient and apply middleware that should run on every // request this service makes. const client = (yield* HttpClient.HttpClient).pipe( // Prepend a base URL and ask for JSON on all requests. HttpClient.mapRequest(flow( HttpClientRequest.prependUrl("https://jsonplaceholder.typicode.com"), HttpClientRequest.acceptJson )), // Turn non-2xx responses into a typed failure. HttpClient.filterStatusOk, // Retry transient failures (network errors, 5xx, 429) with backoff. HttpClient.retryTransient({ schedule: Schedule.exponential(100), times: 3 }) )
const getTodo = Effect.fn("JsonPlaceholder.getTodo")(function*(id: number) { return yield* client.get(`/todos/${id}`).pipe( // Decode the JSON body with the Todo schema. Effect.flatMap(HttpClientResponse.schemaBodyJson(Todo)), // Collapse client and decode errors into one domain error. Effect.mapError((cause) => new JsonPlaceholderError({ cause })) ) })
return JsonPlaceholder.of({ getTodo }) }) ).pipe( // Provide the fetch-based implementation of HttpClient. Layer.provide(FetchHttpClient.layer) )}
export class JsonPlaceholderError extends Schema.TaggedErrorClass<JsonPlaceholderError>()( "JsonPlaceholderError", { cause: Schema.Defect }) {}A few things worth noticing:
HttpClient.HttpClientis a service. You access it from the context and derive a configured client with.pipe(...). The configuration is shared by every request, so you write it once.FetchHttpClient.layerprovides the concrete implementation backed by the platformfetch. You provide it at the edge of your application, which makes the client trivial to swap in tests.- Errors are values. A request fails with
HttpClientError, schema decoding fails withSchema.SchemaError, andmapErrorlets you fold them into a single domain error you control.
In this section
Section titled “In this section”To provide the HttpClient service, see Platform for runtime
specifics, and Services & Layers for how layers compose.