리액트 네이티브를 사용한 개발을 진행하던 중 한 가지 문제가 생겼습니다. FormData를 통해 백엔드에 데이터를 보내주었는데 백엔드 측에서 이를 읽지 못하였습니다. 분명 키와 값 쌍을 정해주었는데 해당 키를 null로 조회하였습니다. 백엔드 측의 코드도, 프론트엔드 측의 코드도 아무런 문제가 없어 보였는데 말이죠.

FormData는 무엇인가?

FormData는 키와 값으로 이루어진 데이터 형식입니다. 출처: Microsoft copilot으로 제작


FormData는 form 필드와 그 데잍터를 나타내는 키-값 쌍을 쉽게 생성할 수 있는 인터페이스입니다. 이름에서도 유추할 수 있듯이 html의 form을 구성하는 데이터를 쉽게 구성할 수 있도록 도와주는 객체입니다. HTML의 경우 다음과 같은 코드를 작성하면 해당 폼 요소가 가진 필드 전체가 자동으로 FormData로 구성이 됩니다. FormData의 인자는 form 태그 element가 들어갑니다.

const form = document.querySelector('form')
const formData = new FormData(form);

fetch나 axios 등의 네트워크 요청을 보내면 브라우저가 보내는 HTTP 메시지는 인코딩되고 Content-Type 속성은 multipart/form-data로 지정된 후 전송됩니다. get 요청을 보낼 때 FormData 객체를 사용한다면 URLSearchParams에 직접 키와 값 쌍을 전달하여 쉽게 get 요청에 필요한 params를 추가 할 수 있습니다. 참고로 서버 관점에선 FormData를 사용한 방식과 일반 폼 전송 방식 간에 큰 차이가 없다고 합니다.

사용법

앞서 설명드린 HTML 폼 요소 외에도 new FormData() 생성자를 사용하여 직접 키-값 쌍을 추가할 수 있습니다.

const formData = new FormData();
formData.append('name', 'Curt');
formData.append('age', 27);

디버깅

FormData로 구성된 객체를 console.log()를 이용해 콘솔에 메세지를 표시하는 것은 불가능 합니다. 대신 FormData.entries() 메서드를 사용하여 FormData 객체의 모든 키-값 쌍을 반복 가능한 이터레이터 객체로 얻을 수 있습니다.

const formData = new FormData(document.querySelector('form'));

for (const [key, value] of formData.entries()) {
  console.log(key, value);
}

문제 해결

하지만 리액트 네이티브에서 FormData는 Web에서의 FormData와는 차이가 있습니다. FormData가 자바스크립트의 스펙이 아닌 Web API이기 때문입니다. MDN문서에서도 Web API 섹션에서 찾을 수 있죠. 그래서 우리가 일반적으로 사용하는, 그러니깐 위에서 설명한 FormData와 완전히 일치하지는 않습니다. 올바르게 보였던 제 코드를 백엔드에서 이해하지 못했던 이유이죠.

 

앞서 언급했듯이 일반적으로 FormData를 body로 보내면 cotent-type은 자동으로 multipart/form-data로 지정되어 보내집니다. 하지만 리액트 네이티브에서는 자동으로 지정되지 않을 수 있습니다. 그래서 header에 명시적으로 "Content-Type": 'multipart/form-data' 필드를 추가해 주어야 하죠. 대부분의 경우 여기까지 설정한다면 해결되겠지만 저의 경우는 추가적인 설정이 필요하였습니다. "Content-Type"의 값에 boundary="boundary"를 추가해 주어야했죠. 즉, header를 다음과 같이 정해주어야 하죠.

headers: {
            "Content-Type": 'multipart/form-data; boundary="boundary"',
        },

그렇다면 boundary="boundary"는 무엇을 의미하는 걸까요?

boundary="boundary"

boundary는 키-값 쌍을 각각 구분하는 분기점이 됩니다. 출처: Microsoft copilot으로 제작

boundary="boundary"는 multipart/form-data 형식의 HTTP 요청에서 각 파트를 구분하는 역할을 합니다. 각 이름-값 쌍을 구분하는 마커 역할이죠. multipart/form-data는 여러 종류의 데이터를 하나의 HTTP 요청에 포함할 수 있는데, 이때 각 부분은 boundary로 구분됩니다. 이 boundary 파라미터는 HTTP 요청 헤더의 Content-Type에 자동으로 추가되어 각 쌍의 키와 값 사이를 구분하는 역할을 하며, 서버는 이를 통해 파라미터 값이 어디서 시작하고 끝나는지를 알 수 있습니다. 참고로 boundary는 임의의 문자열이지만 일반적으로 --로 시작하는 문자열을 사용합니다.

--boundary
Content-Disposition: form-data; name="name"
Curt
--boundary
Content-Disposition: form-data; name="age"
27
--boundary

위의 예시에서 boundary 값은 boundary입니다. --boundary라는 문자열을 만나면 해당 문자열의 앞까지를 이전 키에 대한 값 혹은 시작점, 뒤를 다음 키로 인식하여 서버가 데이터를 올바르게 인식할 수 있도록 도와줍니다.

 

일반적으로는 개발자가 직접 boundary 값을 설정할 필요는 없으며, HTTP 클라이언트 라이브러리가 자동으로 이 값을 생성하고 처리합니다. 하지만 저의 경우(axios를 사용하였습니다.)는 자동으로 설정된 boundary 값이 올바르지 않았고 이때문에 수동으로 설정해주어야 했습니다.

참고 자료

MDN, FormData
모던 JavaScript 튜토리얼, FormData 객체
What Is A Boundary In Multipart/Form-Data
Reddit 질문글, Struggling with FormData()

+ Recent posts