Lsiron

HELLO FOLIO(express, mongoose)- 10(AWS S3, multer를 이용한 이미지 업로드 및 삭제 기능 구현 2) 본문

개발일지/HELLO FOLIO

HELLO FOLIO(express, mongoose)- 10(AWS S3, multer를 이용한 이미지 업로드 및 삭제 기능 구현 2)

Lsiron 2024. 8. 14. 05:56

현재 폴더 구조

web_project/
├── db/
│   ├── model             # 각종 모델 폴더
│   ├── schemas           # 각종 스키마 폴더
│   └── index.js          # db 연결파일
├── middleware/           # 각종 middleware 폴더
├── passport/	          # 로그인 관련 passport 폴더
│   ├── strategies        # 전략 구성
│   └── index.js          # 전략 exports 파일
├── routes/               # 라우트 관련 폴더
│   └── index.js     	  # 라우트 exports 파일
├── services/             # 유저 관련 service 폴더
├── views/                # EJS 템플릿 폴더
├── public/               # static 파일 폴더
│   ├── css/              # CSS 파일들
│   ├── js/               # JS 파일들
│   └── images/           # 이미지 파일들
├── .env                  # 환경 변수 파일
├── .gitignore            # Git 무시 파일
├── package.json          # 프로젝트 메타데이터 및 종속성 목록
├── app.js                # 애플리케이션 진입점
├── index.js              # 서버 실행 파일
└── README.md             # 프로젝트 설명 파일

현재 설치한 모듈

  "dependencies": {
    "bcrypt": "^5.1.1",
    "cookie-parser": "^1.4.6",
    "dotenv": "^16.4.5",
    "ejs": "^3.1.10",
    "express": "^4.19.2",
    "jsonwebtoken": "^9.0.2",
    "mongodb": "^6.7.0",
    "mongoose": "^8.4.3",
    "nodemailer": "^6.9.13",
    "passport": "^0.7.0",
    "passport-jwt": "^4.0.1",
    "passport-local": "^1.0.0"
  },

app.js

const express = require('express');                     
const app = express();
const bcrypt = require('bcrypt');
const passport = require('passport');				

const { userRouter, myPageRouter } = require("./routes");		               

require('./passport')();                                           
const authMiddleware = require('./middleware/authMiddleware');       
app.use(express.json());							
app.use(express.urlencoded({ extended: false }));	
app.use(cookieParser());                               

app.use(passport.initialize());					

app.use('/', userRouter);
app.use('/', resetPwRouter);				
app.use('/my-page', authMiddleware ,myPageRouter); 

module.exports = { app };

.env

# DB 접속 URL
DB_URL="디비주소"
# DB 프로젝트 이름
DB_NAME="디비이름"
# 서버 포트 번호
PORT=3000
# JWT토큰 발급 키
SECRET="secret"
# REFRESH 토큰 발급 키
REFRESH_SECRET="toosecret"
# 비밀번호 찾기 이메일 발송메일
EMAIL_USER=내이메일
# 비밀번호 찾기 이메일 비밀번호
EMAIL_PASS=내비번
# aws에서 발급받은 엑세스 키
S3_KEY="엑세스 키"    
# aws에서 발급받은 시크릿 키
S3_SECRET="시크릿 키"

 

aws s3 셋팅을 해 주었으니, 이제 서버에서 유저가 이미지를 업로드 할 수 있도록 로직을 짜 주어야한다. 

 

업로드와 삭제 기능을 넣어야 한다. 먼저 업로드를 구현 해 보자.

 

업로드 과정은 이렇다.

 

1. 유저가 이미지를 업로드 하여, 서버로 넘겨준다.

2. 서버는 이미지를 s3에 저장을 한다.

3. s3는 업로드 된 이미지를 URL 형태로 발급해준다.

4. 서버는 URL 형태로 발급된 이미지를 DB에 저장한다.

