[React] File과 JSON 데이터를 multipart/form-data로 한번에 보내는 방법

황수콩·2024년 1월 18일
3
post-thumbnail

🚨 사건의 발단

모델 회원가입을 마무리 하려고 민서가 만들어둔 데이터를 서버에 전달하는 것을 내가 맡아서 하던 중이었다.

나는 그대로 POST 요청을 했는데 어랍쇼? 자꾸 에러가 났다

그래서 스웨거를 확인해봤다

💽 multipart/form-data?

multipart/form-data 이게뭔데.. 먼저 multipart가 무엇인지 알아보자

📀 multipart

클라이언트와 서버 간에 전송되는 HTTP 요청 또는 응답에서 여러 종류의 데이터를 동시에 전송하기 위해 사용되는 방식이다

일반적으로 파일 업로드와 관련된 데이터를 전송하는데 주로 사용된다

멀티파트 요청은 'Content-Type' 헤더에 'multipart/form-data' 값을 가지며 여러개의 파트(part)로 구성된다. 각 파트는 개별적인 데이터 조각으로 파일이나 텍스트 데이터 등을 포함할 수 있다. 각 파트는 헤더와 본문(body)으로 구성되어 있으며 헤더에는 파트의 메타 데이터가 포함되어 있고 본문에는 실제 데이터가 포함된다.

📁 form-data

<input
	type="file"
	accept="image/*"
	onChange={(e) => {
	  uploadImg(e);
	}}
/>

formData는 form 필드와 그 값을 나타내는 일련의 key/value 쌍을 쉽게 생성할 수 있는 방법을 제공한다.

new FormData() 를 통해 빈 객체를 만들 수 있고 FormData.append 을 사용하여 key/value 쌍을 추가할 수 있다

🤯 그렇다면 어떻게 보내야하지?!

그런데 민서의 코드는 이렇게 돼있었다.

const imgUrl = URL.createObjectURL(imgObj[0]);

위처럼 URL로 한다고 하더라도 blob:localhost ~~… 이런식으로 나오고 있었다 서버에서 s3이미지 url을 가져와서 보내는 형식이 아니라면 우리는 File을 그대로 보내줘야 하는 것이다.

🔫 첫번째 시도

profileImg를 formData 객체로 선언하고 넣어준 다음 회원가입에 필요한 데이터와 함께 requestBody에 넣어주고 Content-type은 multipart/form-data로 설정해줬다. 그래서 서버에서는 MultiPart형식의 이미지 url을 받아야하는데 계속 [Object FormData]형식으로 받았다

알고 보니 그렇게 하는 것이 아니었고 reaquestBody를 FormData 형식으로 넘겨줘야 하는 것인데 이렇게 하니까 designerInfo 데이터를 서버에서 읽을 수가 없었다 이걸 어떻게 처리해야할까,,,

🔫 두번째 시도

우리가 보내야 할 데이터 profileImg와 designerInfo는 완전히 다른 Content-type이다. 두 종류의 데이터가 하나의 HTTP Request Body에 들어가야 하는데, 한 Body에서 이 2 종류의 데이터를 구분에서 넣어주는 방법이 필요하다. 그래서 우리는 multipart 타입을 사용해야하는 것이다.

결론부터 말하자면 FormData 객체로 만들어주고 이 객체에 profileImg 파일과 designerInfo json데이터를 넣어줬다. 그리고 이 객체를 reaquestBody로 넣어줬다!

const requestBody = new FormData();
const jsonSignUpData = JSON.stringify(signUpData);
const deisgnerInfo = new Blob([jsonSignUpData], { type: 'application/json' });
requestBody.append('designerInfo', deisgnerInfo);
requestBody.append('profileImg', profileImg.file);

🤯 그러다가 갑자기 의문이 들었다... 저걸 왜 json으로 바꿔줘야하지?

우리가 평소에 보내는 requestBody는 JavaScript 객체이다. 우리가 따로 변환해서 보내지 않았다. 그렇다면?

일반적으로 JavaScript 객체가 제공되면 이 객체는 내부적으로 JSON 문자열로 변환되어 HTTP 요청 본문에 포함됩니다.

🤯 그렇다면 여기서 Blob이 뭐지?

Blob 객체는 파일류의 불변하는 미가공 데이터를 나타냅니다. 텍스트와 이진 데이터의 형태로 읽을 수 있다

아하!! 그래서 JSON 형태를 유지해서 서버에서 읽을 수 있겠구나...

🤯 그렇다면 File은? 왜 그냥 넣어준거지?

File 객체는 Blob의 한 종류로, Blob을 사용할 수 있는 모든 맥락에서 사용할 수 있습니다.

✨ 최종 코드

const postDesignerSignUp = async () => {
  const signUpData: DesignerInfo = {
    name: name.data,
    gender: gender.data,
    ...
  };

  const requestBody = new FormData();
  const jsonSignUpData = JSON.stringify(signUpData);
  const deisgnerInfo = new Blob([jsonSignUpData], { type: 'application/json' });
  requestBody.append('designerInfo', deisgnerInfo);
  requestBody.append('profileImg', profileImg.file);

  try {
    const data = await api.post('/auth/signup/designer', requestBody, {
      headers: {
        'Content-Type': `multipart/form-data`,
      },
    ...
};
};

🔍 작성 중 찾은 사실

네트워크 메서드가 FormData 객체를 바디로 받는다는 건 FormData의 특징입니다. 이때 브라우저가 보내는 HTTP 메시지는 인코딩되고 Content-Type 속성은 multipart/form-data로 지정된 후 전송됩니다.

헉 그럼 header content 타입도 설정안해줘도 되나?????

🔫 시도해보기

const data = await api.post('/auth/signup/designer', requestBody);

왕 ! 성공 ~~!!!! 🥳🥳🥳🥳

👀 서버는 어떻게 처리했을까?

나와 함께 이 문제를 해결한 우리 멋쟁이 서버리드 안현주씨의 아티클 ~ ㅋㅋ 볼람볼 ~
https://hellozo0.tistory.com/414

profile
@binllionaire

1개의 댓글

comment-user-thumbnail
2024년 1월 30일

저는 이거 실패했었는데 추후에 이 글을 보고 다시 도전해보겠습니다. 이런 글 작성해주셔서 너무너무 고맙습니다!!

답글 달기