Vanilla JS + Node.js로 이미지 파일 업로드

krkorklo·2022년 10월 4일
0

업로드 로직 구상

  • 우선 Vanilla JS에서 input type="file"로 이미지를 입력받고
  • 서버에 이미지 전송
  • DB에 이미지 저장

으로 단순하게 흐름을 생각하고 시작했다.

Vanilla JS에서 이미지 업로드

<section id="post-images">
  <label for="file" id="select-image">
    <img alt="image select" src="./views/assets/image.svg" width="24px" height="24px" />
	<input type="file" id="file" name="files" >
  </label>
</section>

커스텀을 위해서 label을 생성해주고 input을 넣어줬다.
이제 file이 입력될때마다 데이터를 저장하는 이벤트가 필요하다.

const file = document.querySelector("#file");
file.addEventListener("change", (e) => {
  const reader = new FileReader();

  reader.onload = (e) => {
    const preview = `
      <section id="preview">
        <img id="preview-image" alt="image preview" src="${e.target.result}" width="76px" height="76px" />
        <img id="delete" alt="delete image" src="./views/assets/delete.svg" />
      </section>
    `;
  }

  reader.readAsDataURL(e.target.files[0]);
});

처음에는 FileReader 객체를 사용했고 해당 객체를 통해 생성된 Base64 URL을 사용해서 프리뷰를 띄웠다.
그리고 Base 64 URL을 그대로 DB에 저장해뒀는데

어우,,,
이렇게 긴 내용을 src로 설정하고 DB에 저장하는건 좀,,,

그래서 createObjectURL

const file = document.querySelector("#file");
file.addEventListener("change", (e) => {
  const imageUrl = URL.createObjectURL(e.target.files[0]);
  const preview = `
	<section id="preview">
	  <img id="preview-image" alt="image preview" src="${imageUrl}" width="76px" height="76px" />
	  <img id="delete" alt="delete image" src="./views/assets/delete.svg" />
	</section>
	`;
});

URL.createObjectURL은 객체를 가리키는 URL을 DOMString으로 반환해서 창을 닫을때까지 유지된다고 한다. 포인터를 사용해 효율적이라고 한다.

훨씬 짧아졌다. 그리고 FileReaderreadAsDataURL은 blob을 읽고 변환할때 비동기적으로 작동하며 시간이 많이 걸리는데, createObjectURL은 동기적으로 작동하며 임시 URL을 생성하고 blob에 바인딩해 blob을 읽을 필요가 없어 훨씬 빠르다고 한다.

DB에 저장은 어떻게?

처음에 바보처럼 createObjectURL로 생성된 url을 그대로 DB에 저장해서 접근하려고 했다.
ㅎㅎㅎ
임시 URL이기 때문에 해당 URL은 프리뷰만 띄우고 DB에는 다른 방법으로 저장해야 했다.

Blob 저장

  • 처음에는 DB에 이미지를 Blob으로 저장하고 읽어오려고 했다.
  • 그런데 바이너리 데이터로 DB에 이미지를 저장하면 부하가 크기도 하고 DB에는 이미지 자체를 저장하는 것보다 이미지/데이터를 효율적으로 다룰 수 있는 데이터를 저장하는 게 좋다고 생각했다.

서버에 이미지 저장

  • S3같은 스토리지를 쓰는게 정석일 것 같지만,,, 그럴 시간이 없다🥹
  • formData로 멀티미디어 이미지를 전송할 수 있는데, 이때 Node.js는 multer라는 모듈을 사용해서 편리하게 파일을 처리할 수 있다고 한다.
let formData = new FormData();
images.forEach((image) => {
  formData.append("files", image);
});

const response = await fetch("/post/post", {
  method: "POST",
  body: formData,
}).then((res) => res.json());

클라이언트에서는 다음과 같이 FormData를 생성하고 서버에 전송할 수 있다.

const storage = multer.diskStorage({
  destination: function (req, file, callback) {
    callback(null, "src/client/public/");
  },
  filename: function (req, file, callback) {
    const ext = path.extname(file.originalname);
    callback(null, `${path.basename(file.originalname, ext)}-${Date.now()}${ext}`);
  },
});
const upload = multer({ storage: storage }).array("files");
  • 서버에서는 formData를 처리하기 위해 multer 설정부터 해줘야하는데
  • multer : 파일 업로드를 도와주는 모듈
  • diskStorage : 전송된 파일을 저장하기 위한 엔진
    • destination : 파일을 저장할 경로
    • filename : 저장할 파일 이름 설정
  • multer().array(name) : 파일을 배열로 받겠다는 선언으로 하나의 파일만 받을때는 multer().single(name)

upload를 middleware로 설정하고 나면
→ 서버로 전달된 파일이 저장
→ 서버에 저장된 파일 경로를 가져와서 DB에 업로드할 수 있다.

router.post(
  "/post",
  upload,
  async (req, res) => {
    const values = req.files.map((item) => `public/${item.filename}`);

    return await new Promise((resolve, reject) => {
      connection.query(`INSERT INTO images (src) VALUES ?`, [values], (err, rows) => {
        if (err) reject(Error(err));
        resolve(rows);
      });
    });
  });

DB에 저장된 src를 그대로 가져와서 img src에 넣어주면 업로드한 이미지가 제대로 뜨는 것을 확인할 수 있다.


참고자료
https://velog.io/@kykim/readAsDataURL-vs-createObjectURL

0개의 댓글