그저 내가 되었고

항해99) 3주차:: 주특기 입문; 📚MongoDB → mongoose 본문

개발/항해99 9기

항해99) 3주차:: 주특기 입문; 📚MongoDB → mongoose

hyuunii 2022. 10. 3. 14:40

01. 준비하기

데이터베이스와 MongoDB의 개념

1) What is DB?

- DBMS: Database Management System: 단순히 데이터를 잘 저장하고 잘 찾기 위해 만들어진 소프트웨어

- server(DB server): DBMS가 설치된 서버 컴퓨터

- 흔히 "데이터베이스에 저장한다" 라고 말하면? 이 DBMS가 설치된 서버에 데이터를 저장한다고 말하는것

- 즉, DB 서버의 모든 데이터는 DBMS가 관리함

- DB의 종류

  • 관계형 데이터베이스 - Relational Database (RDB): 데이터 형식이 정해져 있고, 데이터 끼리 관계를 맺어 모순이 없는(무결성과 정합성이 높은) 데이터를 유지할 수 있도록 도와주는것에 집중한 데이터베이스
  • 비관계형 데이터베이스 - Non-relational Database (NoSQL): 관계형 데이터베이스에 속하지 않는 모든 데이터베이스. 데이터의 형태가 고정되어 있지 않고 유연하게 확장할 수 있음. 그러나 유연한 만큼 저장되는 데이터를 제대로 관리하지 않으면 데이터베이스에 저장된 데이터를 신뢰할 수 없게 되기도 함. 최근 많은 스타트업에서 유연한 설계를 위해 많이 채택되는 데이터베이스 유형

2) What is MongoDB?

- 국내, 외 수많은 개발자들에게서 사용되고있는 가장 인기있는 비관계형 데이터베이스(NoSQL) 중 하나

- 모든 데이터가 JSON 형태로 저장됨

- 복잡한 구조를 쉽게 저장할 수 있는 장점이 있음

- 무료

- 스케일 조정에 유리(쉽게 늘리고 줄일 수 있음)

 

3) 웹 서버 vs DB 서버

- 웹 서버: 웹 클라이언트가 원하는 데이터와 기능을 제공

- DB 서버: 데이터를 최대한 성능 좋게 저장하고 DB 클라이언트가 원하는 데이터를 제공

- IAN, 두 서버는 어떤것을 제공하는지만 다를 뿐 기본 원칙은 비슷함...

- 웹 서버는 DB 서버를 이용하는 DB 클라이언트가 될 수 있음~!! 브라우저-웹서버-DB서버 이런식으로 연결됨,,


02. 시작하기

MongoDB Client: Studio 3T 학습

1) Studio 3T:

API의 사용을 도와주는 API Client처럼 MongoDB를 위해서 만들어진 MongoDB Client(DB Client 종류 중 하나)!! Studio 3T의 GUI를 통해 MongoDB에 저장된 데이터를 관리하기 쉽게 보여준당

2) DB Client vs API Client:

서버에 연결해 데이터를 보내는것 까지는 같은 개념이지만 DBMS는 웹처럼 단순하지 않아 프로그램 사용법이 조금 더 복잡하고 DB의 데이터를 조회하거나, 관리할 수 있는 기능을 제공

3) 데이터 제어 명령어? Studio 3T 자체에서 쓰는 애들👇🏻👇🏻

  • db.collectionName.find({})
  • db.collectionName.insertOne({ key: "value", key2: "값" })
  • db.collectionName.deleteOne({ _id: ObjectId("...")})

 

코드에서 MongoDB 이용

1) 내 코드에서 MongoDB에 연결하려면 뭘 해야 하려나~?

: mongoose 설치해서 기기~ 설치는,, 개쉬움

 

2) 그냥 작업하던 플젝에서 터미널창에 npm install mongoose 입력하면 끗ㅋ

 

 

3) mongoose의 문서(document)?!

- MongoDB에서 가지고 있는 각 데이터 하나하나

- 1개 이상의 Key-Value의 쌍으로 이루어져있음.

{
    "_id": ObjectId("6682192a1c155bd2f27881"),
    "name": "lyw",
}

 

4) mongoose의 컬렉션(Collection)이란?

- JSON 형식의 여러가지 문서(Document)를 보유할 수 있음

- 이후에 설명할 관계형 데이터베이스(RDB)Table과 동일한 역할을 함

 

5) mongoose의 스키마(Schema)란?

- 컬렉션(Collection)에 들어가는 문서(Document)에 어떤 종류의 이 들어가는지를 정의

- 데이터를 모델링할 때 사용

- 대표적인 스키마 타입? null, string, number, date, butter, boolean, objectId(Schema.Types.ObjectId: 다른 객체를 참조할 때 넣음), array

 

