Post

mongo db를 사용해보자

⭐ Mongo DB

과거에 프론트엔드 공부를 하면서 몇 번 마주쳤던 Mongo DB, 사실 그 때는 지식이 많이 부족한 상태에서 사용하려고 하니 사용하는 것이 너무 어려워서 개인적으로는 조금 더 쉬운 Firebase를 사용했었다.

이번에 개인 프로젝트를 진행하면서 db를 사용하고 싶은데, 어떤걸 사용해볼까 하다가 그 때 잠시 미뤄둔 Mongo DB가 생각나서 이를 사용해보고자 한다.

Mongo DB는 대표적인 NoSQL 데이터베이스로, BSON 형식을 사용한다

BSON 또는 Binary JSON은 MongoDB가 데이터를 구성하고 저장하는 데 사용하는 데이터 형식

🌟 Mongoose

Mongoose (이하 몽구스)는 Node.js에서 Mongo DB를 쉽게 다루기 위해 사용하기 위한 ODM라이브러리다.

ODM이란 Object Document Mapper의 약어로, 자바스크립트 객체를 Mongo DB 문서로 사용하게 해주며, 반대로 Mongo DB 문서를 자바스크립트 객체처럼 사용하게 해주는 도구를 뜻한다.

몽고 DB는 기본적으로 스키마가 없지만, 몽구스를 이용해 스키마를 정의하고 모델을 생성하며, JS 객체처럼 DB를 조작할 수 있게 된다.

🍎 populate

mongoDB는 JOIN 기능이 없기 때문에 이와 비슷한 역할을 해줄 populate를 사용한다.

Schema 부터 시작해보자.

먼저 users Collection(Table)에 저장될 Document(개별 데이터) 의 구조를 정의하기 위해 Schema를 작성한다.

1
2
3
4
5
6
const userSchema = new Schema({
  email: String,
  nickname: String,
})

const User = mongoose.model("User", userSchema)

만약 post에서 userId에 users Collection에 저장된 내용을 담아오고 싶다면 반드시 ref를 작성해야 한다.

1
2
3
4
5
6
7
8
9
10
const postSchema = new Schema({
  userId: {
    type: Schema.Types.ObjectId,
    ref: "User", //이 ObjectId는 User 모델의 _id임을 나타냄
  },
  title: String,
  content: String,
})

const Post = mongoose.model("Post", postSchema)

posts.userId에 저장된 ObjectId 값과 users._id 값이 일치하는 Document를 기준으로 populate가 실행된다.

1
Post.find().populate("userId")

정상적으로 데이터가 찾아진다면 결과는 다음과 같다.

{
  _id: ObjectId("64f000bbb222bbb222bbb222"),
  title: "첫 번째 글",
  content: "내용입니다",
  userId: {
    _id: ObjectId("64f000aaa111aaa111aaa111"),
    email: "example@example.com",
    nickname: "짱구는 못말려"
  }
}

🧡 용어 정의

🔸 MongoDB의 구조 계층

1
2
3
4
Cluster
 └── Database
      └── Collection
           └── Document

1. Cluster

Mongo DB가 실행되는 서버

2. Database

1번 Cluster(서버)에 존재하는 데이터 공간.

3. Collection

데이터 집합. RDBMS의 Table과 거의 동일함 관례상 복수형 소문자를 사용

1
2
3
// 블로그 서비스 예시 (Collections)
posts
users

4. Document

Collection 안의 개별 데이터 하나. 각 Document는 _id 를 가진다.

ObjectId는 MongoDB가 각 Document를 구분하기 위해 자동으로 만드는 고유 ID 타입

1
2
3
4
5
6
{
  _id: ObjectId,// 자동 생성
  createdAt: Date,
  userId: ObjectId,// users._id 참조
  content: String
}

🔸 Mongoose의 주요 개념

1. Schema

Document (개별 데이터 하나)의 구조, 피드, 타입, 제약 조건 등을 정의 Mongo DB에는 스키마가 없기 때문에 Mongoose를 이용해 스키마를 작성하게 됨

2. Model

특정 Collection 하나를 전담하여 다루는 객체. Schma를 기반으로 생성되어, 전담하는 Collection의 CRUD를 진행하게 됨

⭐ Mongo DB와 CRUD

🍊 Create

주로 사용되는 메소드는 다음과 같다.

  • insertOne() → 단일 문서 생성
  • insertMany() → 여러 문서 생성

1️⃣ insertOne()

