본문 바로가기

카테고리 없음

multer를 이용하여 client에서 텍스트 + 여러개 이미지를 한번에 서버로 업로드 + 업로드된 파일을 클라이언트로 보내주는 방법

[기본구성]

- 클라이언트

--> title / context / price는 일반적인 기본타입(primitive type)데이터를 전달(string, number)

--> image는 input[type=file]을 이용하여 여러개의 파일을 업로드한다(multiple속성 추가시 여러개 파일 업로드 가능)

--> 제출 버튼 클릭시 클라이언트에서 기본타입 데이터(title,context,price) + image 파일 전송

--> 작성한 게시글 정보 얻기 버튼 : 정상적으로 요청이 가고 응답이 오는지 테스트용 버튼


- 서버

--> post_images : 클라이언트에서 업로드한 파일이 내가 지정한 파일명으로 저장되는 폴더

--> index.js : 클라이언트에서 업로드된 파일을 처리하고 업로드된 파일을 다시 클라이언트로 전송하는 것을 처리하는 controller역활 + route역활

[코드]

const express = require('express');

const cors = require('cors');

const multer = require('multer')

const path = require('path');

 

const app = express();

 

let posts = {postid : 1, image : []};

 

app.use(express.json());

app.use(express.urlencoded({ extended: false }));

app.use(

cors({

origin: ['http://localhost:3000'],

credentials: true,

methods: ['GET', 'POST', 'OPTIONS', 'PUT', 'PATCH', 'DELETE']

})

);

 

// 이미지 파일이 저장될 경로 및 파일 이름 설정

const storage = multer.diskStorage({

destination: function (req, file, cb) {

// 저장 경로

cb(null, './post_images');

},

filename: function (req, file, cb) {

// 저장 파일 이름(milesecond초 + 파일명.확장자)

cb(null, Date.now() + '-' + file.originalname);

}

})

 

const upload = multer({ storage: storage }).array('images'); // FormData객체로 들어오는 값의 키값을 지정한다(정확히 키값을 설정해야 정상적으로 가져올 수 있다.)

 

// 여기서 single()메서드의 파라미터 값은 FormData에서 지정한 key값을 지정해야 한다.

// 임의로 작성할 경우 MulterError: Unexpected field 오류 발생(반드시 key값 일치할 것)

app.post('/up', (req, res) => {

 

upload(req, res, function (err) {

if (err) {

console.log(err);

return res.status(500).json({message : 'server image upload error'});

}

// 정상적으로 완료됨

console.log('send');

console.log(req.files) // req.files : 이미지 접근, 배열형태로 이미지파일이 전달(index로 접근) / ex) [{이미지 파일1}, {이미지 파일2}, ...]

console.log(req.body) // req.body : 텍스트 부분 접근

 

// 1. posts테이블에 새 게시글을 추가한다. (userid, username, title, context, price) -> req.body객체에서 접근해서 저장

console.log('userid : ',req.body.userid);

posts['userid'] = req.body.userid;

console.log('username : ', req.body.username);

posts['username'] = req.body.username;

console.log('title : ', req.body.title);

posts['title'] = req.body.title;

console.log('context : ', req.body.context);

posts['context'] = req.body.context;

console.log('price : ', req.body.price);

posts['price'] = req.body.price;

// 1-1. 새로 삽입된 post의 postid를 가져온다

// 2. images테이블에 이미지들을 추가한다 (postid, image) -> req.files배열 내 모든 요소 수만큼 images테이블에 추가

// image필드에 저장할 값은 req.files[n].path 값을 저장('경로+변경된파일명' 으로 저장되어 있음)

req.files.map((fileImage) => {

// console.log(fileImage.path);

posts.image.push(fileImage.path);

});

// console.log(req.files);

})

 

});

 

app.get('/getpost', (req, res) => {

res.status(200).json({message : 'hello'});

})

 

app.get('/post_images/:postImage', (req, res) => {

// res.sendFile()메서드를 사용하게 되면 경로및 파일명 지정시 서버에 업로드된 파일을 클라이언트로 내려줄 수 있다.

// 업로드된 이미지 파일을 클라이언드에 응답으로 전달

// path는 모듈(required 필요), join()메소드 파라미터 : 고정변수명, 업로드된 파일이 저장된 폴더경로, 업로된 파일명

console.log(req.params.postImage) // 업로드된 이미지중 불러올 이미지를 지정

res.sendFile(path.join(__dirname , 'post_images', req.params.postImage)); // 클라이언트로 지정된 이미지를 응답으로 전달

});

 

app.get('/', (req, res) => {

res.status(200).json({message : 'hello'});

})

 

app.listen(3001, () => {

console.log('server listen on 3001');

});

 

module.exports = app;


1. client에서 텍스트와 이미지를 한번에 전송하기 위해서는 FormData객체 내부에 모든값을 append()메소드를 통해 넣어서 서버에 전달해야 서버에서 multer모듈로 전송된 데이터를 접근할 수 있다.

 

image : <input type="file" name="uploadFile[]" onChange={getloadimage} multiple/>

