Dashboard template with Vue & UI Pro

Custom useFetch in Nuxt

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

When working with Nuxt, you might be making the frontend and fetching an external API, and you might want to set some default options for fetching from your API.

The $fetch utility function (used by the useFetch composable) is intentionally not globally configurable. This is important so that fetching behavior throughout your application remains consistent, and other integrations (like modules) can rely on the behavior of core utilities like $fetch.

However, Nuxt provides a way to create a custom fetcher for your API (or multiple fetchers if you have multiple APIs to call).

Custom $fetch

Let's create a custom $fetch instance with a Nuxt plugin.

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

Let's pretend here that:

  • The main API is https://api.nuxt.com
  • We are storing the JWT token in a session with nuxt-auth-utils
  • If the API responds with a 401 status code, we 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) {
        const headers = options.headers ||= {}
        if (Array.isArray(headers)) {
          headers.push(['Authorization', `Bearer ${session.value?.token}`])
        } else if (headers instanceof Headers) {
          headers.set('Authorization', `Bearer ${session.value?.token}`)
        } else {
          headers.Authorization = `Bearer ${session.value?.token}`
        }
      }
    },
    async onResponseError({ response }) {
      if (response.status === 401) {
        await navigateTo('/login')
      }
    }
  })

  // Expose to useNuxtApp().$api
  return {
    provide: {
      api
    }
  }
})

With this Nuxt plugin, $api is exposed from useNuxtApp() to make API calls directly from the Vue components:

app.vue
<script setup>
const { $api } = useNuxtApp()
const { data: modules } = await useAsyncData('modules', () => $api('/modules'))
</script>
Wrapping with useAsyncDataavoid double data fetching when doing server-side rendering (server & client on hydration).

Custom useFetch

Now that $api has the logic we want, let's create a useAPI composable to replace the usage of useAsyncData + $api:

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
  })
}

Let's use the new composable and have a nice and clean component:

app.vue
<script setup>
const { data: modules } = await useAPI('/modules')
</script>
Watch a video about custom $fetch and Repository Pattern in Nuxt.
We are currently discussing to find a cleaner way to let you create a custom fetcher, see https://github.com/nuxt/nuxt/issues/14736.