13. Mongoose

김관주·2023년 12월 16일
0

웹시스템

목록 보기
13/22

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, // String is shorthand for {type: String}
    author: String, // “author” is the path of this field
    body: String,
    comments: [{ body: String, date: Date }],
    date: { type: Date, default: Date.now },
    hidden: Boolean,
    meta: { // path is meta.votes
        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); // mongoose.model(modelName, schema)
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

  • Create a document
const Tank = mongoose.model('Tank', yourSchema);
const small = new Tank({ size: 'small' });
await small.save();
// or
await Tank.create({ size: 'small' });
// or, for inserting large batches of documents
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.
// Updated at most one doc
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’); // return Promise resolves to this if succeeded

Connection: Error Handling

  • Error on initial connection
mongoose.connect('mongodb://127.0.0.1:27017/test').catch(error => handleError(error));
// Or:
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) { // do stuff
    next();
});

Populate

  • populate() lets you reference documents in other collections

  • Population is the process of automatically replacing the specified paths in the document with document(s) from other collection(s).

  • Saving refs

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 // assign the _id from the person
});
await story1.save();

Population

  • Populating our story's author using the query builder:
const story = await Story.findOne({title: 'Casino Royale'}).populate('author').exec();
// prints "The author is Ian Fleming"
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);

0개의 댓글