Preface
When it comes to align fetch in both frontend and backend environment, axios is the most frequent library which be mentioned. However, what is the difference between Client/Server side fetch?
Axios
We could use axios to make a request in both Browser side or server side directly. This is because axios itself handles and wraps the different environments of the browser and Node, and then exposes them through a single interface. In the browser, axios uses XMLHttpRequest(XHR) to handle requests, while in the Node environment, it uses the library http/https. Below is a simplified demo based on the axios source code.
Handling in the browser — function
xhrAdapterwould return a Promise based onXMLHttpRequest.export async function xhrAdapter(config) { return new Promise((resolve, reject) => { const request = new XMLHttpRequest(); request.open(config.method.toUpperCase(), config.url, true); Object.keys(config.headers).forEach((key) => { request.setRequestHeader(key, config.headers[key]); }); request.onload = function () { const response = { data: JSON.parse(request.responseText), status: request.status, statusText: request.getAllResponseHeaders(), config: config, request: request, }; resolve(response); }; request.onerror = function () { reject(new Error("Network Error")); }; request.ontimeout = function () { reject(new Error(`Timeout of ${config.timeout} ms exceeded`)); }; request.send(config.data); }); }In the Node environment, is
httpAdpaterfunction, which returns a Promise based onhttporhttps.import http from "http"; import https from "https"; export async function httpAdpater(config) { return new Promise((resolve, reject) => { const url = new URL(config.url); const isHttps = url.protocol === "https"; const options = { method: config.method, headers: config.headers, }; const request = (isHttps ? https : http).request( url, options, (response) => { let data = ""; response.on("data", (chunk) => { data += chunk; }); response.on("end", () => { resolve({ data: JSON.parse(data), status: response.statusCode, statusText: response.statusMessage, headers: response.headers, config: config, request: response, }); }); }, ); request.on("error", (error) => { reject(error); }); if (config.data) { request.write(config.data); } request.end(); }); }Finally, will check whether the environment is in the browser or in the Node , in order to return the right instance—if browser, return
xhrAdapter; if Node environment, returnhttpAdpater.async function getDefaultAdapter() { if (typeof XMLHttpRequest !== "undefined") { // for browser, using XHR adapter const { xhrAdapter } = await import("./xhrAdapter.js"); return xhrAdapter; } else if (typeof process !== "undefined") { // for Node env, using HTTP Adpater const { httpAdpater } = await import("./httpAdpater.js"); return httpAdpater; } throw new Error("No suitable adapter found"); }The completed mocking axios source code could refer through the link
fetch
Since 2015, there has been a new Fetch API in Browser environment, and it has become the standard in web async request. However, there is still no comparable Fetch API in Node environment, so many SDKs choose node-fetch library to make up for this.
Basically, it is also related with checking global environment—if in Browser environment, could use native fetch directly; if in Node environment, could import node-fetch dynamically. (no dynamically would cause compile error)
export async function initFetch() {
let fetch;
if (typeof window !== "undefined" && typeof window.fetch === "function") {
fetch = window.fetch;
} else {
const nodeFetch = await import("node-fetch");
fetch = nodeFetch.default;
}
if (typeof global !== "undefined") {
global.fetch = fetch;
} else if (typeof window !== "undefined") {
window.fetch = fetch;
}
}
However, the latest version of node-fetch only supports ESM, so it would cause compiling error in the commonjs project. In this situation, could replace node-fetch with http/https—
import http from "http";
import https from "https";
import { URL } from "url";
function fetch(url, options = {}) {
return new Promise((resolve, reject) => {
const urlObj = new URL(url);
const isHttps = urlObj.protocol === "https:";
const { method = "GET", headers = {}, body } = options;
const requestOptions = {
method,
headers,
};
const request = (isHttps ? https : http).request(
urlObj,
requestOptions,
(response) => {
let data = "";
response.on("data", (chunk) => {
data += chunk;
});
response.on("end", () => {
const responseData = {
ok:
response.statusCode >= 200 &&
response.statusCode < 300,
status: response.statusCode,
statusText: response.statusMessage,
headers: response.headers,
url: response.url || url,
json: () => Promise.resolve(JSON.parse(data)),
text: () => Promise.resolve(data),
blob: () => Promise.resolve(Buffer.from(data)),
};
resolve(responseData);
});
},
);
request.on("error", (error) => {
reject(error);
});
if (body) {
request.write(body);
}
request.end();
});
}
export default fetch;
Finally, make the global fetch point to the fetch function.
- could check the completed mock fetch source code through link
Conclusion
Furthermore, many third party libraries and SDKs avoid using Axios for network requests, primarily to reduce additional dependencies and minimize the bundle size. Additionally, since the native Fetch API in Node.js is only available starting from version 21, manual wrapping of the http/https modules or directly using node-fetch is still necessary for backward compatibility.
The completed source code is on github js-fetch_vs_axios
Ref
ChangeLog
- 20240609-init
- 20260501–translate by claude code