토이프로젝트I 마이페이지에서 프로필사진 업로드 기능 구현하기
express를 사용하는 서버에서 파일 업로드 기능을 쉽게 구현할 수 있도록 도와주는 라이브러리로 객체를 통해 업로드된 파일에 접근할 수 있고 mv
메서드를 통해 서버의 특정 디렉토리에 파일을 저장 할 수 있다.
파일과 디렉토리 경로를 다루기에 유용한 도구를 제공하는 모듈로 path.dirname
으로 경로의 디렉토리 이름을 추출하고 path.join
으로 경로를 결합하여 하나의 경로로 만들 수 있다.
Cross-Origin Resource Sharing의 약어
API를 호출 할 때 같은 출처로 호출을 해야 정상적인 데이터를 불러올 수 있는데 기본적으로 웹 브라우저는 보안상의 이유로 동일한 출처에서만 불러 올 수 있는 정책이 적용되어 있다.
cors 라이브러리는 다른 출처인 웹 애플리케이션에서 오는 요청을 서버가 접근하고 통신 할 수 있도록 허용할 수 있도록 설정한다. cors를 통해 클라이언트와 서버가 서로 다른 출처에 있을 때도 원활하게 통신할 수 있다.
즉 우리의 서버주소 "http://localhost:8080" 에서 실행중인 서버가 "http://localhost:5173" 에서 오는 요청을 혀용한다.
<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 !!!
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)
})
}
}
}
모듈, 라이브러리 불러오기
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의 사용자 데이터