Search
▪️

Working with Mongoose

What is Mongoose?

SQL 데이터베이스에서 SQL Code 없이 편리하게 쓸 수 있는 것을 ORM이라고 했다면, NoSQL에서의 해당 역할은 ODM (Object-Document Mapping Library)라고 한다.
차이점이라고 함은 Relational Database냐 Document Database냐의 차이다.
그렇다면 Mongoose와 같은 ODM이 해주는 역할은 무엇인가? → 데이터가 있을 때, 해당 데이터를 JSON 형태로 Mapping 시켜 Collection으로 쉽게 저장해주는 역할을 한다. (원래대로면 해당 Query를 직접 작성해야 했지만, ODM을 쓰면 Query보다는 데이터 자체와 데이터가 어떤 형태로 어떻게 작용하는지에 더 신경 쓸 수 있게 된다.)
이런 ODM의 Core Concepts는 Schemas & Models, Instances, Queries에 있다.

Connecting to the MongoDB Server with Mongoose

Mongoose의 설치는 별도 프로그램 설치 없이 Third Party Package를 설치하는 것만으로 이용할 수 있다. 해당 명령어를 통해서 설치 가능하다.
npm install —save mongoose
app.js에서 Mongoose의 Import가 됐다면, mongoose.connect('$mongoUrl').then().catch()를 통해서 서버를 구동한다.

Creating the Product Schema

Mongoose를 통해서 스키마를 생성하는 것은 다음과 같다. 생성하려는 모델의 Script에서 Mongoose를 Import하여 변수에 할당하고, mongoose.Schema를 새로운 Constant에 할당한다.
Schema라는 새로운 Constant를 만들었다면, Model 생성은 해당 Constant를 이용하여 생성한다. $modelName = new Schema()와 같이 생성하며, Schema Constructor의 인자로는 JSON 타입의 데이터가 들어가게 된다. 이 데이터는 일종의 설계도로 작동하게 되며, Key와 Key의 타입을 쌍으로 정의하게 된다.
그렇다면..! MongoDB는 Schemaless인 NoSQL이었는데, Schema 정의를 하는 이유는 무엇인가? → Mongoose는 엄격한 스키마에 종속되지 않는 유연성을 가졌지만, 적어도 특정 구조를 가진 데이터로 작업하기 때문에 이에 대해서 정의하게 되는 것이다. 따라서 실제로는 정의한 스키마에 따라서 데이터를 갖지 않아도 이용할 수 있다. 즉, Mongoose는 데이터로 작업하는데 있어서 해당 데이터의 구조를 잡는데 도움을 주는 것이다.
Mongoose를 이용하여 Schema를 정의할 때, Key와 Key의 타입을 쌍으로 주는 방법 이외에도 Key와 Object로 Schema 내부의 필드를 채울 수 있다. 만일 위와 같이 이용할 시, Object에는 Key의 타입과required에 대해서 정의할 수 있다. required를 true로 줄 시, NoSQL 특유의 Flexibility는 잃게 되고 해당 필드를 채우지 않으면 데이터를 이용할 수 없다.

Saving Data Through Mongoose

Mongoose도 Model을 통해서 작업하게 되는데, 이 Model은 Schema를 통해서 생성할 수 있다. 별도의 선언 필요 없이 Export할 때, Schema를 Function에 넣어서 Model로 만든 뒤 Export한다. 아래와 같이 한다.
module.exports = mongoose.model('$modelName', $schemaName)
해당 Module을 활용할 때는, 이전에 MongoDB를 썼을 때처럼 인자를 많이 넣을 필요가 없다. 오로지 JSON 형태의 데이터 한 개만을 인자로 받는다.
Sequelize와 마찬가지로 Mongoose도 Model에 대해서 save()와 같은 Method를 기본으로 제공한다.
save() Method를 통해서 데이터를 추가했을 때, 별도로 Collection Name을 지정하지 않았음에도 자동으로 Collection Name이 생성되는 것은 해당 이름이 Model을 기반으로 생성되기 때문이다. Mongoose에서 Model명을 보고 자동으로 복수로 바꾼 뒤, 소문자로 Collection을 생성하게 된다.

Fetching All Products

모든 항목들을 Fetch할 때, MongoDB에서는 find() Method를 이용했었고 해당 Method는 Cursor를 Return하는 Method였다.
Mongoose에서도 find() Method를 이용하기는 하나, Cursor을 Return하지는 않는다. 단순히 find()만 쓰더라도 Collection이 갖는 데이터를 모두 가져올 수 있다. 만일 Cursor를 받아야 하거나, Cursor의 다음 Element에 대해서 접근을 해야한다면, find().cursor().next()와 같은 것도 사용할 수 있다.
또한 find() Method로 Return 하는 데이터들은 자동으로 Array로 넘겨주기 때문에, 별도로 toArray()와 같은 Method를 쓰지 않아도 된다.
즉, Mongoose가 사용하는 find()관련 (findById()등...) Method들은 Full Mongoose Model이므로 그대로 사용이 가능하다.
** find() Method로 Filter를 하고 싶다면 Sequelize와 마찬가지로 {} 내에 조건으로 걸 인자를 넣는다. Sequelize와의 차이는 Key에 대한 Quotation Mark 존재 유무이다. Mongoose에서는 Key의 Path를 줘야하므로 Quotation Mark로 Key를 감싼다.

