[Node.js]express 서버에 파일 업로드 / api 응답으로 클라이언트에 파일 제공

Yeon Jeffrey Seo·2021년 10월 13일
4

사용자가 이미지를 업로드하고, 그 이미지를 다시 사용자에게 제공하는 기능을 만들어보려 한다.

파일 업로드

multer라는 모듈을 사용할 예정이다. express에서 파일 업로드 시 가장 많이들 사용하는 모듈이다.

설치 및 사용법

multer 공식 문서
https://www.npmjs.com/package/multer

$ npm multer --save

FE

공식문서에는, form 태그로 입력을 받을 때, enctype="multipart/form-data"를 반드시 설정해 주라고 나와 있다.

내 테스트 템플릿은 아래와 같다.

<form action="/api/images" method="post" enctype="multipart/form-data">
    <label for="img"></label>
    <input type="file" id="img" name="img", accept="image/*">
    <input type="submit" value="제출">
  </form>

form tag 내부에 accept="image/*"를 추가해 줌으로서, 이미지 형식 파일만 선택하게 만들 수 있다. 더 자세한 내용은 공식 문서를.... 👇
https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept

BE

express 앱을 세팅할 차례.
위 form tag는 input type이 submit이고, action과 method가 정의되어있으므로, 제출 버튼을 누르면 바로 Http request가 만들어진다. 해당 uri에 응답할 api를 만들어준다.

apiRouter
  .route("/images")
  .post(uploadFiles.single("img"), (req, res) => {
    const { file } = req;
    console.log(file);
    return res.send({ result: "success" });
  });

공식 문서를 보면, multer를 미들웨어로 사용한다. 해당 미들웨어에서는, multer 모듈의 기본 설정을 해주는 듯 하다. 나는 파일이 저장될 경로만 지정해주었다.

import multer from "multer";

export const uploadFiles = multer({
  dest: "uploads/",
});

나는 단일 이미지 파일을 업로드 할 것이므로, .single()을 사용했다. ()에 들어갈 문자열은 form 태그 내 input 태그의 name 속성과 같아야 한다.
실제 미들웨어를 사용할 때에는 아래 이미지를 보고 어떤 메서드를 사용할 지 선택해야 한다.

api controller에서 파일이 어떻게 생겼는지 확인했다. multer를 사용하면 파일은 req.file에 담겨 온다. array를 사용했을 경우 req.files로 파일이 온다.

딱 봐도 filename은 unique해 보인다. 프로젝트에 적용 시에는 path의 값을 db에 저장하면 될 듯 하다.

프로젝트 디렉터리에 uploads 폴더가 생기고 그 안에 파일이 저장된 것을 확인 할 수 있다.

클라이언트에게 파일 제공하기

express.static()을 사용해 정적 파일을 제공하는 것은 이미 여러번 시도해봤으므로, 다른 방법으로 파일을 제공해보고 싶었다. express Request 메서드 중 sendFile()있길래, 활용해 보았다.

API 응답으로 파일 보내기

페이지를 로딩할때 API를 호출한 뒤, 특정 이미지를 보내도록 만들었다.

BE

apiRouter
  .route("/images")
  .post(uploadFiles.single("img"), (req, res) => {
    const { file } = req;
    console.log(file);
    return res.send({ result: "success" });
  })
  .get(async (req, res) => {
    console.log(process.cwd());
    return res.sendFile(
      process.cwd() + "/uploads/fef621f1c1e0e5394d7f4dea70c8f452"
    );
  });

res.sendFile("파일 경로"); 로 사용한다. 경로는 절대경로로 만들어주었다.

FE

<script>
  const getOneImage = async () => {
    const container = document.getElementById("img-container")
    
    const response  = await fetch ("/api/images",{
      method : "GET"
    });

    console.log(response);
    const blobImg = await response.blob();
    console.log(blobImg);
    const imgUrl = URL.createObjectURL(blobImg);
    console.log(imgUrl);
    const html = `<img src="${imgUrl}" alt="">`;
    container.innerHTML = html;
    
  }
  window.onload = () => {
    getOneImage();
  };
</script>

페이지가 로드되면 GET /api/images/ 를 호출한다. 처음에는 response를 아무리 뒤져서 데이터가 나오지 않았다. 여기저기 수소문한 끝에 이미지, 동영상 등의 데이터는 blob 메서드를 한번 거쳐줘야 한다는 것을 알았다.

이 blob 객체를 어떻게 쓰지? 고민하다, blob을 url로 변환한 뒤 img src로 사용할 수 있다는 것을 알았다.
blob을 url로 변환한 결과는 아래와 같다.

성공........

응답으로 파일을 보내줄 시의 문제점

클라이언트 api 호출이 잦아지므로, 서버 부하가 증가할 수 있다.또한 결정적으로, res.sendFile()은 1개의 파일만 보낼 수 있다.

결국 백엔드 서버 측에서 정적 파일을 제공한 뒤에, 클라이언트에서 직접 접근을 하도록 하는 방법밖에 없을 듯 하다.

profile
The best time to plant a tree was twenty years ago. The second best time is now.

0개의 댓글