multer를 이용하여 React - Node.js 파일 업로드 구현하기

윤나혜·2022년 6월 2일
7
post-thumbnail

multer 사용 계기

React, Node.js 로 진행 중인 프로젝트에서, 파일 업로드 기능을 구현하게 되었다.
사용자가 업로드 하고자 하는 파일은 선택하여 업로드 버튼을 누르면 해당 파일이 서버에 저장되도록 구현할 것이다.

왜 multer를 사용해야 하지?

일반적으로 클라이언트에서 서버로 폼 데이터(form data)가 전송될 때 해당 데이터는 인코딩되어 전송된다.
이러한 방식은 JSON 형식의 데이터를 전송하는 것에는 어려움이 없으나, 파일 자체를 전송하려 할 때는 생각한 것처럼 전송되지 않는다.
나 역시 파일을 무작정 body에 넣어서 POST 요청을 보냈으나 서버에서는 body안에 있어야 할 파일을 가져오지 못하였다...😭
이런 상황에서 파일을 온전하게 전송하기 위해서는 파일을 입력하는 form 태그의 encType 속성을 multipart/form-data로 바꿔 주어야 한다.
encType 속성은 입력된 데이터가 서버에 전송될 때 인코딩 되는 방식을 결정한다. 디폴트는 application/x-www-form-urlencoded인데, 이 경우 모든 문자를 인코딩하여 전송하게 되며, 위에서 언급한 multipart/form-data는 모든 문자를 인코딩하지 않고 전송을 한다.
multer가 바로 이 multipart/form-data를 다루기 위한 node.js의 미들웨어이다!😎

구현

1. 파일 업로드를 위한 form 구현

//FileUpload.jsx
import React from 'react';

function FileUpload() {
	return(
    	<form encType='multipart/form-data'>
            <input type='file' name='file' />
            <button type='submit'>업로드</button>
        </form>
    );
}

사용자가 업로드 할 파일을 선택하고 버튼을 눌러 전송할 수 있도록 form을 만들어 준다.
이때 전송하고자 하는 데이터가 파일이기 때문에 form의 encType은 multipart/form-data로 해준다.
아직까지 버튼에 아무런 이벤트가 없기 때문에 파일을 선택하고 업로드 버튼을 눌러도 아무일도 일어나지 않을 것이다.

//FileUpload.jsx
import React from 'react';
import { uploadFile } from '../../apis/fileAPI

function FileUpload() {
	const upload = async(e) => {
      	e.preventDefault();
      	const formData = new FormData();
      	formData.append('file', e.target.file.files[0];
        await uploadFile(formData);
    };
  
	return(
    	<form encType='multipart/form-data' onSubmit={upload}>
            <input type='file' name='file' />
            <button type='submit'>업로드</button>
        </form>
    );
}
//fileAPI.js
import apiClient from './apiClient';

export const uploadFile = async( file ) => {
	try {
      	const { data } = await apiClient.post('/upload', file);
      	return data;
    }
  	catch(e) {
      	console.log(e);
    }
}
//apiClient.js
import axios from "axios";


export const apiClient = axios.create({
    baseURL: process.env.BACKEND_URL
});

기존의 리액트 프로젝트가 api호출 코드를 한 데 모아 관리하고 있었기 때문에 갑자기 코드가 확 늘어난 것처럼 보이지만 실제로는 FileUpload.jsx에 upload 함수와 fileAPI.js에 uploadFile 함수가 추가 되었을 뿐이다.
FileUpload.jsx에 추가한 upload 함수에는 FormData 객체를 생성하여 form에서 선택한 파일을 append하고 있다.
FormData는 ajax로 form data 전송을 가능하게 해주는 객체인데, 지금과 같이 form 전송이 필요한 경우 사용한다.
이렇게 이렇게 호출된 uploadFile은 사용자가 선택한 파일을 담은 FormData 객체를 서버에 보내게 된다.
❗❗FormData를 전송할 때 body에 담아서 전송하면 안된다.❗❗

2. multer를 이용하여 전송받은 파일 저장하는 api 구현

일단 api를 만들 node.js 프로젝트에 multer를 설치한다.

const multer = require('multer');
const upload = multer({ dest: 'uploads/' });
app.post('/upload', upload.single('file'), (req, res) => {
  console.log(req.file);
});

가장 기본적인 형태의 multer 사용이다.
dest를 통해 경로를 지정해 주었는데, 이로 인해 전송받은 파일은 node.js 내부의 uploads 디렉터리에 저장될 것이고 만약 uploads 디렉터리가 없다면 자동으로 생성이 되어 그 안에 저장될 것이다!
사실 위의 코드만으로 파일을 받아서 저장하는 것 자체에는 문제가 없다. 하지만 이 경우, 저장된 파일의 이름이 기존의 파일 이름이 아닌 새로 생성된 복잡한 문자열로 되어있을 것이다.

보안을 위한 기능이지만 나의 경우는 그냥 기존의 파일 이름으로 저장되길 원했기 때문의 코드를 약간 수정해야 했다.

위 코드에서 req.file 찍어보면 이미지와 같이 찍힌다. 보이는 것과 같이 filename은 d5992...로 되어있지만, originalname에 기존의 파일 이름이 그대로 남아있다.

const multer = require('multer');
const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, 'uploads/');
  },
  filename: (req, file, cb) => {
    const newFileName = file.originalname;
    cb(null, newFileName);
  }
});
const upload = multer({ storage: storage });

multer의 diskStorage는 파일을 저장할 때의 모든 제어 기능을 제공한다.
destination으로 이전 코드에서 지정해준 경로를 넣어주고, filename을 file의 originalname로 저장하도록 변경하였다.

다시 업로드해보면 파일의 원래 이름으로 저장된 것을 확인할 수 있다.

마치며

encType이나 FormData와 같이 처음 접하는 것들이 다소 등장했다.
처음에는 습관적으로 FormData를 body 담아서 전송했었는데 이 부분이 잘못되었다는 것을 알기까지 꽤 시간이 걸렸다.😭
알고 나니깐 왜 이걸 여태 못봤나 싶었지만, 이런 방향의 실수는 항상 존재하는 것 같다.
이 글은 클라이언트에서 전송한 파일을 서버에 저장하는 것에만 목표를 두고 있기 때문에 예외 처리도 안되어있고 코드가 전체적으로 깔끔하지는 않다. 1차 적인 목적은 달성했기 때문에 다시 코드를 다듬고 쓸만하게 고쳐야 겠다.

profile
공부하면서 잡다하게 기록하는 공간입니다.

0개의 댓글

관련 채용 정보