6) mongoose의 모델(Model)이란?

- 데이터베이스에 데이터를 저장해줄때 데이터의 구조를 담당

- 스키마를 사용하여 만들고, MongoDB에서 실제 작업을 처리할 수 있는 함수들을 지니고 있음

- 문서(Document)를 생성할 때 사용

 

7) 웹 서버에서 MongoDB에 연결

- 우리가 만들 Directory Structure::

.
├── app.js
├── routes
│     ├── carts.js
│     └── goods.js
└── schemas   
        ├── index.js   
        ├── cart.js   
        └── goods.js

 

 - mongoose를 이용해 데이터베이스에 연결

/schemas/index/js 예시

더보기
const mongoose = require("mongoose");  //mongoose 라이브러리 가져와서 connect

const connect = () => {
    mongoose
        .connect("mongodb://localhost:27017/sparta_prac")  //여기에 연결하고 sparta_prac이란 db에 연결한다
        .catch(err => console.log(err));  //connect가 실패(몽고db에 연결 실패)하면 에러처리 진행(콘솔로그로 발생한 에러 보여주세요~!)
};

mongoose.connection.on("error", err => console.error("몽고디비 연결 에러", err));  //몽구스 커넥션 실패하면 콘솔 에러로 보여줌

module.exports = connect;  //현재 모듈에서 connect를 내보내줘서 밖에서 mongodb랑 연결하고 사용할 수 있게 해 줌

 

app.js 예시

더보기
const connect = require("./schemas");  //connect라는 변수로 require로 ./schemas의 모듈 갖고 올 것.
//노드에서는 모듈 갖고 올 때 폴더 이름까지만 지정해줘도 자동적으로 index 파일 갖고올 수 있음
connect();

 

8) 상품 모델 작성:mongoose를 더 편하게 사용하기 위한 방법! Schema를 생성하여 데이터를 관리하기 위해 모델을 작성.

/schemas/goods.js 예시

더보기
//실제 상품 모델 작성. 몽고디비에선 스키마를 작성한다고 함. 굿즈라는 파일에서 굿즈라는 스키마 작성.

const mongoose = require("mongoose");

const goodsSchema = new mongoose.Schema({  //몽구스에 스키마를 새롭게 정의한다는 뜻
    goodsId: {  //내부의 이렇게 하나하나 중괄호들이 들어가는 스키마!! goodsId 이런게 하나하나 키값인데,
    //얘들이 어떤 타입인지. 무조건 필요한지. 유니크한값 필요한지 하나하나 상세하게 알려줄 수 있음.
        type: Number,
        required: true,
        unique: true
    },
    name: {
        type: String,
        required: true,
        unique: true
    },
    thumbnailUrl: {
        type: String
    },
    category: {
        type: String
    },
    price: {
        type: Number
    }
});

module.exports = mongoose.model("Goods", goodsSchema);

 

9) 상품 생성 API 작성

- 상품 데이터를 코드에 넣어두는게 아닌, 데이터베이스에 추가할 수 있게 해봅시당

- 지금까지 만든 API는 상품 목록 조회, 개별 상품 조회 로 모두 조회하는 API!!

- 그러니 이제는 GET 메서드 뿐만 아니라 POST와 같은 메서드에 대응하는 API도 개발ㄱㄱ~

- REST API 에 따르면 새로운 데이터를 추가하는 method는 POST 를 쓰는 것을 권장

- POST 메소드의 특징은 GET 메소드와는 다르게 body 라는 추가적인 정보를 담아 서버에 전달 할 수 있기 때문에 정보값을 body라는 이름으로 넘겨줄 예정

- /app.js 예시

 + body로 전달 받은 JSON 데이터를 바로 사용할 수 없음!

 + Express.js에서 제공하는 JSON middleware를 사용해 body로 전달된 데이터를 사용할 수 있도록 해야함!

  ++주의점; middleware(app.use(express.json()); - app이란 객체에 use라는걸 호출해서 express.json()이란 미들웨어를 거치도록 하는 것. 결국은 전역 미들웨어를 하는 것.... 뭔소리야ㅆㅣ발)가 app.use("/api", [goodsRouter]) 보다 위에 작성되어야 함. 미들웨어는 순차적으로 거쳐가기 때문!!! 만약에 /api에 해당하는 router부터 가게 되면 Json middleware를 사용해 body로 전달된 데이터를 사용할 수 있도록 변경되지 않은 상태이므로 그러면 안됨안됨

- /routes/goods.js 예시

