Packages
http_fetch
0.6.0
0.21.19
0.21.18
0.21.17
0.21.16
0.21.15
0.21.14
0.21.13
0.21.12
0.21.11
0.21.10
0.21.9
0.21.8
0.21.7
0.21.6
0.21.5
0.21.4
0.21.3
0.21.2
0.21.1
0.21.0
0.20.0
0.19.1
0.19.0
0.18.1
0.18.0
0.17.2
0.17.1
0.17.0
0.16.7
0.16.5
0.16.1
0.16.0
0.15.1
0.15.0
0.14.0
0.13.0
0.12.0
0.11.0
0.10.0
0.9.1
0.9.0
0.8.0
0.6.0
0.5.0
0.4.3
0.4.2
0.4.1
0.4.0
0.3.1
0.3.0
0.2.0
0.1.0
A browser-like HTTP fetch API for Elixir using Erlang's built-in socket modules
Current section
Files
Jump to
Current section
Files
http_fetch
README.md
README.md
# HTTP Fetch
[](https://github.com/gsmlg-dev/http_fetch/actions/workflows/elixir.yml)
[](https://hex.pm/packages/phoenix_react_server)
[](https://hexdocs.pm/http_fetch/)
[](https://hex.pm/packages/http_fetch)
[](https://hex.pm/packages/http_fetch)
A modern HTTP client library for Elixir that provides a fetch API similar to web browsers, built on Erlang's built-in `:httpc` module.
## Features
- **Browser-like API**: Familiar fetch interface with promises and async/await patterns
- **Full HTTP support**: GET, POST, PUT, DELETE, PATCH, HEAD methods
- **Complete httpc integration**: Support for all :httpc.request options
- **Form data support**: HTTP.FormData for multipart/form-data and file uploads
- **Streaming file uploads**: Efficient large file uploads using streams
- **Type-safe configuration**: HTTP.FetchOptions for structured request configuration
- **Promise-based**: Async operations with chaining support
- **Request cancellation**: AbortController support for cancelling requests
- **Automatic JSON parsing**: Built-in JSON response handling
- **Zero dependencies**: Uses only Erlang/OTP built-in modules
## Quick Start
```elixir
# Simple GET request
{:ok, response} =
HTTP.fetch("https://jsonplaceholder.typicode.com/posts/1")
|> HTTP.Promise.await()
# Get response data
IO.puts("Status: #{response.status}")
text = HTTP.Response.text(response)
{:ok, json} = HTTP.Response.json(response)
# Read response body as raw binary
response =
HTTP.fetch("https://jsonplaceholder.typicode.com/posts/1")
|> HTTP.Promise.await()
# response.body contains the raw binary data
binary_data = response.body
# POST request with JSON
{:ok, response} =
HTTP.fetch("https://jsonplaceholder.typicode.com/posts", [
method: "POST",
headers: %{"Content-Type" => "application/json"},
body: JSON.encode\!(%{title: "Hello", body: "World"})
])
|> HTTP.Promise.await()
```
# Form data with file upload
```elixir
file_stream = File.stream!("document.pdf")
form = HTTP.FormData.new()
|> HTTP.FormData.append_field("name", "John Doe")
|> HTTP.FormData.append_file("document", "document.pdf", file_stream)
{:ok, response} =
HTTP.fetch("https://api.example.com/upload", [
method: "POST",
body: form
])
|> HTTP.Promise.await()
```
## API Reference
### HTTP.fetch/2
Performs an HTTP request and returns a Promise.
```elixir
promise = HTTP.fetch(url, [
method: "GET",
headers: %{"Accept" => "application/json"},
body: "request body",
content_type: "application/json",
options: [timeout: 10_000],
signal: abort_controller
])
```
Supports both string URLs and URI structs:
```elixir
# String URL
promise = HTTP.fetch("https://api.example.com/data")
# URI struct
uri = URI.parse("https://api.example.com/data")
promise = HTTP.fetch(uri)
```
### HTTP.Promise
Asynchronous promise wrapper for HTTP requests.
```elixir
{:ok, response} = HTTP.Promise.await(promise)
# Promise chaining
HTTP.fetch("https://api.example.com/data")
|> HTTP.Promise.then(fn response -> HTTP.Response.json(response) end)
|> HTTP.Promise.await()
```
### HTTP.Response
Represents an HTTP response.
```elixir
text = HTTP.Response.text(response)
{:ok, json} = HTTP.Response.json(response)
# Access raw response body as binary
response =
HTTP.fetch("https://api.example.com/large-file")
|> HTTP.Promise.await()
# response.body contains the raw binary response data
binary_data = response.body
# Write response to file (supports both streaming and non-streaming)
:ok = HTTP.Response.write_to(response, "/tmp/downloaded-file.txt")
# Write large file downloads directly to disk
{:ok, response} =
HTTP.fetch("https://example.com/large-file.zip")
|> HTTP.Promise.await()
:ok = HTTP.Response.write_to(response, "/tmp/large-file.zip")
```
### HTTP.Headers
Handle HTTP headers with utilities for parsing, normalizing, and manipulating headers.
```elixir
# Create headers
headers = HTTP.Headers.new([{"Content-Type", "application/json"}])
# Get header value
type = HTTP.Headers.get(headers, "content-type")
# Set header
headers = HTTP.Headers.set(headers, "Authorization", "Bearer token")
# Set header only if not already present
headers = HTTP.Headers.set_default(headers, "User-Agent", "CustomAgent/1.0")
# Access default user agent string
default_ua = HTTP.Headers.user_agent()
# Parse Content-Type
{media_type, params} = HTTP.Headers.parse_content_type("application/json; charset=utf-8")
```
### HTTP.Telemetry
Comprehensive telemetry and metrics for HTTP requests and responses.
```elixir
# All HTTP.fetch operations automatically emit telemetry events
# No configuration required - just attach handlers
:telemetry.attach_many(
"my_handler",
[
[:http_fetch, :request, :start],
[:http_fetch, :request, :stop],
[:http_fetch, :request, :exception]
],
fn event_name, measurements, metadata, _config ->
case event_name do
[:http_fetch, :request, :start] ->
IO.puts("Starting request to #{metadata.url}")
[:http_fetch, :request, :stop] ->
IO.puts("Request completed: #{measurements.status} in #{measurements.duration}μs")
[:http_fetch, :request, :exception] ->
IO.puts("Request failed: #{inspect(metadata.error)}")
end
end,
nil
)
# Manual telemetry events (for custom implementations)
HTTP.Telemetry.request_start("GET", URI.parse("https://example.com"), %HTTP.Headers{})
HTTP.Telemetry.request_stop(200, URI.parse("https://example.com"), 1024, 1500)
HTTP.Telemetry.request_exception(URI.parse("https://example.com"), :timeout, 5000)
```
### HTTP.Request
Request configuration struct.
```elixir
request = %HTTP.Request{
method: :post,
url: URI.parse("https://api.example.com/data"),
headers: [{"Authorization", "Bearer token"}],
body: "data",
http_options: [timeout: 10_000, connect_timeout: 5_000],
options: [sync: false, body_format: :binary]
}
```
**Field Mapping to :httpc.request/4:**
- `http_options`: 3rd argument (request-specific HTTP options)
- `options`: 4th argument (client-specific options)
### HTTP.FormData
Handle form data and file uploads.
```elixir
# Regular form data
form = HTTP.FormData.new()
|> HTTP.FormData.append_field("name", "John")
|> HTTP.FormData.append_field("email", "john@example.com")
# File upload
file_stream = File.stream!("document.pdf")
form = HTTP.FormData.new()
|> HTTP.FormData.append_field("name", "John")
|> HTTP.FormData.append_file("document", "document.pdf", file_stream, "application/pdf")
# Use in request
HTTP.fetch("https://api.example.com/upload", method: "POST", body: form)
```
### HTTP.AbortController
Request cancellation.
```elixir
controller = HTTP.AbortController.new()
HTTP.AbortController.abort(controller)
```
## Error Handling
The library handles:
- Network errors and timeouts
- HTTP error status codes
- JSON parsing errors
- Invalid URLs
- Cancelled requests
## Development
This project uses several code quality tools to maintain high standards:
### Code Quality Tools
**Credo** - Static code analysis to enforce Elixir style guidelines and identify code smells:
```bash
# Run standard checks
mix credo
# Run with strict mode (includes readability checks)
mix credo --strict
# Explain a specific issue
mix credo explain <issue_category>
```
**Dialyzer** - Static type analysis to catch type errors and inconsistencies:
```bash
# Run type checking
mix dialyzer
# Generate/rebuild PLT (first time setup, takes 2-3 minutes)
mix dialyzer --plt
```
**ExDoc** - Generate comprehensive documentation:
```bash
# Generate HTML documentation
mix docs
# View generated docs
open doc/index.html
```
### Running Tests
```bash
# Run all tests
mix test
# Run specific test file
mix test test/http_test.exs
# Run with coverage
mix test --cover
```
### Code Formatting
```bash
# Format all code
mix format
# Check formatting without changes
mix format --check-formatted
```
## Requirements
- Elixir 1.18+ (for built-in `JSON` module support)
- Erlang OTP with `:inets`, `:ssl`, `:public_key` applications
## License
MIT License