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

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

Go async in Vue 3 with Suspense

Learn Suspense, one of the most exciting Vue 3 features used to make async request easy and interactive. Don't miss this tutorial with code snippets!

Vinicius Kiatkoski

Vinicius Kiatkoski

Jun 15, 2020

Create a i18n Plugin with Composition API in Vue.js 3

A example on how to use the inject and provide functions to create a i18n plugin using Composition API in Vue.js 3.

Alex Jover Morales

Alex Jover Morales

Feb 17, 2020


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

Learning Partner