[포스코x코딩온] 웹개발자 풀스택 과정 5주차 회고록 - 파일 업로드(multer)

⚒️ 오늘 할 실습

로그인을 한 후 자신의 프로필을 바꿀 수 있는 설정이 있다. 오늘은 이 프로필을 설정하는 방법에 대해 배울 것이다.

사실 현재는 백엔드와의 통신을 하는 것으로 끝이나 이후에 db와도 연동해서 할 계획이다.  

 

multer

이미지 파일을 올리기 위해서는 multer라는 미들웨어가 필요하다. (대중적으로 가장 많이 쓰인다고 하셨다.)

 

😮 그전에!

기본에는 데이터만 보냈기 때문에 body-parser만 썼다.

//body-parser
app.use(express.urlencoded({ extended: true }));
app.use(express.json());

 

하지만 body-parser는 이미지, 동영상, 파일 등을 보낼 수 없다는 단점이 있기 때문에 이번 시간에 보낼 이미지를 보내기 위해서는 또 다른 미들웨어가 필요하다.

 

그게 바로 이번에 배울 multer이다.

 

그전에 이미지를 넣을 정적 파일을 설정해 줘야 한다. 클라이언트에서 이미지를 보내면 이 파일에 들어갈 것이다.

 

// 정적파일 생성
app.use("/uploads", express.static(__dirname + "/uploads"));

 

uploads라는 폴더를 생성 후 위의 코드를 써주면 된다. 

 

🗂️ multer 사용법

이제 진짜 multer 사용법에 대해 알아볼 것이다.

대략적인 구조

multer 패키지 안에는 파일 저장 관련 설정을 해주는 storage객체파일 크기를 제한해주는 limits객체가 존재한다. 

 

const storage = multer.diskStorage({
  // destination: 저장될 경로 지정(요청객체, 업로드된 파일객체, 콜백함수)
  destination: (req, file, cb) => {
    cb(null, "uploads/");	// uploads 폴더에 저장
  },
  // filename: 파일 이름결정(요청객체, 업로드된 파일객체, 콜백함수)
  filename: (req, file, cb) => {
    // extname: 확장자를 추출
    const ext = path.extname(file.originalname);
    // basename : 파일이름 추출(파일이름,확장자) => 확장자를 제외해서 파일이름 추출
    const newName = path.basename(file.originalname, ext) + Date.now() + ext;	//basename + 오늘 날짜(겹치지 않게 하기 위해) + 확장자
    cb(null, newName);
  },
});

 

먼저 storage는 multer의 diskStorage를 불러와서 설정한다. 이 안에는 저장될 경로와 저장할 파일의 이름을 지정해 준다.

destination : 파일이 저장될 공간
filename : 파일이 저장될 때의 이름 설정

콜백함수의 첫 번째 인자는 에러 객체이고, 두 번째 인수는 결괏값이다. 에러가 발생하지 않았을 경우에는 첫 번째 인수에 'null'값을 전달하여 에러가 없음을 나타내고, 두 번째 인수에는 원하는 결괏값을 전달한다. 

 

// 파일 크기 제한
const limits = {
  fileSize: 5 * 1024 * 1024, // 5MB
};

 

파일 크기는 간단하게 적을 수 있다. 나는 5MB로 지정해 줬다.

 

이렇게 만든 변수들을 이젠 multer 객체에 넣을 것이다.

 

// key-value에서 key값과 value의 변수가 동일 하면 합칠 수 있음
const upload = multer({ storage, limits });

 

multer객체를 생성 후 storage: storage, limits:limits로 해준다. 이때 key와 value의 값이 동일하면 합칠 수 있다는 것을 알아두자!!

 

파일을 올리는 방법에는 총 3가지가 있다. 

 

밑줄 친 부분만 달라지기 때문에 주의해서 보면 좋을 거 같다.

또 주의할 점은 노란색으로 칠 해진 부분은 프런트의 input=file 부분의 name과 일치시켜줘야 한다. (필수)

1️⃣ 하나의 파일만 올리기

upload.single

 

2️⃣ 한 번에 여러 개의 파일 올리기

upload.array

한번에 올릴 수 있는 최대 개수를 지정해 줄 수 있다.

3️⃣ 하나의 폼 안에 여러개의 파일 올리기

upload.fields

하나의 폼 안에 여러 개의 파일을 올릴 때는 배열 형식으로 보내준다. name에는 위에도 말했다시피 프런트의 input=file의 name과 일치 시켜줘야 한다. 그리고 각각에 한번에 올릴 수 있는 최대 개수도 지정해 줄 수 있다. 

 

📖 실습하기

비동기 방식으로 여러개의 이미지 파일을 주고받는 과정을 해보겠다. 

 

🖥️ 프런트

 

<h1>파일 여러개 올리기</h1>
<form onsubmit="fileupload(event)">
    <input type="file" id="dynamic" multiple><br>
    <input type="text" id="title"><br>
    <button type="submit">업로드하기</button>
</form>
<div class="result"></div>

 

이번에는 form의 onsubmit 방식을 사용해서 백엔드에 전송할 것이다.

여러 개의 파일을 올릴 때는 해당 input 태그에 multiple이라는 명령어를 써주자.

 

event.preventDefault();	// onsubmit() 함수가 새로 고침되는 것을 막아준다.
const title = document.querySelector("#title").value;
const file = document.querySelector("#dynamic");

// 폼 데이터 생성
const formData = new FormData();

// 폼 데이터에 제목 추가
formData.append("title", title)

// 추가한 파일의 수만큼 폼 데이터에 추가한다.
for (let i = 0; i < file.files.length; i++) {
    formData.append("dynamic", file.files[i])
}

 

event.preventDefault()를 가장 위에 쓰는 이유는 페이지가 새로고침되는 것을 막기 위해서다.  폼 안에 onsubmit 함수는 폼 안의 내용을 백엔드로 보내주고 새로고침 되도록 설정되어 있다. 만약 event.preventDefault()를 쓰지 않는다면 어떠한 값도 백엔드로 넘어가지 않을 것이다.

 

이후 추가한 파일의 수만큼 폼 데이터에 추가해 줘야 한다.

그러기 위해서는 for문을 통해 파일의 수 만큼 반복해줘야 한다. 그렇다면 file.files는 뭘까?

 

file.files를 콘솔로 찍은 모습이다. 2개의 파일을 추가했기 때문에 길이가 2인 배열이 나오는 것을 볼 수 있다. 

 

이젠 이 값을 백엔드로 보낼 것이다.

axios({
    method: "post",
    url: "/dynamic",
    data: formData,
    headers: {
        "Content-Type": "multipart/form-data"
    }
}).then(res => {
    console.log("res", res)
})

헤더에는 필수로 Content-Type을 명시해줘야 한다.

백엔드에서 req.files로 받으면 

 

req.files

이런 값이 나오는데 이 값들을 가지고 프론트에 이미지를 띄어주거나 조작할 수 있다.

 

uploads 폴더

uploads 폴더에도 잘 들어간 것을 볼 수 있다.