Fluxon Docs
Batteries

http — server & client

Declare routes on one line, handle uploads and redirects, and call external APIs.

Server

You declare a route on a single line:

use http

http.on :post "/notes" \req -> rep 201 {ok:true}
http.on :get "/notes/:id" \req -> rep 200 {id:req.params.id}
http.serve 8080
  • http.on :method "/path" handler — a route. The method is a symbol (:get, :post, :put, :patch, :del).
  • The handler is a lambda. Its argument is req:
    • req.body — the JSON body (automatically converted to a map)
    • req.params.id — the :id in the path
    • req.query — query parameters (?key=val)
    • req.headers — headers
  • rep status body — the response. If body is a map, it automatically becomes JSON.
  • http.serve port — starts the server.
  • http.serve port {max_body: BYTES} — configures the request body size limit (DoS guard). Default 10 MiB (10485760 bytes); over the limit the server returns 413 Payload Too Large without buffering the body. max_body: 0 disables the limit (unlimited — only behind a trusted internal network).

File upload (multipart/form-data)

Files sent by a browser form or curl -F land in req.files, plain form fields in req.body (symmetric with JSON):

http.on :post "/upload" \req ->
  f = req.files.0
  fs.write f.filename f.content
  rep 201 {saved:f.filename size:f.size}
  • Each file: {name filename content size}. content is a str for UTF-8 text, bytes for binary (image, PDF); size is always the byte count.
  • req.files is always a list — empty when the request is not multipart (each works without a nil check).
  • The max_body limit applies to multipart bodies too.

Redirect

There is no special verb — with rep you give a 302 status and a location key; it becomes the Location header:

http.on :get "/:code" \req ->
  link = db.one "select * from links where code=$1" [req.params.code]
  link ?? (rep 404 {error:"not found"})
  rep 302 {location:link.url}

Route precedence

If two routes overlap (/:code and /stats/:code), the literal (exact) path automatically wins — regardless of the order written. /stats/:code is always checked before /:code.

Client

Calling an external API:

res = http.get "https://api.example.com/data"
res = http.post url {key:"val"}      # the body becomes JSON automatically
# res.status, res.body, res.headers (a map, keys lowercased)
loc = res.headers.location           # or res.headers["content-type"]

A redirect (3xx) is not followed by defaultres.status is 30x, and res.headers.location is read. If you need automatic following, add an options map:

res = http.get url {follow:true}         # 3xx → follows Location
res = http.get url {follow:true max:5}   # hop limit (default 10)
# res.hops — how many redirects happened

Exceeding max is an error. The options map is the last argument: http.post url body {follow:true}.

Custom request headers

For APIs that require authentication (x-api-key, Authorization, anthropic-version...), add headers to the options map — this is symmetric with res.headers in the response:

res = http.post "https://api.anthropic.com/v1/messages" body {
  headers: {
    "x-api-key": env.ANTHROPIC_API_KEY
    "anthropic-version": "2023-06-01"
  }
}

If a header value is not a string it is converted to text; a header with a nil value is dropped. If the user provides content-type, that is used instead of the automatic application/json.

On this page