본문 바로가기
TIL: Today I Learned

[TIL] 20201217 MongoDB / Mongoose

by 김알리 2020. 12. 17.

Database

Why Use DB

  • DB can handle large amounts of data effectively and store it compactly.
  • DB provide tools for easy insertion, querying, and updating of data.
  • DB generally offer security features and control over access to data.
  • DB (generally) scale well.

 

SQL vs. NoSQL Databases

SQL NoSQL
Structured Query Language Do not use SQL. (Newer, diverse group)
Relational DB : Can relate tables, often by referencing one another There are many types of NoSQL DB, including document, key-value, and graph stores.
Pre-define a schema of tables before any data is inserted More Flexible
EX) MySQL, Postgres, SQLite, Oracle, Microsoft SQL Server EX) MongoDB, CouchDB, Neo4j, Cassandra, Redis

 

MongoDB

  • A general perpose, document-based, distributed database.
  • Why Mongo
    • Mongo is very commonly used with Node anad Express
      • MEAN stack : Mongo-Express-Angular-Node
      • MERN stack : Mongo-Express-React-Node
    • Easy to get started with
    • Plays particularly well with JS
    • Strong community of developers using Mongo
  • Document ⊂ Collection ⊂ Database
  • Basic Mongo Shell Commends
    • db : prints current database
    • show dbs : prints a list of all databases on the server
    • use <db> : switch current database to <db>. If <db> doesn't exist, creates one.

 

BSON

  • Binary JSON
  • Issuses with JSON
    1. JSON can be slow since it is text-based. (Parsing text is very slow)
    2. JSON's readable format is not space-efficient.
    3. JSON supporst a limited number of basic data types.
  • BSON looks like JSON, but MongoDB stores it as binary, making it to be parsed much more quickly.

 

Inserting 

  • If the collection doesn't exist when a DB is inserted, insert operations will create the collection.
  • Documents don't have to follow the same pattern.
  • Syntax : db.collection.insert();
  • Example
db.cats.insert({name: 'Monji', age: 3}); //collection 'cats' created
db.cats.find(); //gives {"_id" : ObjectId("String"), "name" : "Monji", "age" : 3}

//"_id" : Primary key created by Mongo. 
//ObjectId : A particular type in Mongo

 

