본문 바로가기
내일배움 강의/강의- Node.js 입문, 숙련

입문 2주차 3 mongoose

by GREEN나무 2024. 11. 21.
728x90

2.3 mongoose

1. MongoDB의 구성 요소를 알아봅니다. 
2. Express.js에서 Mongoose ODM을 적용해봅니다.

 

01. 코드에서 MongoDB 적용하기

1) 내 코드에서 MongoDB에 연결하려면 뭘 해야 할까요?

이제부터 API에 MongoDB를 연결해서 데이터를 주고 받아볼 예정입니다.
이를 위해 JavaScript 코드에서도 DB Client 역할을 하는 무언가가 있어야 데이터베이스에 연결을 할 수 있는데요, 우리는 mongoose 라는 도구를 이용해 데이터베이스에 연결할 예정입니다.

2) mongoose라는 라이브러리의 개념

mongoose는 MongoDB에 데이터를 쉽게 읽고 쓰게 해주는 JavaScript 라이브러리입니다. 

mongoose를 ODM(Object Document Mapper)이라고도 부릅니다.
ODM(Object Document Mapper)이란, JavaScript의 객체(Object)와 MongoDB의 문서(Document) 사이에서 ‘매핑’을 수행하는 도구입니다.

Mongoose는 JavaScript 객체(Object)와 MongoDB 문서(Document) 간의 변환을 도와주는 ODM으로, 데이터를 손쉽게 저장, 조회, 및 관리할 수 있도록 지원합니다.


3) mongoose 설치

1 주차에 학습하던 spa-shop 프로젝트를 VS Code로 열어주세요!
기존 프로젝트에서 터미널을 열어 아래와 같이 입력해 mongoose를 설치 할 수 있습니다.

yarn add mongoose

mongoose와 관련된 yarn 설치

dependencies가 추가되었습니다.


4) mongoose의 문서(Document)란?


RDBMS(관계형 DB) vs MongoDB

MongoDB에서 가지고 있는 각 데이터 하나하나를 문서(Document)라고 정의합니다.
Document는 1개 이상의 Key-Value의 쌍으로 이루어져있습니다.
JSON 형식으로 구성되어있습니다.
아래의 문서(Document)는 _id와 name이라는 2개의 Key를 가지고 있습니다.

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

 

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

 

컬렉션(Collection)은 여러개의 문서(Document)를 보유할 수 있는 MongoDB의 구성요소입니다.
JSON 형식의 여러가지 문서(Document)를 보유할 수 있습니다.
컬렉션(Collection)은 고정된 구성요소가 존재하지 않고, 유연하게 구성할 수 있습니다.
이후에 설명할 관계형 데이터베이스(RDB)의 Table과 동일한 역할을 합니다.

 

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

스키마(Schema)는 컬렉션(Collection)에 들어가는 문서(Document)가 어떤 종류의 을 가질 것인지 정의하기위해 사용합니다.
스키마(Schema)는 데이터의 구조와 어떤 제약 사항을 가지는지 정의하기 위해 사용하며, 일반적으로 데이터를 모델링할 때 사용합니다. 
스키마(Schema)는 어떤 필드가 있어야 하는지, 필드는 어떤 데이터 타입을 가져야 하는지를 정의합니다.
아래는 사용자(Users) 정보를 정의한 스키마의 예시입니다.

const UsersSchema = new mongoose.Schema({
  name: String, // 문자열 타입입니다.
  age: Number, // 숫자 타입입니다.
  favorites: [String], // 문자열 배열 타입입니다.
  createdAt: { type: Date, default: Date.now }, // 날짜 타입입니다.
  someId: mongoose.Schema.Types.ObjectId // ObjectId 타입입니다.
});


대표적인 스키마의 타입은?

null : null 값과 존재하지 않는 필드
      ex: null
String : 문자열
      ex: "mongoDB"
Number : 숫자
       ex: 3.14
Date : 날짜
       ex: new Date()
Buffer : 파일을 담을 수 있는 버퍼, UTF-8이 아닌 문자열을 저장
       ex: 0x65
Boolean : true or false
       ex: true
ObjectId(Schema.Types.ObjectId) : 객체 ID, 주로 다른 객체를 참조할 때 넣음
       ex: ObjectId()
Array : 배열 형태의 값
       ex: ["a", "b", "c"]

 

 

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

모델(Model)은 데이터베이스에 데이터를 저장하고 읽어올 때 사용되는 데이터의 구조입니다.
스키마를 바탕으로 만들어지고, JavaScript의 객체와 MongoDB 간의 상호작용을 하기 위해 사용합니다.
MongoDB의 실제 데이터를 다룰 수 있는메서드를 지니고 있습니다.
만약, 사용자의 데이터를 저장하려면, 사용자(Users) 모델을 사용하여 데이터를 생성하고, 데이터베이스에 저장할 수 있습니다.

8) 웹 서버에서 MongoDB에 연결하기


우리가 만들 Directory Structure는 아래와 같아요!


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

 

 


