Custom useFetch in Nuxt

How to create a custom fetcher for calling your external API in Nuxt 3.

When working with Nuxt, most of the time your are making the frontend and fetching an API made with another language (ex: PHP with Laravel).

When we created the useFetch composable and $fetch utility function, we made them immutable on purpose to avoid any side-effects. This is important to keep a consistent behaviour through your application and avoid any modules to break your API calls, for example.

Instead, we prefer that you create a custom fetcher for your API, which could also be useful if you have multiple APIs to call.

Custom $fetch

First, we need to create a Nuxt plugin to create a custom $fetch instance.

$fetch is a configured instance of ofetch which support adding the base URL of your Nuxt server as well a direct function calls during SSR (avoiding HTTP roundtrips).

I am pretending here that:

  • The main API is https://api.nuxt.com
  • I am storing the JWT token in a session with nuxt-auth-utils
  • If the API responds with a 401 status code, I redirect the user to the /login page
plugins/api.ts
export default defineNuxtPlugin(() => {
  const { session } = useUserSession()
  const $api = $fetch.create({
    baseURL: 'https://api.nuxt.com',
    onRequest({ request, options, error }) {
      if (session.value?.token) {
        // Add Authorization header
        options.headers = options.headers || {}
        options.headers.Authorization = `Bearer ${session.value?.token}`
      }
    },
    onResponseError({ response }) {
      if (response.status === 401) {
        return navigateTo('/login')
      }
    }
  })
  // Expose to useNuxtApp().$api
  return {
    provide: {
      api: $api
    }
  }
})

With this, I am able to use $api to make API calls directly in my Vue components:

app.vue
<script setup>
const { $api } = useNuxtApp()
const { data: modules } = await useAsyncData('modules', () => $api('/modules'))
</script>
I am wrapping with useAsyncData here in order to avoid double data fetching when doing server-side rendering (server & client on hydration).

Custom useFetch

Now that I have $api with the logic I want, I can simplify my useAsyncData usage by creating a useAPI composable:

composables/useAPI.ts
import type { UseFetchOptions } from 'nuxt/app';

export function useAPI<T>(
  url: string | (() => string),
  options: Omit<UseFetchOptions<T>, 'default'> & { default: () => T | Ref<T> },
) {
  return useFetch(url, {
    ...options,
    $fetch: useNuxtApp().$api,
  })
}

Now I can use my new composable and have a nice and clean component:

app.vue
<script setup>
const { data: modules } = await useAPI('/modules')
</script>

Notes

We are currently discussing with the core team to find a cleaner way to let you create a custom fetcher, stay tuned on Twitter / X.

Happy Nuxting 🚀

PS: thanks to @danielroe and @pi0 for their precious help 🙏