Markdown Files
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?).
| verb | default disposition |
|---|---|
get, head | URL-encode the body into a query string |
post, put, patch | send the body in the request body |
del | a 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:
| input | sent as |
|---|---|
| FormData | as-is |
| HTMLFormElement | new FormData(el) |
| single named input element | FormData with one [name, value] entry |
| iterable of elements (e.g. moxi q(...)) | FormData collecting each element's [name, value] |
| plain object | JSON.stringify, Content-Type: application/json |
| string / Blob / URLSearchParams / ArrayBuffer | passed straight to fetch |
| null / undefined | no 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:
| helper | returns |
|---|---|
.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:
| event | when |
|---|---|
rexi:before | the request is about to be sent. Mutate evt.detail.cfg = {url, init} to inject headers, rewrite URLs, etc. Cancelable: preventDefault() aborts with AbortError. |
rexi:after | fires 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
- Use verb shortcuts -
get(),post(),put(),patch(),del()are more readable than raw fetch - Chain parsers -
.json(),.text(), etc. make response handling concise - Leverage body normalization - Pass whatever you have (object, form, FormData) and rexi figures out the rest
- Use global events -
rexi:beforefor auth headers,rexi:afterfor logging - Handle errors - Always catch non-2xx responses
- Abort unused requests - Use
.abort()to cancel requests when they're no longer needed - Keep it simple - rexi is designed for the common case; use fetch directly for complex scenarios
- Combine with moxi - Use rexi for API calls, moxi for DOM manipulation
Comparison to Other Approaches
| approach | readability | features | size | use case |
|---|---|---|---|---|
| rexi | ✅ High | Chainable, body normalization | ~1kb | Simple JSON calls |
| fetch | ❌ Low | Full browser API | Built-in | Complex scenarios |
| axios | ✅ High | Full-featured | ~4kb | Applications needing all features |
| jQuery.ajax | ✅ Medium | Legacy | ~30kb | jQuery 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) │
└─────────────────────────────────────────────────────────────┘