import { QueryClient } from "@tanstack/react-query";
export const baseQuery = new QueryClient({
defaultOptions: {
queries: {
staleTime: 300,
retry: 1,
refetchOnWindowFocus: false,
},
},
});
import { dashboardMutations } from "./dashboard";
export const apiMutations = {
dashboard: dashboardMutations,
};
export * from "./dashboard";
import { dashboardQueries } from "./dashboard";
import { loadsQueries } from "./loads";
export const apiQueries = {
dashboard: dashboardQueries,
loads: loadsQueries,
};
export * from "./dashboard";
export * from "./loads";
const API_BASE_URL = import.meta.env.VITE_API_URL || "http://localhost:5000/api";
export async function apiRequest<T>(endpoint: string, options: RequestInit = {}) {
const response = await fetch(`${API_BASE_URL}${endpoint}`, {
...options,
headers: {
"Content-Type": "application/json",
...options.headers,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return (await response.json()) as T;
}
function withJsonBody(method: string, body: any, options?: RequestInit) {
return {
...options,
method,
body: body === undefined ? undefined : JSON.stringify(body),
} satisfies RequestInit;
}
export const apiClient = {
get: apiRequest,
post: <T>(endpoint: string, body?: any, options?: RequestInit) =>
apiRequest<T>(endpoint, withJsonBody("POST", body, options)),
put: <T>(endpoint: string, body?: any, options?: RequestInit) =>
apiRequest<T>(endpoint, withJsonBody("PUT", body, options)),
patch: <T>(endpoint: string, body?: any, options?: RequestInit) =>
apiRequest<T>(endpoint, withJsonBody("PATCH", body, options)),
delete: <T>(endpoint: string, options?: RequestInit) =>
apiRequest<T>(endpoint, { ...options, method: "DELETE" }),
} as const;
Readme
# `src/services` overview
This folder is the “data layer” of the app. It centralizes:
- HTTP/network logic
- TanStack Query option builders (`queryOptions`, `mutationOptions`)
- Feature service modules (Dashboard, Loads, etc.)
The goal is that **pages/components contain minimal data logic** and mainly call predefined queries/mutations.
## Main flow (end-to-end)
1. **Route / Component** calls a query or mutation option
- Queries: `useQuery(...)` / `useSuspenseQuery(...)`
- Mutations: `useMutation(...)`
2. **Feature query/mutation module** provides the options
- Example: `dashboardQueries.stats()`, `dashboardMutations.createAlert(queryClient)`
3. **Feature service module** performs the HTTP request
- Uses the shared HTTP client
4. **Mapper (optional)** transforms raw API data into UI-ready shapes
5. **TanStack Query cache** stores results by `queryKey`
6. **Mutations** invalidate related queries via centralized keys (so components don’t do invalidation)
## File/folder responsibilities
### `http/`
- Shared HTTP client code used by all features.
- Where base URL, headers, auth hooks, and global error handling typically live.
Current file:
- `http/client.ts` — `apiRequest()` + `apiClient` helpers (`get/post/put/...`).
### Feature folders (example: `dashboard/`, `loads/`)
- Each feature owns its data layer in one place:
- `services.ts` — API calls only (HTTP)
- `queries.ts` — `queryOptions()` only
- `mutations.ts` — `mutationOptions()` only (with invalidation)
- `keys.ts` — centralized query keys for caching + invalidation
- `mappers.ts` — UI transformations (icons, hrefs, colors, etc.) when needed
- `mocks.ts` — fallback/mock responses (optional but useful in dev)
- `index.ts` — barrel exports for clean imports
Each feature folder has its own `README.md` if it needs deeper explanation.
### Aggregators
- `queries.ts`
- Optional “single import” surface for app-wide queries:
- `apiQueries.dashboard.stats()`
- `apiQueries.loads.list(params)`
- Re-exports feature queries for convenience.
- `mutations.ts`
- Optional “single import” surface for app-wide mutations:
- `apiMutations.dashboard.createAlert(queryClient)`
- Re-exports feature mutations for convenience.
### QueryClient configuration
- `base-query.ts`
- Creates and exports the app’s `QueryClient` instance.
- Sets default behavior (retry, staleTime, refetch policies, etc.).
### Helpers
- `useInvalidate.ts`
- Utility for invalidating queries (handy when not using the “invalidation inside mutations” pattern).
- If you consistently invalidate inside `mutationOptions`, you may rarely need this in components.
## Quick usage examples
Queries:
ts
const { data } = useSuspenseQuery(apiQueries.dashboard.alerts());
Mutations:
ts
const queryClient = useQueryClient();
const createAlert = useMutation(apiMutations.dashboard.createAlert(queryClient));
Top comments (0)