# Frontend Vite API Layer

> Vite admin Axios client and TanStack React Query: api.ts, query keys, queries, mutations, backend URL conventions. Use when making API calls or React Query hooks in Vite admin apps.

- Skill: `xmuhameed/frontend-vite-api-layer` (Agent Skill)
- Install (CLI): `npx skillmds@latest add xmuhameed/frontend-vite-api-layer`
- Raw SKILL.md: https://api.skillmd.com/api/skills/xmuhameed/frontend-vite-api-layer/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: xmuhameed (https://skillmd.com/u/xmuhameed)
- Updated: 2026-09-21
- Page: https://skillmd.com/skills/xmuhameed/frontend-vite-api-layer

---


# Admin API Layer

## Axios Client

```typescript
// lib/api.ts
export const API_URL = import.meta.env.VITE_API_URL ?? 'http://localhost:4000';
const api = axios.create({ baseURL: API_URL });
// No default Content-Type (FormData needs auto boundary)
// Bearer from cookie; 401 → clear auth → /login?redirect=
```

Typed env: `config/env.ts`

## Feature API Structure

```
features/category/api/
├── keys.ts       # query key factory
├── api.ts        # raw axios calls
├── queries.ts    # useQuery hooks
└── mutations.ts  # useMutation hooks
```

## Query Keys

```typescript
export const categoryKeys = {
  all: ['categories'] as const,
  lists: () => [...categoryKeys.all, 'list'] as const,
  list: (f: CategoryFilters) => [...categoryKeys.lists(), f] as const,
  detail: (id: string) => [...categoryKeys.all, 'detail', id] as const,
};
```

## Query Hook

```typescript
export const useGetCategories = (filters: CategoryFilters) =>
  useQuery({
    queryKey: categoryKeys.list(filters),
    queryFn: () => getCategories(filters),
    staleTime: 5 * 60 * 1000,
    refetchOnWindowFocus: false,
    placeholderData: (prev) => prev,
  });
```

## Mutation Hook

```typescript
export const useDeleteCategory = () => {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (id: string) => deleteCategory(id),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: categoryKeys.all });
      toast.success('Deleted');
    },
    onError: (e: any) => toast.error(e?.response?.data?.message),
  });
};
```

## Backend Response

```typescript
{ status: 'success', data: T[], count: number }  // list
{ status: 'success', data: T }                    // single
error.response?.data?.message | .key
```

## Backend URL Pattern

| Action | URL |
|--------|-----|
| List | `GET /{module}/get-all-{resources}?page=&pageItemsCount=&search=` |
| Get one | `GET /{module}/get-{resource}?id=` or `?slug=` |
| Create | `POST /{module}/create-{resource}` |
| Update | `PATCH /{module}/update-{resource}?id=` |
| Delete | `DELETE /{module}/delete-{resource}?id=` |

## Reference Data

`staleTime: Infinity` for countries, enums — `useCountryStore` pattern (React Query, not Zustand).

## Images

Display: `` `${API_URL}${entity.imageUrl}` ``

