A protocol-agnostic, Web API-compliant library for Elixir that mirrors the JavaScript Fetch Standard.
Built with a "Dispatcher" architecture, Web provides a unified interface for HTTP, TCP, and connection-string-based protocols while maintaining a zero-buffer, streaming-first approach.
You can try Web immediately without creating a project using Mix.install:
Mix.install([
{:web, "~> 0.2.0"}
])
defmodule GitHub do
use Web
def repositories(query \\ "elixir") do
# 1. Construct a new URL
url = URL.new("https://api.github.com/search/repositories")
# 2. Modify properties via URLSearchParams
params =
URL.search_params(url)
|> URLSearchParams.set("q", query)
|> URLSearchParams.append("sort", "stars")
# 3. Apply params back to the URL
url = URL.search(url, URLSearchParams.to_string(params))
# 4. Construct a Request with the URL
request = Request.new(url,
method: "GET",
headers: %{"Accept" => "application/vnd.github.v3+json"}
)
# 5. Send to fetch
fetch(request)
end
end
{:ok, response} = GitHub.repositories()
IO.puts("Status: #{response.status}")
# Stream the body lazily (Zero-Buffer)
# The body is an Elixir Stream yielding chunks as they arrive from the socket
response.body
|> Stream.take(5)
|> Enum.each(&IO.write/1)- JS Fetch Parity: Familiar
Request,Response, andHeadersstructs. - Polymorphic Entry:
Web.fetch/2accepts a URL string, aWeb.URL, or aWeb.Requeststruct. - Pure Data Architecture:
Web.URLandWeb.URLSearchParamsare pure Elixir structs—no Agents or hidden state. - Overloaded Functional API:
Web.URL.href(url, "new_val")style setters that return new immutable structs. - Fetch-Style Redirects:
Web.Dispatcher.HTTPsupports"follow","manual", and"error"modes. - AbortController Support: Standardized cancellation for in-flight fetches and active body streams.
- Zero-Buffer Streaming:
Web.Response.bodyis an ElixirStreamyielding chunks directly from the socket. - Rclone-Style Resolution: Native support for connection strings (
remote:path/...).
Add :web to your dependencies in mix.exs:
def deps do
[
{:web, "~> 0.1.0"}
]
endYou can easily import the main API and aliases into your module using the use Web macro:
defmodule MyClient do
use Web
def fetch_example() do
url = URL.new("https://example.com")
req = Request.new(url)
{:ok, resp} = fetch(req)
resp
end
endThis will:
- Import
fetch/1,fetch/2, and theawait/1macro - Alias the following modules for convenience:
Web.URLWeb.URLSearchParamsWeb.HeadersWeb.RequestWeb.ResponseWeb.AbortControllerWeb.AbortSignal
The await/1 macro allows you to write:
response = await fetch(req)instead of pattern matching on {:ok, response}:
{:ok, response} = fetch(req)If the result is {:error, reason} or not in the expected format, await will raise an error. The macro is automatically imported when you use Web.
This makes it easy to use the core types and functions without verbose module names.
The Request struct represents a complete I/O operation. It is protocol-agnostic, allowing the same struct to be used for HTTP, TCP, or custom dispatchers.
- Normalization: The
:headersfield is automatically converted into aHeadersstruct. - Signal Support: Pass a
AbortSignalvia the:signaloption to enable timeouts or manual cancellation. - Redirect Control: Supports
follow,manual, anderrormodes.
The result of a successful fetch.
- Streaming Body: The
:bodyis anEnumerable(Stream), ensuring large resources are never fully buffered into memory. - Metadata: Includes the final
url(post-redirects),statuscode, and a normalizedHeaderscontainer.
A case-insensitive, multi-value container for protocol headers.
- Spec Parity: Implements
append,set,get,delete, andhas. - Multi-Value Support: Correctly handles multiple values for a single key, including the specific
getSetCookieexception. - Protocols: Implements
AccessandEnumerable, allowing forheaders["content-type"]andEnum.map(headers, ...).
Pure data structs that handle both standard URIs and rclone-style connection strings.
- Direct Access: Access
url.protocol,url.hostname, orurl.pathnamedirectly via dot notation. - Overloaded Getters/Setters: Use
URL.href(url, "new_url")to parse and update the entire struct immutably. - Ordered Params:
URLSearchParamspreserves key order and duplicate keys.
Standardized mechanism for coordinating the cancellation of one or more asynchronous operations.
AbortController: The management object used to trigger cancellation. Create one with AbortController.new()`..signalProperty: AAbortSignalinstance linked to the controller. This is the "read-only" observer passed tofetchto monitor the aborted state.AbortSignalState: Signals include anabortedboolean and areasonfor the cancellation.AbortSignal.timeout(ms): Static helper that returns a signal that automatically aborts after a specified duration—perfect for handling request timeouts.AbortSignal.any(signals): Static helper that combines multiple signals into one; the combined signal aborts if ANY of the provided source signals abort.AbortSignal.abort(reason): Static helper that returns a signal that is already in an aborted state.
Dispatcher: The core behavior for all protocol handlers.Dispatcher.HTTP: Powered byFinch, handles connection pooling and redirects internally.Dispatcher.TCP: Base TCP implementation using:gen_tcpwith abort-aware streaming.Headers: A case-insensitive, multi-value header container withAccessandEnumerablesupport.
Web is built with a commitment to reliability, featuring 100% test coverage including property-based tests for URL parsing and header normalization.
mix test --cover