{ Mongoose CRUD. }

Objectives:

By the end of this chapter, you should be able to:

  • Perform CRUD operations with Mongoose
  • Compare and contrast different CRUD methods on Mongoose models and documents

CRUD

Now that we have created a schema and a corresponding model, let's see what kinds of methods exist on our model to perform CRUD operations! Remember, CRUD stands for Create, Read, Update, and Delete.

Create

In mongoose, documents are instances of the model. There are two ways of creating documents, either by creating an object from the model using the new keyword, or through using the built in create method.

// invoke the constructor 
var elie = new Instructor({firstName: 'Elie' });
// call .save on the object created from the constructor (which is the model)
elie.save().then(function(){
    console.log('saved!');
}, function(err){
    console.log('Error saving!', err);
})

// OR

// invoke the create method directly on the model
Instructor.create({firstName: 'Elie'}).then(function(instructor){
    console.log('saved!');
}, function(err){
    console.log('Error creating!');
})

Read

There are quite a few ways to query for information in mongoose.

// finding multiple records
Instructor.find({}).then(function(instructors){
    console.log(instructors);
}, function(err){
    console.log('error!', err);
})

// finding a single record
Instructor.findOne({firstName: 'Elie'}).then(function(data){
    console.log(data)
}, function(err){
    console.log('error!', err);
})
// finding by id - this is very useful with req.params!
Instructor.findById(2).then(function(data){
    console.log(data)
}, function(err){
    console.log('error!', err);
})

// Finding in a nested object 
var query = Person.findOne({ 'name.first': 'Elie' });

// selecting the `name` and `occupation` fields
query.select('name occupation');

// execute the query at a later time
query.exec().then(function(person) {
    console.log(person);
}, function(err){
    console.log("ERROR!");
})

You can read more about querying here

Update

There are quite a few ways to update with mongoose as well. Here are some examples:

// update multiple records
Instructor.update({firstName:'Elie'}, {firstName:'Bob'}).then(function(data){
    console.log(data);
}, function(err){
    console.log('error!', err);
});

// update a single record
Instructor.findOneAndUpdate({firstName:'Elie'}, {firstName:'Bob'}).then(function(data){
    console.log(data);
}, function(err){
    console.log('error!', err);
});

// update a single record and find by id (very useful with req.params!)
Instructor.findByIdAndUpdate(1, {firstName:'Bob'}).then(function(data){
    console.log(data);
}, function(err){
    console.log('error!', err);
});

You can read more about each of these methods here:

http://mongoosejs.com/docs/api.html#model_Model.update

http://mongoosejs.com/docs/api.html#model_Model.findOneAndUpdate

http://mongoosejs.com/docs/api.html#model_Model.findByIdAndUpdate

Delete

As you might have guessed, there are also quite a few ways as well to remove a document with mongoose. Here are some examples:

// remove multiple records
Instructor.remove({firstName:'Elie'}, {firstName:'Bob'}).then(function(data){
    console.log(data);
}, function(err){
    console.log('error!', err);
});

// find and remove
Instructor.findOneAndRemove({firstName:'Elie'}).then(function(data){
    console.log(data);
}, function(err){
    console.log('error!', err);
});

// find by id and remove (very useful with req.params)
Instructor.findByIdAndRemove(1).then(function(data){
    console.log(data);
}, function(err){
    console.log('error!', err);
});

You can read more here about findOneAndRemove and findByIdAndRemove.

Now that we have seen how to use mongoose, let's add this logic to a CRUD application. Let's also think about a better folder structure! Let's start with our application in the terminal. We're going to build a simple CRUD application with pets as our resource. Let's start with our folder structure in terminal.

mkdir pets-app && cd pets-app
touch app.js
npm init -y
npm install express pug body-parser method-override mongoose 
mkdir views routes models
touch views/{base,index,new,edit,show}.pug
touch models/{index,pet}.js
touch routes/pets.js

