익명의 개발노트

[Node.js] Vue(Vuerify) + express image upload 삽입, 삭제기능 본문

코딩일기/TIL

[Node.js] Vue(Vuerify) + express image upload 삽입, 삭제기능

캡틴.JS 2019. 11. 3. 23:45
반응형

프론트단 Vue 프레임워크인 Vuetify, 백엔드는 노드 js를 사용하여 파일 업로드 구현하기.

1.  프론트사용 라이브러리 : Vue-filepond

2. 백엔드 라이브러리 : multer

결론은 단순히 Vue-filepond와 multer 라이브러리 간의 호환이 안된다....(우회하는 방법은 있는 듯 하나..)

통상 Node.js에서 사용하는 이미지저장 라이브러리는 multer를 주로 사용한다. 사용하기가 편하다.

단순히 폴더 저장, 메모리저장, 디스크 저장 기능이 있어서 사용자 입맛에 맞게 사용가능하며,

AWS S3를 위해서도 자주 사용하는 라이브러리이다.

Vue-filepond는 드래그앤 드랍부터 커스터마이징 기능이 굉장히 많으며, 업로드하면 자동(multipart/form-data)

으로 서버로 보내주는 역할까지한다.

공식문서에는 프론트단에서 data부분에서 이미지 CRUD 기능까지 있는 걸로봐서는 굉장히 편리했다.

공식문서 : https://pqina.nl/filepond/

https://bestofjs.org/projects/filepond

다만.. 두번째 블로그를 찾기전까지는.. 삽질만 했다.

백엔드는 장고, PHP, 라라벨 만 지원하는 듯 싶었다.

multer를 사용하면 백엔드 라우터에서 req 값에서 file과 files를 사용할 수 있다.

ex) req.file 

filepond를 사용하지 않은 이유는 multer에서 diskstorage에 메모리 스토리지에 저장이 되지 않는다.

직접 fs를 사용해서 저장하는 방법밖에 없다. 버퍼식으로 넘어오기 때문에 직접 저장을 해줘야 한다.

이미지 파일이나 컴퓨터의 모든 자료는 0,1로 되어있고, 이러한 형식을 binary 형식이라고 한다.

이러한 binary 형식은 버퍼에 담겨서 보관이 되기때문에 버퍼를 이용해서 내용물을 빼서 보내거나, 받아서 저장한다.

ex) <buffer 00 01 31 43 03 .... 이런형식을 버퍼형식이라고 한다,

const buffer = imageParam.filepond.data //buffer형식 

//buffer를 img file로 저장

var buf = Buffer.from(buffer, 'base64');
const filePath = path.join('public/images/'+req.files.filepond.name)

fs.writeFile(filePath,buf, (err) => {
   if(err){
       throw err;
   }else{
       return true;
   }
})

이럴 경우 이미지, 수정, 삭제 구현시 로직이 엄청 복합해 질 것 같아서 (내가 맡고 있는 프로젝트의 요구사항을 충족하려면, 복잡해진다는 이야기이다. )

프론트 단에서 라이브러리를 빼고, FormData 방식을 이용해서 처리하기로 했다.

이미지 또는 파일을 전송하려면, 프론트에서 가장 기본적으로 많이 사용하는 방식이 formdata방식이다. 

백엔드로 보내기위한 프로토콜은 Content-Type : multipart/form-data 형식이다.

1. form 태그에 enctype="multipart/form-data" 로 직접 지정을 해주어도 되고, axios에서 헤더를 직접 지정해줘도 된다.

2. input type="file" 을 주로사용한다.

3. 일반 바닐라 자바스크립트가 아닌 SPA에서는 DOM에 접근하기 위해서는 ref를 사용하기때문에 뷰나, 리액트에서 구현하려면

 ref를 사용해주어야 한다. 

  1) 프론트 코드

<template>
  <form>
	<input type="file" id="ex_file" 
  	   ref='uploadImageFile'   //1번
     	   @change="onFileSelected" 
           accept="image/*">
   </form>
</template>



<script>
import axios from 'axios';

(...생략)
data(){
	return {
    	uploadImageFile: ''  //2번
    }
},
methods: {
            onFileSelected(event){
                this.uploadImageFile = this.$refs.uploadImageFile.files[0] //3번
            },
            
            async onSave(){
            	 const fd = new FormData();  //반드시 필요
                 fd.append('upLoadImage', this.uploadImageFile); //4번
                 
                 await axios.post('API주소',fd);
            }
 } 
