토이프로젝트I 마이페이지에서 프로필사진 업로드 기능 구현하기

🔗 프로젝트 server폴더 모듈 구성

  • express
  • express-fileupload
  • path
  • cors
  • fs

📖 express-fileupload

express를 사용하는 서버에서 파일 업로드 기능을 쉽게 구현할 수 있도록 도와주는 라이브러리로 객체를 통해 업로드된 파일에 접근할 수 있고 mv 메서드를 통해 서버의 특정 디렉토리에 파일을 저장 할 수 있다.

📖 path

파일과 디렉토리 경로를 다루기에 유용한 도구를 제공하는 모듈로 path.dirname 으로 경로의 디렉토리 이름을 추출하고 path.join으로 경로를 결합하여 하나의 경로로 만들 수 있다.

📖 cors

Cross-Origin Resource Sharing의 약어

API를 호출 할 때 같은 출처로 호출을 해야 정상적인 데이터를 불러올 수 있는데 기본적으로 웹 브라우저는 보안상의 이유로 동일한 출처에서만 불러 올 수 있는 정책이 적용되어 있다.
cors 라이브러리는 다른 출처인 웹 애플리케이션에서 오는 요청을 서버가 접근하고 통신 할 수 있도록 허용할 수 있도록 설정한다. cors를 통해 클라이언트와 서버가 서로 다른 출처에 있을 때도 원활하게 통신할 수 있다.
즉 우리의 서버주소 "http://localhost:8080" 에서 실행중인 서버가 "http://localhost:5173" 에서 오는 요청을 혀용한다.

🔗 HTML

<div class="user-img"></div>
<button id="uploadButton"><span class="material-symbols-outlined">photo_camera</span></button>
<input type="file" id="profileInput" style="display: none;"> 

input은 보이지 않도록 display: none !!!

🔗 /src/profile.js

const getlocalStorage = JSON.parse(localStorage.getItem("userInfo"))

로그인 시 로컬스토리지에 사용자의 id와 email정보를 저장해둔 상태이고, 프로필 사진을 바꿀 사용자의 id값을 통해 users.json에 해당 id값을 갖고있는 사용자의 프로필 사진의 경로만 수정하기 위해 로컬스토리지에 저장된 값을 불러왔다.

document.getElementById("uploadButton").addEventListener("click", () => {
  document.getElementById("profileInput").click() // 버튼이 눌리면 input도 클릭되도록 이벤트
})
document.getElementById("profileInput").addEventListener("change", () => {
  profileEdit()
})

async function profileEdit() {
  const userImage = document.querySelector(".user-img")
  const fileInput = document.getElementById("profileInput")
  const file = fileInput.files[0]
  
  const res = await axios.get("/api/users.json")
  const users = res.data.data // json에 저장된 사용자 id 값 가져오기
  
  let userId = "" // 초기값
  for (let user of users) {
    if (user.email == getlocalStorage.userEmail) {
      userId = user.id // json에 저장된 사용자id 할당
    }
  }

  if (userId) {
    if (file) {
      const formData = new FormData() // 인코딩된 데이터로 전송하기 위해 formdata생성
      formData.append("profileImage", file) // key, value 할당

      fetch(`http://localhost:8080/profileImgs/${userId}`, { // API 호출
        method: "POST",
        body: formData, // POST방식으로 formData 보내기
      })
        .then((res) => res.text()) // 성공 이후 응답 출력
        .then(() => {
          userImage.setAttribute("style", `background-image : url("${user.profileImage}")`) 
        // 프로필이미지 저장 이후 바뀐 이미지 적용
        })
        .catch((error) => {
          console.error("Error:", error)
        })
    }
  }
}

🔗 /server/index.js

모듈, 라이브러리 불러오기

import express from "express"
import fs from "fs"
import fileUpload from "express-fileupload"
import path from "path"
import cors from "cors"

const port = process.env.PORT || 8080
const app = express()

app.use(cors())
app.use(fileUpload())
app.use(express.json())

마이페이지 프로필사진 업로드

// 프로필이미지 저장 경로 ("profileImgs"폴더)
const profileImgUploadPath = path.join(__dirname, "profileImgs") 

if (!fs.existsSync(profileImgUploadPath)) { // 경로가 없을 시에만 폴더 생성하기
  fs.mkdirSync(profileImgUploadPath)
}

const userJsonPath = path.join(__dirname, "data", "users.json") // 저장된 사용자의 데이터 파일 경로 선언

app.post("/profileImgs/:id", (req, res) => { // 프로필 이미지 업로드 
  const userId = parseInt(req.params.id) // 클라이언트 요청의 params를 통해 id 추출

  if (!req.files || Object.keys(req.files).length === 0) {
    return res.status(400).send("업로드된 파일이 존재하지 않습니다.")
  }

  const profileImage = req.files.profileImage // 요청받은 formData에서 profileImage 선언
  const uploadFilePath = path.join(profileImgUploadPath, profileImage.name) //업로드한 파일 경로 설정

  profileImage.mv(uploadFilePath, (err) => { // 받은 프로필이미지를 설정한 파일 경로로 이동
    if (err) {
      return res.status(500).send(err)
    }

    fs.readFile(userJsonPath, "utf8", (err, data) => { // users.json 읽어오기
      if (err) {
        return res.status(500).send("유저 정보를 불러오지 못했습니다.")
      }

      let users = JSON.parse(data)
      let userIdx = 0 // users.json에 저장된 사용자 인덱스 초기값
      if (Array.isArray(users.data)) {
        userIdx = users.data.findIndex((item) => { // 사용자 Id 값의 인덱스 할당
          return item.id === userId
        })
      }

      const oldProfileImagePath = users.data[userIdx].profileImage // 프로필이미지 수정 전 경로
      users.data[userIdx].profileImage = path.join("/server/profileImgs/", profileImage.name) 
      // 사용자 인덱스의 프로필이미지 경로를 새로운 이미지의 상대경로로 수정

      // users.json 파일 내용 변경
      fs.writeFile(userJsonPath, JSON.stringify(users, null, 2), (err) => {
        if (err) {
          return res.status(500).send("프로필 이미지 업데이트 실패")
        }

        // 수정 전의 이미지 경로가 존재한다면 삭제 처리
        if (oldProfileImagePath && fs.existsSync(oldProfileImagePath)) {
          fs.unlink(oldProfileImagePath, (err) => {
            if (err) {
              console.log("이전 프로필사진 삭제 실패 사유 :", err)
            }
          })
        }
        res.send("프로필 이미지 수정 성공.")
      })
    })
  })
})

app.listen(port, () => {
  console.log(`ready to ${port}`)
})

각 줄마다 console.log로 데이터를 하나씩 찍어보고 에러를 확인하며 코드를 수정했다..🫠

"profileImgs" 디렉토리에 저장된 이미지

바뀐 user.json의 사용자 데이터

profile
성장하는 과정에서 성취감을 통해 희열을 느낍니다⚡️

0개의 댓글

관련 채용 정보

Powered by GraphCDN, the GraphQL CDN