$api Plugin
The $api plugin provides a pre-configured $fetch client instance exposed globally as useNuxtApp().$api. It centralizes API baseURL resolution, automatic session Bearer token attachment on { auth: true }, and global auto-logout on 401 unauthenticated responses.
While useApi is designed for declarative data fetching during component setup, $api is built for imperative actions such as form submissions, event handlers, and data mutations.
Prerequisites
1. External Modules & Libraries
nuxt-auth-utils: Required for session state and Bearer token management viauseUserSession().Register inpnpm add nuxt-auth-utilsnuxt.config.ts:export default defineNuxtConfig({ modules: ["nuxt-auth-utils"], });
2. Environment Variables & Runtime Config
- Session Secret: Add a 32+ character secret to
.env:NUXT_SESSION_PASSWORD="a-long-random-string-at-least-32-characters" - Runtime Config: Configure API base URL and proxy toggle in
nuxt.config.ts:export default defineNuxtConfig({ runtimeConfig: { public: { apiBaseUrl: process.env.NUXT_PUBLIC_API_BASE_URL || "https://api.example.com", enableProxy: process.env.NUXT_PUBLIC_ENABLE_PROXY === "true", }, }, });
3. Server Proxy (Optional)
- Catch-all proxy route (
server/api/proxy/[...].ts) or NitrorouteRulesproxy if routing throughenableProxy: true.
Features
- Singleton
$fetchInstance: Created once during plugin initialization instead of rebuilding options on every invocation. - Dynamic Base URL: Automatically routes through the same-origin
/api/proxyendpoint whenenableProxyis active, or defaults topublic.apiBaseUrl. - Opt-in Bearer Authentication: Pass
{ auth: true }to automatically injectAuthorization: Bearer <token>fromuseUserSession(). - Nuxt Context Preservation: Interceptors run outside the synchronous Vue lifecycle, so composable operations (
fetchSession,clearSession,navigateTo) are executed vianuxtApp.runWithContext(). - Global 401 Auto-Logout: Clears the user session and redirects to
/whenever an unauthenticated response is encountered.
Usage Example
1. Imperative Client-Side Request (Mutations / Form Submission)
const { $api } = useNuxtApp();
// POST request with credentials
const res = await $api<{ token: string; user: User }>("/login", {
method: "POST",
body: { email: form.email, password: form.password },
});
// Authenticated request
await $api("/user/profile", {
method: "PATCH",
auth: true,
body: { name: "Updated Name" },
});
2. SSR-Safe Fetching with Custom $fetch
You can pass $api directly to Nuxt's native useFetch via the $fetch option:
const { data: courses, status } = await useFetch("/courses", {
$fetch: $api,
auth: true,
});
Implementation Code
1. Plugin Implementation (app/plugins/api.ts)
Design Details & Rationale
- Interceptors Outside Context: Fetch interceptors (
onRequest,onResponseError) execute asynchronously outside Nuxt's current instance context. Any composable that depends on context—such asfetchSession(),clearSession(), ornavigateTo()—must be wrapped innuxtApp.runWithContext(). - Plain Ref Reads: Reading
session.valueis a reactive Vue ref read and does not requirerunWithContext(). - Headers Mutability: Sets headers directly on
options.headersusingoptions.headers.set("Authorization", ...)rather than replacing the headers object, preserving any custom headers passed by the caller.
export default defineNuxtPlugin((nuxtApp) => {
const {
session,
fetch: fetchSession,
clear: clearSession,
} = useUserSession();
const config = useRuntimeConfig();
// When ENABLE_PROXY is set, route requests through the same-origin server
// proxy (/api/proxy) instead of hitting the external API directly.
const baseURL = config.public.enableProxy
? "/api/proxy"
: (config.public.apiBaseUrl as string);
const api = $fetch.create({
baseURL,
headers: { Accept: "application/json" },
async onRequest({ options }) {
const { auth } = options as { auth?: boolean };
if (!auth) return;
if (!session.value) {
await nuxtApp.runWithContext(() => fetchSession());
}
if (session.value?.token) {
options.headers.set("Authorization", `Bearer ${session.value.token}`);
}
},
async onResponseError({ response }) {
// if 401 (e.g. token invalidated by login from another device) then logout
if (
response.status === 401 ||
(response._data as { message?: string })?.message === "Unauthenticated"
) {
await nuxtApp.runWithContext(async () => {
await clearSession();
await navigateTo("/");
});
}
},
});
return { provide: { api } };
});
2. Configuration (nuxt.config.ts)
export default defineNuxtConfig({
modules: ["nuxt-auth-utils"],
runtimeConfig: {
public: {
apiBaseUrl: process.env.NUXT_PUBLIC_API_BASE_URL || "https://api.example.com",
enableProxy: process.env.NUXT_PUBLIC_ENABLE_PROXY === "true",
},
},
});
Related Documentation
- useApi Composable: Declarative SSR-friendly data fetching composable based on
createUseFetch. - Authentication & Login Flow: Architecture pattern covering browser-egress authentication and encrypted session cookies.