5. 서버는 DB에 저장된 이미지 URL을 유저에게 보여준다.

 

먼저 이미지를 업로드 및 삭제하기 위한 기능을 구현하기 위해 

@aws-sdk/client-s3 와 multer, multer-s3 모듈을 다운 받아주자.

 

@aws-sdk/client-s3 는 AWS S3와의 통신을 위해 사용되며, multer는 파일 업로드를 처리하는 미들웨어이다.

 

마지막으로, multer-s3 는 multer를 사용하여 파일을 s3에 직접 업로드 할 수있게 도와주는 모듈이다.

 

아래 명령어를 통해 한꺼번에 다운을 받아주자.

$ npm install @aws-sdk/client-s3 multer multer-s3

 

이 후, myPageRouter.js 로 가서 s3 클라이언트를 생성 해 주고, multer를 설정 해 주자.

 

코드 예제는 아래 링크에 있다.

https://www.npmjs.com/package/@aws-sdk/client-s3

 

@aws-sdk/client-s3

AWS SDK for JavaScript S3 Client for Node.js, Browser and React Native. Latest version: 3.629.0, last published: a day ago. Start using @aws-sdk/client-s3 in your project by running `npm i @aws-sdk/client-s3`. There are 3170 other projects in the npm regis

www.npmjs.com

https://www.npmjs.com/package/multer-s3

 

multer-s3

Streaming multer storage engine for AWS S3. Latest version: 3.0.1, last published: 2 years ago. Start using multer-s3 in your project by running `npm i multer-s3`. There are 229 other projects in the npm registry using multer-s3.

www.npmjs.com

 

const { Router } = require('express');
const router = Router();
// 마이페이지 이미지 업로드 기능
const { S3Client } = require('@aws-sdk/client-s3')
const multer = require('multer')
const multerS3 = require('multer-s3')

const s3 = new S3Client({
  region : 'ap-northeast-2',  // aws s3 region(서울 데이터센터에 저장)
  credentials : {
      accessKeyId : process.env.S3_KEY,          // aws에서 발급받은 엑세스 키
      secretAccessKey : process.env.S3_SECRET,   // aws에서 발급받은 시크릿 키
  }
})

const upload = multer({
  storage: multerS3({
    s3: s3,
    bucket: 'elicewebproject',                       // aws 버킷 이름
    key: function (req, file, cb) {
      cb(null, Date.now().toString())                // 업로드할 이미지 파일명 작성하는 곳
    }
  })
})

router.get('/', async (req, res) => {
    res.render('myPage.ejs')
})

 

참고로 코드예시에는 없는 credentials 부분은 현재 로컬 개발 환경이기 때문에 환경변수를 통해 명시적으로 자격 증명을 해 주었다. 이는 AWS 리소스에 접근하기 위해 로컬에서 환경 변수 등을 통해 자격 증명을 제공해야 하기 때문이다.

 

허나, 만약 EC2 인스턴스나 Lambda 함수 같은 aws 서비스 환경에서 서버가 실행 중이라면, 기본적으로 연결 된 IAM 역할을 통해 자동으로 자격 증명이 설정되므로, credentials를 통해 명시적으로 자격증명을 해 줄 필요가 없다.

 

즉, 배포 환경이 아니기 때문에 credentials를 통해 aws s3와 연결 해 준 것이다. 

 

나중에 AWS 환경에서 배포할 때는 credentials 설정을 빼줘도 된다. 허나, 다른 클라우드 환경에서 배포할 경우, 해당 클라우드의 인증 방식에 따라 자격 증명 설정이 필요할 수 있다. 이때는 AWS 자격 증명 대신, 해당 클라우드 제공자의 인증 방식을 사용해야 한다.

 

 

이어서 https://www.npmjs.com/package/multer 에서 기본 API를 그대로 가져와서 입력 해 보자.

 

multer