</script>

위 코드에서 1번과 3번은 ref로 bind 시켜준 것이라 이름이 같아야한다. 그래야 연결된다.

참고로 FormData로 저장된 파일은 console.log로 나오지 않는다.

내부값을 확인하고 싶을때는 아래와 같이 entries()함수를 이용한다.

for (let key of fd.entries()){
	console.log(key[0] + ' '+key[1])
}

전송은 axios.post를 이용한다.  (예제들이 많으므로 찾아보면 좋다)

* 만약에 axios를 mvc구조 등으로 변경해서 사용할 경우 fd값에 따라, 헤더값을 분기해주어야 한다.(아래는 예시)

import axios from 'axios'

export default class ApiCaller {
	static async callApi (callMethod, url, config, reqParam) {
		return new Promise(function (resolve, reject) {
			if (Object.keys(config).length === 0) {
				config = {
					mode: 'no-cors',
					headers: {
						'Content-Type': 'application/json',
						'token': sessionStorage.getItem('MOONGOOSE'),
					},
				}
			}

			config['url'] = url;
			config['method'] = callMethod;

			if(Object.keys(reqParam).length !== 0){
				config['data'] = JSON.stringify(reqParam);
			}else{
				config['data'] = reqParam;  //인자값이 formdata형식인 경우 그대로 보내야한다.
			}
			
			axios(config)
			.then(response=>{
				resolve(response);
			})
			.catch(error=>{
				reject(error);
			})
		});
	}
}

 

2) 백엔드 코드(이미지 저장)

multer는 node.js라이브러리이며, post방식과 multipart/form-data 방식의 데이터만 받을 수 있다.

npm install multer
const multer = require('multer');
const imageSavePath = 'public/images/';

const storage = multer.diskStorage({
	//파일저장경로
	destination(req, file, callback){
		callback(null, imageSavePath)
	},
    //저장되는 파일이름 형식 커스텀 가능
	filename(req, file, callback){
		callback(null, file.originalname)
	}
});
const upload = multer({storage : storage});

router.post('',upload.single('upLoadImage'),async (req, res, next) => {
		console.log(req.file);// 파일정보나옴
		<..중략>

 

위 방식은 디스크메모리를 이용하여 저장하는 방식이고, upload.single('fieldName')은 formdata에서 추가로 사용한 키값(위 코드 4번)을

넣어주어야 해당 파일정보를 받아서 읽을 수 있다.

파일을 여러개 보내고 싶으면 array를 이용하기도한다. 공식문서 참고.

만약에 파일자료와 일반 값(이름 값, 가격 등등 )을 formdata에 추가했을 경우 백엔드에서 받을때, 파일 형식만 자동으로 req.file로 분류되

고 나머지는 req.body에 저장된다.

경로를 지정해주면 폴더가 자동으로 생성되는 장점이 있다.

통상 이미지 저장할때, DB에는 이름과 형식 이런것만 저장을 해주고 실제 파일은 서버 로컬에 저장하거나, AWS S3에 저장해서 이름값으로

불러온다.

그렇다면 반대로, 저장된 이미지를 삭제하고 싶다면??

delete router안에다가 아래와 같이 fs.unlink를 이용하면 비동기방식으로 삭제할 수있다. 

const fs = require('fs')

.delete('/:noticePopupSeq', async (req, res, next) => {
			(...중략)
fs.unlink('public/images/'+value.fileName, (err) => {
		if (err) {
			console.error(err)				
		}
});

인자값으로는 경로와 파일이름이 같이 있어야한다.

* 삽질을 너무 심하게 했다....

추가적으로 백엔드 파일 업로드 라이브러리 중에 express-fileupload도 있는데,  이걸 설치하면 무조건 req.files로 받을 수 있다.

vue의 filepond를 사용하려면 백엔드는 express-fileupload를 사용해야 넘어오는 값을 노드에서 인식할 수 있다.

RESTFul api기준으로 사용하려면 수정할때는 PUT 메서드를 사용해야한다.

하지만, multipart/form-data방식은 post방식으로 보내야한다. 

이유는 여기 참고 : https://blog.outsider.ne.kr/1001

반응형
Comments