프로젝트 중 여러 장의 사진을 한번에 받아 나의 데이터베이스에 저장했다가, 한번의 요청에 여러 장의 사진을 담아 보내주는 작업을 마주한 일에 대한 회고
망고플레이트 클론코딩에는 사용자가 리뷰를 작성할 때 여러 장의 사진을 올리는 기능이 있다. 이 기능을 구현하기 위해 앞서 공부했던 multer를 활용하기로 했다.
지난번 사용했던 기능은 multer의 single이라는 기능이었다. 하지만 이번에는 여러 장의 사진을 주고 받아야하기 때문에 multer의 array 메소드를 사용하기로 했다.
single과의 차이점으로는 프론트가 주는 req 객체 중 file이 아닌 files 키로 받기 때문에 그에 맞게 변수를 선언해야 했다.
그리고 기존 single 메소드로 받는 req.file은 객체 형식이지만 array 메소드로 받는 req.files는 배열이며 각각의 인덱스 값으로 객체 형식의 사진 정보를 받았다.
우선 나는, array 메소드는 single 메소드의 복수형이라고 이해했다.
const router = require("express").Router();
const { reviewController } = require("../controllers");
const { upload } = require("../middlewares");
router
.route("/new/:storeId")
.post(upload.array("reviewImg", 10), reviewController.postNewReview);
이렇게 하나의 multer 기능을 가진 함수를 미들웨어로 활용했다.
우선 여러장의 사진을 보낸 files를 console.log에 찍어보면 다음과 같은 값이 찍힌다.
console.log(files)
///////
[
{
fieldname: 'reviewImg',
originalname: '스크린샷 2022-09-26 오후 5.06.08.png.png',
encoding: '7bit',
mimetype: 'image/png',
destination: './uploads',
filename: '스크린샷 2022-09-26 오후 5.06.081664640179942.png',
path: 'uploads/스크린샷 2022-09-26 오후 5.06.081664640179942.png',
size: 359192
},
{
fieldname: 'reviewImg',
originalname: '스크린샷 2022-09-25 오후 4.56.28.png',
encoding: '7bit',
mimetype: 'image/png',
destination: './uploads',
filename: '스크린샷 2022-09-25 오후 4.56.281664640179947.png',
path: 'uploads/스크린샷 2022-09-25 오후 4.56.281664640179947.png',
size: 1258835
}
]
여기서 나는 path 키의 값을 변수에 담고 그 변수의 값을 배열로 구성하는 로직을 생각했다. 왜냐하면 이 기능의 핵심 요소는 한번에 사진을 주고 받는 것이기 때문이다. 그래서 여러 개별된 경로를 배열로 한꺼번에 들고 다니다가 데이터베이스에 insert 할 때만 분리하면 된다고 판단했기 때문이다.
그래서 files에서 각각의 인덱스 중 path 키값만 갖고 있는 배열을 만들기로 했다.
let imgArray = new Array();
files.map(file => imgArray.push(file.path));
우선 imgArray라는 빈 배열을 만들고 거기에 files 각 인덱스들의 path 값을 넣어주었다.
이제 가장 중요한, 어떻게 여러 사진을 한번에 db 테이블에 넣을 수 있을 지 고민한 흔적이다.
이때 관건은 크게 세 가지이다.
우선 테이블을 보자면, 사용자, 가게 테이블을 참조하는 컬럼과 댓글 내용을 담은 리뷰 테이블과 그 리뷰 테이블을 참조하는 리뷰 이미지 테이블을 구성했다.
그렇기 때문에 먼저 리뷰 테이블에 데이터가 담기고, 그 행을 참조하는 데이터가 리뷰이미지 테이블에 넣어지는 순서가 지켜져야 내가 원하는 로직이 구현된다고 생각했다.
두 번째는 files 변수값 자체가 undefined인, 즉 사진이 아예 담겨있지 않은 요청을 받았을 때를 대비해 다른 리턴값을 구현해야 했다.
마지막으로 댓글은 입력됐지만 어떤 문제로 인해 사진이 테이블에 담기지 않아 전체 리뷰가 등록되지 않는 경우, 에러 메세지를 주는 로직을 구현해야 했다.
이를 위해 Service 단에서 사진을 제외한 정보를 Dao에 보내 리뷰 테이블에 정보를 넣고 리턴받으면, 그 정보를 갖고 다시 Dao에 이미지 테이블 삽입을 위한 변수를 보내주는 것이 최선이라고 판단했다.
let rowImage = new Array();
imgArray.map((img) => rowImage.push([newReviewInsertId, img]));
return await reviewDao.createReviewImage(rowImage);
여기서 newReviewInsertId 변수는, 리뷰 테이블에 필요한 변수를 Dao에 보낸 뒤 받은 리뷰 테이블에 삽입된 행의 id 값이다.
이 변수를 활용해 여러 장의 사진 정보가 이미지 테이블에 삽입되더라도 같은 행을 참조하도록 하는 로직을 구현할 수 있었다!
하지만 내가 간과한 부분이 있었다.
바로, 사용자가 댓글을 작성할 때 사진이 오지 않을 경우도 있지만, 내가 구현한 로직은 모두 사진이 여러 장 오는 것만 대비한 로직이 되었다는 것이다.
그래서 사진없이 프론트의 요청이 올 경우 다음과 같은 문법 에러 메세지가 뜬다.
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 4
하지만 이 문제는 생각보다 간단하게 해결했다.
바로 위 첫 번째 관건에 해결한 로직을 전부 조건문 블럭에 감싸 버리면 해결되는 것이다. files 변수 자체가 undefined라면 imgArray의 모든 인덱스가 undefined이기 때문에, 해당 로직을 if문으로 활용해 해결했다.
가장 어려운 부분이었다. 사실 처음부터 우려를 갖던 부분이었지만 엄두가 나지 않던 부분이었다.
만약 req.body에 담긴 모든 텍스트 인풋이 제대로 나의 db에 담겼지만 어떤 에러로 인해 이미지는 내 테이블에 입력되지 못했다면 어떻게 될까?
그러면 사용자는 에러 메세지를 받았지만 사진을 제외한 모든 리뷰 작성 정보는 제대로 db에 담겨 브라우저에 보여질 것이다.
이건 향후 문제를 일으킬 것 같았다.
여기서 새로운 개념을 하나 접했는데, 바로 Transaction이다. 비록 시간이 많지 않아 Transaction까지 사용하진 못했지만, 추후에 사용해보도록 하자
기존에 공부했던 multer의 array 메소드를 새로 사용해 나름의 성공을 거둔 작업이었다. 하지만 세 번째 관건을 만족스럽게 해결하지 못해 아쉬움이 남는다. 쉬는 기간에 꼭 경험하도록 하자!