이제 mongoose를 이용해 데이터베이스에 연결하도록 하겠습니다!
schemas 폴더를 생성하고, index.js 파일을 아래와같이 작성해주세요.
→ connect 내부에 들어가는 주소는 MongoDB Atlas에서 대여받은 주소로 변경해주세요!

app.js 파일에서 /schemas/index.js 파일에서 생성한 connect 함수를 실행해주세요!

/schemas/index.js 파일

더보기
// /schemas/index.js

import mongoose from "mongoose"; // mongoose를 가져옴(mongoose 설치 필요)

// mongoDB 연결 함수
const connect = () => {
  mongoose
    .connect(
      // mongoDB에서 가져온 URL
      "mongodb+srv://Introduction_nodejs:Introduction_nodejs@introduction-mongo.cewv5.mongodb.net/?retryWrites=true&w=majority&appName=Introduction-mongo",
      {
        dbName: "spa_mall", // spa_mall 데이터베이스명을 사용합니다.(DB 구현)
      }
    )
    .catch((err) => console.log(err)) // 에러코드 출력
    .then(() => console.log("몽고디비 연결 성공")); // 연결 성공 시 출력
};

// err 처리
mongoose.connection.on("error", (err) => {
  console.error("몽고디비 연결 에러", err);
});

// 연결 함수를 외부로 내보내기
export default connect;

 

app.js 추가 코드

// app.js
import connect from './schemas/index.js';

connect();
더보기
// app.js

import express from "express";
import goodsRouter from "./routes/goods.js";
import newsRouter from "./routes/news.js";
import connect from "./schemas/index.js";

const app = express();
const PORT = 3000; // 서버를 열 떄 사용할 포트 번호

connect(); // MongoDB를 연결하기 위한 커넥트 함수를 실행한다.

// Express에서 req.body에 접근하여, body 데이터를 사용할 수 있도록 설정하는 미들웨어

// json 형태로 서버에 body 데이터를 전달하면, req.body에 데이터를 변환하여 넣어줍니다.
// API 클라이언트를 사용 할 때 사용
app.use(express.json());

// form content type에서 body 데이터를 전달하면, req.body에 데이터를 변환하여 넣어줍니다.
// 프론트엔드 동료와 협업시 사용
app.use(express.urlencoded({ extended: true }));

// localhost:3000/api -> goodsRouter, newsRouter
app.use("/api", [goodsRouter, newsRouter]);

app.listen(PORT, () => {
  console.log(PORT, "포트로 서버가 열렸어요!");
});

app.js 파일을 실행하고, 아래와 같이 출력되면 MongoDB와 연결이 성공한 것입니다!

※ 'killall node'는 윈도우에서 사용 못하는 명령어입니다.

 


02. mongoose를 이용해 REST API 구현하기

1) 상품 모델 작성하기

아래는 mongoose를 사용하여 스키마를 정의하고 모델을 생성하는 기본 템플릿입니다
→ 해당하는 기본 템플릿을 바탕으로 상품(goods) 모델을 구현

import mongoose from 'mongoose'
// defaultSchema를 정의합니다. 
const defaultSchema = new mongoose.Schema({ // 1. 스키마 선언 시 new 사용
  defaultId: { // 이 필드는 실제 사용될 데이터의 이름입니다.
    type: Number, // 이 필드의 데이터 타입이 숫자임을 나타냅니다.
    required: true, // 이 필드가 반드시 있어야 함을 나타냅니다.
    unique: true // 이 필드의 값이 유일해야 함을 나타냅니다.
  }
});

// 2. defaultSchema를 사용하여 'Defaults'라는 이름의 mongoose 모델을 생성, export
export default mongoose.model("Defaults", defaultSchema);


[문제] 상품(goods)을 관리하는 스키마를 구현해주세요!

더보기

상품(goods) 스키마의 요구사항
1. /schema/goods.js 파일을 생성한 후 구현해주세요!
2. 스키마의 이름은 goodsSchema입니다.
3. 요구사항 구현이 완료되었으면, 해당하는 스키마를 ‘Goods’ 이름을 가진 mongoose 모델로 export 해주세요!


상품(goods) 스키마 요구사항 살펴보기
▶ goodsId
      숫자 타입입니다.
       필수 항목입니다.
       중복된 값을 허용하지 않습니다.
name
       문자열 타입입니다.
       필수 항목입니다.
       중복된 값을 허용하지 않습니다.
thumbnailUrl
       문자열 타입입니다.
category
       문자열 타입입니다.
price
       숫자 타입입니다.

// /schemas/goods.js
/**
 * 1. mongoose 가져오기
 * 2. 스키마 작성
 * 3. 스키마를 통해 모델 구현하기
 * 4. 모델 외부로 보네기
 */
import mongoose from "mongoose"; // 1. mongoose 가져오기

