HTTP

March has two HTTP modules: HttpClient for making requests and HttpServer for handling them.


Making requests

Simple GET:

match HttpClient.get("https://api.example.com/users") do
  Ok(resp)  -> println(resp.body)
  Err(msg)  -> println("error: " ++ msg)
end

POST with a JSON body:

let body = Json.to_string(Json.Object([
  ("name", Json.String("Alice")),
  ("age",  Json.Number(30.0))
]))

match HttpClient.post("https://api.example.com/users", body) do
  Ok(resp) when resp.status == 201 -> println("created")
  Ok(resp) -> println("unexpected: " ++ int_to_string(resp.status))
  Err(e)   -> println("failed: " ++ e)
end

With a configured client (base URL, auth, retries):

let client =
  HttpClient.new_client()
  |> HttpClient.add_request_step(HttpClient.step_base_url("https://api.example.com"))
  |> HttpClient.add_request_step(HttpClient.step_bearer_auth(token))
  |> HttpClient.with_retry(3)

match HttpClient.run(client, HttpClient.get("/users")) do
  Ok(resp) -> resp.body
  Err(msg) -> Err(msg)
end

Handling requests

A minimal HTTP handler:

fn handle(conn) do
  match HttpServer.method(conn) do
    :get  -> HttpServer.text(conn, 200, "Hello!")
    :post ->
      let body = HttpServer.req_body(conn)
      HttpServer.json(conn, 201, "{\"received\": true}")
    _     -> HttpServer.text(conn, 405, "Method Not Allowed")
  end
end

Path-based routing:

fn route(conn) do
  match (HttpServer.method(conn), HttpServer.path(conn)) do
    (:get,  "/")        -> HttpServer.text(conn, 200, "home")
    (:get,  "/health")  -> HttpServer.text(conn, 200, "ok")
    (:post, "/echo")    ->
      HttpServer.text(conn, 200, HttpServer.req_body(conn))
    _                   -> HttpServer.text(conn, 404, "not found")
  end
end

Complete example: a JSON echo server

mod EchoServer do
  fn handle(conn) do
    match (HttpServer.method(conn), HttpServer.path(conn)) do
      (:post, "/echo") ->
        let body = HttpServer.req_body(conn)
        match Json.parse(body) do
          Ok(_)  -> HttpServer.json(conn, 200, body)
          Err(e) ->
            let msg = "{\"error\": \"" ++ e ++ "\"}"
            HttpServer.json(conn, 400, msg)
        end
      (:get, "/health") ->
        HttpServer.json(conn, 200, "{\"status\": \"ok\"}")
      _ ->
        HttpServer.text(conn, 404, "not found")
    end
  end

  fn main() do
    println("Listening on :8080")
    HttpServer.new(8080)
    |> HttpServer.plug(handle)
    |> HttpServer.listen()
  end
end
march — interactive
Click run on any snippet to try it here.
march>