Constantin Druccdruc

Hey, I'm Constantin Druc!

I'm a web developer sharing everything I know about building web applications.

Axios for Laravel API Requests

You can create an axios instance with a bunch of config options that massively reduces the boilerplate you write when making Laravel API Requests.

// lib/axios.js
import axiosLib from "axios";

const axios = axiosLib.create({
  // config options
});

export default axios;

baseUrl

The first option we'll add is baseURL - this way we only have to write the relative path when making requests, and if we'll ever need to change the base url, we only need to do it in one place.

const axios = axiosLib.create({
  baseURL: "http://localhost:8000"
});

timeout

Under the hood, axios uses XMLHttpRequest objects to send requests; and these things have no timeout. If somehow, for some reason, a request takes one billion trillion billion years to complete the browser will just sit there waiting. We don't want that.

Setting the timeout to 60000ms is more than enough to accommodate all network speeds and you can override it on a per-request basis if needed.

const axios = axiosLib.create({
  baseURL: "http://localhost:8000",
  timeout: 60000,
});

withCredentials

If you're using axios to make cross-origin requests, such as when integrating Laravel with an SPA using Sanctum, you'll need to ensure that credentials (cookies) are included with every request; otherwise, authentication won't work.

const axios = axiosLib.create({
  baseURL: "http://localhost:8000",
  timeout: 60000,
  withCredentials: true
});

xsrf

Speaking of cookies, axios comes with support for protecting against XSRF attacks.

You can specify the xsrf token cookie and header names to be used and axios will automatically grab the cookie value and include it as a header.

const axios = axiosLib.create({
  baseURL: "http://localhost:8000",
  timeout: 60000,
  withCredentials: true,
  xsrfCookieName: "XSRF-TOKEN",
  xsrfHeaderName: "X-XSRF-TOKEN",
});

The xsrf config values above are actually axios's defaults - you don't have to add them. I just wanted to show they exists because a lot of people are confused about "Why Axios works with Sanctum and fetch doesn't?".

The answer is... because Axios does the "getting the xsrf token cookie and sending it as a header" automatically and fetch does not - you have to do it yourself, that's why 😃.

headers

Most of the time I expect Laravel to send me JSON responses:

const axios = axiosLib.create({
  baseURL: "http://localhost:8000",
  timeout: 60000,
  withCredentials: true,
  xsrfCookieName: "XSRF-TOKEN",
  xsrfHeaderName: "X-XSRF-TOKEN",
  headers: {
    Accept: "application/json"
  }
});

response interceptor

I like to add a response interceptor to standardize the error thrown when a failure occurs - either due to a failed server response, network, or configuration failure. It makes it a ton easier to handle and display validation and server errors.

const axios = axiosLib.create({
  // config
});

axios.interceptors.response.use(null, err => {
  const error = {
    status: err.response?.status,
    original: err,
    validation: {},
    message: null,
  };

  if (err.response?.status === 422) {
    for (let field in err.response.data.errors) {
      error.validation[field] = err.response.data.errors[field][0];
    }
  } else {
    error.message = "Something went wrong. Please try again later.";
  }

  return Promise.reject(error);
});

The above preserves the original response and status. It then checks for 422 responses to loop through the validation errors and add them to error.validation. If any other kind of failure occurs, it just sets a generic error.message.

before vs after

Ultimately, the above axios instance allows us to write less boilerplate and streamline error handling.

Here's the before:

// ❌ BEFORE
try {
  await axios.get("http://localhost:8000/sanctum/csrf-cookie", {
    withCredentials: true,
  });
  await axios.post("http://localhost:8000/login", form.value, {
    withCredentials: true,
  });
  const {data} = await axios.get("http://localhost:8000/api/user", {
    withCredentials: true,
  });
  user.value = data;
} catch (e) {
  const err = {
    validation: {},
    message: null,
  };

  if (e.response?.status === 422) {
    for (let field in e.response.data.errors) {
      err.validation[field] = e.response.data.errors[field][0];
    }
  } else {
    err.message = "Something went wrong. Please try again later.";
  }

  error.value = err;
}

And the after:

// ✅ AFTER
try {
  await axios.get("/sanctum/csrf-cookie");
  await axios.post("/login", form.value);
  const {data} = await axios.get("/api/user");
  user.value = data;
} catch (err) {
  error.value = err;
}