Search code examples
restdirectorynuxt3.js

Nuxt 3 folder structure for API call functions


I am building a Nuxt 3 app, I am doing only the frontend part. I created .env file and added my base URL, created a composable

export const useApiFetch: typeof useFetch = (request, opts?) => {
  const config = useRuntimeConfig();
  const defaultHeaders = {
    "Content-Type": "application/json",
    Accept: "application/json",
  };

  return useFetch(request, {
    headers: defaultHeaders,
    baseURL: config.public.baseURL,
    ...opts,
  });
};

And I have couple of functions that I am doing only API calls to the server: similar to the function below.

const subscribeBeta = async (email: string) => {
    const response = await useApiFetch("endpoint", {
      method: "POST",
      body: { email },
    });
    return response;
  };

I want to keep similar functions in the same folder and use them in the different components,
What is the best folder structure to store my functions to do API calls?
In React there is an API folder but I don't know about Nuxt 3.

I put them in composables, and also put them in utils folder.


Solution

  • UPDATE (2023-07-16):

    The implementation described in the original response presents a problem of duplication of API calls. To fix this, it is necessary to wrap the call method with the useAsyncData composable in the repository classes and adjust how the api plugin is consumed on pages and components (basically use the return type of the useAsyncData composable).

    To make it easier I set up this repository with these modifications, plus it uses the ofetch library (the one Nuxt 3 uses behind the scene) instead of ohmyfetch.

    If you are interested in a more detailed explanation see this article I wrote commenting on the implementation of the repository code mentioned above.


    ORIGINAL RESPONSE:

    Take a look at: https://www.vuemastery.com/blog/api-management-in-nuxt-3-with-typescript/

    In case the link no longer works, I copy the main parts of the design pattern below:

    The article uses the repository pattern, which serves as an intermediary between the datasource and the business logic.

    Create a folder called repository and the following files inside it:

    File: repository/factory.ts : HttpFactory is an encapsulation that accepts an HTTP client.

    import { $Fetch } from 'ohmyfetch';
    
    class HttpFactory {
      private $fetch: $Fetch;
    
      constructor(fetcher: $Fetch) {
        this.$fetch = fetcher;
      }
    
      /** 
        * method - GET, POST, PUT
        * URL
      **/
      async call<T>(method: string, url: string, data?: object, extras = {}): Promise<T> {
        const $res: T = await this.$fetch(url, { method, body: data, ...extras });
        return $res;
      }
    }
    
    export default HttpFactory;
    

    File: repository/modules/auth.ts: this will act as a repository for all things authentication, including creating a user account, logging in a user, resetting a user’s password, and so on.

    import HttpFactory from './factory';
    import { ICreateAccountInput, ICreateAccountResponse, ILoginInput, ILoginResponse } from 'types';
    
    class AuthModule extends HttpFactory {
      private RESOURCE = '/auth';
    
      async login(credentials: ILoginInput): Promise<ILoginResponse> {
        return await this.call<ILoginResponse>('POST', `${this.RESOURCE}/login`, credentials);
      }
    
      async create(account: ICreateAccountInput): Promise<ICreateAccountResponse> {
        return await this.call<ICreateAccountResponse>('POST', `${this.RESOURCE}/register`, account);
      }
    }
    
    export default AuthModule;
    

    Then we create a plugin so that we can access it from within a component.

    File: plugins/api.ts

    import { $fetch,FetchOptions } from 'ohmyfetch';
    import { defineNuxtPlugin } from '#app';
    import AuthModule from '~~/repository/modules/auth';
    
    /** ApiInstance interface provides us with good typing */
    interface IApiInstance {
      auth: AuthModule
    }
    
    export default defineNuxtPlugin((nuxtApp) => {
    
      
      const fetchOptions: FetchOptions = {
        baseURL: nuxtApp.$config.API_BASE_URL,
      }
    
      /** create a new instance of $fetcher with custom option */
      const apiFetcher = $fetch.create(fetchOptions);
    
      /** an object containing all repositories we need to expose */
      const modules: IApiInstance = {
        auth: new AuthModule(apiFetcher),
      };
    
      return {
        provide: {
          api: modules,
        },
      };
    });
    

    Example of use: File: pages/login.vue

    // ...
    // ...
    const { $api } = useNuxtApp();
    
    const form = reactive<ILoginInput>({
      email: '',
      password: '',
    });
    
    async function handleSubmit(): void {
    
      try {
            const credentials: ILoginInput = {
              email: form.email,
              password: form.password,
            };
      
            const response = await $api.auth.login(credentials);
            console.log(response)
            // allow user access into the app
        } catch (error) {
            console.error(error);
        }
    
    }