더보기
const Goods = require("../schemas/goods");  //스키마 선언한 굿즈 스키마 가져와서 굿즈라는 변수에 할당한 굿즈를 실제로 API 동작시 사용할 것
router.post("/goods", async (req, res) => {
    const {goodsId, name, thumbnailUrl, category, price} = req.body;  //post 메소드로 요청 했을 때 body에 데이터가 있었다면
    //있는 데이터를 객체 구조 분해 할당을 통해 가지고 오란 것(내부에 들어갈 데이터는 저렇게 goodsId부터 4개)
    const goods = await Goods.find({goodsId});  //데이터 조회는 find 명령어 씀. goodsId에 해당하는 값이
    //존재하는지에 대해서 확인한 후, 그 값이 있거나 없거나 무조건 goods란 변수에 할당.
    if (goods.length) {  //만약 goods란 변수에 데이터가 존재한다면
        return res.status(400).json({success: false, errorMessage: "이미 있는 데이터입니다."});  //만약 데이터 삽입 전 동일 데이터가
        //존재하는것을 확인 했었다면 status 400번 코드로, json 형식으로 반환할 것.
    }  //goods라는 데이터가 똑같이 존재하지 않는다면 goods란 스키마를 통해서 데이터를 생성할 것.
    //goodsId, name 등등 이런걸 통해서 데이터 생성하고 생성된 데이터를 createdGoods에 할당해서
    const createdGoods = await Goods.create({goodsId, name, thumbnailUrl, category, price});
    res.json({goods: createdGoods});  //res.json 형식으로 이런 goods가 생성되었다고 반환
});

 + API 작성이 완료되면 Thunder Client로 상품 생성 API를 호출해보세용

 + post에 http://localhost:3000/api/goods 적구 send 클릭클릭

 + 이번에는 Body에 추가할 정보값을 입력해보까요. 아래의 예시를 넣어서 Json Content 안에 넣어준뒤 Send 버튼을 눌러서 호출 기기

더보기
{
   "goodsId": 2,
   "name": "시원한 콜라",
   "thumbnailUrl": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRk7JqMw7ZYZP4ZW136wcoMTmLzbrMIJzUWb1Dhu9cHwCPp0gA&usqp=CAc",
   "category": "drink",
   "price": 3000
}

 

10) 저장된 데이터 Studio 3T로 확인

Success 메시지를 확인한 후 Studio 3T를 열어 hyuni_mall 데이터베이스를 새로고침 해보면 방금 입력한 데이터가 MongoDB에 들어간것을 확인할 수 이뜸


03. 장바구니 구현(1)

데이터베이스와 MongoDB의 개념

1) 장바구니를 구현하기 위해서는 어떤 기능들이 필요할까염?

보통 이런 👇🏻게 필요해여

  1. 장바구니 목록 조회
  2. 장바구니에 상품 추가
  3. 장바구니의 상품 제거
  4. 장바구니의 상품 수량 수정

2) 장바구니 모델 작성

- 이제 상품을 장바구니에 담기 위한 모델을 작성해볼까유

- 어떤 데이터를 넣어야 할까여? 어떤 상품을 담았는지, 몇 개를 담았는지 알 수 있어야죠~!

- /schemas.cart.js 예시

더보기
const mongoose = require("mongoose");

const cartSchema = new mongoose.Schema({
  goodsId: {
    type: Number,
    required: true,
    unique: true
  },
  quantity: {
    type: Number,
    required: true
  }
});

module.exports = mongoose.model("Cart", cartSchema);

 

3) 장바구니 목록 조회 API 작성

- 첫째로 장바구니의 데이터를 찾아줌

- 그리고 장바구니 데이터베이스에는 goodsId와 quantity 정보밖에 담겨있지 않기 때문에 장바구니에 담겨있는 상품의 아이디에 맞는 상품 정보를 한번 더 찾아와 가져옴

- 아래처럼 나오는게 목적!

더보기
{
	"carts": [
		{
			"quantity": 10,
			"goods": {
		    "goodsId": 3,
		    "name": "시원한 콜라3333",
		    "thumbnailUrl": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRk7JqMw7ZYZP4ZW136wcoMTmLzbrMIJzUWb1Dhu9cHwCPp0gA&usqp=CAc",
		    "category": "drink",
		    "price": 3000
		  }
		},
		{
			"quantity": 3,
			"goods": {
		    "goodsId": 1,
		    "name": "시원한 콜라1",
		    "thumbnailUrl": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRk7JqMw7ZYZP4ZW136wcoMTmLzbrMIJzUWb1Dhu9cHwCPp0gA&usqp=CAc",
		    "category": "drink",
		    "price": 3000
		  }
		}
	]
}

 

- 그럼 routes/carts.js 파일을 생성해서 작성 시작해봅시당

/routes/carts.js 예시

더보기
const express = require("express");
const Goods = require("../schemas/goods");
const Cart = require("../schemas/cart");
const router = express.Router();

