Axios: HTTP Requests in JavaScript Without the Fetch Boilerplate
Picture a small shop dashboard, the kind of page that lists recent orders and lets you click into one. The fetch calls work, but every one of them looks the same: check response.ok, call response.json(), wrap the whole thing in try and catch, then repeat the pattern for the next endpoint. Three endpoints, three copies of the same ceremony, and the actual work, showing an order, stays buried underneath. That repetition is the whole question behind Axios. It does not add a new kind of request. It removes the repeated setup around requests you already make, and the question is whether that saving is worth a dependency.
What I would check before using it
My first check is how many requests the project actually makes. One call to load a page, and fetch is fine; the boilerplate costs you five lines once. Five or ten endpoints with shared headers, an auth token, and a base URL, and the same five lines start appearing everywhere. That is the point where a shared client earns its place, because it holds the common setup in one spot.
My second check is error handling. Fetch treats a 404 as a successful promise, so you must check the status every time. Axios throws on error statuses instead, and the 404 lands in your catch block. I prefer that, since a missing order is a failure, not a result, but mixing the two styles in one codebase is how a missing record gets treated as an empty page.
My third check is timeouts and cancellation. Fetch has no built-in timeout, so a hung server can leave a request open until the browser gives up. Axios takes a timeout in milliseconds, which is a small thing until the day a slow endpoint freezes your page.
My fourth check is the environment. Axios runs in the browser and in Node.js with the same calls, which matters when a small script and a web page share the same API logic.
My verdict, as judgement: use Axios when a project has several endpoints, shared headers, or a need for timeouts. Skip it for a single one-off request, or when bundle size is a hard constraint, since fetch is already in the browser. The recommendation flips the moment the request count drops to one or two, or the project is a tiny page where a dependency outweighs the boilerplate it removes.
One small shop, fetched end to end
The example is a small shop orders page. I ran a local server on my own machine, no real shop and no internet API, serving three orders and a couple of awkward routes on purpose. The client is one short script, and it does the two jobs a real page needs: list the orders, then load one order by id. The shared client holds the base URL and a two second timeout, so each call is a single line.
Here is the server. It is plain Node.js with the built-in http module, and it serves the order list, one order by id, a 404 for an unknown id, and a slow route that answers after three seconds:
const http = require("node:http");
const orders = [
{ id: 101, customer: "Ayesha", item: "Canvas tote bag", qty: 2, total: 36.0, status: "shipped" },
{ id: 102, customer: "Bilal", item: "Ceramic mug", qty: 1, total: 14.5, status: "processing" },
{ id: 103, customer: "Sara", item: "Notebook set", qty: 3, total: 27.75, status: "delivered" },
];
const server = http.createServer((req, res) => {
res.setHeader("Content-Type", "application/json");
if (req.url === "/orders" && req.method === "GET") {
res.end(JSON.stringify(orders));
} else if (req.url.startsWith("/orders/") && req.method === "GET") {
const id = Number(req.url.split("/")[2]);
const order = orders.find((o) => o.id === id);
if (order) {
res.end(JSON.stringify(order));
} else {
res.statusCode = 404;
res.end(JSON.stringify({ error: "Order not found", id }));
}
} else if (req.url === "/slow" && req.method === "GET") {
setTimeout(() => res.end(JSON.stringify({ note: "slow response" })), 3000);
} else {
res.statusCode = 404;
res.end(JSON.stringify({ error: "Route not found" }));
}
});
server.listen(4173, () => console.log("Shop API listening on http://localhost:4173"));
And here is the client, with the shared setup in one place:
const axios = require("axios");
const api = axios.create({
baseURL: "http://localhost:4173",
timeout: 2000,
});
async function main() {
const list = await api.get("/orders");
console.log("Order list, status:", list.status);
console.log("Orders:", list.data.length);
for (const o of list.data) {
console.log(`#${o.id} ${o.customer} bought ${o.qty} x ${o.item} = $${o.total} (${o.status})`);
}
const one = await api.get("/orders/102");
console.log("\nSingle order:", one.data);
}
main().catch((err) => {
console.error("Request failed:", err.message);
process.exit(1);
});
This is the real output from running it:
Order list, status: 200
Orders: 3
#101 Ayesha bought 2 x Canvas tote bag = $36 (shipped)
#102 Bilal bought 1 x Ceramic mug = $14.5 (processing)
#103 Sara bought 3 x Notebook set = $27.75 (delivered)
Single order: {
id: 102,
customer: 'Bilal',
item: 'Ceramic mug',
qty: 1,
total: 14.5,
status: 'processing'
}
Two things are worth noticing in that output. First, the data arrived already parsed: list.data is an array I could loop over straight away, with no response.json() call anywhere. That is the part of Axios that saves the most typing across a project, because every endpoint returns parsed data the same way. Second, the single order came back as an object with named fields, so the page code reads like the thing it shows, not like plumbing.
The thing that went wrong was the missing order. I asked for order 999, which does not exist, and expected a response I could check. Instead the script crashed outright, because Axios throws on a 404 rather than handing back a response. Here is the exact error it printed, shortened to the first line, followed by the exit code:
const missing = await api.get("/orders/999");
console.log("Order:", missing.data);
AxiosError: Request failed with status code 404
exit code: 1
The fix is a try and catch around the call, reading the status from err.response. That gives the page a clean branch: show "order not found" for a 404, and treat anything without a response as a network problem. The same script with the catch in place printed this:
try {
const missing = await api.get("/orders/999");
console.log("Order:", missing.data);
} catch (err) {
if (err.response) {
console.log("Server answered with status:", err.response.status);
console.log("Body:", err.response.data);
} else {
console.log("No response received:", err.message);
}
}
Server answered with status: 404
Body: { error: 'Order not found', id: 999 }
The lesson is small but it changes how you write the rest of the page. With fetch you check the status after every call; with Axios you plan for the catch block, and you decide there what each status means. Once that habit is in place, the rest of the code stays short, and the error paths stop being an afterthought.
Common mistakes and how to fix them
First, expecting a 404 to behave like fetch. Axios throws, so an unguarded call crashes the script with `Request failed with status code 404`. Wrap the call in try and catch and read `err.response.status` to tell a missing record from a network failure.
Second, parsing the data yourself. The response body is already parsed, so `JSON.parse(response.data)` throws `Unexpected token 'o', "[object Obj"... is not valid JSON`. Use `response.data` directly and save the parse for the rare case where you asked for raw text.
Third, forgetting a timeout. A slow endpoint can hang the page with no error at all, and the only clue is a request that never returns. The fix is a timeout on the shared client, as in the example above, and a catch that handles the `ECONNABORTED` error with the message `timeout of 2000ms exceeded`, usually by showing the user a retry button instead of a frozen screen.
The verdict, after running it
Axios earns its place the moment requests stop being a one-off. A shared client with a base URL, a timeout, and one error pattern replaces a page full of repeated checks, and the worked example above shows the saving is real, not just promised. The catch is the error model: you have to learn that a 404 throws, and write the catch blocks first instead of last. If your project makes a handful of requests a day, try it on one page and see whether the code gets shorter. If it makes one request ever, fetch already does the job, and the dependency is a cost with nothing to pay for.
Further reading
- Axios documentation, https://axios-http.com/docs/intro
- MDN, Using the Fetch API, https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
Comments
Post a Comment