Finding

  • Selects document sin a collection or view and returns a cursor to the selected documents.
  • Syntax
    • db.collection.find() : find all the documents from the collection.
    • db.collection.find(query, projection
  • Example
db.dogs.find({"catFriendly" : true})
//Finds all the dogs that are cat friendly.

 

Updating

  • Select document and change the contents
  • Syntax
    • db.collection.updateOne(<filter>, <update>, <options>)
    • db.collection.updateMany(<filter>, <update>, <options>)
    • <filter> : query
    • <update> : $<update operator>: {key : value}
    • Can use multiple update operators at once.
  • Example
db.cats.updateOne({name:"Monji"}, {$set: {age:5}})
//Monji's age changed from 3 to 5

 

Replace

  • Conpletely replaces the document with another one. (Primary key unchanged)
  • Syntax : db.collection.replaceOne(<filter>, <update>, <options>)

 

Delete

  • Syntax
    • db.collection.deleteOne(query)
    • db.collection.deleteMany(query)
  • Example
    • db.dogsdeleteMany({isAvailable: false})

 

Additional Mongo Operators

  • How to find nested properties
//Example

{
    "personality" : {"catFriendly": true, "childFriendly": true}
}

db.dogs.find({'personality.catFriendly': true});
  • Query Operators
    1. Comparison Operators
      • $gt : greater than
      • $gte : greater than or equal to
      • $in : in an array
      • $lt : less than
      • $lte : less than or equal to
      • $eq : equal to
      • $ne : not equal to
      • $nin : not in an array
    2. Logical Operators
      • $and
      • $not
      • $nor
      • $or
    3. Array Operators
      • $all : all arrays containing all queries inside.
      • $elemMatch : select documents if elements in the qrray field matches all the specified $elemMatch conditions.
      • $size : select documents if the array field is a specified size.
//Comparison Operators Example
db.inventory.find({qty: {$gt: 20}});
db.dogs.find({age: {$lt: 10}});
db.dogs.find({breed: {$in: ['Mutt', 'Corgi']}});

//Logical Operators Example
db.dogs.find({$or: [{'personality.catFreindly': true}, {'age': {$lte: 2}}]})

 

 

Mongoose

ODM

  • Object Data Mapper / Object Document Mapper 
    • ↔ ORM (for SQL) : Object Relational Mapper
  • Map documents comming from a database into usable JavaScript objects
  • [Mongoose] provides ways for us to model out our application data and define a schema. It offers easy ways to validate data and build comples queries from the comfort of JS.

 

What Mongoose does

  • Connects MongoDB & JS(Node.js)
  • Makes working with MongoDB easier.
  • Make Mongo data into JS objects.

 

How to connect Mongoose

  • Node : npm i mongoose
  • index.js
const mongoose = require('mongoose');

mongoose.connect('mongodb://localhost:27017/DBname', {useNewUrlParser: true, useUnifiedTopology: true});
    .then(() => {
        //code
    })
    .catch(e => {
        //code
        console.error(e);
    })
    
//connect : returns a promise
//DBname : Name of DB. If it doesn't exist, it will be created.

 

Model

  • JS class that represents information in a Mongo database or collection.
  • "Models" the information coming back from MongoDB.
  • Helps interacting with information in the DB & sending new information to the DB, etc.
  • How to Model
    1. Creates a Schema
      • Each Schema maps to a MongoDB collection and defines the shape of the documents within the collection.
      • Maps collection keys from MongoDB to different types.
      • Schema Types : String, Number, Date, Buffer, Boolean, Mixed, ObjectId, Array, Decimal128, Map
    2. Pass the Schema into the Model
    3. Create a Document
//Example
//1. Create a Schema
const kittySchema = new mongoose.Schema({
    name: String
});

//2. Pass the Schema into the Model
const Kitty = mongoose.model('Kitty', kittySchema);
//First argument of mongoose.model has to be Capitalized & Singular
//Model name: Kitty, Collection name : kitties

//3. Create a Document
const yoji = new Kitty({name: "Yoji"});
yoji.save(); //saved to the DB

//in Mongo,
//use cats
//db.kitties.find() : gives "Yoji"

 

Model.insertMany()

  • Works like Mongo's insertMany()
  • Returns a promise (No need to save)
  • Example
Movie.insertMany([
    {title:'Amelie', year: 2001},
    {title:'Alien', year: 1979},
    {title:'The Iron Giant', year: 1999}
]).then(data => {
    console.log(data); //data: movie objects
}).catch(e => {
    console.error(e)
})

 

Model.find()

  • Returns Mongoose queries.
    • Not promises, but thenable objects
    • Have .then() function. 
  • Works like Mongo's find()
  • exec()
    • Returns promise.
    • Example
try {
    await Movie.fine({title: 'Amadeus'}).exec();
} catch (e => console.error(e))
  • Model.findById()
    • Syntax : await Model.findById(id).exec()

 

Update with Mongoose

  • Model.updateOne()
    • Updates the first one of matching documents
//Example

Movie.updateOne({title: 'Amadeus}, {year: 1984})
    .then(res => console.log(res)) //Updates Amadeus' year to 1984

  • Model.updateMany()
    • Update all the matching documents
  •  Model.findOneAndUpdate()
    • Syntax: Model.findOneAndUpdate(query, update)
    • options 
      • passed in as third argumetn
      • new: boolean. If true, returns the modified document, not the original.
      • Example : Model.findOneAndUpdate({title: 'Amadeus'}, {score: 7}, {new: true})

 

Deleting with Mongoose

  • Methods for Delete
    • Model.remove(query) : deprecated
    • Model.removeOne(query) : Doesn't give deleted document
    • Model.removeMany(query)
    • Model.findOneAndDelete(query) : Gives deleted document back

 

Mongoose Schema Validations

  • Schema values' properties. (SchemaType Options)
  • Criteria for input
  • Example
const kittySchema = new mongoose.Schema({
    name : {
        type : String //If not String, throws an error
        required : true //If name is not included, throws an error
    }
});

 

Schema Constraints

  • All Types
    • required
    • default : sets default value
  • String
    • lowercase/uppercase
    • trim
    • minlength/maxlength
    • enum : checks if the String input exists in the array
  • Number
    • min/max

 

Validating Mongoose Updates

  • When updating the data, Mongoose doesn't validate the data. Thus, invalid data might get stored.
  • To avoid this, set the option 'runValidators' as true.
  • Example
Product.findOneAndUpdate({name: 'true'}, {price: 19.99}, {new: true, runValidators: true});

 

Validation Error Message

  • The second argument of each validator is the error message.
  • It has to be String.
  • Example
const productSchema = new mongoose.Schema({
    price : {
        type: Number,
        required: true,
        min: [0, 'Price must be positive']
    }
})

 

Model Instance Methods

  • Adding custom methods to Schemas : Defining or adding functionality onto the model
  • Instance Method : Available on every single instance
  • Class/Static Method : available only from the class
  • How to define instance methods
    1. Define a Schema
    2. Assign a function to the "methods" object of the Schema
//Example1

let animalSchema = new Schema({name: String, type: String});
animalSchema.methods.findSimilarTypes = function(cd) {
    return mongoose.model('Animal').find({type: this.type}, cb);
}
//findSimilarTypes : name of method
//function has to be a traditional function, NOT an arrow function.


//Example2
productSchema.methods.addCategory = function (newCat) {
    this.categories.push(newCat);
    return this.save();
}
  • How to defind static methods
    • Assign a function to the "statics" object of the Schema
    • Or, call "Schema.static()"
//Example

animalSchema.statics.findByName= function(name) {
    //code that finds animal by its name
}

animalSchema.static('findByBreed', function(breed) {
    //code that finds animal by its breed
})

productSchema.statics.fireSale = function() {
    return this.updateMany({}, {onSale: true, price: 0})
}

 

Mongoose Virtuals

  • Gives the ability to add properties to a Schema that doesn't actually exist in the database itself.
  • get method : Reads the database to return something else.
  • set method : Update the database using the virtual from get method.
// Example

const personSchema = new mongoose.Schema({
    first: String,
    last: String
})

personSchema.virtual('fullname').get(function() {
    return `${this.first} ${this.last}`
    //'fullname' doesn't exist in the database, but acts like a property.
}).set(function(v) {
    this.name.first = v.substr(0,v.indexOf(' ');
    this.name.last = v.substr(v.indexof(' ')+1);
    //When used for 'tom = {first: 'Tom', last: 'Cruise'}'
    //'tom.fullName = "Tom Hanks"'
    //updates the database to 'tom = {first: 'Tom', last: 'Hanks'}'
})

 

Mongoose Middleware

  • Also called pre and post hooks.
  • functions that are passed control during excution of asynchronous functions.
//Example

personSchema.pre('save', async fucntion() {
    console.log('About to save'); //runs before save()
})

personSchema.post('save', async function() {
    console.log('Just saved'); //runs after save()
})

 

 

 

 

 

 

* This post is a summary of Udemy Course "The Web Developer Bootcamp" by Colt Steele.