나의 개발 일지

수정과 삭제 기능 - URL에 params(path parameter)로 api 요청하고 응답받기

designer DK 2024. 10. 30. 15:04
728x90

이어서 상품의 수정 및 삭제 기능 작업을 진행했는데 상품의 경우 api로 주고받는 값들이 많아보니 CRUD를 구현했을 때 코드가 상당히 복잡하고 길어졌다. 그에따라 JS파일을 기능별로 페이지를 분할하는 작업도 병행했다.

 

모달관련 페이지, 일반 페이지, 겟, 포스트, 업데이트, 딜리트 이렇게 6개 페이지로 나누어보았다.

 

각 페이지에 대한 js소스는 html에서 하나로 통합된다.

<script src="/adminProductModal.js"></script>
<script src="/adminProductPost.js"></script>
<script src="/adminProductPut.js"></script>
<script src="/adminProductDelete.js"></script>
<script src="/adminProduct.js"></script>
<script src="/adminProductCommon.js"></script>
<script src="https://widget.cloudinary.com/v2.0/global/all.js" type="text/javascript"></script>

 

기본적으로 수정과 삭제 기능은 수정이나 삭제를 하려는 특정 아이템 하나를 선택을 해서 기능을 실행해야하는데 그러기 위해서 해당 아이템의 _id 고유값을 확인할 수 있어야한다.

 

그리고 그 값은 목록으로 펼쳐져있는 아이템들로부터 얻을 수 있기 때문에 고유 아이디를 얻는 작업은 get 부분에서 이루어졌다.

 

수정의 경우 꽤 번거로우면서 복잡한 부분이 기존 아이템 항목의 정보를 다 불러와야한다. 그래야 그 정보를 수정할 수 있으니까.

 

 

따라서 모달이 열렸을 때 모든 필드에 다시 수정 정보들을 가져오는 작업이 진행되고

이어서 수정된 정보를 저장하는 버튼을 누르면 매개변수를 통해 고유 아이디 값을 editProduct 함수로 전달하는 작업이 이루어졌다.

// 상품 수정 모달 열기
const itemEdit = document.getElementById(`item-edit${i}`);

itemEdit.addEventListener("click", () => {

console.log("수정할 아이템", item);

// 인풋 필드에 값 설정
document.getElementById("item-sku").value = item.sku;
document.getElementById("item-name").value = item.name;
document.getElementById("item-description").value = item.description;
document.getElementById("item-price").value = item.price;
document.getElementById("item-status").value = item.status;

// 이미지 미리 보기 설정
document.getElementById("image-container").innerHTML = `<img src="${item.image}" alt="Uploaded Image">`;
imgData = item.image;

// 카테고리 값 설정
document.getElementById("item-category").value = item.category;
categoryArray = item.category;

// 기존 재고 정보 표시
stockContainer.innerHTML = "";

Object.keys(item.stock).forEach((size, index) => {
const stockInputs = `
<div class="stock-inputs" id="stock-data${index}">
<span>
<label>사이즈</label>
<input type="text" class="stock-input" id="item-size${index}" value="${size}">
</span>
<span>
<label>수량</label>
<input type="number" class="stock-input" id="item-qty${index}" value="${item.stock[size]}">
</span>
<button id="stock-delete${index}">삭제</button>
</div>
`;
stockContainer.insertAdjacentHTML("beforeend", stockInputs);

// 재고 삭제 기능 설정
document.getElementById(`stock-delete${index}`).addEventListener("click", () => {
console.log("삭제?")
document.getElementById(`stock-data${index}`).remove();
updateStockValues();
});
});
// 수정 버튼 클릭 시 모달 표시
modal.style.display = 'flex';

// 수정 완료 버튼 클릭 시 editProduct 함수 실행해서 수정 요청
const editBTN = document.getElementById("edit-item-btn");
editBTN.addEventListener("click", () => {
editProduct(item._id);
});
});

 

여기서 특이한 부분은 Object.keys() 부분인데 이 기능을 쓰면 인자로 넣어준 객체의 키 값들을 배열로 반환한다.

예를들어 지금 item.stock 객체가 { L:2, M:2, S:5 } 이라면 [ L, M, S ] 로 반환하는 것이다.

 

이렇게 나온 사이즈 배열 값들을 forEach를 통해서 html 인풋을 채워주고 그에 대응하는 qty 값들은 item.stock[size] (키에 대한 값)으로 넣어주었다.

 

삭제 기능도 이와 같은 방식인데 비교적 쉽게 고유아이디만 매개변수로 deleteProduct 함수로 전달하면서 get 페이지 작업은 마무리.

// 상품 삭제
const itemDelete = document.getElementById(`item-delete${i}`);
 
itemDelete.addEventListener("click", () => {

deleteModal.style.display = 'flex';
 
const cancleBTN = document.getElementById(`cancle-btn`);
const deleteBTN = document.getElementById(`delete-item-btn`);

cancleBTN.addEventListener("click", () => {
deleteModal.style.display = 'none';
});
// 삭제 요청
deleteBTN.addEventListener("click", () => {
console.log(item._id);
deleteProduct(item._id);
});
});

 

 

