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