--> 파일 업로드를 할경우 getloadimage함수가 이벤트 핸들러로 실행된다 / multiple속성은 여러개의 파일이 업로드 되도록 설정하는 속성

 

const [image, setImage] = useState('');

--> 이미지들을 배열형태로 저장할 state 변수

 

const getloadimage = (e) => {

 let length = e.target.files.length;

 let arr = []

 for(let i = 0; i < length; i++) arr.push(e.target.files[i]);

  setImage(arr);

 }

--> input[type='file']에 파일을 업로드 할경우 onChange 이벤트 핸들러가 실행될 때 e.target.files를 이용하여 업로드된 파일들을

접근할 수 있으며 이 파일들을 이용하여 서버로 업로드할 파일을 보낸다(객체)

--> length도 데이터에 포함되어 있으므로 약간의 가공을 통해 실제 파일데이터만 배열로 저장후 state변수에 저장

 

e.target.value

--> input[type='text']에 넘겨진 값은 onChange 이벤트 핸들러로 e.target.value로 입력된 값을 가져와서 서버에 전송한다.

 

const submitImage = () => {

console.log(post);

console.log(image);

const formData = new FormData();  // 클라이언트에서 업로드된 파일(이미지 등)을 서버에 전송하기 위해서 사용하는 객체(FormData)

// formData.append('files',image); --> 만역 1개의 파일만 전송하려는 경우에는 1개만 FormData객체에 append()메소드를 이용하여 추가하면 된다.

 

// 업로드된 파일을 서버에 전달시 FormData객체에 append()메소드를 통해 전달할 파일을 추가후 FormData를 전달하여 결과적으로 업로드된 파일을 전송하는데 이때 append()메소드에 사용된 key값을 이용하여 서버에서 multer모듈을 이용하여 접근한다.

image.map((el) => formData.append('images',el)); // 여기서 지정한 string값으로 서버에서 접근, 여러개의 이미지 모두 append

/* 각 이미지 파일을 일일히 formData의 한개의 변수에 넣으면 배열형태로 전달이 된다 / 배열자체를 전달하지 않는다.(배열자체 전달시 제대로 파일이 전달되지 않음)

 

--> 일일히 넣어야 한다. / 위에서는 map을 이용하여 반복수행중(사실상 for문과 같은 역활) / 실제로는 업로드된 파일을 일일히 append를 통해 넣어야 한다. / 일일이 넣을때 반드시 key값은 일치해야 한다

formData.append('files',image[0])

formData.append('files',image[1])

formData.append('files',image[2])

...

*/

 

// 텍스트 field부분  --> 일반적인 primitive type 데이터도 FormData객체에 append() 메소드를 이용하여 전송해야 서버에서 값을 받아 처리가 가능하다

// append()메소드는 key,value를 파라미터로 지정하여 전송할 데이터를 FormData에 추가한다. / 여기서 사용되는 key값으로 서버에서 해당 값에 접근할 수 있으며 primitive type데이터는 서버에서 request.body로 접근 가능

// 여기서 formData는 FormData객체

formData.append('userid', post.userid);

formData.append('username', post.username);

formData.append('title', post.title);

formData.append('context', post.context);

formData.append('price', post.price);

 

// POST http://localhost:3001/up (endpoint)

axios({

method: 'post',

url: 'http://localhost:3001/up',

data: formData // 서버에 파일 + primitive type데이터를 모두 FormData객체에 담아서 한번에 전달한다.

})

.then((res) => {

console.log(res);

})

}

 

--> <button onClick={submitImage}>제출</button> 이 버튼이 클릭되면 실행되는 이벤트 핸들러 함수

--> 우선 업로드된 파일을 서버에 전송하기 위해서는 FormData객체를 이용하여 파일을 전송해야 한다.

--> 파일과 같이 primitive type데이터를 같이 전송하기 위해서는 FormData객체에 해당 primitive type데이터(string, number등)를 추가 후 전송  

 

2. 서버에서는 multer설정을 통해 클라이언트에서 FormData로 전달된 데이터를 접근하고 지정된 폴더에 지정된 파일명으로 클라이언트에서 전달된 파일을 서버내에서 저장한다.

 

const multer = require('multer')

const path = require('path'); 

--> 서버에서 FormData를 처리하기 위해 필요한 모듈

 

// 이미지 파일이 저장될 경로 및 파일 이름 설정

const storage = multer.diskStorage({

destination: function (req, file, cb) {

// 저장 경로

cb(null, './post_images');

},

filename: function (req, file, cb) {

// 저장 파일 이름(milesecond초 + 파일명.확장자)

cb(null, Date.now() + '-' + file.originalname);

}

})

 

const upload = multer({ storage: storage }).array('images'); // FormData객체로 들어오는 값의 키값을 지정한다(정확히 키값을 설정해야 정상적으로 가져올 수 있다.)

 

--> 클라이언트에서 전달한 파일을 저장하는 폴더 경로 지정 및 어떤 파일명으로 저장할지 지정 / 전달된 FormData객체에서 지정된 키값을 통해 전달된 파일을 접근한다. / 다수일 경우 array()메소드 사용, 1개일 경우 single()메소드 사용 