//  2. 스키마 작성   상품(goods)에 대한 정보를 나타내는 스키마를 정의합니다.
const goodsSchema = new mongoose.Schema({
  goodsId: {
    type: Number, // 상품의 고유 ID를 나타냅니다.
    required: true, // 필수 항목입니다.
    unique: true, // 중복된 값을 허용하지 않습니다.
  },
  name: {
    type: String, // 상품의 이름을 나타냅니다.
    required: true, // 필수 항목입니다.
    unique: true, // 중복된 값을 허용하지 않습니다.
  },
  thumbnailUrl: {
    type: String, // 상품의 썸네일 이미지 URL을 나타냅니다.
  },
  category: {
    type: String, // 상품의 카테고리를 나타냅니다.
  },
  price: {
    type: Number, // 상품의 가격을 나타냅니다.
  },
});

// 3, 4. 위에서 정의한 스키마를 이용하여 'Goods'라는 이름의 모델을 생성합니다. 모델 외부로 보네기
export default mongoose.model("Goods", goodsSchema);


2) 상품 생성 API 작성하기

상품 데이터를 데이터베이스에 추가하기
/routes/goods.js 파일을 아래의 코드스니펫으로 변경한 후 서버를 실행

// /routes/goods.js

import express from "express";
// Express.js의 라우터를 생성합니다.
const router = express.Router();

// 1. mongoose Goods 모델 가져오기
import Mongoose from "mongoose";
import Goods from "../schemas/goods.js";

// 2. API 구현
/** 상품 등록 **/
// localhost:3000/api/goods POST
router.post("/goods", async (req, res) => {
  // 3. 클라이언트로 부터 전달받은 데이터를 가져온다.
  // goodsId, name, thumbnailUrl, category, price
  const { goodsId, name, thumbnailUrl, category, price } = req.body;

  // 4. goodsId 중복되지 않았는지 검사
  // -> 실제로 MongoDB에 데이터르르 조회해서, 해당하는 MongoDB에 존제하는지 확인한다.
  const goods = await Goods.find({ goodsId: goodsId }).exec(); // 데이터 생성X 조회시 사용
  // await Goods.find()는 위에서 import Goods from "../schemas/goods.js";해서 가져온 파일에서 찾습니다.
  // await Goods.find({ goodsId: goodsId }).exec();
  /**
   * await를 통해서 실제로 존재하는 상품을 조회하는 동안 기다립니다.
   * Goods : 조회할 위치
   * .find({ 키:벨류 }) : find 명령어는 전체를 조회하는 명령어기에 배열상태로 반환
   *          오류 확인 할 때 find로 조회한 배열에 값이 존제하는가로 확인할 것입니다.
   * 
   * .exec() :.exec()없이 await사용시 Promise가 정상적인 작동이 안될 수 있음.
   *    ctrl + .exec()클릭하면 'query.d.ts'파일에 
            // Executes the query 
            exec(): Promise<ResultType>;
   *    Promise(동기적)형태로 반환되기에 비 동기적인 것을 동기적으로 반환할 수 있습니다.
   */

  // 4-1. 만약, goodesId가 중복된 다면, 에러메시지 전달.
  if (goods.length) {
    // find로 반환된 배열에 값이 존제하는가로 확인
    return res
      .status(400)
      .json({ success: false, errorMessage: "이미 존재하는 데이터입니다." });
    /**
     * return res
     * .status(400) // 사용자 잘못이기에 400사용
     * .json({ success: false, errorMessage: "이미 존재하는 데이터입니다." });
     */
  }

  // 5. 상품(Goods) 생성
  const createdGoods = await Goods.create({
    // .create() : 생성
    goodsId: goodsId,
    name: name,
    thumbnailUrl: thumbnailUrl,
    category: category,
    price: price,
  });
  // 6. 생성된 상품 정보를 클라이언트에게 응답(Response)반환 한다.
  return res.status(201).json({ goods: createdGoods });
});

export default router;

 

코드스니펫

더보기
// /routes/goods.js

import express from 'express';
import Goods from '../schemas/goods.js';

// Express.js의 라우터를 생성합니다.
const router = express.Router();

/** 상품 등록 **/
// localhost:3000/api/goods POST
router.post('/goods', async (req, res) => {
  const { goodsId, name, thumbnailUrl, category, price } = req.body;

  const goods = await Goods.find({ goodsId }).exec();
  if (goods.length) {
    return res
      .status(400)
      .json({ success: false, errorMessage: '이미 존재하는 데이터입니다.' });
  }

  const createdGoods = await Goods.create({
    goodsId,
    name,
    thumbnailUrl,
    category,
    price,
  });

  return res.status(201).json({ goods: createdGoods });
});

export default router;




API 작성이 완료되면 Insomnia로 상품 생성 API를 호출해보세요.

(서버가 열려있어야 합니다.)

1. HTTP method를 POST, URL은 http://localhost:3000/api/goods 를 입력해줍니다.
2.  Body에 추가할 정보값을 입력. 아래의 상품 등록 예시를 넣어서 Json 안에 넣어준뒤 Send 버튼을 눌러서 호출을 해봅시다.

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

1번째
2번째 Send - goodsId, name은 중복될 수 없습니다. -> 에러 발생


 


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

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