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