Documentation Index
Fetch the complete documentation index at: https://mintlify.com/effect-TS/effect-smol/llms.txt
Use this file to discover all available pages before exploring further.
The HttpClient module provides a functional, composable HTTP client built on Effect. It features automatic retries, rate limiting, tracing, cookie management, and powerful transformation APIs.
Overview
HttpClient offers:
- Composable request/response transformations
- Built-in retry strategies for transient errors
- Rate limiting with automatic header inspection
- Distributed tracing integration
- Cookie jar management
- Type-safe error handling
- Request/response streaming
Basic Usage
import { Effect, HttpClient } from "effect"
const program = Effect.gen(function*() {
// Get the client service
const client = yield* HttpClient.HttpClient
// Make a GET request
const response = yield* client.get("https://api.example.com/users")
// Parse JSON response
const users = yield* response.json
yield* Effect.log("Users:", users)
})
Making Requests
HttpClient provides convenience methods for all HTTP verbs:
get
get: (
url: string | URL,
options?: HttpClientRequest.Options.NoUrl
) => Effect.Effect<HttpClientResponse, HttpClientError, HttpClient>
post
post: (
url: string | URL,
options?: HttpClientRequest.Options.NoUrl
) => Effect.Effect<HttpClientResponse, HttpClientError, HttpClient>
Other Methods
put - HTTP PUT requests
patch - HTTP PATCH requests
del - HTTP DELETE requests
head - HTTP HEAD requests
options - HTTP OPTIONS requests
Example:
import { Effect, HttpClient } from "effect"
const program = Effect.gen(function*() {
const client = yield* HttpClient.HttpClient
// POST with JSON body
const response = yield* client.post("https://api.example.com/users", {
headers: { "content-type": "application/json" },
body: HttpBody.json({ name: "Alice", email: "alice@example.com" })
})
const user = yield* response.json
yield* Effect.log("Created user:", user)
})
mapRequest
Transforms the request before sending.
mapRequest: {
(
f: (req: HttpClientRequest) => HttpClientRequest
): <E, R>(self: HttpClient.With<E, R>) => HttpClient.With<E, R>
<E, R>(
self: HttpClient.With<E, R>,
f: (req: HttpClientRequest) => HttpClientRequest
): HttpClient.With<E, R>
}
Example:
import { Effect, HttpClient, HttpClientRequest } from "effect"
const program = Effect.gen(function*() {
const client = yield* HttpClient.HttpClient
// Add auth header to all requests
const authenticatedClient = HttpClient.mapRequest(client, (req) =>
HttpClientRequest.setHeader(req, "authorization", "Bearer TOKEN")
)
const response = yield* authenticatedClient.get("https://api.example.com/profile")
})
mapRequestEffect
Transforms the request with an Effect.
mapRequestEffect: {
<E2, R2>(
f: (req: HttpClientRequest) => Effect.Effect<HttpClientRequest, E2, R2>
): <E, R>(self: HttpClient.With<E, R>) => HttpClient.With<E | E2, R | R2>
}
Example:
import { Effect, HttpClient, HttpClientRequest } from "effect"
const program = Effect.gen(function*() {
const client = yield* HttpClient.HttpClient
// Dynamically add timestamp header
const clientWithTimestamp = HttpClient.mapRequestEffect(client, (req) =>
Effect.map(
Effect.sync(() => new Date().toISOString()),
(timestamp) => HttpClientRequest.setHeader(req, "x-timestamp", timestamp)
)
)
})
Transforms both request and response.
transform: {
<E, R, E1, R1>(
f: (
effect: Effect.Effect<HttpClientResponse, E, R>,
request: HttpClientRequest
) => Effect.Effect<HttpClientResponse, E1, R1>
): (self: HttpClient.With<E, R>) => HttpClient.With<E | E1, R | R1>
}
Example:
import { Console, Effect, HttpClient } from "effect"
const program = Effect.gen(function*() {
const client = yield* HttpClient.HttpClient
// Log all requests and responses
const loggingClient = HttpClient.transform(client, (effect, request) =>
Effect.gen(function*() {
yield* Console.log(`Request: ${request.method} ${request.url}`)
const response = yield* effect
yield* Console.log(`Response: ${response.status}`)
return response
})
)
})
Error Handling
catch
Handles errors with a recovery function.
catch: {
<E, E2, R2>(
f: (e: E) => Effect.Effect<HttpClientResponse, E2, R2>
): <R>(self: HttpClient.With<E, R>) => HttpClient.With<E2, R2 | R>
}
catchTag
Handles specific error types by tag.
catchTag: {
<K extends Tags<E>, E, E1, R1>(
tag: K,
f: (e: ExtractTag<E, K>) => Effect.Effect<HttpClientResponse, E1, R1>
): <R>(self: HttpClient.With<E, R>) => HttpClient.With<E1 | ExcludeTag<E, K>, R1 | R>
}
Example:
import { Effect, HttpClient } from "effect"
const program = Effect.gen(function*() {
const client = yield* HttpClient.HttpClient
const resilientClient = client.pipe(
HttpClient.catchTag("TransportError", (error) =>
Effect.gen(function*() {
yield* Effect.log("Network error, using cache")
// Return cached response
return yield* getCachedResponse()
})
)
)
})
Handles multiple error types.
catchTags: {
<E, Cases>(
cases: Cases
): <R>(self: HttpClient.With<E, R>) => HttpClient.With<...>
}
Example:
import { Effect, HttpClient } from "effect"
const resilientClient = client.pipe(
HttpClient.catchTags({
TransportError: (error) => Effect.succeed(fallbackResponse),
InvalidUrlError: (error) => Effect.succeed(defaultResponse)
})
)
Filtering Responses
filterStatus
Filters responses by status code predicate.
filterStatus: {
(f: (status: number) => boolean): <E, R>(
self: HttpClient.With<E, R>
) => HttpClient.With<E | HttpClientError, R>
}
Example:
import { Effect, HttpClient } from "effect"
const program = Effect.gen(function*() {
const client = yield* HttpClient.HttpClient
// Only accept 2xx responses
const strictClient = HttpClient.filterStatus(client, (status) =>
status >= 200 && status < 300
)
})
filterStatusOk
Filters to only 2xx status codes.
filterStatusOk: <E, R>(
self: HttpClient.With<E, R>
) => HttpClient.With<E | HttpClientError, R>
Retry Strategies
retry
Retries requests based on a schedule or policy.
retry: {
<E, O extends Effect.Retry.Options<E>>(
options: O
): <R>(self: HttpClient.With<E, R>) => Retry.Return<R, E, O>
<B, E, ES, R1>(
policy: Schedule.Schedule<B, E, ES, R1>
): <R>(self: HttpClient.With<E, R>) => HttpClient.With<E | ES, R1 | R>
}
Example:
import { Effect, HttpClient, Schedule } from "effect"
const program = Effect.gen(function*() {
const client = yield* HttpClient.HttpClient
// Retry up to 3 times with exponential backoff
const resilientClient = HttpClient.retry(client,
Schedule.exponential("100 millis").pipe(
Schedule.upTo("5 seconds"),
Schedule.compose(Schedule.recurs(3))
)
)
})
retryTransient
Automatically retries transient errors (timeouts, 429, 500-504).
retryTransient: {
<E, B, ES, R1>(
options: {
readonly retryOn?: "errors-only" | "response-only" | "errors-and-responses"
readonly while?: Predicate<E | ES>
readonly schedule?: Schedule.Schedule<B, any, ES, R1>
readonly times?: number
}
): <R>(self: HttpClient.With<E, R>) => HttpClient.With<E | ES, R1 | R>
}
Example:
import { Effect, HttpClient, Schedule } from "effect"
const program = Effect.gen(function*() {
const client = yield* HttpClient.HttpClient
// Retry transient errors with exponential backoff
const resilientClient = HttpClient.retryTransient(client, {
schedule: Schedule.exponential("1 second"),
times: 5
})
// Retry only on error responses (not transport errors)
const responseRetryClient = HttpClient.retryTransient(client, {
retryOn: "response-only",
times: 3
})
})
Transient errors include:
- Network timeouts
- HTTP 408 (Request Timeout)
- HTTP 429 (Too Many Requests)
- HTTP 500 (Internal Server Error)
- HTTP 502 (Bad Gateway)
- HTTP 503 (Service Unavailable)
- HTTP 504 (Gateway Timeout)
Rate Limiting
withRateLimiter
Applies rate limiting using the RateLimiter service.
withRateLimiter: {
(options: {
readonly limiter: RateLimiter.RateLimiter
readonly window: Duration.Input
readonly limit: number
readonly key: string | ((req: HttpClientRequest) => string)
readonly algorithm?: "fixed-window" | "token-bucket"
readonly tokens?: number | ((req: HttpClientRequest) => number)
readonly disableResponseInspection?: boolean
}): <E, R>(self: HttpClient.With<E, R>) => HttpClient.With<E | RateLimiterError, R>
}
Example:
import { Effect, HttpClient, RateLimiter } from "effect"
const program = Effect.gen(function*() {
const client = yield* HttpClient.HttpClient
const limiter = yield* RateLimiter.RateLimiter
// Rate limit to 10 requests per second
const rateLimitedClient = HttpClient.withRateLimiter(client, {
limiter,
window: "1 second",
limit: 10,
key: "api-requests"
})
// Per-user rate limiting
const perUserClient = HttpClient.withRateLimiter(client, {
limiter,
window: "1 minute",
limit: 100,
key: (req) => req.headers.authorization ?? "anonymous"
})
})
Rate limiting features:
- Automatic limit updates from response headers (
RateLimit-*, X-RateLimit-*)
- Automatic retry of 429 responses
- Support for fixed-window and token-bucket algorithms
- Per-request token consumption
Cookie Management
withCookiesRef
Attaches a Ref for cookie jar management.
withCookiesRef: {
(ref: Ref.Ref<Cookies.Cookies>): <E, R>(
self: HttpClient.With<E, R>
) => HttpClient.With<E, R>
}
Example:
import { Effect, HttpClient, Ref } from "effect"
const program = Effect.gen(function*() {
const client = yield* HttpClient.HttpClient
const cookieJar = yield* Ref.make(Cookies.empty)
// Client automatically manages cookies
const sessionClient = HttpClient.withCookiesRef(client, cookieJar)
// Login and get session cookie
yield* sessionClient.post("https://api.example.com/login", {
body: HttpBody.json({ username: "user", password: "pass" })
})
// Session cookie automatically included
const profile = yield* sessionClient.get("https://api.example.com/profile")
})
Redirects
followRedirects
Automatically follows HTTP redirects.
followRedirects: {
(maxRedirects?: number): <E, R>(
self: HttpClient.With<E, R>
) => HttpClient.With<E, R>
}
Example:
import { Effect, HttpClient } from "effect"
const program = Effect.gen(function*() {
const client = yield* HttpClient.HttpClient
// Follow up to 10 redirects (default)
const redirectClient = HttpClient.followRedirects(client)
// Custom max redirects
const limitedClient = HttpClient.followRedirects(client, 5)
})
Scoped Requests
withScope
Ties the request lifetime to a Scope.
withScope: <E, R>(
self: HttpClient.With<E, R>
) => HttpClient.With<E, R | Scope>
Example:
import { Effect, HttpClient, Scope } from "effect"
const program = Effect.scoped(
Effect.gen(function*() {
const client = yield* HttpClient.HttpClient
const scopedClient = HttpClient.withScope(client)
// Request is cancelled if scope is interrupted
const response = yield* scopedClient.get("https://api.example.com/data")
// Process response...
})
)
Tracing
HttpClient automatically integrates with Effect’s tracing system:
import { Effect, HttpClient, Tracer } from "effect"
const program = Effect.gen(function*() {
const client = yield* HttpClient.HttpClient
// Requests automatically create spans with:
// - http.request.method
// - server.address
// - url.full
// - http.response.status_code
const response = yield* client.get("https://api.example.com/users")
})
Customizing Tracing
import { Effect, HttpClient } from "effect"
// Disable tracing for specific requests
const program = Effect.locally(
Effect.gen(function*() {
const client = yield* HttpClient.HttpClient
const response = yield* client.get("https://api.example.com/internal")
}),
HttpClient.TracerDisabledWhen,
(req) => req.url.includes("internal")
)
// Custom span names
const customSpanProgram = Effect.locally(
program,
HttpClient.SpanNameGenerator,
(req) => `API ${req.method} ${new URL(req.url).pathname}`
)
Side Effects
tap
Performs a side effect on successful responses.
tap: {
<_, E2, R2>(
f: (response: HttpClientResponse) => Effect.Effect<_, E2, R2>
): <E, R>(self: HttpClient.With<E, R>) => HttpClient.With<E | E2, R | R2>
}
tapError
Performs a side effect on errors.
tapError: {
<_, E, E2, R2>(
f: (e: E) => Effect.Effect<_, E2, R2>
): <R>(self: HttpClient.With<E, R>) => HttpClient.With<E | E2, R | R2>
}
tapRequest
Performs a side effect on requests before sending.
tapRequest: {
<_, E2, R2>(
f: (req: HttpClientRequest) => Effect.Effect<_, E2, R2>
): <E, R>(self: HttpClient.With<E, R>) => HttpClient.With<E | E2, R | R2>
}
Example:
import { Console, Effect, HttpClient } from "effect"
const program = Effect.gen(function*() {
const client = yield* HttpClient.HttpClient
const loggingClient = client.pipe(
HttpClient.tapRequest((req) =>
Console.log(`Sending ${req.method} ${req.url}`)
),
HttpClient.tap((res) =>
Console.log(`Received ${res.status}`)
),
HttpClient.tapError((err) =>
Console.error(`Request failed:`, err)
)
)
})
Creating Custom Clients
make
Creates a custom HttpClient implementation.
make: (
f: (
request: HttpClientRequest,
url: URL,
signal: AbortSignal,
fiber: Fiber
) => Effect.Effect<HttpClientResponse, HttpClientError>
) => HttpClient
makeWith
Creates a client with custom pre/post processing.
makeWith: <E2, R2, E, R>(
postprocess: (
request: Effect.Effect<HttpClientRequest, E2, R2>
) => Effect.Effect<HttpClientResponse, E, R>,
preprocess: (req: HttpClientRequest) => Effect.Effect<HttpClientRequest, E2, R2>
) => HttpClient.With<E, R>
Type Reference
HttpClient
interface HttpClient extends HttpClient.With<HttpClientError> {}
interface HttpClient.With<E, R = never> {
readonly execute: (
request: HttpClientRequest
) => Effect.Effect<HttpClientResponse, E, R>
readonly get: (
url: string | URL,
options?: HttpClientRequest.Options.NoUrl
) => Effect.Effect<HttpClientResponse, E, R>
readonly post: (
url: string | URL,
options?: HttpClientRequest.Options.NoUrl
) => Effect.Effect<HttpClientResponse, E, R>
// ... other HTTP methods
}
See Also