router.get("/carts", async (req, res) => {
  const carts = await Cart.find();
  const goodsIds = carts.map((cart) => cart.goodsId);

  const goods = await Goods.find({ goodsId: goodsIds });

  const results = carts.map((cart) => {
		return {
			quantity: cart.quantity,
			goods: goods.find((item) => item.goodsId === cart.goodsId)
		};
  });

  res.json({
    carts: results,
  });
});

module.exports = router;

 

/app.js 예시

더보기
const cartsRouter = require("./routes/carts");

app.use("/api", [goodsRouter, cartsRouter]);

 

- API가 잘 동작하는지 확인?! get으로 http://localhost:3000/api/carts 호출!~!

그러면,, Status는 200 이 나왔지만 Response 안에 carts에 빈 배열이 오는 것이 정상. 아직 장바구니에 아무것도 추가 안했으니까~!

더보기
{
  "carts": []
}

04. 장바구니 구현 (2)

1) 장바구니에 상품 추가 API 작성: 상품 추가 API와 거의 비슷하다용

- 구현하려면 어떤 값이 필요할까?! goodsId와 quantity~!

- 어떻게 구현해야할까!? POST 메서드와 /goods/:goodsId/cart 주소에 대응하는 API를면 되지!~

- 이후 받아온 :goodsId 값을 통해 이미 장바구니에 상품이 들어가 있는지 확인 후,

- 장바구니에 존재하면 수량만 수정하고,

- 존재하지 않는다면 새로운 카트에 상품정보를 새로 생성하도록 해보자!!!!

- /routes/goods.js 예시

더보기
const Cart = require("../schemas/cart");
router.post("/goods/:goodsId/cart", async (req, res) => {
  const { goodsId } = req.params;
  const { quantity } = req.body;

  const existsCarts = await Cart.find({ goodsId: Number(goodsId) });
  if (existsCarts.length) {
    return res.json({ success: false, errorMessage: "이미 장바구니에 존재하는 상품입니다." });
  }

  await Cart.create({ goodsId: Number(goodsId), quantity: quantity });

  res.json({ result: "success" });
});

- http://localhost:3000/api/goods/:goodsId/cart 여기 url에 수정을 원하는 :goodsId값을 넣어준 후 body에 quantity 값을 json 형태로 입력한 뒤 Post method로 요청 기기

더보기
{
  "quantity": 2
}

 

그 후 json 형태로 "result": "success" 메세지 잘 출력되면 Studi 3T에서 새로고침 하셈! carts document에 장바구니 데이터 저장된것을 확인 가능쓰~!

 

2) 장바구니의 상품 수량 수정 API 작성

- 장바구니에 상품 추가 API와 굉장히 비슷해여

- 하지만 이번에는 POST 메소드가 아닌 PUT 메소드를 사용해 데이터를 update 해줄검당

- URI는 추가 API와 같은 포맷을 가지고 있지만, 이번 API에서는 간단하게 goodsId를 이용해서 상품정보가 장바구니내에 존재한다면 body를 통해 받아온 quantity 값에 맞게 상품 수량을 수정해 줘봅시당

- /routes/goods.js 예시

더보기
router.put("/goods/:goodsId/cart", async (req, res) => {
  const { goodsId } = req.params;
  const { quantity } = req.body;

  const existsCarts = await Cart.find({ goodsId: Number(goodsId) });
  if (existsCarts.length) {
    await Cart.updateOne({ goodsId: Number(goodsId) }, { $set: { quantity } });
  }

  res.json({ success: true });
})

 

- http://localhost:3000/api/goods/:goodsId/cart에 put설정 후 :goodsId에 수량까지 body에 json으로 기입해서 send기기

-  success:true 뜨면 studio3T에서 쳌쳌

 

3) 장바구니의 상품 제거 API 작성

- 장바구니에 상품 추가 API 와 동일한 URI를 사용할거여

- 하지만 이번에는 간단하게 goodsId를 통해 상품이 장바구니 안에 존재한다면 장바구니 내에서 상품 정보를 삭제할껴

- /routes/goods.js 예시

더보기
router.delete("/goods/:goodsId/cart", async (req, res) => {
  const { goodsId } = req.params;

  const existsCarts = await Cart.find({ goodsId });
  if (existsCarts.length > 0) {
    await Cart.deleteOne({ goodsId });
  }

  res.json({ result: "success" });
});

↳데이터를 지울 때는 보통 DELETE method 를 사용(당연한거 아님...? 암튼...;;) 지우는 행동에서는 URL 만으로 지워야할 리소스를 명시하는데 충분하다는 이유로...

 

- http://localhost:3000/api/goods/:goodsId/cart에서 delete 메소드 통해 호출하면 success 메세지 뜨고 Studio3T에서 삭제된 것 눈으로 확인 가능쓰