--> array()메소드와 single()메소드의 파라미터로 사용되는 string값은 클라이언트에서 전송되는 FormData객체에서 append()로 추가한 파일의 키값을 사용한다(정확히 키값을 지정해야 클라이언트에서 보낸 파일에 접근가능, 키값이 일치하지 않으면 파일 못 받음)

 

// 서버내에서 자체적으로 사용하는 객체(값 저장용)

let posts = {postid : 1, image : []}; 

 

// POST /up (api 처리, 클라이언트에서 업로드한 파일을 서버로 전송한 경우 서버에서 전송된 파일을 서버에서 지정된 경로로 지정된

파일명으로 저장)

app.post('/up', (req, res) => {

// upload()메소드를 이용하여 클라이언트에서 전달된 FormData객체에서 위에서 array(), single()메소드로 지정한 key값을 통해 전달된 FormData객체내 저장된 파일을 접근하여 지정된 경로에 지정된 파일명으로 서버에서 저장한다.

// upload()메소드를 통해서 클라이언트에서 전달한 파일을 지정된 경로에 지정된 파일명으로 저장한다. 

upload(req, res, function (err) {

if (err) {

console.log(err);

return res.status(500).json({message : 'server image upload error'});

}

 

// 정상적으로 완료됨

// req.body : 기본타입의 데이터 접근시 사용(stirng, number등), 1개의 객체(키값은 FormData객체로 append()를 이용해 저장시 사용한 key값)

// req.files : 파일 데이터 접근시 사용(2개이상, 1개는 req.file로 접근/single()메소드 사용), 배열(각 요소는 업로드된 파일 정보)

console.log('send');

console.log(req.files) // req.files : 이미지 접근, 배열형태로 이미지파일이 전달(index로 접근) / ex) [{이미지 파일1}, {이미지 파일2}, ...]

// req.files를 통해서 FormData객체로 전달된 파일에 대한 정보에 접근할 수 있다(이미 서버내 업로드 되있는 상태)

[req.files 예시]

[

  {

    fieldname: 'images',

    originalname: '2.png',

    encoding: '7bit',

    mimetype: 'image/png',

    destination: './post_images',

    filename: '1627030427466-2.png',

    path: 'post_images/1627030427466-2.png',

    size: 78286

  },

  {

    fieldname: 'images',

    originalname: '533.jpeg',

    encoding: '7bit',

    mimetype: 'image/jpeg',

    destination: './post_images',

    filename: '1627030427469-533.jpeg',

    path: 'post_images/1627030427469-533.jpeg',

    size: 54752

  },

  {

    파일정보1

  },

  {

    파일정보2

  },

  {

    파일정보3

  }

]

console.log(req.body) // req.body : 텍스트 부분 접근

// req.body를 통해서 primitive type의 데이터에 접근할 수 있다.

[req.body 예시]

{

  userid: '1', // userid라는 키값으로 접근가능 , FormData객체내 append()로 데이터 저장시 userid키값을 사용하여 저장한것

  username: 'test',

  title: '제목',

  context: '내용',

  price: '10000'

}

 

res.status(200).json({message : "upload ok"});

});

 

3. 클라이언트에서 업로드된 파일을 서버에 요청하여 이미지를 출력하고 싶은 경우 <img> 태그의 src속성에 서버 api를 넣으며 

서버에서는 해당 api에서 클라이언트가 요청한 데이터를 path모듈과 sendFile()메소드를 이용하여 서버내 저장되어 있는 업로드된

이미지를 응답으로 전달해 준다.

클라이언트에서는 서버에서 이미지를 받으면 정상적으로 서버내 저장된 업로드 이미지가 출력된다.

 

[클라이언트]

<img src='http://localhost:3001/post_images/1627018346562-2.png' style={{width : '50px', height : '50px'}}/>

--> img태그에서 src속성에 서버내 저장된 이미지를 가져오는 api를 속성값으로 지정한다.

 

[서버]

app.get('/post_images/:postImage', (req, res) => {

// res.sendFile()메서드를 사용하게 되면 경로및 파일명 지정시 서버에 업로드된 파일을 클라이언트로 내려줄 수 있다.

// 업로드된 이미지 파일을 클라이언드에 응답으로 전달

// path는 모듈(required 필요), join()메소드 파라미터 : 고정변수명, 업로드된 파일이 저장된 폴더경로, 업로된 파일명

console.log(req.params.postImage) // 업로드된 이미지중 불러올 이미지를 지정

res.sendFile(path.join(__dirname , 'post_images', req.params.postImage)); // 클라이언트로 지정된 이미지를 응답으로 전달

});

 

--> 클라이언트에서 지정된 이미지를 path를 통해 지정한 경로를 찾아가서 지정한 이미지를 가져온후 sendFile()메소드를 이용하여 응답으로 업로드된 파일을 전송해준다.

 

[클라이언트]

<img src='http://localhost:3001/post_images/1627018346562-2.png' style={{width : '50px', height : '50px'}}/>

--> 서버로 부터 이미지를 전송받게 되므로 업로드된 이미지가 출력된다.

--> 출력 예시