웹 개발

[포스코X코딩온] 파일업로드 Multer

끊임없이 성장중인 개발자 2023. 11. 28. 21:37
728x90
반응형

포스팅 주제

  • 파일 업로드 multer

 

파일 업로드 Multer

 

body-parser는 Post로 정보를 전송할 때 요청의 body(req.body)로 받을 수 있게 도와준다.

단! 멀티파트 데이터를 처리하지 못한다

  • 멀티파트 데이터 : 이미지, 동영상, 파일 등

 

이 때 우리는 multer를 이용해서 이미지,동영상 파일 등을 전송할 수 있다.

 

파일 업로드 - 프론트

 

클라이언트에서 서버로 파일 전송하는 법

input 태그, type = 'file'로 지정

**name 속성은 서버에서 파일을 인식할 이름이 된다! ( 서버와 동일한 name속성을 가져야 한다!! )

 

form 태그의 enctype 속성 'multipart/form-data'를 반드시 설정

  • multer는 multipart( multipart/form-data)가 아닌 폼에서 동작하지 않는다.

 

multer 미들웨어 - 서버

  • 파일을 업로드를 위해 사용되는 미들웨어
  • express로 서버를 구축할 때 가장 많이 사용되는 미들웨어

 

multer를 설치해줘야 한다.

npm install multer

 

express 처럼 지정해줘야 한다.

const multer = require('multer');

 

파일 업로드 경로를 설정해줘야 한다.

const upload = multer({
	dest: 'uploads/'
})

 

**dest : 파일을 업로드하고 그 파일이 저장될 경로를 지정하는 속성

dest는 고정값이다!

 

실습에 앞서 공용 app.js 설정

const express = require('express');
const app = express();
const PORT = 8000;

const multer = require('multer');
const upload = multer({
    dest : 'uploads/' // dest: 클라이언트가 업로드한 파일을 저장할 서버측 경로
});

app.set('view engine', 'ejs');
app.set('views', './views');
app.use(express.urlencoded({extended : true}));
app.use(express.json());

app.get('/', (req, res)=>{
    res.render('index');
})

app.listen(PORT, () => {
    console.log(`${PORT} port is opening!`);
})

 

 

<하나의 파일 업로드>

 

Single File Upload

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>File Upload</title>

</head>
<body>
    <h1>파일 업로드</h1>

    <h2>Single File Upload</h2>

    <p>하나의 input에 하나의 File을 업로드</p>

    <!-- multer는 multipart가 아닌 폼에서는 동작X 그래서 enctype="multipart/form-data" 속성 필수-->
    <form action="/upload" method="post" enctype="multipart/form-data">
        <input type="file" name="userfile"><br>
        <input type="text" name="title"><br><br>
        <button>업로드</button>
    </form>
    </body>
</html>

 

파일을 업로드 하는 가장 기본적인 폼이다.

 

<form action="/upload" method="post" enctype="multipart/form-data">

enctype 부분을 적어줘야 multer각 적용되기 때문에 필수적으로 작성해야 한다.

 

하지만 이것 뿐만 아니라 app.js에서 multer에 세부 정보 추가해줘야 한다.

const path = require('path') // 경로에 관한 내장 모듈

세부 설정
const uploadDetail = multer({
    // storage : 저장할 공간에 대한 정보
    storage: multer.diskStorage({
        // destination : 경로 설정
        destination(req, file, done){
            // done : callback 함수
            // done(null, xx) : null => 에러가 없다는 의미
            done(null, 'uploads/'); // 파일을 업로드할 경로를 설정,
        },
        filename(req, file, done){
            // 파일의 확장자를 추출 => 'path' 모듈을 활용
            const ext = path.extname(file.originalname); // 확장자를 추출
            console.log('ext >', ext);
            console.log('  path.basename(file.originalname, ext) >' ,  path.basename(file.originalname, ext));


            // path.basename(file.originalname, ext)) => 확장자를 제외한 파일 이름, ext를 넣으면 뒤에 확장자를 제거해서 나온다. ex) apple.png => apple
            // 중복된 값을 없애려고 Data.now()를 붙여준거다.
            done(null, path.basename(file.originalname, ext) + Date.now() + ext);
        }
    }),
    // limits : 파일 제한 정보 (파일 크기 제한)
    // limits: {
    //     fileSize: 5 * 1024 * 1024
    // }
});

app.use('/uploads', express.static(__dirname + '/uploads'))

 

