- 어드민 페이지 기능 중 하나인 '상품 등록'을 구현하는 과정에서 마주친 문제인 'input을 통해 서버에 이미지 저장하고 화면에 띄우기'를 어떻게 해결했는지 정리하여 작성했다.
기존에 이미지를 저장하고 띄우는 방식은 인터넷에 떠도는 이미지의 주소를 복사해서 db에 링크를 바로 넣었고, 이미지를 띄울 때도 db에 넣은 링크를 가져와서 img src에 적용하는 방식이였다.
이런 방식은 서버 통신을 활용한 방식이 아닐 뿐더러 사용하고 있는 주소의 이미지에 수정이나 삭제가 발생할 경우 알아차리기 힘들고 대처도 어렵다.
문제를 해결하기 위해선 서버 통신을 이용해 이미지 파일을 서버에 저장하여 불러올 때는 저장된 경로에서 가져오는 방식을 사용해야한다.
input태그의 file type을 사용하면 클릭했을 때 유저의 로컬 파일을 업로드할 수 있는 기능이 마련된다.
file을 서버에 전송하기 위해서는 formData형식으로 가공해서 보내야 한다.
input의 multiple attribute를 추가하면 여러개의 파일을 업로드 할 수 있다.
formData가 서버에 전송되는 경우, Content-Type은 multipart/form-data로 알아서 지정되기 때문에, 서버 요청을 보낼 때 따로 header에 type을 설정할 필요가 없다.(지정할 경우 오히려 에러가 발생한다.)
formData에 append 메서드를 사용하며 폼 필드를 추가할 수 있다. 응요하면 업로드한 이미지를 formData에 추가하기 위해서는 새로운 필드를 추가해야만 한다.
- submit 버튼이 눌리면 formData 인스턴스 생성
- append를 사용하여 필드를 추가
- 백엔드 api를 이용하여 formData를 그대로 전송
admin.js
<input style='display:none' class='input' type='file' id='fileUpload' value='파일 선택'/>
let imgData = new FormData();
imgData.append('image', $fileUpload.files[0]);
let uploadedImage = await Api.formPost('/api/product/upload', imgData);
------
백엔드 api
async function formPost(endpoint, data) {
const apiUrl = endpoint;
const res = await fetch(apiUrl, {
method: 'POST',
headers: { // 별다른 header 설정은 필요없다.
Authorization: `Bearer ${sessionStorage.getItem('token')}`,
},
body: data, // body에 formData를 가공없이 그대로 넣는다.
});
if (!res.ok) {
const errorContent = await res.json();
const {reason} = errorContent;
throw new Error(reason);
}
const result = await res.json();
return result; // 파일명을 반환한다.
}
express에서는 express.static이라는 내장 모듈을 통해 정적 파일을 제공한다.
프론트에서 이미지 파일 경로를 지정할 때도 위의 static 경로를 활용하여 불러오게 되며 아래와 같이 코드를 구성했다.
상품 목록 페이지
const domain = window.location.host; // 도메인 가져오기
for (let e of data) {
const {img} = e;
$itemListFlexbox.insertAdjacentHTML(
'beforeend',
`<img class='img' src='${'http://' + domain + '/static/' + img}'/>`
);
}
app.js
...
app.use('/static', express.static(__dirname + '/public'));
이번 프로젝트는 파일 서버를 로컬로 지정했기 때문에, static 경로를 보면 로컬의 public이라는 폴더에서 이미지를 가져오게 된다.
프론트에서 요청할 때는 http 경로를 사용해야 하며, domain 주소를 추출하기 위해 window객체를 사용했다.
img는 파일명의 문자열 데이터이며, 경로의 마지막에 붙여주면 public에서 일치하는 파일명을 가져오게 된다.