MarkdownCommitsConfig

name: rexi description: | rexi.js is a tiny fluent wrapper around the browser's fetch API. It provides a concise, chainable interface for making HTTP requests in scripting contexts, where fixi's job is to swap HTML and rexi's job is to make JSON calls.

when_to_load: | LOAD when the user:

  • Makes HTTP requests from JavaScript code
  • Needs a fluent, chainable fetch wrapper
  • Works with JSON APIs alongside fixi HTML swaps
  • Uses rexi verb shortcuts (get, post, put, patch, del)
  • Needs response parsers (.json(), .text(), .html(), .blob())
  • Handles non-2xx responses as errors

scope: | The rexi skill covers:

  • HTTP verb shortcuts (get, post, put, patch, del)
  • Request body normalization (FormData, objects, strings, etc.)
  • Chainable response parsers
  • Error handling for non-2xx responses
  • Global events (rexi:before, rexi:after)
  • Integration with fixi and moxi

rexi.js Skill Reference

Overview

rexi.js is a tiny fluent wrapper around the browser's fetch API.

It is useful when you want to issue HTTP requests in scripting contexts, rather than in fixi-driven contexts. Where fixi's job is "swap HTML returned by the server", rexi's job is "make the JSON call you actually need to make from JavaScript".

It exposes six HTTP-verb shortcuts on globalThis. Each call returns a decorated Promise<Response> with chainable parsers, so the common case is a single await.

Non-2xx responses throw an Error carrying .status and .response.

Verbs

All six verbs share the same shape: verb(url, body?, opts?).

verbdefault disposition
get, headURL-encode the body into a query string
post, put, patchsend the body in the request body
dela stand-in for delete (a JS keyword)

Override the default with opts.send: "query" | "body".

Body Normalization

The second argument is a logical "input"; rexi figures out the wire format from its type:

inputsent as
FormDataas-is
HTMLFormElementnew FormData(el)
single named input elementFormData with one [name, value] entry
iterable of elements (e.g. moxi q(...))FormData collecting each element's [name, value]
plain objectJSON.stringify, Content-Type: application/json
string / Blob / URLSearchParams / ArrayBufferpassed straight to fetch
null / undefinedno body

Response Helpers

The promise returned by each verb is decorated with chainable parsers, which avoids the double await problem you usually see with fetch:

helperreturns
.json()parsed JSON
.text()response text
.html()a DocumentFragment parsed via <template>
.blob()a Blob
.raw()the raw Response
.abort()cancels the underlying fetch

Events

rexi dispatches two CustomEvents on document during its lifecycle:

eventwhen
rexi:beforethe request is about to be sent. Mutate evt.detail.cfg = {url, init} to inject headers, rewrite URLs, etc. Cancelable: preventDefault() aborts with AbortError.
rexi:afterfires for every completed fetch (including non-2xx, before rexi throws). evt.detail = {cfg, response}.

Examples

Plain JSON Calls

Fetching the current user, logging in with a form, and posting a new record:

let me = await get("/api/me").json()
await post("/api/login", document.forms.login)   // form element, sent multipart
await post("/api/users", {name: "Ada"})          // plain object, sent as JSON
await get("/search", {q: "hi"})                  // URL-encoded query string

The shape is: pick a verb, pass a URL, hand it whatever you have, and await the parser you want.

Calling rexi From moxi

If a moxi handler needs to call a JSON API it can use rexi.

The handler scope is already async, so await works inline:

<button on-click="let r = await post('/api/calc', {x: 3, y: 4}).json()
                  q('next output').innerText = r.total">
    Add
</button>
<output></output>

Of course, for something like this we would recommend using fixi instead, but we aren't going to judge :)

Adding Auth Headers Globally

Listen for rexi:before on document to mutate every outgoing request before it's sent. This is the right place for auth headers, request-ID injection, or a uniform URL prefix:

document.addEventListener("rexi:before", (e) => {
    e.detail.cfg.init.headers.Authorization = `Bearer ${getToken()}`
})

Error Handling

Non-2xx responses throw an Error with .status and .response:

