Cutting Boilerplate with Mithril's m.request
Stop writing repetitive JSON parsing code. Learn how Mithril's m.request automates serialization and response handling to clean up your API logic.
27 Nov 2025, 04:02 UTC

The JSON Boilerplate Problem
Most modern web applications spend a significant amount of code simply moving data between a client and a server. When using the native fetch API, you typically write the same sequence repeatedly: call the function, check if the response is okay, call .json(), and then handle the resulting data. This creates a repetitive pattern of "double-promising" that clutters your component logic.
Mithril provides a built-in alternative called m.request. Instead of acting as a thin wrapper, it automates the serialization of request bodies and the parsing of responses, allowing you to treat network calls as direct data transformations.
How m.request Simplifies Data Flow
m.request is designed to handle the most common API patterns out of the box. When you pass an object as the request body, Mithril automatically converts it to a JSON string and sets the Content-Type header to application/json. On the return trip, if the server responds with JSON, Mithril parses it before the promise resolves.
This removes the need for manual JSON.stringify() and response.json() calls. Because it returns a standard Promise, it integrates seamlessly with async/await syntax, keeping your view logic clean. Error handling also stays local: if the server returns a non-2xx status, the returned promise rejects, so you can catch failures in the same async function that made the call.
Implementation Example: A Simple User Profile Update
Assume you are using Mithril v2.0+. This example demonstrates updating a user profile. Run this code within a Mithril component or a script where m is available in the global scope.
// Required permissions: Standard browser network access
// Risk: Ensure the endpoint handles CORS if calling a different domain
async function updateProfile(userData) {
try {
// m.request automatically stringifies userData and parses the response
const response = await m.request({
method: 'POST',
url: '/api/user/update',
body: userData // No JSON.stringify needed
});
console.log('Update successful:', response);
return response;
} catch (error) {
// m.request throws an error if the response is not 2xx
console.error('Update failed:', error);
throw error;
}
}
Verification: To verify this is working, check the Network tab in your browser's Developer Tools. You should see the request payload as a JSON string and the response header Content-Type: application/json. The response variable in the code above will be a JavaScript object, not a raw Response stream.
Trade-offs: XHR vs. Fetch
While m.request is convenient, it is important to understand that it is built on XMLHttpRequest (XHR) rather than the newer fetch API. This leads to a few specific technical trade-offs:
- Compatibility: Because it uses XHR, it often works in older environments without needing a fetch polyfill.
- Streaming: Unlike
fetch,m.requestcannot handle streaming responses. You must wait for the entire payload to arrive before it is parsed. - Control: It lacks native integration with
AbortController, though it provides its own mechanisms for cancelling requests. - Proxy Behavior: In rare corporate environments, some legacy proxies may treat XHR and Fetch requests differently; if you encounter unexpected 403 or 502 errors that only happen in a specific office network, this may be the cause.
When to Stick with m.request
If your application primarily interacts with a REST API that exchanges JSON, m.request is almost always the superior choice for productivity. It reduces the surface area for bugs related to manual parsing and keeps your components focused on the UI rather than the transport layer.
To start migrating, identify your most common fetch patterns. If you find yourself calling .json() on every single request, replace that block with m.request and delete the boilerplate. That said, if you need request cancellation tied to component lifecycle or fine-grained control over streaming uploads, evaluate whether those requirements outweigh the convenience.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.