Introduction to MongoDB
Published: 2021-11-22 08:17:05

#1 Introduction to MongoDB
MongoDB is a NoSQL Database. 
Instead of storing data in the form of table and rows, in mongoDB data in stored in the form of objects.
Mongoose is ODM, which makes it easier to communicate with MongoDB.

In this series we will learn about 
MongoDB and Mongoose
CRUD application 
and a testing framework called Mocha.


#2 Installing MongoDB Locally & connecting to MongoDB
1. `npm init` -- to track the dependencies.
2. Install mongoose `npm install mongoose --save`
3. Connecting to database: Mongoose do not automatically knows about database. we've to tell it explicitly.
```
//connection.js
const mongoose = require('mongoose');

mongoose.connect('mongodb://localhost/testaroo');

mongoose.connection.once('open', function(){
    console.log('Connection is made');
}).on('error', function(error){
    console.log('Connection error', error);
})
```


#3 Models and Collection
File: models/marioChar.js
```
const mongoose = require('mongoose');

const Schema = mongoose.Schema;

const MarioCharSchema = new Schema({
    name: String,
    weight: Number
})

const MarioChar = mongoose.model('mariochar', MarioCharSchema)

module.exports = MarioChar;

// Now we can do the following
// var myChar = new MarioChar({ name: "Rayan", weight: 100 });
```


#4 Introduction to Mocha testing
Mocha is a testing framework.
We use it to perform within our application
To make sure everything works correctly
First install Mocha `npm install mocha --save`
Keep the test script `demo_test.js` to a folder `/test`.
demo_test.js
```
const mocha = require('mocha');
const assert = require('assert');

describe('Some demo tests', function(){
    //Create tests
    it('adds two numbers together', function(){
        assert(2+3===5)
    })
})
```
Set the `test` property to `mocha` in `package.json`
and run `npm run test`


#5 Saving data to MongoDB
```
const mocha = require('mocha');
const assert = require('assert');
const MarioChar = require('../models/mariochar');

describe('Some demo tests', function(){

    //Create tests
    it('Saves a record to the database', function(done){
        var character = new MarioChar({
            name: "Mario",
        })
        character.save().then(function(){
            assert(character.isNew === false)
            done();
        })
    })
})
```
```
character.save().then(function(){
        assert(character.isNew === false)
        done();
})
```
The above function saves the `character` in the database.
`character.isNew` returns `true` when the object is created but not present in the database.
and returns `false` when the object is found in the database.
As `save` is an asynchronous function, to perform next tests we have to explicitly tell when it is finished through `done` method.

### Using ES6 promise instead using mogoose's own promise
```
const mongoose = require('mongoose');

// Using ES6 promise instead mogoose's promise
mongoose.Promise = global.Promise 

// Connect to mongodb
mongoose.connect('mongodb://localhost/testaroo');
mongoose.connection.once('open', function(){
    ...
}).on('error', function(error){
  ...  
})
```

At this point our test is being ran even before the connection is established.
To do that we have to put our connection code inside `before`
```
before(function(done){
     mongoose.connect('mongodb://localhost/testaroo');
    mongoose.connection.once('open', function(){
        console.log('Conneciton is made');
        done();
    }).on('error', function(error){
        console.log('Connection error', error);
    })
})
```
Note that we've also used **done** to explicitly tell when the connection is established.


#6 Dropping a Collection
To drop a collection:
`mongoose.connection.collections.mariochars.drop()`
note that we're writing `mariochars` (pluralized) and not `mariochar` but we've declared the collection as `mariochar`. This is the way mongodb works.

To drop the collection before every test so that the result of one test do not affect the other test by using `beforeEach`
```
const mongoose = require('mongoose');mongoose.Promise = global.Promise 
before(function(done){
    // Connect to mongodb
    ...
})
beforeEach(function(done){
    mongoose.connection.collections.mariochars.drop()
    done()
})
```


