{ CRUD with Mongoose. }

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
const elie = new Instructor({ firstName: "Elie" });
// call .save on the object created from the constructor (which is the model)
elie
  .save()
  .then(newElie => {
    console.log(newElie);
  })
  .catch(err => {
    console.log("Error saving!", err);
  });

// OR

// invoke the create method directly on the model
Instructor.create({ firstName: "Elie" })
  .then(newInst => {
    console.log(newInst);
  })
  .catch(err => {
    console.log("Error creating!");
  });

Read

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

// finding multiple records
Instructor.find({})
  .then(instructors => {
    console.log(instructors);
  })
  .catch(err => {
    console.log("error!", err);
  });

// finding a single record
Instructor.findOne({ firstName: "Elie" })
  .then(inst => {
    console.log(inst);
  })
  .catch(err => {
    console.log("error!", err);
  });

// finding by id - this is very useful with req.params!
Instructor.findById(2)
  .then(inst => {
    console.log(inst);
  })
  .catch(err => {
    console.log("error!", err);
  });

// Finding in a nested object, using "query builder syntax"
const 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(person => {
    console.log(person);
  })
  .catch(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({}, { isHilarious: false })
  .then(insts => {
    console.log(insts);
  })
  .catch(err => {
    console.log("error!", err);
  });

// update a single record
Instructor.findOneAndUpdate({ firstName: "Elie" }, { firstName: "Bob" })
  .then(inst => {
    console.log(inst);
  })
  .catch(err => {
    console.log("error!", err);
  });

// update a single record and find by id (very useful with req.params!)
Instructor.findByIdAndUpdate(1, { firstName: "Bob" })
  .then(inst => {
    console.log(inst);
  })
  .catch(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({ isHilarious: false })
  .then(insts => {
    console.log(insts);
  })
  .catch(err => {
    console.log("error!", err);
  });

// find and remove
Instructor.findOneAndRemove({ firstName: "Elie" })
  .then(inst => {
    console.log(inst);
  })
  .catch(err => {
    console.log("error!", err);
  });

// find by id and remove (very useful with req.params)
Instructor.findByIdAndRemove(1)
  .then(inst => {
    console.log(inst);
  })
  .catch(err => {
    console.log("error!", err);
  });

You can read more here about findOneAndRemove and findByIdAndRemove.

Mongoose CRUD - Pets App

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:

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

const Pet = mongoose.model("Pet", petSchema);

module.exports = Pet;

Next, our models/index.js:

const mongoose = require("mongoose");
/*
 Database config, this can be placed in another file if you want
 just make sure it runs when your server starts
*/
mongoose.set("debug", true);
mongoose.Promise = Promise;
mongoose
  .connect(
    "mongodb://localhost/pets-app",
    {
      useMongoClient: true // this option is necessary for Mongoose 4.11 and up
    }
  )
  .then(() => {
    // once connected, give a success message
    console.log("Connected to MongoDB");
  })
  .catch(err => {
    // if something goes wrong let us know
    console.log(err);
  });

// exporting models from other files in our module.exports object
exports.Pet = require("./pet");

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

const express = require("express");
const { Pet } = require("../models");
const router = express.Router();

// all pets at /pets
router
  .route("") // this is equivalent to /pets
  .get((req, res, next) => {
    return Pet.find().then(pets => {
      return res.render("index", { pets });
    });
  })
  .post((req, res, next) => {
    return Pet.create(req.body).then(pet => {
      return res.redirect("/");
    });
  });

// create a new pet form
router.route("/new").get((req, res, next) => {
  return res.render("new");
});

// pets by ID routes /pets/:id
router
  .route("/:id")
  .get((req, res, next) => {
    return Pet.findById(req.params.id).then(pet => {
      return res.render("show", { pet });
    });
  })
  .patch((req, res, next) => {
    return Pet.findByIdAndUpdate(req.params.id, req.body).then(pet => {
      return res.redirect("/");
    });
  })
  .delete((req, res, next) => {
    return Pet.findByIdAndRemove(req.params.id).then(pet => {
      return res.redirect("/");
    });
  });

// edit or delete a pet form /pets/:id/edit
router.route("/:id/edit").get((req, res, next) => {
  return Pet.findById(req.params.id).then(pet => {
    return res.render("edit", { pet });
  });
});

module.exports = router;

Then in our routes/index.js:

exports.petRouter = require("./pets");

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

// npm packages
const bodyParser = require("body-parser");
const express = require("express");
const methodOverride = require("method-override");
const morgan = require("morgan");

// app imports
const { petRouter } = require("../router"); // ES6 object destructuring from the obj

// globals
const app = express();

app.set("view engine", "pug");

// middleware
app.use(morgan("tiny"));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(methodOverride("_method"));

// route handlers
app.use("/pets", petRouter);

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

// catch 404 and forward to error handler
app.use((req, res, next) => {
  const err = new Error("Not Found");
  err.status = 404;
  return next(err);
});

/* 
  error handler - for a handler with four parameters, 
  the first is assumed to be an error passed by another
  handler's "next"
 */
app.use((err, req, res, next) => {
  res.status(err.status || 500);
  return res.render("error", {
    message: err.message,
    /*
     if we're in development mode, include stack trace (full error object)
     otherwise, it's an empty object so the user doesn't see all of that
    */
    error: app.get("env") === "development" ? err : {}
  });
});

// server start
app.listen(3000, () => {
  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.

Screencast

Watch this screencast to solidy your understanding of how to implement CRUD with Mongoose:

When you're ready, move on to Mongoose Associations

Continue

Creative Commons License