그동안 배웠던 node js 실습을 위해 미니 프로젝트를 진행하게 되었고, 컨셉은 쇼핑몰로 잡았다.

 

프로젝트 시작에 앞서, 구현이 필요한 기능들을 정리하였다. 스터디용으로 작성하였기 때문에 실제 쇼핑몰에서 제공하는 

 

다양한 서비스들을 완벽하게 구현하기에는 다소 무리가 있었기 때문에, '쇼핑몰' 이라고 부를 수 있는 필요한 필수적인 기능 위주로 구현하기로 했다.

 

그 중 나는 서버 API 를 만드는 역할을 맡았고, 필요한 기능은 아래와 같았다.

 

  1. 로그인 (판매자, 구매자 구별)
  2. 판매자용 기능
    - 상품 등록하기
    - 상품 조회하기 (판매자가 등록한 상품들만)
    - 상품 정보 (가격, 수량) 수정하기
    - 상품 삭제하기
  3. 구매자용 기능
    - 상품 전체 목록 조회하기
    - 상품 세부 정보 조회하기
    - 장바구니 관련 기능 ( 추가, 확인, 수정, 삭제)

    - 구매하기

본격적으로 기능 구현에 앞서, 기능 구현을 위해

 

  1. 데이터 베이스에 몇 종류의 테이블이 필요한지
  2. 각 테이블에 어떤 column 이 들어갈 지 도식화 하였다.

쇼핑몰 플랫폼 DB

 

로그인 기능 구현하기

 

로그인 기능은 이전에 한 번 구현해 본 경험이 있어 코드를 참고하여 어렵지 않게 구현할 수 있었다.

 

관련 자료 : https://chunws13.tistory.com/21

 

[Nodejs] 쿠키, 세션, jwt 로그인

로그인 기능을 구현할 때, 일반적으로 쿠키 혹은 세션을 기반으로 구현한다. 쿠키는 클라이언트 측에 저장되는 데이터로, 이것을 서버 측으로 전달하여 인증 및 상태 유지가 가능하다. 세션은

chunws13.tistory.com

회원 가입 단계에서 부터  기존 방식에 판매자 / 구매자를 구별할 수 있는 정보를 request 의 body 부분에 추가하는 방식으로 구현하였고

 

회원에 속성을 부여함으로써 구매자는 판매자 전용 API를 사용할 수 없으며, 그 반대의 경우도 접근을 막는 미들 웨어 구현의 토대가 되었다.

 

 

판매자용 API 만들기

판매자용 API - 상품 CRUD 기능을 만들기 위해 가장 앞서서 해야할 작업은 C (create) 라고 생각한다. 

 

일단 데이터가 생성이 되어야 읽거나, 수정을 하거나, 지울수 있기 때문이다. 다만, 본격적인 CRUD 작성에 앞서,

 

해당 기능은 판매자만 이용할 수 있어야 하므로, 판매자를 식별할 수 있는 미들웨어를 만들어 주고 시작하였다.

// checkSeller.js

module.exports = async(req, res, next) => {
    const { userType } = res.locals.user;
    if (userType !== "seller") {
        return res.status(400).json({ errorMessage: "판매자만 이용 가능한 서비스입니다." });
    }

    next();
};

해당 코드는 로그인 인증이 성공하고, 그 결과로 유저에 대한 데이터를 전달 받은 이후, 이 유저가 판매자가 맞는지 확인하는 코드이다.

 

일반적인 경우에는 판매자와 구매자가 쇼핑몰 플랫폼을 이용하는 성격이 완전히 다르기 때문에, 또는 추가적인 이유

 

(판매자의 판매 유지 능력 및 신뢰도 체크 등)로 두 유형의 유저를 구별하고 있으므로 우리도 이를 위해 위와 같은 코드를 작성하였다.

 

판매자 API - 상품 등록하기

판매자 상품 등록을 위해 Products 테이블에 총 7가지 정보를 채워야 한다.

 

prouctsId 는 데이터를  생성할 때, 자동 생성되므로 나머지 6가지 정보를 클라이언트로부터 받아서 등록해 주어야 한다.

 

// 상품 업로드
router.post('/seller/upload', checkLogin, checkSeller, async(req, res) => {
    try {
        const { userName, userType } = res.locals.user;
        const { productName, productAmount, productPrice, productLink } = req.body;

        if (!productName || !productAmount || !productLink || !productPrice) {
            return res.status(412).json({ errorMessage: "상품 정보를 정확하게 등록 해주세요" });
        }

        const existProcuct = await Products.findOne({
            attributes: ["productName"],
            where: { productName }
        });

        if (existProcuct) {
            return res.status(400).json({ errorMessage: "이미 사용중인 상품 이름입니다." });
        }

        await Products.create({ userId, productAmount, productLink, productPrice, productName, hits: 0 })
        return res.status(200).json({ message: "상품이 등록되었습니다.", userName, userType });

    } catch (error) {
        console.log(error)
        return res.status(400).json({ errorMessage: "상품 등록 실패" });
    }
});

