Mongoose: Object Document Mapping for MongoDB
Mongoose
- Translating documents in a MongoDB to objects in the program
- Object Document Mapper for MongoDB
- ODM == Object Relational Mapping (Apache MyBatis, RedHat Hibernates for Java, Sequelize)
- Maps Structure of DB Objects to Application Objects
- It provides an abstraction for the basic CRUD and other operations on documents
- This reduces performance due to abstraction just like ORM
You can choose between MongoDB-native vs. Mongoose
MongoDB: 특징과 단점
- 특징:
- Schema-less
- Sharding
- document 기반
- Bad:
- 유효성 검증 (validating data)
- Indexing-memory
Mongoose: 특징과 단점
- 특징:
- 데이터의 입출력을 schema로 관리
- Additional abstraction layer, Model
- 자동적인 유효성 검증 (validating data)
- Queries use function chaining (more flexible)
- Bad:
- 구조체를 항상 사용해야 하므로 1개의 collection에 다양한 타입의 데이터 입력이 불가.
Mongoose에서는 모델을 정의할 때 명시적인 스키마를 사용하므로, 특정 필드에 대한 구조체를 명시할 때 그 구조를 따라야 합니다. 만약 특정 필드를 String 값으로 스키마에 지정해버린다면 그 필드는 int나, boolean 값을 집어넣을 수 없다는 것을 의미함.
- Native MongoDB 대비 낮은 performance
Mongoose Concept
- Schema
- an object that defines the structure of any documents that will be stored in your MongoDB collection
- enables you to define types and validators for all your data items.
- Connection
- a standard wrapper around a database connection.
- Model
- An object that gives you easy access to a named collection (an instance of a model)
- created by combining a Schema, a Connection, and a collection name.
- allowing you to query the collection and use the Schema to validate any documents you save to that collection
- Document
- an instantiation(인스턴스화) of a Model that is tied to a specific document in your collection.
Schema: the configuration object for a Mongoose model
import mongoose from 'mongoose';
const { Schema } = mongoose;
const blogSchema = new Schema({
title: String,
author: String,
body: String,
comments: [{ body: String, date: Date }],
date: { type: Date, default: Date.now },
hidden: Boolean,
meta: {
votes: Number,
favs: Number
}
});
- If you want to add additional keys later, use the
Schema#add
method.
blogSchema.add({phoneNumber: String })
- Most of the time, you’ll be interacting with models, rather than schemas, when working with Mongoose.
const Blog = mongoose.model('Blog', blogSchema);
const doc = new Blog();
SchemaType
- A Mongoose schema == the configuration object for a Mongoose model.
- A SchemaType == a configuration object for an individual property.
Model
- Models are fancy constructors compiled from Schema definitions.
- An instance of a model is called a document. Models are responsible for creating and reading documents from the underlying MongoDB database.
- includes features such as type casting, validation, query building, and more
- MongoDB는 Schemaless -> Schema를 정의하고자 하는 경우에 mongoose를 사용하자
- Create a model
- The first argument is the singular name of the collection your model is for.
- Mongoose automatically looks for the plural(복수형), lowercased version of your model name. Thus, for the example above, the model Tank is for the tanks collection in the database.
const schema = new mongoose.Schema({ name: String, size: String });
const Tank = mongoose.model('Tank', schema);
- If you create a custom connection, use that connection's model() function instead
const connection = mongoose.createConnection('mongodb://127.0.0.1:27017/test');
const Tank = connection.model('Tank', yourSchema);
CRUD with Model
const Tank = mongoose.model('Tank', yourSchema);
const small = new Tank({ size: 'small' });
await small.save();
await Tank.create({ size: 'small' });
await Tank.insertMany([{ size: 'small' }]);
- Note that no tanks will be created/removed until the connection your model uses is open. Every model has an associated connection. (mongoose.model())
- Validating
- Documents are casted(특정 형식으로 변환) and validated before they are saved.
- Querying
- retrieved using a model's find, findById, findOne, or where static functions.
await Tank.find({ size: 'small' }).where('createdDate').gt(oneYearAgo).exec();
- Deleting (deleteMany(), deleteOne())
await Tank.deleteOne({ size: 'large' });
- Updating
- Each model has its own update method for modifying documents in the database without returning them(documents) to your application.
await Tank.updateOne({ size: 'large' }, { name: 'T-90' });
Connection
- Mongoose fits into the stack inside the Express application by being the liaison(중재자) between the application and the database
- MongoDB only talks to Mongoose, and Mongoose in turn talks to Node and Express. A client-side application will not talk directly to MongoDB or Mongoose, but only to the Express application
client <-> express <-> mongoose <-> mongodb
- Buffering let you use the model immediately. However, there is a problem: Mongoose will not throw any errors by default if you use a model without connecting. You can disable buffering (
set('bufferCommands', false);
)
connect를 하자
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/test’);
Connection: Error Handling
- Error on initial connection
mongoose.connect('mongodb://127.0.0.1:27017/test').catch(error => handleError(error));
try {
await mongoose.connect('mongodb://127.0.0.1:27017/test');
} catch (error) {
handleError(error);
}
- Error after initial connection was established
mongoose.connection.on('error', err => {
logError(err);
});
Validation
- Mongoose has several built-in validators.
- All SchemaTypes have the built-in required validator. The required validator uses the SchemaType's check Required() function to determine if the value satisfies the required validator.
- Numbers have min and max validators.
- Strings have enum, match, minLength, and maxLength validators.
const bookSchema = new mongoose.Schema({
title: { type: String, required: true },
publisher: String,
date: { type: Date, default: Date.now },
onSale: Boolean,
price: Number
});
Validation Example
async function saveBook() {
const book = new Book({
publisher: "Oreilly Media",
onSale: false,
price: 59.99,
});
try {
const result = await book.save();
console.log(result);
} catch (err) {
console.log(err.message)
}
}
saveGame();
sangyoonoh $node validation.js
Book validation failed: title: Path `title` is required.
Middleware
- Middleware (also called pre and post hooks) are functions which are passed control during execution of asynchronous functions. Middleware is specified on the schema level.
- Document middleware is supported for the following document functions.(validate, save, remove, updateOne, deleteOne)
- Query middleware is supported for the following Query functions. Query middleware executes when you call exec() or then() on a Query object.
- All middleware types support pre and post hooks
- Pre middleware functions are executed one after another, when each middleware calls next. Before DB operation
- Post middleware are executed after the hooked method and all of its pre middleware have completed
const schema = new Schema({ });
schema.pre('save', function(next) {
next();
});
Populate
const author = new Person({
_id: new mongoose.Types.ObjectId(),
name: 'Ian Fleming',
age: 50
});
await author.save();
const story1 = new Story({
title: 'Casino Royale',
author: author._id
});
await story1.save();
Population
- Populating our story's author using the query builder:
const story = await Story.findOne({title: 'Casino Royale'}).populate('author').exec();
console.log('The author is %s', story.author.name);
Two Models
const mongoose = require('mongoose');
const { Schema } = mongoose;
const personSchema = Schema({
_id: Schema.Types.ObjectId,
name: String,
age: Number,
stories: [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});
const storySchema = Schema({
author: { type: Schema.Types.ObjectId, ref: 'Person' },
title: String,
fans: [{ type: Schema.Types.ObjectId, ref: 'Person' }]
});
const Story = mongoose.model('Story', storySchema);
const Person = mongoose.model('Person', personSchema);