@mrzr/api-client

Frameworks

Use @mrzr/api-client with TanStack Query, SWR, React, Next.js App Router and Vue. Short, copy-paste setups for each, including server rendering.

The client works with any framework. It sits under your data library and replaces your fetch or axios layer, not TanStack Query or SWR.

TanStack Query

Failures throw, which is what Query expects. Pass signal so Query can cancel requests:

export function useUsers(page = 1) {
  return useQuery({
    queryKey: ["users", page],
    queryFn: ({ signal }) =>
      api.get<User[]>("/users", { params: { page }, signal }).then((r) => r.data),
  });
}

export function useCreateUser() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (input: NewUser) => api.post<User>("/users", input),
    onSuccess: () => qc.invalidateQueries({ queryKey: ["users"] }),
  });
}

SWR

const fetcher = (url: string) => api.get(url).then((r) => r.data);
const { data, error } = useSWR<User[]>("/users", fetcher);

Next.js (App Router)

In the browser, create one client in a client-only module:

lib/api.ts
"use client";
import { createClient } from "@mrzr/api-client";

export const api = createClient({ baseUrl: process.env.NEXT_PUBLIC_API_URL });

On the server, create a client per request and destroy it afterwards. A module-level client would share one user's token with everyone:

lib/server-api.ts
import { createClient, type ApiClient } from "@mrzr/api-client";
import { cookies } from "next/headers";

export async function withApi<T>(fn: (api: ApiClient) => Promise<T>): Promise<T> {
  const api = createClient({ baseUrl: process.env.API_URL, multiTab: false });
  const token = (await cookies()).get("access_token")?.value;
  if (token) await api.setTokens({ accessToken: token });
  try {
    return await fn(api);
  } finally {
    api.destroy();
  }
}

The Web Worker is skipped on the server automatically. Next's cache options pass straight through to fetch.

Vue

import { ref, onUnmounted } from "vue";
import { api } from "@/lib/api";

export function useAuth() {
  const isAuthenticated = ref(false);
  api.getAuthState().then((s) => (isAuthenticated.value = s.isAuthenticated));
  const off = api.onAuthStateChange((s) => (isAuthenticated.value = s.isAuthenticated));
  onUnmounted(off);
  return { isAuthenticated };
}

Setting baseUrl with environment variables

If you leave out baseUrl, the client reads the first of these that is set: NEXT_PUBLIC_API_URL, NEXT_PUBLIC_API_BASE_URL, NEXT_PUBLIC_BASE_URL, VITE_API_URL, VITE_API_BASE_URL, VITE_BASE_URL, NUXT_PUBLIC_API_URL, REACT_APP_API_URL, EXPO_PUBLIC_API_URL, PUBLIC_API_URL, PUBLIC_API_BASE_URL, API_URL, API_BASE_URL. In a browser with none set, it uses the page's origin.

Passing it explicitly is clearer: createClient({ baseUrl: process.env.NEXT_PUBLIC_API_URL }).

On this page