Middleware for handling `multipart/form-data`.. Latest version: 1.4.5-lts.1, last published: 2 years ago. Start using multer in your project by running `npm i multer`. There are 4572 other projects in the npm registry using multer.

www.npmjs.com

 

const { Router } = require('express');
const router = Router();
// 마이페이지 이미지 업로드 기능
const { S3Client } = require('@aws-sdk/client-s3')
const multer = require('multer')
const multerS3 = require('multer-s3')

const s3 = new S3Client({
  region : 'ap-northeast-2',  // aws s3 region(서울 데이터센터에 저장)
  credentials : {
      accessKeyId : process.env.S3_KEY,          // aws에서 발급받은 엑세스 키
      secretAccessKey : process.env.S3_SECRET,   // aws에서 발급받은 시크릿 키
  }
})

const upload = multer({
  storage: multerS3({
    s3: s3,
    bucket: 'elicewebproject',                       // aws 버킷 이름
    key: function (req, file, cb) {
      cb(null, Date.now().toString())                // 업로드할 이미지 파일명 작성하는 곳
    }
  })
})

router.get('/', async (req, res) => {
    res.render('myPage.ejs')
})

// 이미지 업로드 기능 구현
router.post('/upload', upload.single('avatar'), function (req, res, next) { // upload.single 미들웨어가 aws s3로 이미지 업로드 시킴
  // req.file is the `avatar` file
  // req.body will hold the text fields, if there were any
})

 

자 이제, api를 만들어 주었으니, views 폴더로 가서 웹 프로젝트 초반에 만들어 주었던 myPage.ejs 파일로 들어가자.

 

이미지를 업로드 할 수 있는 폼을 추가 하자. 이 폼은 유저가 이미지를 선택하고 업로드 버튼을 클릭하면 이미지를 서버로 전송한다.

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <link rel="stylesheet" href="/css/myPage.css">
</head>
<body>
	<form action="/my-page/upload" method="POST" enctype="multipart/form-data">
		<input type="file" name="profileImage">
		<button type="submit">이미지 업로드</button>
	</form>
</body>
</html>

 

참고로 enctype 속성은 <form> 태그에서 서버로 데이터를 전송할 때 사용할 인코딩 유형을 지정한다. 이 속성은 특히 파일 업로드와 관련된 폼에서 중요한 역할을 한다. 

 

 

파일 업로드와 같은 바이너리 데이터를 포함한 폼 데이터를 전송할 때 사용되며, 이 인코딩 타입은 각 필드의 데이터를 별도로 인코딩하여 전송하고 파일 업로드 시 필수적이다. 또한 폼 필드마다 구분되는 경계를 사용해 데이터를 구분한다. 

 

따라서 파일 업로드를 처리하기 위해 반드시 이 인코딩 방식을 사용해야 한다.

 

그리고 input 태그의 name 속성은 위에서 api를 설정할 때, upload.single 미들웨어의 타겟으로 들어간다.

 

즉, 이제 해야할 것은? 폼 작성을 마무리 했으니, myPageRouter.js 로 가서 아래와 같이 미들웨어의 타겟으로 name 속성으로 넣어준 "profileImage" 를 입력 해 주는것.

router.post('/upload', upload.single('profileImage'), function (req, res, next) { // upload.single 미들웨어가 aws s3로 이미지 업로드 시킴
  // req.file is the `profileImage` file
  // req.body will hold the text fields, if there were any
})

 

그렇다면 서버는 /my-page/upload 엔드포인트를 통해 유저로부터 전달받은 이미지를 s3에 업로드를 시켰다.

 

 

1. 유저가 이미지를 업로드 하여, 서버로 넘겨준다.

2. 서버는 이미지를 s3에 저장을 한다.

3. s3는 업로드 된 이미지를 URL 형태로 발급해준다.

4. 서버는 URL 형태로 발급된 이미지를 DB에 저장한다.

5. 서버는 DB에 저장된 이미지 URL을 유저에게 보여준다.

 

