Making Requests
There are two ways to send a request. For the common case, the HttpClient
service has method accessors — get, post, put, patch, del, head,
options — that take a URL and an options object. For anything richer, you
build a request value with HttpClientRequest and hand it to client.execute.
Both return an Effect that yields a HttpClientResponse.
import { Effect, Schema } from "effect"import { HttpClient, HttpClientRequest } from "effect/unstable/http"
class CreatedTodo extends Schema.Class<CreatedTodo>("CreatedTodo")({ id: Schema.Number, title: Schema.String, completed: Schema.Boolean}) {}
const program = Effect.gen(function*() { const client = yield* HttpClient.HttpClient
// 1. The quick path: a GET with query parameters. const listResponse = yield* client.get("https://api.example.com/todos", { urlParams: { completed: "false", limit: "20" } })
// 2. The builder path: construct a request value, then execute it. // Useful when you want to compose several modifications. const created = yield* HttpClientRequest.post("https://api.example.com/todos").pipe( HttpClientRequest.acceptJson, HttpClientRequest.setHeader("x-request-source", "docs-example"), // bodyJsonUnsafe serializes a value to a JSON body. It is "unsafe" only in // that it assumes the value is JSON-encodable; it does not throw at runtime // for plain data. HttpClientRequest.bodyJsonUnsafe({ title: "Write docs", completed: false }), client.execute )
return { listResponse, created }})The options object
Section titled “The options object”The method accessors and the HttpClientRequest.* constructors accept the same
options (minus method/url, which are already implied). The most common
fields:
import { HttpClientRequest } from "effect/unstable/http"
const request = HttpClientRequest.get("https://api.example.com/search", { // Query string parameters. Accepts a record, an array of pairs, or UrlParams. urlParams: { q: "effect", page: "1" }, // Request headers. Accepts a record or a Headers value. headers: { authorization: "Bearer token" }, // URL fragment. hash: "results", // Ask the server for a JSON response (sets the Accept header). acceptJson: true})Building requests with combinators
Section titled “Building requests with combinators”Every HttpClientRequest is an immutable value. The HttpClientRequest module
exposes combinators that return a new request, so they compose cleanly under
.pipe. The most useful ones:
| Combinator | What it does |
| --- | --- |
| setUrl / prependUrl / appendUrl | Set or extend the request URL. |
| setHeader / setHeaders | Add headers. |
| setUrlParam / setUrlParams / appendUrlParam | Set or append query parameters. |
| bearerToken / basicAuth | Add an Authorization header. |
| accept / acceptJson | Set the Accept header. |
| bodyJson / bodyJsonUnsafe | JSON request body. |
| bodyText / bodyUint8Array | Raw text or bytes. |
| bodyUrlParams | application/x-www-form-urlencoded body. |
| bodyFormData / bodyFormDataRecord | multipart/form-data body. |
| bodyStream / bodyFile | Streamed body, e.g. for large uploads. |
| schemaBodyJson | Encode a value through a schema into a JSON body. |
import { Effect } from "effect"import { HttpClient, HttpClientRequest } from "effect/unstable/http"
// A request value can be assembled independently of any client and reused.const search = HttpClientRequest.get("https://api.example.com/search").pipe( HttpClientRequest.bearerToken("secret-token"), HttpClientRequest.setUrlParams({ q: "effect", lang: "en" }), HttpClientRequest.acceptJson)
const run = Effect.gen(function*() { const client = yield* HttpClient.HttpClient return yield* client.execute(search)})Bodies
Section titled “Bodies”For JSON, prefer bodyJsonUnsafe for plain data you already trust, and
bodyJson when you need the effectful, fully-validated path. To encode a value
through a schema (so the request body matches a contract), use
schemaBodyJson, which returns an Effect because encoding can fail:
import { Effect, Schema } from "effect"import { HttpClientRequest } from "effect/unstable/http"
const NewTodo = Schema.Struct({ title: Schema.String, completed: Schema.Boolean})
// schemaBodyJson(schema) returns a function from a value to an Effect of the// request — encoding through the schema may fail, hence the Effect.const buildRequest = (todo: typeof NewTodo.Type) => HttpClientRequest.post("https://api.example.com/todos").pipe( HttpClientRequest.schemaBodyJson(NewTodo)(todo) )
const _ = Effect.gen(function*() { return yield* buildRequest({ title: "Ship it", completed: false })})Form and binary bodies work the same way — bodyUrlParams for form-encoded
fields, bodyFormDataRecord for multipart uploads, and bodyStream/bodyFile
when you want to stream a large payload without buffering it in memory.
What you get back
Section titled “What you get back”Executing a request yields a HttpClientResponse in the success channel and an
HttpClientError in the failure channel. By default a non-2xx status is not
an error — the response is returned as-is so you can inspect the status. To make
a bad status fail, use HttpClient.filterStatusOk (covered under
Resilience). Decoding the body is covered next, in
Handling responses.