Use Composition API to easily handle API requests in Vue.js

When building a web-app most of the times we need to fetch from the server or execute some action.

Handling the request status can be tedious and we often write the same code over and over.

With the composition API we can write a composable that handles the request status and exposes reactive objects

Let start by defining some requirements:

  • Needs to provide reactive isLoading, result, error and execute
    • isLoading: true is we are waiting for a request
    • result: Success response from the server
    • error: Error object form the server
    • execute: Execute the request
  • Need to provide the base request and the execute should allow sending params or body to the request.

The expected usage will be like:

  const userList =  useApi(()=>({url:``}), (r)=> r.json());
  userList.execute(); // we can await for result
  // ...
  return {  ...userList }

This factory based usage allows great flexibility, the first parameter is a factory that returns the fetchRequest, the second parameter is just a transformer of the fetch result value.

The return execute() will pass all the parameters to the factory, allowing to greater customization on building requests. Allowing you to do:

  const userList =  useApi((page: number)=>({url:`${page}`}), (r)=> r.json());
  userList.execute(1); // we can await for result
  // ...
  return { ...userList }

Allowing greater composability on having automagically having a paginated list:

  // ...
  const page = ref(1);
  // watch for `page` changes
  watch(page, p=> userList.execute(p));
  // ...
  return {

Simplifying the pagination to changing variable page.value instead of calling the userList.execute(page.value).

# Implementation

A simple javascript implementation (with typings)

function useApi(
  const isLoading = ref(false);
  const result = ref(null);
  const error = ref(null);
  const execute = async (...args) => {
    const request = factory(...args);

    isLoading.value = true;
    error.value = null;
    try {
      const response = await fetch(request);
      const valueResponse = await handleResponse(response)

      result.value = valueResponse;
      return valueResponse;
    } catch (e) {
      error.value = e;
      result.value = null;
    } finally {
      isLoading.value = false;

  return {

This implementation might not yield correct results, if the execute is called many times before the first finishes, because the result will always be the latest server response.

I'm the creator of a composable library compatible with vue2 + composition-api and vue3 called vue-composable, where you can do something similar by using usePromise, using it will make sure the result.value will hold the value of the last valid exec() response value.

// pass `true` on the second argument to make it lazy
const userList = usePromise((id)=>fetch(`${id}`).then(r=>r.json(), true)

I've been using this pattern on my projects, from action buttons to fetching the next paginated page, and has been working great so far. It makes it trivial to disable a button if a request is happening or to compose it into other composables.

Don't miss out anything about Vue, your favourite framework.

Subscribe to receive all the articles we publish in a concise format, perfect for busy devs.

Related Articles

The new Provide and Inject in Vue 3

Getting stuck into the prop drilling? Learn how provide/inject can make your components more flexible and independent in this short tutorial.

Anthony Konstantinidis

Anthony Konstantinidis

Jul 18, 2022

The 101 guide to Script Setup in Vue 3

Don't you know about Script Setup yet? Check out this short article now and learn the nicest way to define a Vue component right now!

Anthony Konstantinidis

Anthony Konstantinidis

Jun 20, 2022

Going 3D with Trois.js and Vue 3

Learn about Trois.js, a JS library to render 3D scenes in Vue. In this article, we're learning the basics of using Trois.js in a Vite + Vue 3 app

Alvaro Saburido Rodriguez

Alvaro Saburido Rodriguez

Nov 16, 2021


VueDose is proudly supported by its sponsors. If you enjoy it, consider supporting it to ensure the project maintainability.

Learning Partner