이제 남은건 3,4,5번. 즉, 서버는 이미지 URL을 s3로부터 가져오고, db에 저장 시킨 뒤, 그 데이터를 유저에게 보여줘야 한다.

 

그러면 이미지 URL을 s3로부터 어떻게 가져올까?

 

해답은 https://www.npmjs.com/package/multer-s3 여기에 있다.

File information 탭에 보면 location은 업로드된 파일의 S3 URL을 가리킨다.

 

즉, multer-s3는 s3에 파일을 업로드 한 후, 업로드 된 파일의 정보를 'req.file' 객체에 저장한다. 

req.file 객체에 있는 location이 바로 업로드 된 파일의 URL을 나타낸다는 것. 

 

이 말은 즉 req.file.location이 파일의 URL이 된다는 말이다.

// 이미지 업로드 기능 구현
router.post('/upload', upload.single('profileImage'), function (req, res) { // upload.single 미들웨어가 aws s3로 이미지 업로드 시킴
   // s3에 업로드된 파일의 URL 가져오기
      const imageUrl = req.file.location;
})

 

이제 URL 형태로 발급된 이미지를 가져왔으니 db에 저장을 시켜줘야 한다.

 

우리는 유저의 프로필 사진을 업데이트 하는 것 이니, userSchema에 저장을 해 주어야 하므로,

userSchema로 가서 imageUrl 필드를 추가해주자.

const mongoose = require('mongoose');

const UserSchema = new mongoose.Schema({
    email: {
        type: String,
        required: true,
        unique: true,
    },
    password: {
        type: String,
        required: true,
    },
    name: {
        type: String,
        required: true,
    },
    refreshToken: {
        type: String,
    },
    resetPwToken: {
        type: String,
    },
    resetPwExpires: {
        type: Date,
    },
    // 이미지 업로드 
    imageUrl: {
        type: String,
        default: "https://images.unsplash.com/photo-1520188740392-665a13f453fc?q=80&w=1974&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
    },
{
    collection: "users",
    timestamps: true,
});

module.exports = UserSchema;

 

default 값으로는 적절한 이미지를 넣어주었다. 이제 다시 myPageRouter.js로 가서 db에 저장해 주는 로직을 짜 보자.

const { Router } = require('express');
const router = Router();
const UserModel = require('../db/model/userModel'); 
// 마이페이지 이미지 업로드 기능
const { S3Client } = require('@aws-sdk/client-s3')
const multer = require('multer')
const multerS3 = require('multer-s3')

const s3 = new S3Client({
  region : 'ap-northeast-2',  // aws s3 region(서울 데이터센터에 저장)
  credentials : {
      accessKeyId : process.env.S3_KEY,          // aws에서 발급받은 엑세스 키
      secretAccessKey : process.env.S3_SECRET,   // aws에서 발급받은 시크릿 키
  }
})

const upload = multer({
  storage: multerS3({
    s3: s3,
    bucket: 'elicewebproject',                       // aws 버킷 이름
    key: function (req, file, cb) {
      cb(null, Date.now().toString())                // 업로드할 이미지 파일명 작성하는 곳
    }
  })
})

router.get('/', async (req, res) => {
    res.render('myPage.ejs')
})

// 이미지 업로드 기능 구현
router.post('/upload', upload.single('profileImage'), function (req, res) { // upload.single 미들웨어가 aws s3로 이미지 업로드 시킴
   // s3에 업로드된 파일의 URL 가져오기
      const imageUrl = req.file.location;

      // 사용자 db 업데이트
      await UserModel.findOneAndUpdate(
        { email: req.user.email },
        { imageUrl : imageUrl },
        { new: true } // 이 옵션을 사용하면 업데이트 후의 문서를 반환함.
      );

      // 성공 응답
      res.status(200).json({ message: 'Image uploaded successfully' });
})

 

마지막으로 클라이언트에서 유저 이미지를 db에서 끌어와서 사용 할 수 있도록 my-page 라우터에서 user 데이터를 넘겨주고 예외처리 까지 끝내주자.

const { Router } = require('express');
const router = Router();
const UserModel = require('../db/model/userModel'); 
// 마이페이지 이미지 업로드 기능
const { S3Client } = require('@aws-sdk/client-s3')
const multer = require('multer')
const multerS3 = require('multer-s3')

const s3 = new S3Client({
  region : 'ap-northeast-2',  // aws s3 region(서울 데이터센터에 저장)
  credentials : {
      accessKeyId : process.env.S3_KEY,          // aws에서 발급받은 엑세스 키
      secretAccessKey : process.env.S3_SECRET,   // aws에서 발급받은 시크릿 키
  }
})

const upload = multer({
  storage: multerS3({
    s3: s3,
    bucket: 'elicewebproject',                       // aws 버킷 이름
    key: function (req, file, cb) {
      cb(null, Date.now().toString())                // 업로드할 이미지 파일명 작성하는 곳
    }
  })
})

router.get('/', async (req, res) => {
    const user = await UserModel.findOne({ email : req.user.email })
    res.render('myPage.ejs', { user : user } )		// 유저 정보를 넘겨줌
})

// 이미지 업로드 기능 구현
router.post('/upload', upload.single('profileImage'), async (req, res) => { 
  try {
    // s3에 업로드된 파일의 URL 가져오기
    const imageUrl = req.file.location;

    // 사용자 db 업데이트
    await UserModel.findOneAndUpdate(
      { email: req.user.email },
      { imageUrl: imageUrl },
      { new: true }
    );
    // 성공 응답
      res.status(200).json({ message: 'Image uploaded successfully' });

  } catch (err) {
      res.status(500).json({ message: 'Internal Server Error' });
    }  
  }
});

