🐕 rexi.js: a fluent fetch
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
Below are some rexi examples, building on fixi and moxi.
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()}`
})
Reference
For the full reference (options, include, timeouts, abort signals), see the
rexi README on GitHub.
With rexi covered, all five libraries are on the table. Head to the Demo to see them working together.