Using Mithril’s m.request for Reliable Data Fetching
A concise guide to using Mithril’s m.request for data fetching, covering design, trust boundaries, error handling, and when to reconsider the approach.
09 Aug 2026, 06:37 UTC

Requirements
When building a single‑page application with Mithril, the data‑fetching layer must:
- Return a promise that integrates with Mithril’s automatic redraw cycle.
- Allow configuration of HTTP method, headers, body, and serialization.
- Provide basic error handling without forcing the developer to manually trigger redraws.
- Respect the framework’s XSS‑protection by keeping data‑binding separate from raw markup.
The Smallest Suitable Design
Mithril ships with a utility called m.request that satisfies the above with a single function call. Its signature is:
m.request(options)
Where options is a plain object that can contain:
url– endpoint string.method– HTTP verb (defaults to GET).data– body to send (will be JSON‑stringified ifserializeis not overridden).headers– custom request headers.timeout– milliseconds before aborting.serialize/deserialize– functions to override JSON handling.
The utility returns a native Promise that resolves with the parsed JSON (or whatever deserialize yields). Because Mithril’s rendering system watches for promise resolution, the view updates automatically when the promise settles.
Trust and Data Boundaries
Mithril’s template engine escapes any value inserted via {} syntax. When you render data obtained from m.request directly in a view, the framework treats it as untrusted text and applies HTML escaping. This means that even if a malicious server returns a payload containing script tags, the injected markup will be rendered as plain text, preventing XSS without extra sanitisation steps.
Operational Checks
To make the fetch robust in production, you can add the following checks:
- Timeout – set
options.timeoutto avoid hanging requests. - AbortController – pass a signal via
options(Mithril forwards it to the underlying fetch). This lets you cancel requests when a component is unloaded. - Error handling – the returned promise rejects on network errors, non‑2xx status, or timeout. You can catch it with
.catchortry/awaitinside an async lifecycle method.
Failure Modes
If the server responds with a non‑JSON body (e.g., HTML error page) and you rely on the default JSON deserializer, the promise will reject with a parsing error. Mithril does not automatically retry failed requests; retry logic must be implemented by the application developer (e.g., with exponential back‑off). Additionally, if the promise is rejected and not caught, the error propagates to the console but does not break the redraw cycle—Mithril will continue to render subsequent updates.
Conditions That Would Change the Design
The simple m.request approach is appropriate when:
- Requests are short‑lived and return complete JSON payloads.
- No progress indication or streaming is required.
- The API follows REST‑like JSON semantics.
You would reconsider the design if you needed any of the following:
- Streaming uploads/downloads (e.g., large file transfers).
- Progress events for UI feedback (requires manual XHR or fetch with readable streams).
- Integration with a GraphQL client that expects structured response handling beyond plain JSON.
- Custom retry policies, caching, or request deduplication that are better served by a dedicated data‑layer library.
Practical Example
The following component fetches a list of posts from a public JSON endpoint and renders them. It demonstrates timeout, abort handling, and error catching.
import m from 'mithril';
const PostList = {
oninit: function(vnode) {
vnode.state = { posts: [], error: null };
const controller = new AbortController();
vnode.state.controller = controller;
m.request({
url: 'https://jsonplaceholder.typicode.com/posts',
method: 'GET',
timeout: 8000, // 8‑second timeout
signal: controller.signal,
deserialize: function(data) {
// Assume the server always returns JSON
return JSON.parse(data);
}
})
.then(function(result) {
vnode.state.posts = result;
})
.catch(function(err) {
// Distinguish abort from other errors
if (err.name !== 'AbortError') {
vnode.state.error = err.message;
}
});
},
onremove: function(vnode) {
// Cancel request if component is removed before completion
if (vnode.state.controller) {
vnode.state.controller.abort();
}
},
view: function(vnode) {
if (vnode.state.error) {
return m('.error', 'Failed to load posts: ' + vnode.state.error);
}
if (vnode.state.posts.length === 0) {
return m('p', 'Loading…');
}
return [
m('h2', 'Posts'),
m('ul', vnode.state.posts.map(function(post) {
return m('li', [
m('h3', post.title),
m('p', post.body)
]);
}))
];
}
};
m.mount(document.body, PostList);
Explanation:
- The controller’s signal allows the request to be aborted when the component unmounts.
- A timeout prevents the request from hanging indefinitely.
- The
.catchblock stores an error message; the view renders it without triggering a redraw loop. - When the promise resolves, the component’s state updates and Mithril redraws the list automatically.
Limitations and Verification
Before relying on m.request in a project, verify:
- Version – ensure you are using a recent release (e.g., 2.0.4) where the promise‑based API is stable. Check
package.jsonor runnpm list mithril. - Fetch polyfill – in environments lacking native
fetch(older browsers, some server‑side renderings), include a polyfill such aswhatwg-fetchbefore importing Mithril. - Large payloads – if you expect megabyte‑sized responses, consider pagination or streaming; otherwise, the entire JSON will be parsed and held in memory, potentially causing expensive re‑renders.
To confirm that automatic redraw works, you can:
- Mount the component above.
- Open the browser devtools Network tab, throttle to “Slow 3G”, and observe the “Loading…” placeholder.
- When the request finishes, the list appears without any manual call to
m.redraw. - To test error handling, temporarily change the URL to a non‑existent endpoint and verify that the error message shows up and the component remains usable.
Conclusion
Mithril’s m.request offers a minimal, opinionated way to fetch data that aligns with the framework’s reactive rendering model. It handles JSON parsing, integrates with automatic redraws, and provides hooks for timeout and cancellation. For straightforward REST‑like APIs, it reduces boilerplate while keeping trust boundaries clear. If your application evolves to need streaming, progress reporting, or complex caching, you will need to supplement or replace m.request with a more specialized solution.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.