Front-End에서 유저가 이미지를 업로드 하게 될 경우, 이미지를 저장하는 방법은 많지만
아마존 AWS S3버킷에 이미지 파일을 저장하고 DB에는 이미지 파일경로(이미지 주소)를 저장하여 보여주는 방식을 설명하겠습니다.
이는 Multer-S3와, AWS-SDK 모듈을 사용하여 구현할 수 있습니다.
multer의 주요 메소드 single, array, fields에 대해 알아보겠습니다.
https://www.npmjs.com/package/multer
multer 설치
npm install --save multer
multer를 이용할 파일에 multer 가져오기
const multer = require('multer');
const upload = multer({ destination: 'uploads/' });
destion에는 저장하고 싶은 디렉토리를 적으면 됩니다.
1. multer.single(fieldName) : 하나의 파일을 받아서 저장
router.post('/', upload.single('image'), (req, res) => {
console.log(req.file); // 파일에 대한 정보가 req.file에 FILE 객체로 저장되어 있습니다.
})
- upload.single('image')에서 'image'는 field명으로 클라이언트에서 지정해주는 field명을 뜻합니다. 약속된 field명을 적어주면 됩니다.
- req.file내에는 아래의 정보들이 담겨져 있습니다.
2. multer.array(fieldName [, maxCount] ) : 하나의 필드명을 가지는 여러개의 파일을 받아서 저장
router.post('/', upload.array('photos', 4), (req, res) => {
console.log(req.files);
console.log(req.files[0]); // 파일의 인덱스로 접근
})
3. fields([{fieldname, maxCount}, {fieldname, maxCount}, ...]) : 여러개의 필드명을 가지는 여러개의 파일을 받아서 저장
router.post('/',
upload.fields([
{ name: 'mainImage', maxCount: 1 },
{ name: 'subImages', maxCount: 5 }
]), (req, res) => {
console.log(req.files);
console.log(req.files['접근하려는 fieldname']);
})
❓실제로 어떻게 사용했을까요?
backend/.env
액세스 키, 액세스 암호키, S3버킷을 생성한 지역은 환경변수파일로 관리하였습니다.
{
"accessKeyId": "YOUR AWS ACCESS KEY",
"secretAccessKey": "YOUR AWS ACCESS SECRET KEY",
"region": "ap-northeast-2"
}
middlewares/upload.js
middleware를 생성해주었습니다.
//이미지를 받고 오브젝트 스토리지에 올리기 위한 모듈 import
const multer = require("multer");
const multerS3 = require("multer-s3");
const aws = require("aws-sdk");
const recipeStorage = multerS3({
s3: s3,
bucket: "yorijori-recipes", //The bucket used to store the file
contentType: multerS3.AUTO_CONTENT_TYPE, //업로드되는 파일의 memetype
acl: "public-read",
metadata: function (req, file, cb) {
cb(null, { fieldName: file.fieldname }); //metadata onject to be sent to S3
},
key: function (req, file, cb) {
cb(null, `uploads/${Date.now().toString()}_${file.originalname}`);
}, // The name of the file
//파일 최대크기 설정(5MB)
limits: { fileSize: 5 * 1024 * 1024 },
});
exports.recipeUpload = multer({ storage: recipeStorage });
recipeStorage라는 함수가 실행되면, "yorijori-recipes"에 파일이 업로드 됩니다.
routes/post.js
이미지 field 가 2개여서 fields 를 사용하였습니다.
//레시피 작성
router.post(
"/",
isLoggedIn,
recipeUpload.fields([{ name: "thumbnail", maxCount: 1 }, { name: "copyImage" }]),
asyncHandler(async (req, res, next) => {
const {
recipeName,
desc,
ingredient,
seasoning,
process,
category,
condition,
material,
cook,
servings,
time,
diffic,
} = req.body;
const { id: userId } = req.user || req.cookies;
//thumbnail 이미지 location DB에 넣기
thumbnail = req.files.thumbnail[0].location;
//copyImage DB에 넣기
let arrCopyImage = [];
let CopyImageContents = req.files.copyImage;
for (i = 0; i < CopyImageContents.length; i++) {
arrCopyImage.push(CopyImageContents[i].location);
}
copyImage = arrCopyImage;
//이미지의 key값 입력
thumbnailKey = req.files.thumbnail[0].key;
//copyImage 의 key 값 찾기
let arrCopyImageKey = [];
let CopyImageContentsKey = req.files.copyImage;
for (i = 0; i < CopyImageContentsKey.length; i++) {
arrCopyImageKey.push(CopyImageContentsKey[i].key);
}
copyImageKey = arrCopyImageKey;
const posts = await Post.create({
userId,
recipeName,
desc,
ingredient,
seasoning,
process,
thumbnail,
thumbnailKey,
copyImage,
copyImageKey,
category,
condition,
material,
cook,
servings,
time,
diffic,
});
//process내부 processImage의 location, key값 부여
for (i = 0; i < process.length; i++) {
posts.process[i].processImage = copyImage[i];
posts.process[i].processImageKey = copyImageKey[i];
}
await posts.save();
await User.updateOne({ _id: userId }, { $inc: { numPosts: 1 } });
res.status(201).json({ message: "레시피등록이 완료되었습니다." });
})
);
copyImage와 processImage가 있는데,
프론트엔드단에서 서버의 요청을 한번에 받아오기 위하여 process배열 스키마 안에 processImage가 있어서,
그 배열 스키마 안의 배열로 실제 값을 넣는게 쉽지않았습니다.
그래서 copyImage라는 가상의 스키마를 생성하여 이부분을 processImage에 값을 새로 배정해주는 방식을 사용하였습니다.
아래에 post schemas도 펼치기 하시면 볼 수 있습니다.
schemas/post.js
post스키마입니다.
const mongoose = require("mongoose");
const { Schema } = require("mongoose");
const PostSchema = new mongoose.Schema(
{
//레시피명
recipeName: {
type: String,
required: true,
},
//요리 소개
desc: {
type: String,
required: true,
},
//재료소개
ingredient: [
{
ingreName: { type: String, required: true },
ingreCount: { type: String, required: true },
},
],
//양념소개
seasoning: [
{
ingreName: { type: String, required: true },
ingreCount: { type: String, required: true },
},
],
process: [
{
explain: { type: String, required: true },
processTime: {
min: { type: Number },
sec: { type: Number },
},
processImage: { type: String, default: null },
processImageKey: { type: String, default: null },
},
],
//조리과정 받는 곳
copyImage: [{ type: String, required: true }],
copyImageKey: [{ type: String, required: true }],
// // 썸네일
thumbnail: { type: String, required: true },
thumbnailKey: { type: String, default: null },
//종류별
category: {
type: String,
required: true,
},
//상황별 :
condition: {
type: String,
required: true,
},
//재료별
material: {
type: String,
required: true,
},
//방법별
cook: {
type: String,
required: true,
},
//인원수
servings: {
type: String,
required: true,
},
//요리 시간
time: {
type: String,
required: true,
},
//난이도
diffic: {
type: String,
required: true,
},
//작성글을 유저와 연결합니다
userId: {
type: Schema.Types.ObjectId,
ref: "User",
required: true,
},
//DB에는 삭제안되지만, 게시물 조회할때 useYN:true 인 값만 노출되어주는 컬럼
useYN: {
type: Boolean,
default: true,
},
//게시글을 본 횟수
numViews: {
type: Number,
default: 0,
},
//게시글이 받은 좋아요 수
numLikes: {
type: Number,
default: 0,
},
},
{ timestamps: true, toJSON: { virtuals: true }, toObject: { virtuals: true } }
);
//게시글에 달린 댓글 수를 받습니다.
PostSchema.virtual("numComments", {
ref: "Comment",
localField: "_id",
foreignField: "postId",
count: true,
});
PostSchema.index({ recipeName: "text" }); // text index 등록
module.exports = PostSchema;
이 방식을 이용하면 이미지파일을 웹 서버에 업로드 자유자재로 가능하며, 수정 삭제또한 post.js라우터에 추가 해 놨다 ! :)
처음 이 방식을 고민하며,
field에 대해 잘 나와있는것이 없어 조금 힘들었으니 고민을 하다보니 성공적으로 구현할 수 있었다 !
'PROGRAMING > NODE.JS' 카테고리의 다른 글
Node.js 비동기 코딩 (0) | 2021.12.01 |
---|