try {
    let data = await post("/api/save", {title: "My Post"}).json()
} catch (err) {
    console.log(err.status)  // 401
    console.log(err.response) // Response object
    showError("Save failed: " + (await err.response.text()))
}

Aborting Requests

Use .abort() to cancel a request:

let request = get("/api/slow-data").json()

// Later, if needed:
request.abort()

Or set a timeout:

async function getWithTimeout(url, timeout = 5000) {
    let controller = new AbortController()
    let timer = setTimeout(() => controller.abort(), timeout)
    let signal = controller.signal
    
    try {
        return await get(url, null, {signal}).json()
    } finally {
        clearTimeout(timer)
    }
}

Working with HTML Fragments

Use .html() to get a DocumentFragment:

let fragment = await get("/api/template").html()
document.getElementById("target").appendChild(fragment)

Sending Files

Use FormData directly or pass a form element:

// Direct FormData
let formData = new FormData()
formData.append("file", fileInput.files[0])
await post("/api/upload", formData)

// From form element
let form = document.querySelector("form")
await post("/api/upload", form)

Custom Headers

Pass headers in the options object:

let data = await get("/api/secure", null, {
    headers: {
        "Authorization": `Bearer ${token}`,
        "X-Custom": "value"
    }
}).json()

Best Practices

  1. Use verb shortcuts - get(), post(), put(), patch(), del() are more readable than raw fetch
  2. Chain parsers - .json(), .text(), etc. make response handling concise
  3. Leverage body normalization - Pass whatever you have (object, form, FormData) and rexi figures out the rest
  4. Use global events - rexi:before for auth headers, rexi:after for logging
  5. Handle errors - Always catch non-2xx responses
  6. Abort unused requests - Use .abort() to cancel requests when they're no longer needed
  7. Keep it simple - rexi is designed for the common case; use fetch directly for complex scenarios
  8. Combine with moxi - Use rexi for API calls, moxi for DOM manipulation

Comparison to Other Approaches

approachreadabilityfeaturessizeuse case
rexi✅ HighChainable, body normalization~1kbSimple JSON calls
fetch❌ LowFull browser APIBuilt-inComplex scenarios
axios✅ HighFull-featured~4kbApplications needing all features
jQuery.ajax✅ MediumLegacy~30kbjQuery projects

Quick Reference Card

┌─────────────────────────────────────────────────────────────┐
│ REXI.JS QUICK REFERENCE                                        │
├─────────────────────────────────────────────────────────────┤
│ VERBS (globalThis)                                             │
│   get(url, body?, opts?)   - GET request                       │
│   post(url, body?, opts?)  - POST request                      │
│   put(url, body?, opts?)   - PUT request                       │
│   patch(url, body?, opts?) - PATCH request                     │
│   del(url, body?, opts?)   - DELETE request                    │
│   head(url, body?, opts?)  - HEAD request                      │
├─────────────────────────────────────────────────────────────┤
│ BODY NORMALIZATION                                            │
│   FormData          -> as-is                                  │
│   HTMLFormElement   -> new FormData(el)                       │
│   Object            -> JSON.stringify                          │
│   String/Blob       -> passed straight to fetch               │
│   null/undefined    -> no body                                 │
├─────────────────────────────────────────────────────────────┤
│ RESPONSE PARSERS                                              │
│   .json()       - Parsed JSON                                 │
│   .text()       - Response text                               │
│   .html()       - DocumentFragment                            │
│   .blob()       - Blob                                       │
│   .raw()        - Raw Response                               │
│   .abort()      - Cancel request                              │
├─────────────────────────────────────────────────────────────┤
│ OPTIONS                                                         │
│   send: "query" | "body"    - Override body disposition        │
│   signal: AbortSignal    - For request cancellation           │
│   headers: {}             - Custom headers                     │
├─────────────────────────────────────────────────────────────┤
│ EVENTS                                                         │
│   rexi:before    - Before request (cancelable)                │
│   rexi:after     - After response (before throw)              │
└─────────────────────────────────────────────────────────────┘