Repository F# setup
open System
open System.IO
open System.Threading
open System.Threading.Tasks
open Axial
open Axial.Layers
open Axial.Console
open Axial.FileSystem
open Axial.Hosting
open Axial.Hosting.Browser
open Axial.Hosting.Node
open Axial.HttpClient
open Axial.PlatformService
open Axial.Process
open Axial.State
open Axial.Telemetry
open Axial.Telemetry.JavaScript

HTTP Clients

This page shows the shortest path from HttpClient boilerplate to a typed, testable HTTP workflow.

A direct HttpClient call mixes four failure channels into exceptions and manual status checks:

// Untracked: exceptions for transport, manual status checks, unchecked parsing.
let! response = client.GetAsync($"https://api.example.com/users/{userId}") |> Async.AwaitTask
response.EnsureSuccessStatusCode() |> ignore
let! body = response.Content.ReadAsStringAsync() |> Async.AwaitTask
let user = parseUser body // throws on bad payloads
The same call as an Axial workflow:
open Axial.HttpClient
open Axial.HttpClient.DSL

let user =
    GET $"https://api.example.com/users/{userId}"
    |> bearer token
    |> fetchJson decodeUser
`user` is a `Flow<#IHasHttp, HttpError, User>`. The URL hole is URL-encoded as one value, the bearer token is redacted from every plan and error transcript, connection failures, timeouts, unexpected statuses, and decode failures all arrive as one typed `HttpError`, and nothing is sent until a Flow runtime runs the workflow.

Two Levels

Axial.HttpClient has two deliberate levels:

  1. The Http and Request modules wrap the common HttpClient operations with explicit requests, typed errors, and service-based execution. Use them when you want full control over every request field.
  2. The DSL module adds interpolated URL builders (GET $"..."), pipe-friendly configuration, and terminal verbs (fetch, fetchText, fetchJson) for the everyday call that should read as one line.

Both levels build the same immutable HttpRequest value, so they mix freely: start a request with GET $"..." and finish it with Request.expect [ 200; 404 ] >> Http.sendResult.

Mental Model

  1. Http.get/GET $"..." create an immutable HttpRequest. Construction never performs I/O.
  2. Request.* and DSL combinators (query, bearer, timeout, jsonBody, expect) configure it.
  3. Http.send, fetch, fetchText, or fetchJson convert it to Flow<#IHasHttp, HttpError, _>.
  4. The Flow runtime resolves IHttp from the environment and performs the exchange.

Because the service boundary is one IHttp.Send method, a complete test fake is a few lines, and Request.plan renders a redacted description of any request without sending it.

Choose A Guide

  • Requests: safe interpolated URLs, query parameters, headers, bodies, and secret redaction.
  • Responses and errors: response transcripts, typed JSON decoding, and the HttpError model.
  • Reliability: per-request timeouts, expected statuses, and transient-failure retries.
  • Testing HTTP: fakes, the live HttpClient service, and layer composition.