Using Mithril’s m.request for Reliable Declarative HTTP Calls
Learn how Mithril’s m.request simplifies AJAX calls with automatic method selection, unified error handling, and optional custom deserialization, plus practical tips for background requests and error safety.
29 Nov 2025, 11:36 UTC

The problem: repetitive AJAX boilerplate
When building a UI with Mithril, you often need to fetch JSON from a REST endpoint. Writing raw XMLHttpRequest or fetch calls inside components leads to repetitive code for setting headers, parsing responses, and handling errors. This duplication makes components harder to read and increases the chance of missing error cases.
Thesis: m.request centralises HTTP logic
Mithril’s m.request utility provides a single, promise‑based function that handles method selection, automatic JSON parsing, and unified error rejection. By delegating the HTTP mechanics to this helper, components stay focused on UI logic while gaining consistent behaviour across browsers (IE11+).
How m.request works
The function signature is m.request(options) where options is a plain object. Key properties include:
url– the endpoint to call.method– optional; if omitted, Mithril choosesGETwhendatais absent andPOSTwhendatais present.data– payload sent with the request; automatically serialized withJSON.stringifyunless a customserializeis supplied.background– whentrue, the request does not trigger Mithril’s automatic redraw cycle.deserialize– function that transforms the raw response text; defaults toJSON.parse.config– low‑level hook to modify theXMLHttpRequestobject before sending.
The call returns a Mithril promise that resolves with the deserialized body on any 2xx status, or rejects with an object containing the original XMLHttpRequest for network failures or non‑2xx responses.
Unified error handling
Because errors are always promise rejections, you can use a single .catch (or try/await) to handle:
- Network errors (e.g., DNS failure, offline).
- HTTP error statuses (4xx, 5xx).
- Deserialization failures when the default JSON parser cannot parse the response.
This consistency eliminates the need to check status codes manually in each component.
When the default deserialize isn’t enough
If an endpoint returns plain text, XML, or a custom format, the default JSON.parse will throw and cause the promise to reject. Supplying a custom deserialize function avoids this:
m.request({
url: '/api/plain-text',
deserialize: function (text) {
return text; // return raw string
}
}).then(function (result) {
console.log('Plain text:', result);
}).catch(function (err) {
console.error('Request failed', err);
});
Similarly, a custom serialize lets you send FormData or query‑string bodies when needed.
Worked example: fetching a post and handling errors
The following minimal page mounts a Mithril component that requests a post from JSONPlaceholder, logs the result, and demonstrates error handling for a 404. Note that the example is illustrative; you should run it in your own environment to verify behaviour.
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/mithril/mithril.js"></script>
</head>
<body>
<div id="app"></div>
<script>
var PostComponent = {
oninit: function () {
// Successful request
m.request({url: 'https://jsonplaceholder.typicode.com/posts/1'})
.then(function (post) {
console.log('Fetched post:', post);
})
.catch(function (err) {
console.error('Unexpected error:', err);
});
// Simulated error request (404)
m.request({url: 'https://jsonplaceholder.typicode.com/posts/999999'})
.catch(function (err) {
// err contains the original XMLHttpRequest
var xhr = err;
console.log('Status:', xhr.status); // 404
console.log('Response text:', xhr.responseText);
});
},
view: function () { return m("div", "Check console for results"); }
};
m.mount(document.getElementById('app'), PostComponent);
</script>
</body>
</html>
Open the page, open DevTools → Console, and you should see a log with the post object followed by a log showing the 404 status and empty response text. The Network tab confirms that the first request uses GET (no data supplied) and the second also uses GET.
Trade‑off: background requests can hide failures
Setting background: true prevents Mithril from triggering a redraw when the request settles. This is useful for prefetching data that does not affect the current view. However, errors in background requests are **not** automatically surfaced through Mithril’s rendering cycle; they are still promise rejections, so if you omit a .catch (or await with try/catch) the failure will be silently ignored. A practical safeguard is to always attach a .catch or use await inside an async function, even for background calls.
Limitation: default deserialize expects JSON
The built‑in deserializer assumes the response body is valid JSON. When dealing with legacy APIs that return XML or plain text, you must provide a custom deserialize as shown earlier. Forgetting to do so will cause the promise to reject with a parsing error, which may be mistaken for a network failure if you only inspect the error object’s status.
Actionable checklist
- Use
m.requestfor all AJAX calls to centralise method selection and error handling. - Always attach a
.catch(orawait/try) to avoid silently dropped errors, especially whenbackground:true. - If the API returns non‑JSON data, supply a custom
deserializefunction. - For advanced needs (custom headers, credentials, timeout), use the
configoption to tweak the underlyingXMLHttpRequest. - Verify behaviour in the Network tab: check method, status, and that no extra libraries beyond mithril.js are loaded.
By following these steps, you gain predictable HTTP interactions while keeping your Mithril components clean and focused on UI concerns.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.