1
2
3
4
5
await db.collection("users").insertOne({
  email: "coco@example.com",
  nickname: "coco",
  createdAt: new Date(), // 문서 생성 시각
})
  • collection("users"): users 컬렉션에 접근 (없으면 자동 생성됨)
  • insertOne(): 문서 1개를 DB에 삽입

_id 필드를 명시하지 않으면 MongoDB가 자동으로 ObjectId 생성한다.

2️⃣ insertMany()

1
2
3
4
5
await db.collection("posts").insertMany([
  { title: "1일차!", content: "첫 날 일기" },
  { title: "2일차!", content: "둘째 날 일기" },
  { title: "3일차!", content: "셋째 날 일기" },
])

모든 document에는 _id가 자동 생성된다.

insertMany의 주요 옵션에는 ordered가 있다.

1
2
3
await db.collection("posts").insertMany(docs, {
  ordered: false,
})
1
2
ordered: true (기본값) 중간에 하나라도 실패하면 전체 중단
ordered: false 실패한 문서만 건너뛰고 나머지는 계속 삽입

🍊 Read

  • find()
  • findOne()

1️⃣ find()

1
2
3
4
5
const userList = await db
  .collection("users")
  .find({ role: "guest" })
  .sort({ name: 1 }) // 1: 오름차순, -1: 내림차순
  .toArray()

role이 guest인 값들을 찾아 sort 하고 배열로 내보내겠다는 의미로 위와 같이 메소드 체이닝을 통해 간단하게 데이터를 불러올 수 있다.

결과가 Cursor로 나오기 때문에 toArray()를 이용해 배열로 바꾸게 된다.

만약 일부 필드만 힐요할 경우 projection을 사용한다.

1
2
3
4
const users = await db
  .collection("users")
  .find({ role: "guest" }, { projection: { email: 1, nickname: 1 } })
  .toArray()

이 경우는 role이 user인 문서 중에서 email, nickname만 가져온다.

2️⃣ findOne()

조건에 맞는 문서 1개를 조회하며, 여러 개가 있어도 첫 번째 1개만 반환한다.

1
2
3
const user = await db.collection("users").findOne({
  email: "coco@example.com",
})

findOne은 toArray가 필요없다.

🍊 Update

  • updateOne()
  • replaceOne()
  • updateMany()

1️⃣ updateOne()

일부 필드만 수정할 때 사용한다.

updateOne을 사용할 때는 $set 를 꼭 사용해주어야 한다.

1
2
3
4
5
6
7
8
9
await db.collection("users").updateOne(
  { email: "coco@example.com" },
  {
    $set: {
      nickname: "newCoco",
      updatedAt: new Date(),
    },
  }
)

2️⃣ replaceOne()

문서 전체를 수정할 떄 사용한다.

1
2
3
4
5
6
7
8
9
await db.collection("users").replaceOne(
  { email: "coco@example.com" },
  {
    email: "coco@example.com",
    nickname: "coco",
    role: "user",
    createdAt: new Date(),
  }
)

_id를 제외하고 전부 수정되기 때문에 사용시 주의한다.

3️⃣ updateMany()

조건에 맞는 데이터를 한 번에 수정해준다.

updateMany를 사용할 때에도 마찬가지로 $set 를 꼭 사용해주어야 한다.

1
2
3
4
5
6
7
8
await db.collection("users").updateMany(
  { role: "guest" },
  {
    $set: {
      isActive: false,
    },
  }
)

해당 명령은 다음과 같다.

users 컬렉션에서 role 값이 guest인 모든 사용자 문서를 찾아서 그 문서들의 isActive 값을 false로 변경한다.

1
2
3
4
5
6
7
8
await db.collection("users").updateMany(
  {},
  {
    $set: {
      updatedAt: new Date(),
    },
  }
)

처음에 문서를 필터링 할 때 {} 를 사용했는데, 의미는 users 컬렉션에 존재하는 모든 문서에를 선택하는 것이며 이에 대해 updatedAt 필드를 현재 시간으로 설정한다는 의미이다.

🍊 Delete

  • deleteOne()
  • deleteMany()
1
2
3
await db.collection("users").deleteOne({
  _id: new ObjectId(userId),
})

deleteOne은 조건과 일치하는 문서 중 하나만 삭제한다. 보통 _id기반 삭제에 활용한다.

1
2
3
await db.collection("users").deleteMany({
  role: "guest",
})

deleteMany는 조건에 일치하는 모든 문서를 삭제한다.

만약 특정한 조건 없이, 컬렉션을 전체 삭제해야 한다면 다음과 같이 빈객체를 호출한다.

1
await db.collection("users").deleteMany({})
This post is licensed under CC BY 4.0 by the author.