Skip to content
Node.js nd libraries 4 min read

Axios: HTTP Client

Axios is a promise-based HTTP client that runs in both Node.js and the browser, wrapping the lower-level request machinery in a small, ergonomic API. It became popular for features the platform historically lacked — automatic JSON parsing, request and response interceptors, configurable instances with a shared baseURL, and timeouts — and it remains a productive choice for talking to REST APIs. This page covers GET/POST requests, interceptors, default configuration, timeouts, error handling, and how Axios stacks up against the now-built-in fetch.

Installing and a first request

Axios runs on any maintained Node.js release; Node 20 or 22 LTS is the sensible default. Install it from npm and import it as an ES module (it also ships CommonJS, so const axios = require("axios") works unchanged).

npm install axios

A GET request returns a promise that resolves to a response object. The parsed body lives on response.data — Axios deserializes JSON for you, so there is no separate parse step.

import axios from "axios";

const response = await axios.get("https://api.github.com/repos/nodejs/node");

console.log(response.status);              // 200
console.log(response.data.full_name);      // "nodejs/node"
console.log(response.headers["content-type"]);

Output:

200
nodejs/node
application/json; charset=utf-8

The response carries data, status, statusText, headers, and the config that produced it — useful for logging and debugging.

GET and POST

Query parameters go in a params object rather than being hand-concatenated, and Axios encodes them safely. For writes, pass a body as the second argument; objects are serialized to JSON and the Content-Type header is set automatically.

// GET /search?q=node&per_page=5
const { data } = await axios.get("https://api.github.com/search/repositories", {
  params: { q: "node", per_page: 5 },
});
console.log(data.total_count);

// POST a JSON body
const created = await axios.post(
  "https://httpbin.org/post",
  { title: "Learn Axios", done: false },
  { headers: { "X-Request-Source": "devcraftly" } },
);
console.log(created.data.json);

Output:

12873456
{ title: 'Learn Axios', done: false }

The companion methods axios.put, axios.patch, and axios.delete follow the same shape.

Default config and instances

Rather than repeating a host and headers on every call, create a configured instance with axios.create. Every request from that instance inherits the baseURL, default headers, and timeout, so call sites stay short and consistent.

const api = axios.create({
  baseURL: "https://api.example.com/v1",
  timeout: 5000,
  headers: { Authorization: `Bearer ${process.env.API_TOKEN}` },
});

const users = await api.get("/users");      // -> GET https://api.example.com/v1/users
await api.post("/users", { name: "Ada" });

Prefer per-instance config over mutating the global axios.defaults. A dedicated instance keeps each integration’s settings isolated and avoids surprising other parts of the app.

Interceptors

Interceptors let you run logic on every request or response — attaching auth tokens, logging timings, or normalizing errors in one place. A request interceptor receives the config before it is sent; a response interceptor receives the response (or, in its second callback, the error) before it reaches your await.

api.interceptors.request.use((config) => {
  config.metadata = { start: Date.now() };
  return config;
});

api.interceptors.response.use(
  (response) => {
    const ms = Date.now() - response.config.metadata.start;
    console.log(`${response.config.url} -> ${response.status} (${ms}ms)`);
    return response;
  },
  (error) => {
    // Centralize error shaping; rethrow so callers still see the failure.
    console.error("Request failed:", error.message);
    return Promise.reject(error);
  },
);

Output:

/users -> 200 (143ms)

Timeouts and error handling

The timeout option (in milliseconds) aborts a request that stalls, raising an error with code === "ECONNABORTED". Axios treats any non-2xx status as a rejection by default, so you handle HTTP and network failures with the same try/catch. Inspect error.response to distinguish a server error (the server replied) from a network or timeout error (no response).

try {
  const { data } = await api.get("/users/999", { timeout: 2000 });
  console.log(data);
} catch (error) {
  if (error.response) {
    // Server responded with 4xx/5xx
    console.error("HTTP", error.response.status, error.response.data);
  } else if (error.code === "ECONNABORTED") {
    console.error("Request timed out");
  } else {
    console.error("Network error:", error.message);
  }
}

Output:

HTTP 404 { message: 'Not Found' }

Axios vs. native fetch

Node 18+ ships a global fetch, so a dependency is no longer mandatory for HTTP. The trade-off is convenience versus footprint.

FeatureAxiosNative fetch
JSON parsingAutomatic via response.dataManual await res.json()
Non-2xx handlingRejects the promiseResolves; check res.ok yourself
InterceptorsBuilt inRoll your own wrapper
baseURL / instancesaxios.createNot built in
Timeoutstimeout optionAbortSignal.timeout(ms)
DependencyExternal packageBuilt into Node 18+

For a couple of simple calls, fetch keeps the dependency tree lean. For apps making many requests that need shared config, interceptors, and uniform error handling, Axios removes a lot of boilerplate.

Best practices

  • Create one configured instance per upstream API with axios.create; avoid mutating global defaults.
  • Pass query strings through params instead of building URLs by hand — Axios handles encoding.
  • Always set a timeout so a hung dependency cannot stall your service indefinitely.
  • Centralize auth and logging in interceptors, but rethrow errors so callers can still react.
  • In catch blocks, branch on error.response vs. error.code to tell HTTP errors from network failures.
  • Keep secrets like API tokens in environment variables, not in source.
  • If you only make a few requests with no shared config, weigh native fetch to drop the dependency.
Last updated June 14, 2026
Was this helpful?