DEV Community

pjdev2d
pjdev2d

Posted on

outer flow

import { QueryClient } from "@tanstack/react-query";
export const baseQuery = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 300,
      retry: 1,
      refetchOnWindowFocus: false,
    },
  },
});

Enter fullscreen mode Exit fullscreen mode
import { dashboardMutations } from "./dashboard";

export const apiMutations = {
  dashboard: dashboardMutations,
};

export * from "./dashboard";

Enter fullscreen mode Exit fullscreen mode
import { dashboardQueries } from "./dashboard";
import { loadsQueries } from "./loads";

export const apiQueries = {
  dashboard: dashboardQueries,
  loads: loadsQueries,
};

export * from "./dashboard";
export * from "./loads";

Enter fullscreen mode Exit fullscreen mode
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;

Enter fullscreen mode Exit fullscreen mode

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:
Enter fullscreen mode Exit fullscreen mode


ts
const { data } = useSuspenseQuery(apiQueries.dashboard.alerts());


Mutations:
Enter fullscreen mode Exit fullscreen mode


ts
const queryClient = useQueryClient();
const createAlert = useMutation(apiMutations.dashboard.createAlert(queryClient));




Enter fullscreen mode Exit fullscreen mode

Top comments (0)