상품 업로드에 앞서, 로그인 유무, 판매자 여부를 차례대로 확인한 후, 응답을 시작한다.

 

로그인 인증 때 유저 정보를 알아낼 수 있으므로, 상품 정보 (이름, 수량, 가격, 이미지 링크) 가 등록되었는지 확인한다.

 

만일 이 중 하나라도 입력되어있지 않은 상태로 들어오는 경우, 오류를 반환하고, 이미 등록된 상품 이름을 사용하는 경우에도 오류를 반환하도록

 

서비스 로직을 구현하였다. 위의 검증이 모두 끝나면, DB에 등록 유저 ID, 상품 이름, 수량과 가격, 링크를 저장하고, 완료메세지와 함께

 

유저 정보를 반환하도록 구성하였다. 이 때, 초기 등록이므로 조회수는 0으로 처리하였다.

 

그리고 위의 검증에 해당하지 않는 오류들 (잘못된 데이터 타입으로 요청하는 등)에 대해서는

 

상품 등록 실패 메세지를 반환하도록 하였다.

 

판매자 API - 상품 조회하기

// 판매자별 등록 상품 조회
router.post('/seller', checkLogin, checkSeller, async(req, res) => {
    try {
        const { userName, userType } = res.locals.user;

        const userProducts = await Products.findAll({
            attributes: ["productsId", "productName", "productAmount", "productPrice", "productLink", "hits", "createdAt"],
            where: { userId },
            order: [
                ["createdAt", "DESC"]
            ]
        });

        return res.status(200).json({ userProducts, userName, userType });

    } catch (error) {
        return res.status(400).json({ errorMessage: "페이지 로드 실패" });
    }
});

해당 로직은 판매자가 상품을 조회하는 경우 판매자가 등록한 상품만 조회할 수 있도록 하는 로직이다.

 

이는 상품 수정 및 삭제를 위해 내가 등록한 상품 파악이 선행되어야 하기 때문에 필요한 기능이다.

 

상품 등록하기 API와 마찬가지로, 유저 로그인 여부, 판매자 여부를 확인한 후, 확인된 유저의 ID에 해당하는 데이터를

 

Products 테이블 내에서 조회 후 등록 역순으로 반환한다. (최신 데이터가 상단에 위치할 수 있도록)

 

이 때, Products 테이블의 모든 필드를 불러오도록 했다.

 

해당 기능이 POST로 되어있는 이유는 프로젝트 마무리 단계에서 Axios 를 통한 GET 요청 시 Body 데이터를 포함할 수 없음을 인지하지 못하여서 

 

수정하였다. 당시에는 시간이 부족하여 요청 방식을 수정하고, 확인하기에 충분한 시간이 주어져있지 않았으므로, 일단 서버에서 POST 요청을 받아

 

로직을 처리하는 것으로 마무리했다. 일반적인 경우에는 토근 인증은 request header에 담아 처리하기 때문에, request Body 데이터가 없다면

 

GET 요청으로 처리 가능하다.

 

판매자 API - 상품 수정하기

// 상품 수량 & 금액 수정
router.put('/seller/:productsId', checkLogin, checkSeller, async(req, res) => {
    try {
        const { userName, userType } = res.locals.user;
        const { productsId } = req.params;
        const { newAmount, newPrice } = req.body;

        await Products.update({ productAmount: newAmount, productPrice: newPrice }, {
            where: { productsId }
        });

        return res.status(200).json({ message: "상품이 수정되었습니다.", userName, userType });

    } catch (error) {
        console.log(error)
        return res.status(400).json({ errorMessage: "상품 수정 실패" });
    }
})

(인증 과정 설명 생략) 위의 로직은 Url 파라미터로부터 수정할 상품의 ID를 받고, 수정할 상품의 수량과 가격 정보를 request Body 로 받아

 

해당 상품 ID를 기준으로 기존 상품 정보를 갱신 해준다.

 

판매자 API - 상품 삭제하기

// 상품 삭제
router.delete('/seller/:productsId', checkLogin, checkSeller, async(req, res) => {
    try {
        const { userName, userType } = res.locals.user;
        const { productsId } = req.params;
        await Products.destroy({
            where: { productsId }
        });
        
        return res.status(200).json({ message: "상품이 삭제되었습니다.", userName, userType });

    } catch (error) {
        return res.status(400).json({ errorMessage: "상품 삭제 실패" });
    }

})

상품 삭제 로직은 Url 파라미터로 상품 ID를 전달 받고, 삭제 처리를 진행한다.

 

상품이 없거나, 처리 중 오류가 발생하는 경우에는 (본인 소유가 아닌 상품 삭제 시도 등) 에러 메세지를 반환 하도록 했다.