跪拜 Guibai
← Back to the summary

Stop Sprinkling Axios Calls Through Your React Components

Many people who are just starting React projects write directly in their components:

axios.post('/api/login', {
  username,
  password
})

It's fine at the beginning.

But as the project grows, an obvious problem emerges:

Page code and request logic start getting tangled together.

The login page has to handle input fields, API endpoints, Axios, and server response data all at once.

So this article won't rush into JWT. Let's first sort out a fundamental issue:

How should a React page communicate with the backend?


1. First, think clearly: Why does React need an API layer?

Suppose there's a login page.

The most direct way to write it is:

async function handleLogin() {
  const res = await axios.post('/api/login', {
    username,
    password
  })
}

It looks simple.

But later the project might also have:

Login
Get user info
Get orders
Get repositories
Create order
Delete data

If every component uses Axios directly, it becomes:

Login.jsx     → axios
Home.jsx      → axios
Pay.jsx       → axios
Profile.jsx   → axios
Order.jsx     → axios

Request logic is scattered across various pages.

So we want to extract the "calling the interface" part separately.

It ends up as:

React Page
    ↓
Call API method
    ↓
API file handles the request
    ↓
Axios sends the HTTP request

This way the page only cares about:

"I want to log in."

And doesn't need to care about:

"Is the login request POST or GET, what's the endpoint, how is Axios configured?"


2. Create the api directory

Under src, create:

src/
└── api/

This directory specifically holds the frontend code for calling backend interfaces.

Next, let's first create:

src/api/config.js

Why create this first?

Because all interfaces need to use Axios.

If each file creates its own Axios instance:

axios.create(...)

Then later, if you want to uniformly modify:

You'd have to change many files.

So first create a unified Axios instance.


3. Create a unified Axios instance

In src/api/config.js:

import axios from 'axios';

const instance = axios.create({
  baseURL: '/api',
  timeout: 5000
});

export default instance;

The most important part here is:

baseURL: '/api'

Later when we write:

axios.post('/login')

The actual request is:

/api/login

Instead of writing the full address every time.

So:

config.js

Solves:

How the entire project uniformly uses Axios.

It doesn't handle any specific business logic.


4. Create the user API

Now we need to handle login.

Under src/api, create:

src/api/user.js

Why create user.js?

Because config.js only handles Axios's common configuration.

While:

user.js

Handles:

User-related interfaces.

For example:

Therefore, in user.js:

import axios from './config';

export const login = async (data) => {
  const res = await axios.post('/login', data);
  return res.data;
}

Now we have two files:

src/api/
├── config.js   // Unified Axios configuration
└── user.js     // User-related interfaces

The responsibilities of these two files are already distinct.


5. Pages no longer use Axios directly

The login page no longer needs to write:

axios.post('/api/login')

Instead:

import { login } from '../api/user';

On login:

const res = await login(formData);

At this point, the whole process is very clear:

User fills in username and password
        ↓
Login page calls login()
        ↓
user.js handles sending the /login request
        ↓
config.js provides the Axios instance
        ↓
Axios sends the request

The page handles "what to do."

The API layer handles "how to request."

This is the most basic frontend layering.


6. But there's still one problem: Where is the backend?

We've now written:

axios.post('/login')

But who receives this request?

If we really write a backend, we'd also need:

For learning frontend, these things can sometimes distract from the main focus.

So this demo uses:

Mock

The purpose of Mock is simple:

Pretend we already have a backend.


7. Use Vite Mock to simulate the backend

Create in the project root:

mock/

Then create:

mock/user.js

Also configure the Mock plugin in Vite.

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { viteMockServe } from 'vite-plugin-mock'

export default defineConfig({
  plugins: [
    react(),
    viteMockServe({
      mockPath: 'mock',
      localEnable: true
    })
  ],
})

Here:

mockPath: 'mock'

Tells Vite:

Mock interfaces are in the mock directory.


8. Create the first Mock login interface

In:

mock/user.js

Write:

export default [
  {
    url: '/api/login',
    method: 'post',
    response: req => {
      const body = req.body || {};

      if (
        body.username !== 'admin' ||
        body.password !== '123456'
      ) {
        return {
          code: -1,
          message: 'username or password error'
        };
      }

      return {
        code: 0,
        user: {
          username: body.username
        }
      };
    }
  }
]

Now, when the browser requests:

POST /api/login

Mock will catch it.

So the whole process becomes:

User fills in username and password
        ↓
Login page calls login()
        ↓
user.js sends /login
        ↓
Axios handles the request
        ↓
Vite Mock receives /api/login
        ↓
mock/user.js validates username and password
        ↓
Returns JSON

At this point you should understand:

Mock is not the frontend API.

It simulates the backend during development.

And the API layer is:

The code organization the frontend uses to call the backend.

These are two completely different things.


9. Why can't we say "the login system is complete" yet?

Because right now we can only do:

Username and password are correct
        ↓
Server returns success

But the next time the user visits:

/api/repo

How does the server know:

"This person just logged in?"

That's the problem to solve in the next article.