C
Cassa UI

@cassa/api-client

A typed fetch wrapper with automatic token injection, structured error handling, and TanStack Query integration. Replace the base URL and token getter to point at any backend.

Import

import { createApiClient, ApiError } from '@cassa/api-client'
import { CassaQueryProvider, createQueryClient } from '@cassa/api-client'

1. Create the client

Create a singleton in lib/api.ts and import it wherever you need data.

// lib/api.ts
import { createApiClient } from '@cassa/api-client'

export const api = createApiClient({
  baseUrl: process.env.NEXT_PUBLIC_API_URL ?? 'https://api.example.com',
  getToken: () => {
    if (typeof window === 'undefined') return null
    return localStorage.getItem('access_token')
  },
  onError: (error) => {
    if (error.status === 401) window.location.href = '/login'
  },
})
PropTypeDefaultDescription
baseUrl*stringBase URL prepended to every request path.
getToken() => string | nullCalled before each request. Returns the Bearer token or null.
onError(error: ApiError) => voidGlobal error handler — useful for 401 redirects.

2. Set up the provider

Wrap your app with CassaQueryProvider once in the root layout to enable TanStack Query caching throughout.

// app/providers.tsx
'use client'
import { CassaQueryProvider, createQueryClient } from '@cassa/api-client'

const queryClient = createQueryClient()

export const Providers = ({ children }) => (
  <CassaQueryProvider client={queryClient}>
    {children}
  </CassaQueryProvider>
)

3. Fetch data in components

'use client'
import { useQuery, queryOptions } from '@tanstack/react-query'
import { api, ApiError } from '../lib/api'

type User = { id: number; name: string; email: string }

const usersQuery = queryOptions({
  queryKey: ['users'],
  queryFn: () => api.get<User[]>('/users'),
})

export const UserList = () => {
  const { data, isPending, isError, error } = useQuery(usersQuery)

  if (isPending) return <p>Loading…</p>
  if (isError) {
    const status = error instanceof ApiError ? error.status : null
    return <p>Failed{status ? ` (${status})` : ''}</p>
  }

  return <ul>{data.map(u => <li key={u.id}>{u.name}</li>)}</ul>
}

Mutations

import { useMutation, useQueryClient } from '@tanstack/react-query'
import { api } from '../lib/api'

export const useCreateUser = () => {
  const qc = useQueryClient()

  return useMutation({
    mutationFn: (body: { name: string; email: string }) =>
      api.post('/users', body),
    onSuccess: () => qc.invalidateQueries({ queryKey: ['users'] }),
  })
}

API methods

PropTypeDefaultDescription
api.get<T>(path, options?)Promise<T>GET request.
api.post<T>(path, body?, options?)Promise<T>POST request with JSON body.
api.put<T>(path, body?, options?)Promise<T>PUT request.
api.patch<T>(path, body?, options?)Promise<T>PATCH request.
api.delete<T>(path, options?)Promise<T>DELETE request.