HTTP Client
Source: tutorial/05_http_client See in Playground
Fetches one post from a public test API (jsonplaceholder.typicode.com ), re-serialises its JSON body, and writes it to a file.
Running
cd tutorial/05_http_client
melodium run Compo.toml --post_id 1The request URL is built from post_id with |format. The example logs when the request completes, and separately handles a technical failure (DNS, connection, timeout) versus a body that fails to parse, then writes the parsed and re-serialised JSON body to post.txt.
Optional: add --api-report and an API token (MELODIUM_API_TOKEN) to see this run’s full trace on Cadence.CI .
How it works
This example uses no models: get needs no client model for a one-off request. A connection-reusing HttpClient model is used instead in other examples, once a connection should stay open across requests.
Data flow
Firing the request
get(url=...) fires on startup.trigger and streams the response body through data, independently of status/completed/failed/error, which each fire once. The URL is built with |format, called directly as a value, with no ports or connections involved:
fetch: get(url=|format("https://jsonplaceholder.typicode.com/posts/{id}", |map([|entry("id", post_id)])))
startup.trigger -> fetch.trigger
logOk: logInfoMessage(label="http", message="request completed")
fetch.completed -> logOk.triggerTechnical failure versus data failure
fetch.failed/fetch.error fire when the request itself could not be completed: DNS, connection, or timeout issues. A response that arrives successfully but contains invalid JSON is a completely separate, later failure mode, handled below when toJson’s Option comes back none. The two should not be conflated:
netFailed: logErrorMessage(label="http", message="request failed technically")
netError: logError(label="http")
fetch.failed -> netFailed.trigger
fetch.error -> netError.messageDecoding and re-serialising the body
The raw byte body is turned into a Stream<string> with decode, parsed with toJson, unwrapped with unwrapOr, and re-serialised. This round-trip is a good way to confirm a response really is valid JSON without changing its meaning:
decode: decode()
fetch.data -> decode.data
parsed: toJson()
body: unwrapOr<Json>(default=|null())
asText: toString<Json>()
write: writeTextLocal(path=output)
logDone: logInfoMessage(label="http", message="response written to file")
decode.text -> parsed.text,json -> body.option,value -> asText.value,into -> write.text
write.finished -> logDone.triggerDependencies
[dependencies]
std = "0.10.3" # core flows, logging, data structures
http = "0.10.3" # HTTP client and server
json = "0.10.3" # JSON parsing and serialisation
encoding = "0.10.3" # UTF-8 encode / decode
fs = "0.10.3" # local file I/O