위 코드를 부분별로 살펴 보자

 

storage: multer.diskStorage({
        // destination : 경로 설정
        destination(req, file, done){
            // done : callback 함수
            // done(null, xx) : null => 에러가 없다는 의미
            done(null, 'uploads/'); // 파일을 업로드할 경로를 설정,
        },

 

저장 할 공간에 대한 정보를 정의하는 부분이다.

destination 경로 설정을 통해 uploads 폴더 안에 파일을 업로드 하겠다는 의미이다.

 

filename(req, file, done){
            // 파일의 확장자를 추출 => 'path' 모듈을 활용
            const ext = path.extname(file.originalname); // 확장자를 추출
            console.log('ext >', ext);
            console.log('  path.basename(file.originalname, ext) >' ,  path.basename(file.originalname, ext));


            // path.basename(file.originalname, ext)) => 확장자를 제외한 파일 이름, ext를 넣으면 뒤에 확장자를 제거해서 나온다. ex) apple.png => apple
            // 중복된 값을 없애려고 Data.now()를 붙여준거다.
            done(null, path.basename(file.originalname, ext) + Date.now() + ext);
        }

 

ext는 파일의 확장자를 의미한다,  ex) png, jpng 등..

 

path.extname(file.originalname)으로  파일의 확장자를 추출 하여 ext 변수에 넣는다.

path.basename(file.originalname)은 기존 파일 이름에 확장자 까지 전부 포함한 이름이다! => like 이름.png

하지만 뒤에 path.basename(file.originalname, ext)를 붙이면 이름에서 확장자를 때 겠다는 의미이다.

 

이렇게 만드는 이유는 뒤에 + Date.now() 즉 현재 시간을 붙여 같은 파일이 들어왔을 때 중복을 피하기 위함이다.

즉, 이름 + 날짜 + 확장자 형태로 만들기 위함이다.

 

app.use('/uploads', express.static(__dirname + '/uploads'))

 

static 등록 => 미들웨어를 사용하겠다는 의미이고 프론트에서 이미지 경로를 해당 경로로 접근하겠다는 의미이다.

 

app.post('/upload' ,uploadDetail.single('userfile'),(req,res) =>{
    console.log(req.file);
    console.log(req.body);
    res.send('파일 업로드 완료!');

    // req.file
    /**
     * fieldname: 'userfile', // 폼에 정의한 name 값
    originalname: 'PPT2.jpeg', // 원본 파일 명
    encoding: '7bit', // 파일 인코딩 타입
    mimetype: 'image/jpeg', // 파일 타입
    destination: 'uploads/', // 파일 저장 경로
    filename: '2b0db9962ebf531e605e6085baff2b92', // 저장된 파일명
    path: 'uploads/2b0db9962ebf531e605e6085baff2b92', // 업로드 된 파일 전체 경로
    size: 5442988 // 파일 크기
     */
})

 

이제 app.js에 싱글 업로드에 접근을 설정해 주면 된다.

uploadDetaile.single('이름') => 이름은 클라이언트에서 지정한 이름과 같은 이름을 사용해야 한다.

 

 

 

 

파일을 등록하면 오른쪽 사진과 같이 지정된 경로에 파일이 생긴 것을 볼 수 있다.

 

또한 로컬 폴더에도 uploads폴더가 생성되고 안에 위 사진처럼 파일이 생긴 것을 확인할 수 있다.

 

 

<Multi File Upload ver.1>

 

<h2>Multi File Upload ver.1</h2>

    <p>하나의 input에 여러개의 File을 업로드</p>

    <!-- 하나의 input에 여러개 업로드 할 경우 multiple 속성추가 -->
    <form action="/upload/array" method="post" enctype="multipart/form-data">
        <input type="file" name="userfiles" multiple><br>
        <input type="text" name="title"><br><br>
        <button>업로드</button>
    </form>

 

여러개의 파일을 업로드 하기 위해서는 input태그안에 multiple을 작성해야 한다.

 

// 2. array() : 하나의 인풋에 여러 파일 업로드
app.post('/upload/array', uploadDetail.array('userfiles'), (req, res) => {
    // [{file1} 정보}, {file2 정보}, ....] : 배열 형태
    console.log(req.files);
    console.log(req.body);
    res.send('하나의 인풋에 여러개 파일 업로드');
})

 

또한 uploadDetail.single이 아닌 uploadDetail.array로 넣는다.

이렇게 하면 결과가 배열 형태로 나올 것이다.

 

 

 

<Multi File Upload ver.2>

 

<h2>Multi File Upload ver.2</h2>

    <p>여러 개의 인풋에 각각의 파일을 업로드</p>

    <!-- 하나의 input에 여러개 업로드 할 경우 multiple 속성추가 -->
    <form action="/upload/fields" method="post" enctype="multipart/form-data">
        <input type="file" name="userfile1"><br>
        <input type="text" name="title"><br><br>
        <input type="file" name="userfile2"><br>
        <input type="text" name="title"><br><br>
        <button>업로드</button>
    </form>

 

이번에는 두개의 인풋을 먼들고 여러개의 파일을 입력하는 방법이다.

 

// 3. fields() : 여러 파일을 각각의 인풋에 업로드
app.post('/upload/fields', uploadDetail.fields([{name:'userfile1'},{name:'userfile2'}]), (req, res) =>{
    /**
     * {
     *  userfile1: [
     *          { 파일 정보 }
     *      ],
     * userfile2: [
     *          { 파일 정보 }
     *      ]
     * }
     */
    console.log(req.files);
    console.log(req.body);
    res.send('여러개의 인풋 여러개 파일 업로드');
})

 

fields 속성을 주면 배열 배열처럼 입력 가능하다.

 

 

동적 파일 업로드

 

<h2> 동적 파일 업로드 </h2>
    <input type="text" name="title" id="title" placeholder="Title">
    <input type="file" name="dynamicFile" id="dynamicFile"> <br>
    <button type="button" onclick="fileUpload()">업로드</button>
    <br>
    <!-- 업로드한 이미지를 보여줄 img 태그 -->
    <img src="" alt="">

    <script>
        function fileUpload(){
            // js만으로 폼 전송
            // file을 같이 전송 -> FormData 객체를 활용하기!
            // FormData란?
            // form 태그의 데이터를 동적으로 제어할 수 있는 기능, 보통 axios, ajax 등등 과 함께 사용

            const formData = new FormData();
            const file = document.querySelector('#dynamicFile');
            const title = document.querySelector('#title');

            console.log(file); // 선택한 파일 요소
            console.log(file.files); // 업로드한 파일 객체
            console.log(file.files[0]); // 업로드한 첫 파일

            // append(key, value)
            formData.append('dynamicFile', file.files[0]);
            formData.append('title', title.value);

            axios({
                method: 'post',
                url: '/dynamic',
                data: formData,
                headers: {
                    'Content-Type': 'multipart/form-data',
                    // enctype 지정과 동일 
                }
            }).then((res) => {
                console.log(res.data);
                const {
                    file,
                    title
                } = res.data;

                console.log('file >', file);
                // 이미지가 저장된 경로인 file.path 이용
                const imgElem = document.querySelector('img');
                imgElem.src = '/' + file.path;
                imgElem.alt = title;
                imgElem.classList.add('profile');
            })
        }

    </script>

 

img 태그에 직접 보여주기 위해 button을 type을 button으로 만들었다.

 

FormData()를 이용하면 form 태그의 데이터를 동적으로 제어할 수 있다.

file.files를 보면 console로 확인해 보면 0번째 인덱스에 파일이 들어 있는 것을 확인할 수 있는데,

이를 formData에 append하면 axios에서 formData값에 접근하여 서버에 전송가능하다.

 

기존에 다른 방식들은 form에 enctype을 추가 했지만 동적 파일 업로드에서는 header안에 enctype을 정의해준다.

 

이후 소스 화면에 사진을 보여주기 위해 값을 path경로와 title을 넘겨주고, class를 추가해서 이미지를 스타일을 꾸며 주었다.

 

<style>
        .profile{
            width: 200px;
            height: 200px;
            border-radius: 50%;
            box-shadow: 0 0 20px #0002;
        }
    </style>

 

app.js

// 동적 폼 전송
app.post('/dynamic', uploadDetail.single('dynamicFile'), (req, res) =>{
    console.log(req.file);
    console.log(req.body);
    res.send({file: req.file, title: req.body.title});
})

 

 

 


 

Multer 총 정리!!!

코딩온 교육 자료

 

 

파일 업로드 순서

  • 프론트
    • input 태그, type='file'로 만들기
    • 일반 form 전송 or 동적 파일 업로드 선택하기
    • multer 세부 설정(경로, 파일 이름 등)
    • req.file 객체 -> 업로드 된 파일
    • req.body -> 파일 외에 데이터 값들

 

반응형