Fetching a Single Product

Mongoose는 Model에 대해서 findById()라는 Method를 기본으로 지원한다.
MongoDB를 쓸 때, ID 값으로 찾기 위해서 String을 ObjectId Type으로 바꾼 뒤 찾아야 했는데 이런 과정 역시 별도로 구현하지 않아도 Mongoose에서 자동으로 처리해주므로 String Value를 인자로 넘겨도 찾고자 하는 항목을 쉽게 찾을 수 있다.

Updating Products

Update시에는 MongoDB를 썼을 때처럼 Product Instance를 만들고 save() Method를 호출하는 것과 달리, 모든 데이터들 중에서 Update하려고 하는 항목을 찾아낸 후 then() Method Block에서 찾은 항목의save() Method를 Dotting으로 호출하면 Mongoose에서 Create의 save()가 아닌 Update의 save()로 인식하게 된다. 따라서 findById()로 찾은 항목을 then()내에서 값 변경 후 Model의 save()를 호출하면 된다.

Deleting Products

Mongoose에는 deleteById()라는 이름의 Method가 없다. 하지만 findByIdAndRemove()라는 Method가 있다.

Adding and Using a User Model

Schema 생성에 있어서 ObjectId와 같은 Type Definition이 필요하다면, Schema.Types.ObjecId와 같이 이용할 수 있다.
Array 타입임은 []로 나타낼 수 있다.
findOne()이라는 Method는 별도의 인자를 주지 않으면 가장 먼저 Hit된 Object를 Return하게 된다.

Using Relations in Mongoose

Schema 정의 시, 특정 필드가 Mongoose내에 있는 다른 Schema의 특정 필드 값으로 관련 되어 정의 된다면 이에 대해서 Reference를 둘 수 있다. 스키마 정의 시 {}에 ref라는 Configuration을 정의한다. ref에 주는 Key 값은 '$modelName'이 된다. 즉, 특정 필드가 다른 Model과 Related되어 있음을 알 수 있는 것이다.
이렇게 ref 설정을 해두면, Model에 대한 Instance를 생성할 때 인자로 값을 지정하지 않고 Model의 Object 통째로 넘겨도 알아서 인식할 수 있다.

One Important Thing About Fetching Relations

Mongoose에는 populate()라는 특별한 Method가 존재한다. find()와 같이 데이터를 찾아내는 Method뒤에 populate()라는 Method를 붙일 수 있고, populate() Method의 첫 번째 인자가 참조하고 있는 Model의 값들을 모두 불러와 찾아낸 데이터에 함께 나타낸다. populate()의 두 번째 인자의 역할로는, 참조하고 있는 Model의 데이터를 첫 번째 인자를 통해서 불러왔다면 이에 대해서 나타낼 필드들만 지정하여 나타낼 수 있게 한다.
참조하고 있는 Model에 대한 데이터를 가져오는 populate()외에도 단순히 특정 필드 값을 나타낼 것인지 배제할 것인지 정하는 Method도 있다. select() Method가 그 역할을 수행하며, 나타낼 필드는 그냥 쓰고 배제할 필드는 -를 붙여서 인자로 준다.
** populate() Method의 경우 find()와 같이 Promise를 Return하는 Method의 Chaining으로 쓰면 상관 없는데, populate()는 Promise를 Return 하는 Method가 아니므로 단순히 populate()만 쓰는 경우에는 반드시 execPopulate()이라는 Method를 호출 해주어야 정상적인 Promise Return이 가능하다.

Working on the Shopping Cart

어떤 Model이 특정 기능을 수행해야 한다고 하면, 해당 기능을 Method로써 추가할 수 있다. 추가하는 방법은 Schema를 정의할 때 Method를 같이 정의하는 것이다. 아래와 같이 선언하며, 반드시 function으로 정의해야 한다. function내에서 this 키워드는 스키마 자체를 가리킨다.
$schemaName.methods.$methodName = function() {}

Storing All Order Releatd Data

실제로 데이터들을 Fetch해올 때, 불필요한 많은 Meta Data들이 붙어서 나오게 된다. 이에 대해서 실질적으로 쓰이는 데이터들만 받아오고 싶다면, Spread Operator와 Mongoose가 제공하는 _doc라는 Dotting Operator를 이용하면 가능하다.
{}로 감싼 후, _doc으로 데이터만 불러온 뒤, Spread Operator로 요소들을 모두 펼쳐주면 된다. Meta Data들 없이 정확히 쓰이는 데이터들만 불러올 수 있게 된다.