Choosing Between m.request and Fetch API in Mithril Applications
Guide to choosing between m.request and Fetch API in Mithril apps: constraints, feature table, trade‑offs, runnable example, and verification steps.
31 Jul 2026, 08:53 UTC

Decision and constraints
When building a Mithril v2+ application you must decide how to perform HTTP requests. The choice affects code size, browser support, and how tightly the request integrates with Mithril’s auto‑redraw cycle. The decision assumes a target environment that supports ES6 (IE11 is only supported via Mithril’s XHR fallback).
Option comparison
| Feature | m.request | Fetch API |
|---|---|---|
| Automatic JSON parsing | Yes (default) | No – requires .json() |
| Request interception | Via config option | Requires custom wrapper |
| Response interception | Via extract option | Requires .then handling |
| Abort support | Through background:true + manual XHR abort | Via AbortController |
| Error handling | Throws on non‑2xx unless serialize set | Requires checking response.ok |
| Bundle size impact | ~0.5 KB gzipped (part of Mithril core) | 0 KB (native) |
| IE11 support | Yes (uses XHR fallback) | No (needs polyfill) |
Trade‑offs
m.request provides convenience: automatic JSON conversion, built‑in interception hooks, and seamless integration with Mithril’s redraw system. The downside is abstraction – you have less direct control over streams or low‑level options.
Fetch API gives full control over the request lifecycle, works with readable streams, and avoids any extra bundle weight. However, you must manually parse JSON, handle aborts, and, if you call fetch outside Mithril’s lifecycle, wrap state updates in m.redraw() to trigger a redraw.
Concrete implementation
The following component shows both approaches side‑by‑side. Comment out one function to switch implementations.
import m from 'mithril';
const Users = {
// Using m.request
// getUsers() { return m.request({method: 'GET', url: '/api/users'}); },
// Using Fetch API
getUsers() {
return fetch('/api/users')
.then(resp => {
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
return resp.json();
});
},
oninit: () => {
this.getUsers().then(data => Users.users = data);
},
view: () => {
return m('div', [
m('h2', 'Users'),
Users.users ? m('ul', Users.users.map(u => m('li', u.name))) : m('p', 'Loading…')
]);
}
};
m.mount(document.body, Users);
Verification steps
- Create an HTML file that loads Mithril (via a CDN or local bundle) and the script above.
- Serve the file with a static server (e.g.,
python -m http.server). - Open the page in a modern browser; the user list should render without errors.
- Toggle between the
m.requestandFetchimplementations by commenting/uncommenting the relevant lines and reload to confirm identical UI output. - Optional: test in IE11 (or an emulator) to observe that the Fetch version fails unless a polyfill is loaded, while the m.request version continues to work.
Limitations
- m.request does not support streaming responses; for large payloads use Fetch with
ReadableStream. - When using Fetch outside Mithril’s lifecycle hooks, remember to call
m.redraw()after state changes to trigger a view update.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.