Let's start with our models/pet.js:

var mongoose = require("mongoose");
var petSchema = new mongoose.Schema({
    name: String
})

var Pet = mongoose.model('Pet',petSchema);

module.exports = Pet;

Next, our models/index.js:

var mongoose = require("mongoose");
mongoose.set('debug', true);
mongoose.connect('mongodb://localhost/pets-app');
mongoose.Promise = Promise;

module.exports.Pet = require('./pet');

Now let's move to our routes/pets.js:

var express = require("express");
var router = express.Router();
var db = require('../models'); // by default, require will look for a file called index.js so we dont need ../models/index

router.get('/', function(req, res, next){
    db.Pet.find().then(function(pets){
        res.render('index', {pets});
    });
});

router.get('/new', function(req, res, next){
    res.render('new');
});

router.get('/:id', function(req, res, next){
    db.Pet.findById(req.params.id).then(function(pet){
        res.render('show', {pet});
    });
});

router.get('/:id/edit', function(req, res, next){
    db.Pet.findById(req.params.id).then(function(pet){
        res.render('edit', {pet});
    });
});

// instead of app.post...
router.post('/', function(req, res, next){
    db.Pet.create(req.body).then(function(pet){
        res.redirect('/');
    });
});

// instead of app.patch...
router.patch('/:id', function(req, res, next){
    db.Pet.findByIdAndUpdate(req.params.id, req.body).then(function(pet){
        res.redirect('/');
    });
});

// instead of app.delete...
router.delete('/:id', function(req, res, next){
    db.Pet.findByIdAndRemove(req.params.id).then(function(pet){
        res.redirect('/');
    });
});

module.exports = router;

Before we move onto our views, let's finish our app.js:

var express = require("express");
var app = express();
var methodOverride = require("method-override");
var morgan = require("morgan");
var bodyParser = require("body-parser");
var petsRoutes = require("../routes/pets");

app.set("view engine", "pug");
app.use(express.static(__dirname + "/public"));
app.use(morgan("tiny"));
app.use(bodyParser.urlencoded({extended:true}));
app.use(methodOverride("_method"));

app.use('/pets', petsRoutes);

app.get("/", function(req, res, next){
  res.redirect("/pets");
});

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

// error handlers

// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
  app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
      message: err.message,
      error: err
    });
  });
}

// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
  res.status(err.status || 500);
  res.render('error', {
    message: err.message,
    error: {}
  });
});

app.listen(3000, function(){
  console.log("Server is listening on port 3000");
});

Now let's move onto our views. First, here's views/base.pug:

<!DOCTYPE html>
html(lang="en")
head
    meta(charset="UTF-8")
    title Document
body
    block content

Now our views/index.pug:

extends base.pug

block content
    a(href="/pets/new") Create a new pet
    each pet in pets
        p `Name ${pet.name}` | a(href=`/pets/${pet.id}/edit`) Edit

Now our views/new.pug

extends base.pug

block content
    h1 Add a new pet!
    form(action="/pets", method="POST")
        input(type="text", name="name")
        input(type="submit", value="Add a Pet!")

Now our views/show.pug

extends base.pug

block content
    h1 Welcome to `${pet.name}'s` show page!

Finally, our views/edit.pug

extends base.pug

block content
    h1 Edit a pet!
    form(action=`/pets/${pet.id}?_method=PATCH`, method="POST")
        input(type="text", name="name", value=`${pet.name}`)
        input(type="submit", value="Edit a Pet!")
    form(action=`/pets/${pet.id}?_method=DELETE`, method="POST")
        input(type="submit", value="X")

Wrapping up and Example App

As you can see above, building these applications is a process that requires repetition to understand and become comfortable with. Take the time to build a few of these applications and see and debug errors as you go along!

You can see another sample CRUD app with Mongoose here.

When you're ready, move on to Mongoose Associations

Continue