Networking
One shared axios instance, configured once, used everywhere. It owns the base URL and cross-cutting concerns (auth headers, error shape); react-query owns caching and retries on top of it.
// src/shared/services/api.ts
import axios from "axios";
import { env } from "@shared/config/env";
export const api = axios.create({
baseURL: env.API_URL,
timeout: 15_000,
});
// Attach the auth token on every request
api.interceptors.request.use((config) => {
const token = getAuthToken(); // from your auth layer
if (token) config.headers.Authorization = `Bearer ${token}`;
return config;
});
// Normalize errors so callers get a predictable shape
api.interceptors.response.use(
(res) => res,
(error) => Promise.reject(normalizeApiError(error)),
);
How it composes with react-query
A query’s queryFn just calls api; react-query decides when to fetch, cache,
and retry:
queryFn: async () => (await api.get("/orders")).data,
Keep endpoint calls in each feature’s services/ folder (e.g.
src/features/orders/services/ordersApi.ts), not inline in components. Screens
call hooks; hooks call services; services call api.