axios icon indicating copy to clipboard operation
axios copied to clipboard

Fetch adapter Post request do not working in serverside

Open Scottpm1401 opened this issue 1 year ago • 1 comments

Describe the bug

I use latest axios write an interceptor for both client and server in my NextJS project. When use it with default fetch it working but when i try it with new axios fetch adapter it return to my server with no body in it (params still work and method still POST). Screenshot 2024-06-24 at 13 56 22

To Reproduce

Install nextJS version ^14 (using App Router) Install axios 1.7.2 in the nextJS middleware.ts file call an api by using axios:

export async function middleware(request: NextRequest) {
  // Check if the request is for the sign-in page
  let isAuthenticated = false;
  const response = intlMiddleware(request);
  try {
    const user = await getMyProfile();
    if (user) {
      isAuthenticated = true;
      response.cookies.set('user', JSON.stringify(user), { httpOnly: false });
    }
  } catch (e) {
    if (isAxiosError(e)) {
      console.log('Error:', e.message);
    }
  }
  if (includes(NOT_AUTH_APP_ROUTES, removeLocale(request.nextUrl.pathname))) {
    if (isAuthenticated) {
      return NextResponse.redirect(new URL('/', request.url));
    }
  }

  // Apply internationalization middleware
  return response;
}

write an interceptor like below

Code snippet

import axios, { AxiosError } from 'axios';
import Cookies from 'js-cookie';

import API from '@/constant/API';
import { BE_URL } from '@/constant/common';
import { isServer } from '@/utils/common';
import { parseCookies } from '@/utils/cookie';

const axiosClient = axios.create({
  baseURL: BE_URL,
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json',
  },
  withCredentials: true,
  adapter: isServer() ? 'fetch' : undefined,
});

let isRefreshing = false;
type FailedQueue = {
  resolve: (value: unknown) => void;
  reject: (reason?: any) => void;
};
let failedQueue: FailedQueue[] = [];

function processQueue(error: AxiosError | Error | null) {
  failedQueue.forEach((prom) => {
    if (error) prom.reject(error);
    else prom.resolve(null);
  });
  failedQueue = [];
}

axiosClient.interceptors.response.use(
  (response) => {
    // Any status code that lie within the range of 2xx cause this function to trigger
    // Do something with response data
    return response;
  },
  async (err) => {
    let refresh_token: string | undefined = undefined;
    if (err.config.adapter === 'fetch') {
      const cookies = parseCookies(err.config.headers.Cookie);
      refresh_token = cookies.refresh_token;
    } else {
      refresh_token = Cookies.get('refresh_token');
    }

    if (!refresh_token) {
      return Promise.reject(err);
    }
    const originalConfig = err.config;
    if (originalConfig.url !== '/' && err.response) {
      if (isRefreshing) {
        return new Promise((resolve, reject) => {
          failedQueue.push({ resolve, reject });
        }).catch((err) => Promise.reject(err));
      }

      isRefreshing = true;

      if (err.response.status === 401 || err.response.status == 403)
        return new Promise((resolve, reject) => {
          const f = async () => {
            try {
              const formData = new FormData();
              formData.append('refresh_token', refresh_token);
              await axios.post(API.AUTH.REFRESH_TOKEN, formData, {
                baseURL: BE_URL,
                adapter: 'fetch',
                withCredentials: true,
              });

              // Working with default fetch
              // const res = await fetch(`${BE_URL}${API.AUTH.REFRESH_TOKEN}`, {
              //   method: 'POST',
              //   body: formData,
              //   credentials: 'include',
              // });

              processQueue(null);
              resolve(axiosClient(originalConfig));
            } catch (err) {
              if (axios.isAxiosError(err) || err instanceof Error) {
                processQueue(err);
              }

              if (!isServer()) window.location.href = '/';
              reject(err);
            } finally {
              isRefreshing = false;
            }
          };
          f();
        });
    }

    // Any status codes that falls outside the range of 2xx cause this function to trigger
    // Do something with response error
    return Promise.reject(err);
  }
);

export default axiosClient;

Expected behavior

Axios with fetch request still send an POST request with body in it

Axios Version

1.7.2

Adapter Version

FETCH

Browser

Firefox

Browser Version

127.0

Node.js Version

18.17.0

OS

Mac OS

Additional Library Versions

Next 14.2.2
React 18

## Server
Django 5.0.4
Python 3.11

Additional context/Screenshots

Testing with Default WebAPI fetch() Screenshot 2024-06-24 at 14 17 24

Scottpm1401 avatar Jun 24 '24 07:06 Scottpm1401

To make your code looks easier, use https://github.com/Flyrell/axios-auth-refresh this plugin to refresh token.

suhaotian avatar Jul 20 '24 12:07 suhaotian

Have you managed to resolve this issue? 👀

michalpulpan avatar Mar 06 '25 13:03 michalpulpan

Okay, seems like I found the problem. Apparently adapter Fetch was sending the requests with 'Transfer-Encoding': 'chunked' header and 'Content-Length': ''. So I created Axios interceptor:

	axiosInstance.interceptors.request.use((config) => {
		if (config.data && typeof config.data === "object") {
			const jsonData = JSON.stringify(config.data);
			config.data = jsonData;
			config.headers["Content-Length"] = Buffer.byteLength(jsonData).toString();
			config.headers["Content-Type"] = "application/json";
		}
		return config;
	});

Hope this helps. 🔥

michalpulpan avatar Mar 06 '25 13:03 michalpulpan

Or try xiorjs, similar API, naturally born with fetch, turn your axios size load speed from 16ms to 4ms.

suhaotian avatar Mar 07 '25 00:03 suhaotian