Suggest an editImprove this articleRefine the answer for “Isolating API from UI”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)Isolating the API layer means **separating** the code that talks to the server from the code that renders the UI, so the UI knows only **what needs to be fetched**, not **how exactly to fetch it**. **Key point:** this is an implementation of the Separation of Concerns principle, the UI handles only rendering, and the API code handles only requests.Shown above the full answer for quick recall.Answer (EN)Image## What "isolating the API layer from the UI" means > Isolating the API layer means **separating** the code that: > > - **talks to the server** (HTTP requests, REST, GraphQL, etc.), > from the code that: > - **renders the interface** (React components, state, events). That is, the UI should not directly call: ```javascript fetch('/api/users') ``` or know **exactly how** the server is built. Instead, the UI calls a **function from a separate layer**, for example: ```javascript // api/users.ts export async function getUsers() { const res = await fetch('/api/users'); return res.json(); } ``` And in the component: ```javascript const { data } = useQuery({ queryKey: ['users'], queryFn: getUsers }); ``` --- ## The main idea > The UI should know only **what needs to be fetched**, > but not **exactly how to fetch it**. This is an implementation of the **Separation of Concerns** principle. --- ## Why this matters ### 1. **Clean architecture and readability** When API logic (requests, headers, authorization) is mixed with UI code, components become "dirty" and hard to read: ```javascript function UserList() { const [users, setUsers] = useState([]); useEffect(() => { fetch('/api/users', { headers: { Authorization: token } }) .then(res => res.json()) .then(setUsers); }, []); // UI + networking details mixed together return users.map(u => <div>{u.name}</div>); } ``` After isolation: ```javascript // api/users.ts export const getUsers = () => api.get<User[]>('/users'); // a centralized fetch wrapper // components/UserList.tsx const { data: users } = useQuery({ queryKey: ['users'], queryFn: getUsers }); ``` The component is responsible **only for rendering**, and the API code is responsible **only for requests**. --- ### 2. **Reusability** If several places need to fetch the same user: ```javascript useQuery({ queryKey: ['user', id], queryFn: () => getUser(id) }); ``` you use **the same API function**, instead of copying `fetch` into every component. --- ### 3. **Testability** When the API is isolated, you can: - mock it in tests; - test the UI without a real server. Example: ```javascript // jest.setup.ts jest.mock('@/api/users', () => ({ getUsers: jest.fn(() => Promise.resolve([{ id: 1, name: 'Alex' }])), })); ``` Tests for the component now don't depend on the network: ```javascript render(<UserList />); expect(screen.getByText('Alex')).toBeInTheDocument(); ``` --- ### 4. **Centralized error handling and authorization** When all requests go through a single point (`apiClient`), you can: - automatically attach a token, - intercept 401 and refresh the JWT, - log errors, - handle timeouts. ```javascript // api/client.ts export const api = axios.create({ baseURL: '/api', }); api.interceptors.response.use( res => res, err => { if (err.response?.status === 401) refreshToken(); return Promise.reject(err); } ); ``` Now you don't need to repeat this code in every component. --- ### 5. **Easy backend replacement** If the API changes (for example, REST → GraphQL, or a new endpoint), only the API layer changes, and the UI code isn't touched at all. ```javascript // before export const getUser = (id: string) => fetch(`/api/users/${id}`).then(r => r.json()); // after export const getUser = (id: string) => graphQLClient.request(GET_USER_QUERY, { id }); ``` The components stay the same: ```javascript const { data } = useQuery({ queryKey: ['user', id], queryFn: () => getUser(id) }); ``` --- ### 6. **Integration with React Query / SWR / Zustand** Modern data managers (`React Query`, `SWR`) expect a plain function: ```javascript queryFn: () => Promise<Data> ``` Isolating the API makes the code natural: ```javascript useQuery({ queryKey: ['products'], queryFn: getProducts }); ``` → React Query caches, updates, and synchronizes the data itself. --- ### 7. **Alignment with architectural principles** This matches the principles of: - **SRP (Single Responsibility Principle)** - each module does one thing; - **Separation of Concerns** - the UI doesn't know about network details; - **Dependency Inversion** - React depends on an abstraction (`getUser`), not on `fetch`; - **Clean Architecture** - the outer layer (API) is isolated from the inner one (UI). --- ## An architecture example ```javascript src/ ├── api/ │ ├── client.ts ← shared fetch/axios setup │ ├── users.ts ← API functions │ └── posts.ts ├── features/ │ ├── users/ │ │ ├── hooks.ts ← useUserQuery(), useCreateUserMutation() │ │ ├── components/ │ │ │ └── UserList.tsx │ │ └── index.ts ├── ui/ │ ├── components/ │ └── layout/ └── app.tsx ``` All the server communication logic lives in `api/` Components → hooks → API → server. --- ## Summary | Advantage | What it gives you | |---|---| | Separation of concerns | The UI handles rendering, the API handles data | | Centralized logic | tokens, errors, retries in one place | | Easy backend changes | the UI doesn't break when the API changes | | Simple testing | you can mock the API | | Reuse | the same calls from different places | | Compatibility with React Query / SWR | pure functions with no side effects | --- ### In short: > Isolating the API layer from the UI makes the application **cleaner, more reliable, and easier to maintain**. > > React components should think only about **what to display**, > not **how to fetch the data**. > > Everything network-related, authorization-related, and technical is moved into the API layer.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.