module.exports = router;

 

1. 유저가 이미지를 업로드 하여, 서버로 넘겨준다.

2. 서버는 이미지를 s3에 저장을 한다.

3. s3는 업로드 된 이미지를 URL 형태로 발급해준다.

4. 서버는 URL 형태로 발급된 이미지를 DB에 저장한다.

5. 서버는 DB에 저장된 이미지 URL을 유저에게 보여준다.

 

이제 마지막 5번 과정이 남았다. myPage.ejs 로 가서 imageUrl을 불러올 수 있도록 태그를 넣어주자.

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <link rel="stylesheet" href="/css/myPage.css">
</head>
<body>
	<div>
	  <% if (user.imageUrl) { %>
	   <img src="<%= user.imageUrl %>" alt="Profile Image">
	  <% } %>
	</div>
	<form action="/my-page/upload" method="POST" enctype="multipart/form-data">
		<input type="file" name="profileImage">
		<button type="submit">이미지 업로드</button>
	</form>
</body>
</html>

 

이제 유저는 자신이 업로드 한 이미지를 볼 수 있다!

 

이제 업로드를 구현했으니 삭제기능을 구현 해 보자.

 

삭제를 하기 위해선, 먼저 aws s3에서 이미지를 삭제 한 뒤, db에서 삭제하는 로직을 구현 해야 한다.

 

단, 삭제는 코드예시가 aws 공식홈페이지에 나와있다는 점. 아래 링크로 들어가면 삭제를 하기위한 코드 예시가 들어 가 있다.

https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/s3/command/DeleteObjectCommand/

 

AWS SDK for JavaScript v3

 

docs.aws.amazon.com

 

