A mini-tutorial on MongoDB and Mongoose
This tutorial provides basic introduction to MondoDB and Mongoose:
Contents:
- MongoDB Concepts
- Mongoose representation of MongoDB Concepts
- Databases, Collections, and Documents
- ObjectIDs and References
- Queries
- Examples
- Resources
MongoDB Concepts
- An installation consists of a set of named databases.
- A database consists of a set of named collections.
- A collection consists of a set of documents.
- A document is a set of (property,value) pairs.
- A schema is a set of (property,type) pairs. All of the documents in a single collection should satisfy the same schema.
Mongoose representation of MongoDB Concepts
Databases, Collections, and Documents
Mongoose provides representations of MongoDB concepts in the TypeScript/JavaScript language.
- In any given program
mongoose
refers to a particular database in a particular MongoDB instance. For example, executing
await mongoose.connect("mongodb://127.0.0.1:27017/pets");
causes mongoose to refer to the pets
database in the local MongoDB instance.
- A MongoDB schema is represented in Mongoose by an object of class
mongoose.Schema
. For example, executing
const kittySchema = new mongoose.Schema({
name: String,
color: String,
});
causes kittySchema
to represent a Mongo schema with a single property, name
, of type String
.
References to other documents are represented by properties with type Types.ObjectID
(more on this later)
In this document, we will use the terms ‘property’ and ‘field’ interchangeably.
- A MongoDB collection of documents
is represented in Mongoose by a TypeScript constructor created by
mongoose.model
. For example
const Kitten = mongoose.model("Kitten", kittySchema);
causes Kitten
to refer to a collection named ‘Kitten’ in the current instance; all the documents in the Kitten
collection should satisfy the schema represented by kittySchema
.
- A document with schema
M
is represented by a TypeScript object created by sayingnew C
, whereC
is constructor created bymongoose.model
. For example
const fluffy = new Kitten({ name: "fluffy", color: "black" });
creates a document intended for insertion in the collection named Kitten
.
- In Mongoose, creation of a document is separate from being inserted in a collection. So to actually insert
fluffy
in theKitten
collection, we need to execute
await fluffy.save();
Note that most of the operations that touch the database are asyncs.
ObjectIDs and References
In MongoDB, every document has a unique identifier, which is kept in its _id
field.
Queries
In Mongoose, a query is a recipe for retrieving documents from a collection. There are lots of ways to do this. Here are some that work (for a collection called Dogs)
Dog.find(); // finds all documents in the `Dog` collection
Dog.findOne({ name: "Buddy" }); // finds one of the dogs named 'Buddy'
Dog.find({ breed: "Labrador" }); // finds all dogs of the given breed
These are all asyncs, so you need to await
them.
The query syntax offers lots of methods for selecting, sorting, etc. Take a look at this example, which two very different ways of writing the same query:
// With a JSON doc
Person.find({
occupation: /host/,
"name.last": "Ghost",
age: { $gt: 17, $lt: 66 },
likes: { $in: ["vaporizing", "talking"] },
})
.limit(10)
.sort({ occupation: -1 })
.select({ name: 1, occupation: 1 })
.exec(callback);
// Using query builder
Person.find({ occupation: /host/ })
.where("name.last")
.equals("Ghost")
.where("age")
.gt(17)
.lt(66)
.where("likes")
.in(["vaporizing", "talking"])
.limit(10)
.sort("-occupation")
.select("name occupation")
.exec(callback);
For small projects like the one in the course, it is probably preferable to use the simplest Mongoose queries you can, and then process the list of documents that the query returns.
There are some circumstances where it is helpful to the query do more work. Consider, for example, the following excerpt from models/application.ts
const q = await QuestionModel.findOneAndUpdate(
{ _id: new ObjectId(qid) },
{ $inc: { views: 1 } },
{ new: true }
).populate({ path: "answers", model: AnswerModel });
return q;
Here we are give an objectID qid
. The call to .findOneAndUpdate
first finds a document whose _id
field matches qid
. It then calls the document’s $inc
method to increment its views
field by 1. The default behavior of findOneAndUpdate
is to return the original (unmodified) document. But that’s not what we wanted here, so we add a third argument { new: true }
to return the updated document. All that would be hard to do except by making it part of findOneAndUpdate
.
Examples
A simple example (i.e, example.ts) can be accessed here.