HTTP Client

Luma provides a built-in HTTP client through the http constructor.
Create a request, configure it step by step, and execute with .get(), .post(), or any HTTP method.
No imports, no dependencies — works out of the box.

Creating a request

h: http = http("https://api.example.com/users")

Pass the full URL including any query parameters:

h: http = http("https://api.example.com/users?page=1&limit=10")

Configuration

Headers

h.header("Authorization", "Bearer token123")
h.header("Accept", "application/json")

Set any HTTP header. Call multiple times to add multiple headers.

Form fields

h.field("username", "alice")
h.field("password", "secret")

Adds form data sent as application/x-www-form-urlencoded. The Content-Type header is set automatically when using .field(). Call multiple times to add multiple fields.

Body

h.body({"name": "Alice", "age": 30})

When the body is a map, it is automatically serialized to JSON and Content-Type: application/json is set.

h.body("<xml><name>Alice</name></xml>")

When the body is a string, it is sent as-is. Set the Content-Type yourself via .header() if needed.

.field() and .body() are mutually exclusive — use one or the other, not both.

Timeout

h.timeout(10)

Set the request timeout in seconds. Default is 30 seconds. If the request takes longer, it panics (handle with .or()).

Executing

res = h.get()
res = h.post()
res = h.put()
res = h.delete()
res = h.patch()

Each method sends the configured request and returns an HttpResponse.

Panics on network errors (DNS failure, connection refused, timeout). Use .or() for error handling.

Response

res.status      // int — HTTP status code (200, 404, 500, etc.)
res.body        // str — response body as string
res.headers     // {str: str} — response headers
Field Type Description
status int HTTP status code
body str Response body as string
headers {str: str} Response headers

Examples

Simple GET

res = http("https://api.example.com/health").get()
print(res.status)
print(res.body)

One line to create and execute. For simple requests, no configuration needed.

GET with authentication

h: http = http("https://api.example.com/users")
h.header("Authorization", "Bearer token123")
res = h.get()

users = json.decode(res.body)
print(users)

POST JSON

h: http = http("https://api.example.com/users")
h.header("Authorization", "Bearer token123")
h.body({"name": "Alice", "age": 30})
res = h.post()

print("Created: ${res.status}")

The map body is automatically serialized to JSON — no manual json.encode(), no escaped quotes.

POST form data

h: http = http("https://api.example.com/login")
h.field("username", "alice")
h.field("password", "secret")
res = h.post()

if res.status == 200 {
    print("Logged in")
} else {
    print("Login failed: ${res.status}")
}

Form fields are sent as application/x-www-form-urlencoded, the standard format for HTML form submissions.

PUT update

h: http = http("https://api.example.com/users/1")
h.header("Authorization", "Bearer token123")
h.body({"name": "Bob", "age": 31})
h.timeout(5)
res = h.put()

print("Updated: ${res.status}")

DELETE

h: http = http("https://api.example.com/users/1")
h.header("Authorization", "Bearer token123")
res = h.delete()

print("Deleted: ${res.status}")

Error handling

res = http("https://unreachable.example.com").get().or(err -> {
    print("Request failed: ${err}")
})

Network errors, DNS failures, and timeouts are caught with .or(). Note that HTTP error status codes (4xx, 5xx) are not errors — they return a valid response with the status code.

Check status codes

res = http("https://api.example.com/users/1").get()

match res.status {
    200 -> print("Found: ${res.body}")
    404 -> print("User not found")
    401 -> print("Not authorized")
    _ -> print("Unexpected: ${res.status}")
}

Read response headers

res = http("https://api.example.com/data").get()

content_type: str = res.headers["Content-Type"]
print("Content-Type: ${content_type}")

Fetch and parse JSON

res = http("https://api.example.com/users").get()

if res.status == 200 {
    users = json.decode(res.body)
    print(json.pretty(users))
}

Combine with the json namespace for API work. json.decode() parses the response body, json.pretty() formats it.

Call an API in a loop

ids: [int] = [1, 2, 3, 4, 5]
ids.walk(id) -> {
    res = http("https://api.example.com/users/${id}").get()
    if res.status == 200 {
        user = json.decode(res.body)
        print("User ${id}: ${user["name"]}")
    }
}

POST with timeout and error handling

h: http = http("https://slow-api.example.com/process")
h.body({"task": "analyze", "data": "large-dataset"})
h.timeout(60)
res = h.post().or(err -> {
    print("Timed out or failed: ${err}")
})

Method summary

Method Arguments Description
http() url: str Create an HTTP request
.header() key: str, value: str Set a request header
.field() key: str, value: str Add a form field
.body() data: {str: any} Set JSON body (auto-serialized)
.body() data: str Set raw string body
.timeout() seconds: int Set request timeout (default: 30)
.get() Execute GET request
.post() Execute POST request
.put() Execute PUT request
.delete() Execute DELETE request
.patch() Execute PATCH request

Error handling

Situation Behavior
Network error (DNS, connection refused) Panics — catch with .or()
Timeout exceeded Panics — catch with .or()
HTTP 4xx or 5xx response Returns normally with res.status set
Invalid URL Panics — catch with .or()

HTTP error codes are not panics. A 404 or 500 is a valid server response — your code decides what to do with it via res.status.

Design notes

  • http is a constructor, not a namespace — http("url") creates a request object, similar to buffer() and tcp.connect()
  • Builder pattern — configure step by step with .header(), .field(), .body(), .timeout(), then execute
  • Auto-JSON — passing a map to .body() serializes to JSON automatically. No manual encoding, no backslash escaping
  • Form fields are separate from body — .field() builds URL-encoded form data, .body() sends raw or JSON. They don’t mix
  • Seconds for timeouth.timeout(10) is immediately clear. Milliseconds would require counting zeros
  • Panics on network errors only — HTTP status codes like 404, 500 are valid responses, not errors. The programmer decides how to handle them
  • No connection pooling — each http() call is independent. Connection reuse can be added later if needed
  • HTTPS by default — just use https:// in the URL. TLS is handled automatically
Last updated on