import { S3Client, DeleteObjectCommand } from "@aws-sdk/client-s3"; // ES Modules import
// const { S3Client, DeleteObjectCommand } = require("@aws-sdk/client-s3"); // CommonJS import
const client = new S3Client(config);
const input = { // DeleteObjectRequest
  Bucket: "STRING_VALUE", // required
  Key: "STRING_VALUE", // required
  MFA: "STRING_VALUE",
  VersionId: "STRING_VALUE",
  RequestPayer: "requester",
  BypassGovernanceRetention: true || false,
  ExpectedBucketOwner: "STRING_VALUE",
};
const command = new DeleteObjectCommand(input);
const response = await client.send(command);
// { // DeleteObjectOutput
//   DeleteMarker: true || false,
//   VersionId: "STRING_VALUE",
//   RequestCharged: "requester",
// };

 

다시 myPageRouter.js로 가서 위 코드를 그대로 내가 짠 코드에 적용 시켜보자.

 

허나, input에 있는 required 주석처리 된 bucket과 key를 제외 하곤 나머지 매개변수들은 특정 시나리오에서만 사용되며, 일반적인 객체 삭제 작업에서는 필요하지 않다.

 

이러한 매개변수는 주로 보안, 버전 관리, 또는 비용 청구와 관련된 고급 기능을 사용할 때 필요하다.

 

따라서 required 주석 처리가 된 bucket과 ket만 가져가겠다.

const { Router } = require('express');
const router = Router();
const UserModel = require('../db/model/userModel'); 
const { S3Client, DeleteObjectCommand } = require('@aws-sdk/client-s3') // DeleteObjectCommand 추가
const multer = require('multer')
const multerS3 = require('multer-s3')

const s3 = new S3Client({
  region : 'ap-northeast-2', 
  credentials : {
      accessKeyId : process.env.S3_KEY,          
      secretAccessKey : process.env.S3_SECRET,   
  }
})

const upload = multer({
  storage: multerS3({
    s3: s3,
    bucket: 'elicewebproject',                   
    key: function (req, file, cb) {
      cb(null, Date.now().toString())            
    }
  })
})

router.get('/', async (req, res) => {
    const user = await UserModel.findOne({ email : req.user.email })
    res.render('myPage.ejs', { user : user } )
})

router.post('/upload', upload.single('profileImage'), async (req, res) => { 
  try {
    const imageUrl = req.file.location;

    await UserModel.findOneAndUpdate(
      { email: req.user.email },
      { imageUrl: imageUrl },
      { new: true }
    );

      res.status(200).json({ message: 'Image uploaded successfully' });

  } catch (err) {
      res.status(500).json({ message: 'Internal Server Error' });
    }  
  }
});

