본문 바로가기
포스코x코딩온

[포스코x코딩온] 풀스택 부트캠프 6주차 정리 2 - 파일업로드, MVC

by 김선지 2023. 11. 30.

파일 업로드

파일 업로드 또한 form을 활용한다. 다만 form과 input에 적어주어야 할 속성이 있다.

 

프론트측 html

    <form action="/upload/array" method="POST" enctype="multipart/form-data">
        <input type="file" name="userfiles" multiple>
        <br>
        <input type="text" name="title">
        <br>
        <button>업로드</button>
    </form>

위 코드와 같이 form에는 enctype="multipart/form-data", input에는 type="file"로 설정해주어야 한다.

* input의 multiple은 여러 파일을 첨부할 때 이용한다.

 

 

파일을 업로드하기 위해서는 서버에서 multer 라는 미들웨어를 이용, 저장 경로와 이름 등을 미리 설정해 줘야 한다.

 

-서버측-

const uploadDetail = multer({
    // storage: 저장할 공간에 대한 정보
    storage: multer.diskStorage({
        // destination: 경로 설정
        destination(req, file, done) {
            // done: 콜백함수
            // done(null, xx) : null => 에러가 없다는 의미
            done(null, "uploads/"); // 파일을 업로드할 경로 설정,
        },
        filename(req, file, done) {
            // 파일의 확장자 추출 => 'path' 모듈 활용
            console.log(req.body);
            const ext = path.extname(file.originalname); // 확장자 추출
            // done(null, path.basename(file.originalname, ext));
            // 확장자를 제외한 파일 이름만
            // console.log(path.basename(file.originalname, ext));
            done(null, path.basename(req.body.id, ext) + Date.now() + ext);
        }
    }),
    limits: {
        fileSize: 5 * 1024 * 1024 // 5MB
    }
})

 

서버측 post, 두 번째 인자에 multer가 들어가는 걸 확인할 수 있다.

// single() 인자는 input 태그의 name 값과 일치시켜야 함
app.post('/upload', uploadDetail.single('userfile'), function(req, res) {
    console.log(req.body);
    console.log(req.file);
    res.redirect('/');
    // {
    //     fieldname: 'userfile', // 폼에 정의한 name 값
    //     originalname: 'christmas.jpg', // 원본 파일 명
    //     encoding: '7bit', // 파일 인코딩 타입
    //     mimetype: 'image/jpeg', // 파일 타입
    //     destination: 'uploads/', // 파일 저장 경로
    //     filename: 'christmas1701053957631.jpg', // 저장된 파일 명
    //     path: 'uploads\\christmas1701053957631.jpg', // 업로드된 파일 전체 경로
    //     size: 103113 // 파일 크기
    //   }
})

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


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

 

동적 파일 업로드 (axios)

 

submit을 하면 자동으로 보내주지만, 수동으로 해야하기 때문에 formData를 이용해서 프론트에서 정보를 쏴주고 fetch해야한다.

-프론트측-

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, vlaue)
            // 주의점! file.files[0]을 다른 것보다 일찍 append하면 서버측 multer를 정의할 때
            // req.body가 undefined로 나온다. 그러니까 file append는 뒤로
            formData.append('title', title.value);
            formData.append('dynamicFile', file.files[0]);

            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;


                const imgElement = document.querySelector('img');
                imgElement.src = '/' + file.path;
                imgElement.alt = title;
                imgElement.classList.add('profile');
            })
        }

 

- 서버측 -

// multer 세부설정
const uploadDetail = multer({
    // storage: 저장할 공간에 대한 정보
    storage: multer.diskStorage({
        // destination: 경로 설정
        destination(req, file, done) {
            // done: 콜백함수
            // done(null, xx) : null => 에러가 없다는 의미
            done(null, "uploads/"); // 파일을 업로드할 경로 설정,
        },
        filename(req, file, done) {
            // 파일의 확장자 추출 => 'path' 모듈 활용
            console.log(req.body);
            const ext = path.extname(file.originalname); // 확장자 추출
            // done(null, path.basename(file.originalname, ext));
            // 확장자를 제외한 파일 이름만
            // console.log(path.basename(file.originalname, ext));
            done(null, path.basename(req.body.id, ext) + Date.now() + ext);
        }
    }),
    limits: {
        fileSize: 5 * 1024 * 1024 // 5MB
    }
}) 

// 이렇게 req.file 객체로 받을 수 있다. 
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});
})

 


MVC

소프트웨어 설계와 관련된 디자인 패턴으로 

쉽게말해

               Model (데이터베이스관련 데이터를 처리하는 부분 )

               View(Router = app.get, app.post와 같은 메소드와 view폴더에 들어간 ejs, UI 관련된 것을 처리하는 부분),

               Controller(get, post 등에 쓰이는 콜백함수들, view와 model을 연결해주는 부분 )

              을 나누어 저장한 것을 말한다.

더 쉽게 설명하자면 찾아보기 편하게 끼리끼리 모으고, 재사용 가능성이 있는 것을 함수 등으로 정의하는 것.

편하자고 하는거다.

장점

유지보수 용이
유연성이 높음
확장성이 높음
협업에 용의
단점

완벽한 의존성 분리가 어려움
설계 단계가 복잡
설계 시간이 오래 걸린다
클래스가 많아진다

 

(controller - 함수들), model(database 연결), routes(app.get 등), views(ejs) 

패턴은 이런 식이다.

 

module.exports 와 class 등을 이용해서 다른 파일에서도 이를 이용할 수 있게 만들면 된다.

간단하게 말하면 모듈과 클래스를 이용해서 비슷한 것 끼리 같은 파일에 묶는거라고 생각하면 편할 듯 하다.