[Express] Fetch 이용해 프론트엔드 -> 백엔드 파일 보내기 시행착오

한음·2021년 8월 26일
1
post-thumbnail
post-custom-banner

프론트엔드에서 생성된 비디오와 이미지를 fetch 를 통해 서버로 보내려고 했다.

form 을 통해 유저가 파일을 업로드 후 POST 보내는 형식이 아닌, navigator.mediaDevices 를 이용한 웹 상의 즉석 동영상 레코더로 찍은 파일을 자동으로 보내는 것이라 이래저래 많이 해맸다.


< 시행착오 >

1. 단순히 JS 오브젝트를 form 에 넣어 fetch 하기.

완전히 잘못된 방법이었다. JS 오브젝트는 말 그대로 JS 내에서만 공유되는 문법이지, 서버나 form 이 이해할 수 없다. 그래서 데이터를 단순 문자열로 변환하는 JSON 이 쓰이는 것. 아무튼 호기로운 첫 시도는 아래와 같았다.

await fetch("/videos/upload-from-recorder", {
	method: "POST",
 	headers: {"Content-Type": "multipart/form-data},
   	body: {
               "title": ~,
               "video": video file
               "thumbnail": image file
		}});

구글링을 통해 headers 를 이래저래 바꿔보고, 파일에 Blob 을 넣기도, createObject 를 이용한 url 을 넣기도 해보았지만 서버 쪽에서 리퀘스트만 받고, form 은 정상적으로 전달되지 않았다.

2. 프론트엔드 JS를 이용해 가상의 form 생성 후 submit 하기

1번 방식의 문제점을 깨달은 뒤, 어떻게 form 에 데이터를 묶어 보낼지 고민하다 생각한 방법. 문자열로만 이루어진 데이터를 JSON 으로 변환해 fetch 하는 법은 알았지만, 당연히 같은 방식으론 파일을 보낼 수 없었다.
두번째 시도는 아래와 같다.

var form = document.createElement("form");
form.setAttribute("charset", "UTF-8");
form.setAttribute("method", "Post");
form.setAttribute("action", "/videos/upload-from-recorder");

var video = document.createElement("input");
hiddenField.setAttribute("type", "hidden");
hiddenField.setAttribute("name", "video");
hiddenField.setAttribute("value", video File);
...
form.appendChild(video);
document.body.appendChild(form);
form.submit();

input 을 하나하나 form 에 모두 넣어준 뒤 submit 했다. 결과는 30% 정도 성공. 서버에서 form 을 받을 수는 있었다. 다만, input 세팅을 잘못한 건지 보낸 Blob 이나 파일의 url 을 정상적으로 처리하지 못했다.
formenctype 을 수정하기도 하고, 파일 형식도, input 세팅도 계속 바꿔보며 삽질했지만 파일이 제대로 처리되지 않아 좌절했다.

3. FormData 이용해 form 생성 후 fetch 하기.

1번과 2번의 조합이라고 할 수 있겠다. document 내 가상 form 이 아닌 JS 내부에 form 을 만들고 fetch 했다.
파일 유형과 헤더 세팅, express 라우터에서 multer 설정 등 수많은 시행착오 후 제대로 파일을 받아냈다. 너무 기뻤다. 결과는 아래와 같다.

const form = new FormData();
form.enctype = "multipart/form-data";
// fetch headers 에 multipart 를 주면 form 을 제대로 받지 못했다. 여기서 한참 해멨다.
form.append("video", video file, "video.mp4);
// createUrl 을 이용한 url 이 아닌 video Blob 을 그대로 보냈다.
form.append("thumbnail", thumb file, "thumb.jpg");
// 마찬가지로 Blob 그대로
...

await fetch("/videos/upload-from-recorder", {
  method: "POST",
  headers: {
  //"Content-Type": "multipart/form-data",
  }, 
  // headers 를 비워준다.
  body: form
  // body 엔 JS object 가 아닌 form 을 그대로 보낸다.
});

② 두 개 이상의 파일을 보내기 때문에 express 라우터에서 multer 세팅을 해준다.

router.post("/videos/upload-from-recorder", multer({dest: "uploads"}).fields([
  {name: "video", maxCount: 1},
  {name: "thumbnail", maxCount: 1}
  ]), controller);                                                                          

multer 이용해 받은 파일은 Controller 에서 다음과 같이 처리한다.

export const controller = async(req, res) => {
  const { video, thumbnail } = req.files;
  const { title, ... } = req.body;
  
  // video 와 thumbnail 이 array 에 담겨오기 때문에 video[0].path 로 Blob 의 url 을 가져올 수 있다.

마치며

도무지 원인을 알 수 없는 에러를 잡을 때에 비해 즐거운 삽질이었다. 과정에서 많이 배우기도 했으며 이런저런 시도 끝에 성공했을 때의 기쁨은 덤.
벽에 부딪는 과정을 늘 즐기려 한다. 화이팅 👾

profile
https://github.com/0hhanum
post-custom-banner

2개의 댓글

comment-user-thumbnail
2023년 2월 2일

한음님 안녕하세요,
정말 감사드립니다. 덕분에 이미지 업로드 해결했습니다!
정말정말정말 감사감사감사드립니다.
새해 복 많이받으시고 하시는 일들 모두다 만사형통하시길 바라겠습니다!!! ㅠㅠ

1개의 답글