input type="file"로 이미지를 입력받고으로 단순하게 흐름을 생각하고 시작했다.
<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에 저장하는건 좀,,,
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으로 반환해서 창을 닫을때까지 유지된다고 한다. 포인터를 사용해 효율적이라고 한다.

훨씬 짧아졌다. 그리고 FileReader의 readAsDataURL은 blob을 읽고 변환할때 비동기적으로 작동하며 시간이 많이 걸리는데, createObjectURL은 동기적으로 작동하며 임시 URL을 생성하고 blob에 바인딩해 blob을 읽을 필요가 없어 훨씬 빠르다고 한다.
처음에 바보처럼 createObjectURL로 생성된 url을 그대로 DB에 저장해서 접근하려고 했다.
ㅎㅎㅎ
임시 URL이기 때문에 해당 URL은 프리뷰만 띄우고 DB에는 다른 방법으로 저장해야 했다.
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");
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