FormData 전송할 때 fetch vs. axios

Joonas' Note

FormData 전송할 때 fetch vs. axios 본문

개발/Javascript

FormData 전송할 때 fetch vs. axios

2025. 4. 16. 00:47 joonas 읽는데 3분
  • 참고

서버에 파일을 업로드하는 함수를 구현하던 중, axios를 fetch 로 변경하였는데 서버쪽에서 500 에러가 발생했다.

문제는 서버에 도달한 요청 데이터에 RequestBody가 사라진 것이다.

아래의 두 함수를 비교하면, 전혀 문제될 것이 없어보인다.
먼저, axios를 사용하고 있던 기존의 함수 로직이다.

axios
  .post(ENDPOINT, formData, {
    headers: {
      'Content-Type': 'multipart/form-data',
    },
  })
  .then((data) => console.log(data))

다음으로 fetch로 변경한 함수 로직이다.

fetch(ENDPOINT, {
  method: 'POST',
  headers: {
    'Content-Type': 'multipart/form-data',
  },
  body: formData,
})
  .then((data) => console.log(data))

결론부터 말하자면, 아래와 같이 고치면 정상적으로 동작한다.

fetch(ENDPOINT, {
  method: 'POST',
  body: formData,
})
  .then((data) => console.log(data))

이유를 알 수 없어서 여러 방법으로 분석해보았다.
서버쪽 로그도 찍어보고, Postman으로도 전송해보았는데 서버 문제는 아니었다.

그렇다면 axios와 fetch의 동작에 차이가 있다는 것으로 보여서 네트워크로 전송되는 데이터를 비교해보았다.

먼저 axios를 통해 전송되는 데이터이다.

POST /upload HTTP/1.1
Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: ko
Cache-Control: no-cache
Connection: keep-alive
Content-Length: 235
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary1x8F0uD1L2QhdmuW
DNT: 1
Host: localhost:3000
Origin: http://localhost:3001
Pragma: no-cache
Referer: http://localhost:3001/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36
sec-ch-ua: "Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"

다음으로 fetch를 통해 전송되는 데이터이다.

POST /upload HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: ko
Cache-Control: no-cache
Connection: keep-alive
Content-Length: 235
Content-Type: multipart/form-data
DNT: 1
Host: localhost:3000
Origin: http://localhost:3001
Pragma: no-cache
Referer: http://localhost:3001/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36
sec-ch-ua: "Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"

두 데이터에서 유일하게 다른 부분은 Content-Type 이다.

 

도대체 왜 fetch 에서는 boundary 가 사라진 것일까?
그렇다면 boundary 를 붙이고 내용물을 동일하게 가공하여 전송하면 제대로 동작할까?

(일단 되는지 확인하기 위해서) 아래처럼 FormData 를 쪼개서 전송 폼에 맞게 만들어보았다.

const boundary = '----WebKitFormBoundary' + 'caKRudE3BYlaVvsk';

const fields = Array.from(formData).map(([key, value], index) => {
  // 먼저 string 타입만 전송 테스트
  return `Content-Disposition: form-data; name="${key}"\n\n${value}\n${boundary}`;
});
const encodedBody = `--${boundary}\n${fields.join('\n')}--`;
console.log(encodedBody);

fetch(ENDPOINT, {
  method: 'POST',
  headers: {
    'Content-Type': 'multipart/form-data; boundary=' + boundary,
  },
  body: encodedBody,
})
  .then((data) => console.log(data))

안타깝게도 세상은 그렇게 호락호락하지 않다.

multipart/form-data payload (fake)

 

내용물을 동일하게 만들었다하더라도, 파라미터로 넘기는 객체가 FormData 타입이 아니기때문에 Content-Length 도 다르고 fetch 함수 내부에서 다르게 처리되는 것인지 올바르지 않은 포맷으로 전송되고 있었다.

axiosXMLHttpRequest 객체를 사용하는 라이브러리이다. 개인적으로 XMLHttpRequest 의 사용 방법이 너무나도 불편했고 axios는 물론 과거 ajax 역시 초기 세팅 등 사용하기에 편하지는 않았다. 그래서 Fetch API가 등장한 이후로는 너무 유용하게 사용하고 있었다.

아무튼 이런 차이때문인지는 몰라도, fetch에서는 multipart/form-data의 경우에는 Content-Type을 지정하지 않아야 boundary가 자동으로 붙어서 전송되어 정상적으로 동작한다.

스택오버플로우의 답변으로부터 약 10년이 지난 지금까지도 유효한 방법이다 (...)


참고

 

fetch - Missing boundary in multipart/form-data POST

I want to send a new FormData() as the body of a POST request using the fetch api The operation looks something like this: var formData = new FormData() formData.append('myfile', file, 'someFileNam...

stackoverflow.com

 

What is the boundary in multipart/form-data?

I want to ask a question about the multipart/form-data. In the HTTP header, I find that the Content-Type: multipart/form-data; boundary=???. Is the ??? free to be defined by the user? Or is it

stackoverflow.com

 

Comments