#7 Finding Records
To search we can use `find` or `findOne` method to the model.
```
MarioChar.findOne({ name: "Mario"}).then(result => {
    ...
})
```
This code, finds the first match where name==="Mario"
Our finding_test.js file is
```
const assert = require('assert');
const MarioChar = require('../models/mariochar');

describe('Some demo tests', function(){

    beforeEach(function(done){
        var character = new MarioChar({
            name: "Mario",
        })
        character.save().then(function(){
            assert(character.isNew === false)
            done();
        })
    })

    //Create tests
    it('Finding a record form the database', function(done){
        MarioChar.findOne({ name: "Mario"}).then(result => {
            assert(result.name === "Mario")
            done();
        })
    })
})
```


#8 ObjectId
To retrieve data by ID from the collection: 
```
MarioChar.findOne({ _id: character._id }).then(result => {
    assert(result._id === character._id)
    done();
})
```
But the above code is not going to work as 
`result._id===character._id` is going to return `false` as 
`result._id` is of type `ObjectID` and `character._id` is of type `String`. So we've to convert both of them to String and then compare.
`result._id.toString()===character._id.toString()`
will return `true`


#9 Deleting Record
Mongoose has 3 records to delete records
* `char.remove()`
* `MarioChar.remove()` Inside the bracket we are going to pass the options
* `MarioChar.findOneAndRemove()` Inside the bracket we're going to pass the options

Use of `findOneAndRemove()`
```
MarioChar.findOneAndRemove({ name:"Mario" }).then(function(){
            MarioChar.findOne({ name:"Mario" })
        })
```


#10 Updating Record
Various mongoose methods
* char.update()
* MarioChar.update()
* MarioChar.findOneAndUpdate()

```
// Saving the data
character = new MarioChar({
    name: "Mario",
    weight: 60
})
character.save().then(function(){
    ...
})

// Updating the data
MarioChar.findOneAndUpdate({ name:"Mario"}, { name:"new_name"}).then(function(){
    MarioChar.findOne({ name:"Mario" }).then(result => {
        ...
    })
})
```
The the new changes will be injected and this will not overwrite the complete objects. This update results to the object:
`{ name: "new_name", weight: 100 }` and not just `{ name: "new_name" }`


#11 Relational Data
Suppose, Author(name, age, books) and Book(title, pages) are two relations.
In the following way we construct it in MongoDB
```
const mongoose = require('mongoose');

const Schema = mongoose.Schema;

const BookSchema = new Schema({
    title: String,
    pages: Number 
});

const AuthorSchema = new Schema({
    name: String,
    age: Number, 
    books: [BookSchema] 
})

const Author = mongoose.model('author', AuthorSchema);

module.exports = Author;
```
`books: [BookSchema]` tells us that `books` property is an array where the array elements will be of the form `BookSchema`


#12 Nesting Documents
```
const mongoose = require('mongoose');
const assert = require('assert')
const Author = require('../models/author');

describe('Nesting Documents', function(){

    //Create tests
    it('Create an author with sub-documents', function(done){

        var pat = new Author({
            name: 'Patrick Ruffus',
            books:[{title:'Name of the wind', pages: 400}]
        });
        pat.save().then(function(){
            Author.findOne({name:'Patrick Ruffus'}).then(function(record){
                assert(record.books.length === 1);
                done();
            })
        })
    })

    it('Add a book to an existing author', function(done){

        var rus = new Author({
            name: 'Ruskin Bond',
            books:[{title:'The eyes have it', pages: 400}]
        });
        rus.save().then(function(){
            Author.findOne({name:'Ruskin Bond'}).then(function(record){
                record.books.push({title:"7 husband", pages:200});
                record.save().then(function(){
                    Author.findOne({ name: 'Ruskin Bond' }).then(function(record){
                        assert(record.books.length===2);
                        done();
                    })
                })
            })
        })
    })
})
```