[Toy Project 기록하기 4] 파일 업로드하기 with multer
프로젝트에서 이미지 파일을 받아 업로드 하는 기능이 필요함에 따라 multer를 도입하게 됐습니다.
multer
파일 업로드 시 사용하는 node.js의 미들웨어이며 오직 multipart/form-data에서만 동작합니다.
multer 설치하기
npm install multer
피드를 업로드하는 화면에서 이미지를 받아 업로드 하는 기능을 추가해보려고 합니다.
/feed로 post 요청 시 이미지를 업로드 할 수 있도록 라우터에 정의합니다. photoUpload 미들웨어를 통과한 뒤 uploadFeed 함수로 넘어갑니다.
// feedRouter.ts
import express from 'express';
import { photoUpload } from '../server/middlewares';
const feedRouter = express.Router();
// 한 개의 파일을 업로드 할 수 있습니다. 'photo'는 form을 통해 전송되는 파일 name
feedRouter.post('/', photoUpload.single('photo'), uploadFeed);
// ...
multer의 diskStorage 엔진은 파일을 디스크에 저장하기 위한 모든 제어기능을 제공하며 destination, filename 옵션이 주어집니다. destination은 업로드 한 파일을 저장할 위치를 결정하고, filename은 폴더 안에 저장되는 파일명을 결정합니다.
// middlewares.ts
import multer from 'multer';
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'uploads/photos/');
},
filename: function (req, file, cb) {
cb(null, `${Date.now()}_${file.originalname}`);
},
});
export const photoUpload = multer({
storage,
// 파일 크기 제한
limits: {
fileSize: 3000000,
},
});
이미지가 업로드되면 req.file에 나타나게 됩니다.
export const uploadFeed = async (req, res) => {
try {
const { title, text } = req.body;
// req.file은 multer가 파일을 업로드 했을 때만 생성됨.
// path는 optional하기 때문에 구조분해할당 불가
const path = req.file?.path;
const newFeed = await Feed.create({
title,
text,
photo: path,
});
return res.status(200).send(newFeed);
} catch (error) {
return res.status(500).send({ message: DEFAULT_ERROR_MESSAGE });
}
};
피드를 업로드하는 페이지에서 이미지를 업로드하는 방법에 대해 알아보겠습니다.
먼저 multipart/form-data 타입의 폼을 만들어 준 뒤, handlePhotoFileChange를 통해 이벤트 발생 시 preview에 추가해 이미지를 미리 보게 했고 form이 submit될 때 /feed로 formData를 post 요청을 보냈습니다.
export default function UploadFeed() {
const [preview, setPreview] = useState();
onst handlePhotoFileChange = async (event) => {
const file = event.target.files[0];
file && setPreview(URL.createObjectURL(file));
};
const onValid = ({
title,
text,
photoFile,
}) => {
const formData = new FormData();
formData.append('title', title);
formData.append('text', text);
photoFile && formData.append('photo', photoFile);
return axiosInstance.post(`/feed`, formData);
};
return (
<form
method="POST"
encType="multipart/form-data"
onSubmit={handleSubmit(onValid)}
>
// ...
<div>
<label htmlFor="photoFile">
사진
</label>
<input
id="photoFile"
type="file"
accept="image/*"
{...register('photoFile', {
onChange: handlePhotoFileChange,
})}
/>
</div>
<div>
{preview && (
<img src={preview} alt="preview"></img>
)}
</div>
{isError && isAxiosError(error) && (
<p>{error.response?.data.message}</p>
)}
<div>
<button
type="submit"
disabled={isLoading}
>
{isLoading ? '업로드 중' : '올리기'}
</button>
</div>
</form>
);
}
🧗♀️ 제가 진행한 프로젝트가 궁금하다면
🔽 프론트엔드는 이곳에서 확인하실 수 있습니다.
https://github.com/Team-Madstone/doljabee-fe
GitHub - Team-Madstone/doljabee-fe: Climbing Community
Climbing Community. Contribute to Team-Madstone/doljabee-fe development by creating an account on GitHub.
github.com
🔽 백엔드는 이곳에서 확인하실 수 있습니다.
https://github.com/Team-Madstone/doljabee-be
GitHub - Team-Madstone/doljabee-be: Climbing Community
Climbing Community. Contribute to Team-Madstone/doljabee-be development by creating an account on GitHub.
github.com