이미지는 multipart/form-data 에 속하므로 기존에 처리하는 두 가지 방식으론 이미지를 서버에서 받기 어렵다.
back/app.js
// url 로 보낸 데이터를 json 형식으로,
// form 형식으로 온 데이터를 처리
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser(process.env.COOKIE_SECRET));
1. multer 라이브러리 설치
npm i multer
- 멀티파트 데이터형식을 백엔드에서 처리하기 위해 multer 미들웨어 라이브러리 설치
2. 이미지 업로드 라우터 작성
back/routes/post.js
// POST /post/images --- 이미지 업로드
router.post('/images', isLoggedIn, upload.array('image'), async (req, res, next) => {
try {
console.log(req.files);
res.json(req.files.map((i) => i.filename));
} catch (err) {
console.error(err);
next(err);
}
});
- 멀터는 라우터마다 폼마다 형식들이 다르기 때문에 멀터 미들웨어를 사용해서 라우터마다 별도의 세팅을 해주어야 함
- router.post('/images', isLoggedIn, upload.array('image'), async (req, res, next) => {...}: HTTP POST 메서드를 통해 '/post/images' 엔드포인트에 대한 핸들러를 정의. isLoggedIn 함수를 미들웨어로 사용하여 사용자가 로그인 상태인지 확인하고, 이미지 업로드를 처리하기 위해 upload.array('image')를 사용. 이는 'image' 필드로 전송된 여러 이미지 파일을 처리하도록 설정한 것. 비동기 함수로 작성됨.
- console.log(req.files);: 업로드된 파일 정보는 req.files 객체에 저장되어 있음. 해당 코드는 업로드된 파일 정보를 콘솔에 출력.
- res.json(req.files.map((i) => i.filename));: 업로드된 각 파일의 파일명을 JSON 형식으로 응답. 클라이언트는 이를 통해 업로드된 파일들의 파일명을 확인할 수 있음
요약해서
이 코드는 클라이언트가 POST 요청을 통해 여러 이미지를 '/post/images' 엔드포인트로 업로드할 때, 해당 이미지들의 정보를 서버에서 받아 콘솔에 출력하고, 업로드된 이미지 파일들의 파일명을 JSON 형식으로 응답하는 엔드포인트를 나타냅니다.
3. multer 사용
back/routes/post.js
// 이미지 미리보기 업로드
const upload = multer({
storage: multer.diskStorage({
destination(req, file, done) {
done(null, 'uploads');
},
filename(req, file, done) {
//사진.png
const ext = path.extname(file.originalname); // 확장자(png) 추출
const basename = path.basename(file.originalname, ext); // 이미지 이름(사진) 추출
done(null, basename + '_' + new Date().getTime() + ext); // 중복을 방지하기 위해 데이터 가공(사진1441523.png)
},
}),
limits: { fileSize: 20 * 1024 * 1024 }, //20MB
});
- const upload = multer({...});: multer 미들웨어를 설정하여 파일 업로드를 처리하는 객체를 생성.
- storage: multer.diskStorage({...}): 파일을 디스크에 저장하기 위한 multer의 저장소 설정. destination 함수는 파일이 저장될 디렉토리를 설정하고, filename 함수는 저장될 파일의 이름을 정의.
- destination(req, file, done): 파일이 저장될 디렉토리를 설정. 현재는 'uploads' 디렉토리로 설정.
- filename(req, file, done): 저장될 파일의 이름을 정의. 파일의 확장자를 추출하고, 기존 파일 이름과 현재 시간을 이용하여 중복을 방지하면서 고유한 파일 이름을 생성.
- limits: { fileSize: 20 * 1024 * 1024 },: 업로드되는 파일의 크기 제한을 설정. 현재는 최대 20MB까지 허용하도록 설정
요약해서
클라이언트로부터 전송된 이미지 파일을 서버의 'uploads' 디렉토리에 저장하며, 중복을 피하기 위해 파일 이름에 현재 시간을 추가한 후, 파일 크기가 20MB를 초과하지 않도록 설정한 것. 이렇게 설정된 upload 객체는 나중에 이를 라우터에서 사용하여 실제로 이미지를 업로드하게 될 것입니다.
지금은 서버를 배포하기 전이라 컴퓨터 하드디스크에 저장했지만, 이 후 배포과정에서 AWS S3 에 저장할 예정
4. input 태그에 name 설정
front/components/PostForm.js
(...)
<div>
<input type='file' name='image' multiple hidden ref={imageInput} onChange={onChnageImages} />
(...)
- imageInput 에서 올린 이미지가 routes/post.js 에서 작성한 upload.array 로 전달될 예정. array 인 이유는 이미지를 여러 장 올릴 수 있게 하기 위해서. 만약 한장 올리는 거라면, single 을 대신해 사용
5. onChnage event 작성
front/components/PostForm.js
// 미리보기 이미지 변경
const onChnageImages = useCallback((e) => {
console.log('images', e.target.files);
const imageFormData = new FormData();
[].forEach.call(e.target.files, (f) => {
imageFormData.append('image', f);
});
dispatch({ type: UPLOAD_IMAGES_REQUEST, data: imageFormData });
});
// 미리보기 이미지 삭제
const onRemoveImage = useCallback((index) => () => {
dispatch({
type: REMOVE_IMAGE,
data: index,
});
});
<input type='file' name='image' multiple hidden ref={imageInput} onChange={onChnageImages} />
- const onChnageImages = useCallback((e) => {...}: 이미지를 선택할 때 호출되는 함수임. useCallback 훅을 사용하여 함수를 캐싱하여 최적화하고 있음.
- console.log('images', e.target.files);: 선택한 이미지 파일의 정보를 콘솔에 출력. 이는 개발 중 디버깅 및 확인을 위한 것.
- const imageFormData = new FormData();: FormData 객체를 생성하여 이미지 데이터를 담을 준비를 함. FormData는 파일 업로드와 같은 경우에 유용하게 사용되는 객체로, 키-값 쌍으로 데이터를 담을 수 있음.
- [].forEach.call(e.target.files, (f) => { imageFormData.append('image', f); });: 선택한 각 이미지 파일을 FormData에 추가. 이 코드는 선택한 모든 파일을 'image'라는 필드에 추가하고 있음. 이는 서버 측에서 해당 필드로 전송된 파일들을 처리할 때 사용될 것.
- dispatch({ type: UPLOAD_IMAGES_REQUEST, data: imageFormData });: Redux의 dispatch 함수를 사용하여 이미지 업로드를 요청하는 액션을 전달. UPLOAD_IMAGES_REQUEST는 Redux 액션의 타입이며, data에는 이미지 데이터가 포함되어 있음. 이를 통해 Redux에서 해당 액션을 처리하고 서버로 이미지 업로드 요청을 보냄.
6. reducer 작성
초기값 작성
front/reducers/post.js
// 초기값
export const initialState = {
(...)
uploadImagesLoading: false, // 좋아요 취소
uploadImagesDone: false,
uploadImagesError: null,
};
액션 크리에이터 작성
front/reducers/post.js
// Action Creator
export const UPLOAD_IMAGES_REQUEST = 'UPLOAD_IMAGES_REQUEST';
export const UPLOAD_IMAGES_SUCCESS = 'UPLOAD_IMAGES_SUCCESS';
export const UPLOAD_IMAGES_FAILURE = 'UPLOAD_IMAGES_FAILURE';
리듀서 작성
front/reducers/post.js
(...)
case UPLOAD_IMAGES_REQUEST: // 이미지 업로드
draft.uploadImagesLoading = true;
draft.uploadImagesDone = false;
draft.uploadImagesError = null;
break;
case UPLOAD_IMAGES_SUCCESS: {
draft.imagePaths = action.data;
draft.loadPostsLoading = false;
draft.loadPostsDone = true;
break;
}
case UPLOAD_IMAGES_FAILURE:
draft.uploadImagesLoading = false;
draft.uploadImagesError = action.error;
break;
(...)
7. saga 작성
api 작성
front/saga/post.js
function uploadImagesAPI(data) {
return axios.post(`/post/images`, data);
}
saga effect 작성
front/saga/post.js
function* uploadImages(action) {
try {
const result = yield call(uploadImagesAPI, action.data);
yield put({
type: UPLOAD_IMAGES_SUCCESS,
data: result.data,
});
} catch (err) {
console.error(err);
yield put({
type: UPLOAD_IMAGES_FAILURE,
data: err.response.data,
});
}
}
액션 작성
front/saga/post.js
(...)
function* watchUploadImages() {
yield takeLatest(UPLOAD_IMAGES_REQUEST, uploadImages);
}
export default function* postSaga() {
yield all([
(...)
fork(watchUploadImages),
]);
}
8. fs 로 파일 조작
(...)
const fs = require('fs');
(...)
try {
fs.accessSync('uploads');
} catch (err) {
console.log('uploads 폴더가 없으므로 생성합니다.');
fs.mkdirSync('uploads');
}
(...)
- const fs = require('fs');: Node.js에서 파일 시스템 작업을 수행하기 위한 내장 모듈인 fs 모듈을 가져옵니다.
- try { fs.accessSync('uploads'); } catch (err) { ... }: fs.accessSync 메서드를 사용하여 'uploads'라는 디렉토리가 존재하는지 확인합니다. 만약 디렉토리가 존재하지 않는다면, catch 블록으로 이동합니다.
- console.log('uploads 폴더가 없으므로 생성합니다.');: 디렉토리가 존재하지 않는 경우, 콘솔에 메시지를 출력합니다.
- fs.mkdirSync('uploads');: 'uploads' 디렉토리를 생성합니다. fs.mkdirSync 메서드를 사용하여 동기적으로 디렉토리를 생성합니다. 디렉토리가 이미 존재하면 에러가 발생할 수 있습니다.
요약해서
서버가 시작될 때 'uploads' 디렉토리가 존재하는지 확인하고, 만약 없다면 해당 디렉토리를 생성하는 역할을 합니다. 업로드된 파일들은 주로 이 디렉토리에 저장되게 됩니다.
'Next.js' 카테고리의 다른 글
[NNN]_트러블슈팅2_Infinite Scroll 멈추기 (0) | 2024.02.22 |
---|---|
[NNN]_express.static 미들웨어, 사진 미리보기 (0) | 2024.02.21 |
[NNN]_credentials 로 쿠키 공유하기(Unauthuorized Error) (0) | 2024.02.09 |
[NNN]_미들웨어로 라우터 검사하기 (0) | 2024.02.03 |
[NNN]_Passport 로 로그인 구현하기 (0) | 2024.02.02 |