I have defined API URLs in my .env file like this
const isProdEnv = process.env.NODE_ENV === 'production'
const DEV_API_URL = "https://example-stage.herokuapp.com/v1/"
const PROD_API_URL = "https://example.herokuapp.com/v1/"
const API_URL = isProdEnv ? PROD_API_URL : DEV_API_URL
and I have a runtime config in my nuxt config file like this
runtimeConfig: {public: { apiURL: process.env.API_URL || "https://example-stage.herokuapp.com/v1/", }, },
I want to use this API URL outside of a component since I have centralized API calls. I defined a composable called UseApiUrl
like this to use the runtime config:
export default function useApiUrl() {
const config = useRuntimeConfig();
const apiUrl = ref(config.public.apiURL);
return { apiUrl };
}
and I use this composable in my services/api.js file
import useApiUrl from "~/composables/useApiUrl";
const { apiUrl } = useApiUrl();
export const HTTP = {
baseURL: apiUrl.value,
headers: {
"Content-Type": "application/json",
},
};
I am using the HTTP object in my request.js file for centeralized HTTP methods and setting headers like this
import { HTTP } from "./api";
export default class Req {
constructor() {
if (localStorage.getItem("token")) {
HTTP.headers.Authorization = "JWT " + localStorage.getItem("token");
} else {
delete HTTP.headers.Authorization;
}
}
async get(url, queryObject) {
let runUrl = url;
if (queryObject) {
const queryParams = new URLSearchParams();
for (const key in queryObject) {
if (Array.isArray(queryObject[key])) {
queryObject[key].forEach((value) => queryParams.append(key, value));
} else {
queryParams.append(key, queryObject[key]);
}
}
runUrl = url + "?" + queryParams.toString();
}
try {
const response = await fetch(HTTP.baseURL + runUrl, {
method: "GET",
headers: HTTP.headers,
});
if (!response.ok) {
throw new Error("Network response was not ok");
}
return await response.json();
} catch (error) {
throw error;
}
}
async post(url, payload) {
try {
const headers = { ...this.headers };
if (payload instanceof FormData) {
delete headers["Content-Type"];
} else {
headers["Content-Type"] = "application/json";
}
const token = localStorage.getItem("token");
if (token) {
headers["Authorization"] = "JWT " + token;
}
const response = await fetch(HTTP.baseURL + url, {
method: "POST",
headers: headers,
body:
(payload instanceof FormData && payload) || JSON.stringify(payload),
});
if (!response.ok) {
throw response;
}
return await response.json();
} catch (error) {
console.log(error);
throw error;
}
}
async put(url, payload) {
try {
const response = await fetch(HTTP.baseURL + url, {
method: "PUT",
headers: HTTP.headers,
body: JSON.stringify(payload),
});
if (!response.ok) {
throw new Error("Network response was not ok");
}
return await response.json();
} catch (error) {
throw error;
}
}
async patch(url, payload) {
// Implement similarly to post
try {
const response = await fetch(HTTP.baseURL + url, {
method: "PATCH",
headers: HTTP.headers,
body: JSON.stringify(payload),
});
if (!response.ok) {
throw new Error("Network response was not ok");
}
return await response.json();
} catch (error) {
throw error;
}
}
async delete(url) {
// Implement similarly to post
try {
const response = await fetch(HTTP.baseURL + url, {
method: "DELETE",
headers: HTTP.headers,
});
if (!response.ok) {
throw new Error("Network response was not ok");
}
return await response;
} catch (error) {
throw error;
}
}
}
and I get this error
[nuxt] A composable that requires access to the Nuxt instance was called outside of a plugin, Nuxt hook, Nuxt middleware, or Vue setup function.
Can anyone help me on how to expose the API_URL outside a component file?
I need to use the API_URL in the api.js file to be used.
I can't really reproduce your whole code from the snippets, but I believe the problematic call is inside const { apiUrl } = useApiUrl();
inside services/api.js
. Because written as standalone constant like this it is execectued at the time the file is being loaded and not upon a call from inside any <script setup>
code.
You should wrap it inside a function call to avoid being called out of context.
I have an idea, but not sure if it would work like this. If there are problems, let me know and hopefuly we'll manage to figure out.
I would try turning services/api.js
into composables/useHTTP.js
:
export useHttp = () => {
const HTTP = {
baseURL: useApiUrl().value,
headers: {
"Content-Type": "application/json",
},
};
return { HTTP };
};
And then in request.js
:
export default class Req {
constructor() {
this.HTTP = useHttp().HTTP
if (localStorage.getItem("token")) {
this.HTTP.headers.Authorization = "JWT " + localStorage.getItem("token");
} else {
delete this.HTTP.headers.Authorization;
}
}
...
}
Like this the code requiring composables shouldn't be called earlier than when you first need the instance of Req
class. And this should be inside <script setup>
already.