Skip to content
Santekno.com | Tech Tutorials and Trends
EN
📖 0%
22 Sep 2025 · 6 min read ·Article 84 / 125
Go

84 Query & Mutation from the Frontend

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

84 Query & Mutation from the Frontend: Managing APIs and Data Efficiently from React

In modern application development, communication between the frontend and backend is the main artery of a system. That communication channel usually consists of two primary types: queries for fetching data and mutations for changing data. In this article, I want to share my experience, best practices, and even real code simulations—about how to manage 84 queries and mutations from the frontend, especially React, in an efficient and scalable way.

Danger
Note: The number 84 here is just an illustration. However, in real-world projects, it’s not uncommon to have dozens, or even hundreds, of API endpoints to handle.

Let’s start with the challenges and opportunities behind this scale.


The Challenge of Managing Dozens of Queries & Mutations on the Frontend

As the number of features in an application grows, the number of API endpoints often spikes right along with it—queries for reading resources (list, detail, search), and mutations for update, delete, create, and so on. A scale like 84 endpoints means:

  • There are many HTTP requests from the frontend to the backend.
  • You need consistent management of response data on the frontend.
  • Error handling becomes complex.
  • State management must be efficient so the user experience stays smooth.

What strategy can an engineer use to avoid drowning in this ocean of requests?


Modern Query/Mutation Management Architecture

1. API Service Abstraction Layer

The current best practice is to build a separate API service layer on the frontend. The goal: to isolate request-response logic from presentation logic.

js
1// src/api/userApi.js
2import axios from 'axios';
3
4export const getUserList = () => axios.get('/api/users');
5export const getUserDetail = (id) => axios.get(`/api/users/${id}`);
6export const updateUser = (id, data) => axios.put(`/api/users/${id}`, data);
7export const deleteUser = (id) => axios.delete(`/api/users/${id}`);
8export const createUser = (data) => axios.post('/api/users', data);
9// ... repeat for other resources up to 84 endpoints

2. Using a Data-Fetching Library

Libraries such as React Query (TanStack Query) are a huge help with:

  • Caching data automatically.
  • Automatic refetch and invalidation when data changes.
  • Loader and error states out of the box.

With React Query, we simply define a useQuery and a useMutation per endpoint.

js
 1// src/hooks/useUsers.js
 2import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
 3import * as userApi from "../api/userApi";
 4
 5export const useUsers = () =>
 6  useQuery(['users'], userApi.getUserList);
 7
 8export const useUser = (id) =>
 9  useQuery(['users', id], () => userApi.getUserDetail(id), {
10    enabled: !!id,
11  });
12
13export const useUpdateUser = () => {
14  const queryClient = useQueryClient();
15  return useMutation(userApi.updateUser, {
16    onSuccess: () => {
17      queryClient.invalidateQueries(['users']);
18    }
19  });
20};

This scheme can be repeated with a similar pattern for other resources, all the way up to 84 endpoints.


Simulation: Listing and Updating Users

Let’s simulate a scenario with two queries and one mutation that are commonly used: viewing the user list, viewing a detail, and updating a user.

1. Displaying the User List

js
 1import { useUsers } from "./hooks/useUsers";
 2
 3function UserList() {
 4  const { data, isLoading, error } = useUsers();
 5
 6  if (isLoading) return <div>Loading...</div>;
 7  if (error) return <div>Error loading users</div>;
 8
 9  return (
10    <ul>
11      {data?.data.map(u => (
12        <li key={u.id}>{u.name}</li>
13      ))}
14    </ul>
15  );
16}

2. User Update Form

js
 1import { useUpdateUser } from "./hooks/useUsers";
 2
 3function UpdateUserForm({ user }) {
 4  const mutation = useUpdateUser();
 5
 6  const onSubmit = (e) => {
 7    e.preventDefault();
 8    mutation.mutate({ id: user.id, data: { name: "Updated name"} });
 9  };
10
11  return (
12    <form onSubmit={onSubmit}>
13      <input defaultValue={user.name} />
14      <button disabled={mutation.isLoading}>Update</button>
15      {mutation.isSuccess && <span>Success!</span>}
16    </form>
17  );
18}

3. Endpoint Management Table

ResourceQuery (READ)Mutation (CREATE)Mutation (UPDATE)Mutation (DELETE)
UsersgetUserListcreateUserupdateUserdeleteUser
ProductsgetProductListcreateProductupdateProductdeleteProduct
OrdersgetOrderListcreateOrderupdateOrderdeleteOrder
…84 API

Flow Diagram: Data Flow from Component to Server

Using a library like React Query, the overall request-response flow can be depicted as follows:

MERMAID
flowchart TD
    A[Komponen UI] -->|Trigger| B[useQuery/useMutation Hook]
    B -->|Call function| C[API Service Layer]
    C -->|HTTP Request| D[Backend REST/GraphQL API]
    D -->|Response| C
    C -->|Return data| B
    B -->|Update| E[State/Cache]
    E -->|Trigger re-render| A

This flow stays consistent regardless of whether our frontend has 5, 50, or 84 endpoints.


Applying DRY (Don’t Repeat Yourself): Automation

One of the traps with a large number of APIs is boilerplate code. For a case with 84 endpoints, copy-pasting becomes a major problem. The solution?

Dynamic Hook Abstraction

We can create a factory function to generate useQuery and useMutation dynamically.

js
 1// src/hooks/useApiResource.js
 2import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
 3
 4export function createResourceHooks(resource, apiFn) {
 5  return {
 6    useList: () =>
 7      useQuery([resource], apiFn.getList),
 8    useDetail: (id) =>
 9      useQuery([resource, id], () => apiFn.getDetail(id), { enabled: !!id }),
10    useCreate: () => {
11      const queryClient = useQueryClient();
12      return useMutation(apiFn.create, {
13        onSuccess: () => queryClient.invalidateQueries([resource]),
14      });
15    },
16    // and so on...
17  };
18}

Then, you just call it:

js
1import * as userApi from "../api/userApi";
2const userHooks = createResourceHooks('users', userApi);
3const { useList: useUsers, useCreate: useCreateUser } = userHooks;

Imagine the time savings if we have 84 resources!


Error Handling: The Key to a Robust System

The frontend must be ready to handle errors—whether from the network, validation, or the backend. The key is centralized error handling.

js
1function ErrorBoundary({ error }) {
2  return <div className="error">{error.message}</div>;
3}

Use it around important components, or turn it into a global interceptor on axios/fetch.


Conclusion and Recommendations

Managing dozens to hundreds of queries & mutations from the frontend can be a big challenge, but it’s far from impossible!

Key tips:

  1. Separate the API Service Layer so it’s easy to maintain.
  2. Use a data fetching library like React Query for automatic data sync, caching, error, and loader states.
  3. Automate hooks & requests with DRY abstraction.
  4. Use a resource table/mapping so endpoints stay monitored.
  5. Implement error handling globally.

With the approach above, even a project with “84 Queries & Mutations” will remain manageable, robust, and easy to develop!

References


Happy coding!
Don’t fear scalability—a good structure at the start will save you time, even for 84 queries & mutations down the road.

Related Articles

💬 Comments