// 업로드 한 이미지 삭제 구현
router.delete('/delete', async (req, res) => {
    const user = await UserModel.findOne({ email: req.user.email });
    if (user.imageUrl) {
      // S3에서 이미지 삭제
      const key = user.imageUrl.split('/').pop();
      const deleteParams = {
        Bucket: 'elicewebproject', //required
        Key: key //required
      };
      await s3.send(new DeleteObjectCommand(deleteParams));
  });
  
  module.exports = router;

 

기본 코드를 그대로 긁어와서 내가 짠 코드와 이름만 맞춰주었다.

 

'key'의 경우 aws s3 버킷 내에서 파일을 식별하는 식별자이다.

 

보통 파일을 삭제하거나 접근할 때 이 'key' 값을 사용하여 해당 파일을 정확히 식별하는데, 이 'key'는 파일 경로의 마지막 부분, 즉 파일명을 의미한다.

 

key값을 추출하는 방법은 아래와 같다.

const key = user.imageUrl.split('/').pop();

 

예를 들어, imageUrl이 https://s3.amazonaws.com/elicewebproject/1234567890이라면,

split('/')은 ["https:", "", "s3.amazonaws.com", "elicewebproject", "1234567890"]이라는 배열을 반환한다.

 

.pop()은 배열의 마지막 요소를 반환하므로, 파일 이름 또는 경로의 마지막 부분을 반환하게 된다. 위 예제에서는 1234567890이 된다.

 

이렇게 bucket에서 key를 통해 이미지를 aws s3에서 삭제 했으니, 이제 db에서 이미지 url을 제거 해 주면 된다. 

// 업로드 한 이미지 삭제 구현
router.delete('/delete', async (req, res) => {
    const user = await UserModel.findOne({ email: req.user.email });
    if (user.imageUrl) {
      // S3에서 이미지 삭제
      const key = user.imageUrl.split('/').pop();
      const deleteParams = {
        Bucket: 'elicewebproject', //required
        Key: key //required
      };
      await s3.send(new DeleteObjectCommand(deleteParams));
      
      // DB에서 이미지 URL 제거
    await UserModel.findOneAndUpdate(
      { email: req.user.email },
      { imageUrl: '' },
      { new: true }
    );
  });

 

이로써 s3와 db에서 모두 이미지를 삭제 했다! 이제 마지막으로 예외처리만 해주자.

const { Router } = require('express');
const router = Router();
const UserModel = require('../db/model/userModel'); 
const { S3Client, DeleteObjectCommand } = require('@aws-sdk/client-s3') // DeleteObjectCommand 추가
const multer = require('multer')
const multerS3 = require('multer-s3')

const s3 = new S3Client({
  region : 'ap-northeast-2', 
  credentials : {
      accessKeyId : process.env.S3_KEY,          
      secretAccessKey : process.env.S3_SECRET,   
  }
})

const upload = multer({
  storage: multerS3({
    s3: s3,
    bucket: 'elicewebproject',                   
    key: function (req, file, cb) {
      cb(null, Date.now().toString())            
    }
  })
})

router.get('/', async (req, res) => {
    const user = await UserModel.findOne({ email : req.user.email })
    res.render('myPage.ejs', { user : user } )
})

router.post('/upload', upload.single('profileImage'), async (req, res) => { 
  try {
    const imageUrl = req.file.location;

    await UserModel.findOneAndUpdate(
      { email: req.user.email },
      { imageUrl: imageUrl },
      { new: true }
    );

      res.status(200).json({ message: 'Image uploaded successfully' });

  } catch (err) {
      res.status(500).json({ message: 'Internal Server Error' });
  }
});

// 업로드 한 이미지 삭제 구현
router.delete('/delete', async (req, res) => {
  try {
    const user = await UserModel.findOne({ email: req.user.email });
    
    if (!user.imageUrl) {
      return res.status(404).json({ message: 'Image not found' });
    }    

    // S3에서 이미지 삭제
    const key = user.imageUrl.split('/').pop();
    const deleteParams = {
      Bucket: 'elicewebproject',
      Key: key,
    };

    await s3.send(new DeleteObjectCommand(deleteParams));

    // DB에서 이미지 URL 제거
    await UserModel.findOneAndUpdate(
      { email: req.user.email },
      { imageUrl: '' },
      { new: true }
    );

    // 성공 응답
      res.status(200).json({ message: 'Image deleted successfully' });

  } catch (err) {
      res.status(500).json({ message: 'Internal Server Error' });
    }
  }
});

  
  module.exports = router;

 

api를 모두 만들었으니, 유저가 삭제할 수 있도록 myPage.ejs로 가서 간단하게 삭제 폼을 만들어주자.

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <link rel="stylesheet" href="/css/myPage.css">
</head>
<body>
	<div>
	  <% if (user.imageUrl) { %>
	   <img src="<%= user.imageUrl %>" alt="Profile Image">
	  <% } %>
	</div>
	<form action="/my-page/upload" method="POST" enctype="multipart/form-data">
		<input type="file" name="profileImage">
		<button type="submit">이미지 업로드</button>
	</form>
	<form action="/delete?_method=DELETE" method="POST">
		<button class="delete">이미지 삭제</button> 
	</form>
</body>
</html>

 

이제 유저는 성공적으로 이미지를 업로드 하고 삭제 할 수 있게 되었다.

 

다음으로는 리팩토링 한 과정을 다뤄보겠다.