다음은 수정페이지의 코드이다.

 

수정은 위에서 설명한 것 처럼 전체 아이템 중 딱 하나를 골라서 요청해야하기 때문에 그 값을 백엔드에 알려줘야한다. 그 방법으로 path parameter, 일명 params라는 곳에 id값을 넣어서 보내게 된다.

fetch(`${URI}/api/product/${editId}`

프론트엔드에서는 이걸 url주소 경로명으로 보내게되는데 그래서 path 파라미터라고 부르는 것 같다.

이런식으로 수정하고자하는 아이템의 고유 id값을 넣어서 요청하게되고 요청 방식은 PUT이다.

수정이나 삭제는 꽤 중요한 권한이기에 헤더에 토큰 정보도 같이 전송해서 인증된 사용자만 가능하도록 설정.

그리고 body에는 각 인풋들의 밸류를 원래 항목 변수들에 재 대입 시켜서 내용 업데이트를 요청하는 방식이다.

function editProduct(editId) {
// PUT 요청으로 데이터 전송
fetch(`${URI}/api/product/${editId}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${sessionStorage.getItem("token")}`
},
body: JSON.stringify({
sku: document.getElementById("item-sku").value,
name: document.getElementById("item-name").value,
image: imgData,
category: document.getElementById("item-category").value,
description: document.getElementById("item-description").value,
price: document.getElementById("item-price").value,
stock: stockValues,
status: document.getElementById("item-status").value
}),
})
.then((response) => response.json())
.then((data) => {
console.log("PUT response:", data);
if (data.status === 'fail') {
// 오류 처리
document.getElementById("error-msg").innerHTML = `<p>${data.message}</p>`;
} else {
// 성공 시 처리
modal.style.display = 'none';

updateStockValues();

console.log("수정 완료!");

// 수정된 내용을 반영하기 위해 상품 목록 새로 고침
getProducts(currentPage);
}
})
.catch((error) => console.error("Error:", error));
}

 

 

딜리트도 비슷한 방식.

function deleteProduct(deleteId) {

fetch(`${URI}/api/product/${deleteId}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${sessionStorage.getItem("token")}`
},
}).then((response) => response.json())
.then((data) => {
console.log("DELETE response:", data);

getProducts(currentPage);
});

deleteModal.style.display = 'none';
};

 

 

이 요청을 응답하는 백엔드도 살펴보면

router.put("/:id", authController.athenticate, authController.checkAdminPermission, productController.updateProduct);
router.delete("/:id", authController.athenticate, authController.checkAdminPermission, productController.deleteProduct);

 

라우터를 통해서 수정, 삭제요청에 대해 응답하는 각 컨트롤러로 보내게 되는데, 둘 다 특정하게 선택한 아이템에 대해서 실행되어야 하기 때문에 아까 프론트에서 params로 요청 받은 id 값을 라우터로도 /:id처리를 해서 해당 컨트롤러에서 그 고유아이디 값을 받아서 처리할 수 있도록 해준다.

그리고 위에서 말한대로 인증된 사용자만 가능하도록 auth관련 미들웨어들도 중간에 배치해준다.

 

컨트롤러를 살펴보면

//상품 수정
productController.updateProduct = async (req, res) => {
try {
const productId = req.params.id;
const { sku, name, image, category, description, price, stock, status } = req.body;

const product = await Product.findByIdAndUpdate(
{ _id: productId },
{ sku, name, image, category, description, price, stock, status },
{ new: true }
);
if (!product) {
throw new Error("상품이 없습니다.");
};
res.status(200).json({ status: "ok", data: product });
} catch (error) {
res.status(400).json({ status: "fail", message: error.message });
};
}

 

프론트엔드로부터 해당 아이템에 대한 고유 아이디 값을 req.params.id로 불러오고 업데이트 된 body 정보들도 불러온다.

가져온 고유 아이디가 있다면 findByIdAndUpdate 라는 기능으로 아이디를 통해 값들을 업데이트 시킬 수 있다.

 

삭제기능도 이와 비슷하다.

//상품 삭제
productController.deleteProduct = async (req, res) => {
try {
const deleteItem = await Product.findByIdAndDelete(req.params.id);
res.status(200).json({ status: "ok", data: deleteItem });
} catch (error) {
res.status(400).json({ status: "fail", message: error.message });
};
};

 

 

 

이번 상품 정보 관련 CRUD 작업을 해보면서 이런저런 오류로 우여곡절도 많았는데 프론트-백엔드 간 api 개념이 많이 정리된 것 같다. 특히 queryString, params, body 등 정보를 보내는 방식의 차이들을 명확하게 알게 된 계기가 되었다. 그와 더불어 cloudinary와 같은 멋진 서비스도 알게되어서 여러모로 많이 배운 케이스가 되겠다.