MongoDB is undoubtedly one of the most popular NoSQL database options today, and it has a great community and ecosystem.
In this article, we’ll cover some best practices to follow when setting up MongoDB and Mongoose using Node.js.
1. Why Mongoose?
To understand why we needed Mongoose, let’s look at how MongoDB (which is also a database) works at an architectural level.
- You have a database server (e.g. MongoDB community server)
- You are running a Node.js script (as a process)
The MongoDB server listens on a TCP socket (usually) and your Node.js process can connect to it using a TCP connection.
But on top of TCP, MongoDB also has its own protocol for figuring out what the client (our Node.js process) wants the database to do.
For this communication, we don’t need to learn the messages we want to send over the TCP layer, but rather abstract them off with a “driver” software, called the MongoDB driver in this case. The MongoDB driver is provided here as an NPM package.
Now keep in mind that the MongoDB driver is responsible for wiring and abstracting your low-level communication requests/responses — but as a developer, that only takes you so far.
Because MongoDB is a schemaless database, it gives you more functionality than beginners need. More power means more potential for errors, and you need to reduce the possibility of errors and broken tables that can be made in code.
Mongoose is an abstraction of the native MongoDB driver (the NPM package I mentioned above).
The general rule of thumb for abstraction (the way I understand it) is that every abstraction loses some underlying operational capability. But that doesn’t necessarily mean it’s bad, sometimes it can be more than a thousand times more productive, because you don’t need full access to the underlying API at all.
A good idea is that you technically create a live chat application in C and Python. As a developer, Python examples will be easier and faster to implement and more productive. C may be more efficient, but it comes at a huge cost in productivity, development speed, errors, crashes. Also, in most cases, you don’t need to have the ability C gives you to implement WebSockets.
Similarly, with Mongoose, you can limit the scope of your underlying API access, but can unlock a lot of potential gains and good DX.
2. How to connect Mongoose and MongoDB
First, let’s take a quick look at how Mongoose should connect to your MongoDB database in 2020.
mongoose.connect(DB_CONNECTION_STRING, {
useNewUrlParser: true.useUnifiedTopology: true.useCreateIndex: true.useFindAndModify: false
})
Copy the code
This connection format ensures that you are using Mongoose’s new URL parser and that you are not using any obsolete practices.
3. How to perform Mongoose operation
Let’s start with a quick discussion of Mongoose operations and how you should perform them.
Mongoose offers you the following two options:
- Cursor-based query
- Query Full fetching
3.1 Cursor-based query
Cursor based queries process one record at a time, fetching one or a batch of documents from the database at a time. This is an efficient way to process large amounts of data in a limited memory environment.
Imagine trying to parse 10GB documents on a 1GB/ 1-core cloud server. You can’t get the whole collection because that’s not appropriate in your system. Cursors are a good (and only) option.
3.2 Full Fetching Query
This is the type of query where you can get all the responses to the query at once. In most cases, this is how you will use it. Therefore, we will focus on this approach here.
4. How to use the Mongoose Model
Models are Mongoose’s superpower. They help you enforce “schema” rules and provide seamless integration of your Node code into database calls.
The first step is to define a good model:
import mongoose from 'mongoose'
const CompletedSchema = new mongoose.Schema(
{
type: { type: String.enum: ['course'.'classroom'].required: true },
parentslug: { type: String.required: true },
slug: { type: String.required: true },
userid: { type: String.required: true}}, {collection: 'completed' }
)
CompletedSchema.index({ slug: 1.userid: 1 }, { unique: true })
const model = mongoose.model('Completed', CompletedSchema)
export default model
Copy the code
This is an example of a direct cut from CodeDamn’s codebase. Here are some interesting things you should be aware of.
-
In all required fields, try to keep required: true. This can save you a lot of trouble if you don’t use a static type checking system like TypeScript to help you properly check property names when creating objects. Plus, free validation is super cool.
-
Define indexes and unique fields. The unique attribute can also be added to the schema. Indexing is a broad topic, so I won’t go into it here. But on a large scale, they can really help you speed up a lot of queries.
-
Explicitly define a collection name. While Mongoose can automatically come up with a collection name based on the name of the model (such as Completed here), this seems too abstract to me. You should at least know the name of your database and the collection in your code base.
-
Use enumerations to limit values if you can.
5. How do I perform CRUD operations
CRUD stands for create, read, update, and delete. These are the four basic options with which you can perform any kind of data manipulation in the database. Let’s take a quick look at some examples of these operations.
5.1 the Create
This simply means creating a new record in the database. Let’s create a record using the model we defined above.
try {
const res = await CompletedSchema.create(record)
} catch(error) {
console.error(error)
// handle the error
}
Copy the code
Again, here are some tips:
- Use async-await instead of callback (looks good, but no breakthrough performance advantage)
- Use try-catch blocks around the query, because your query may fail for a number of reasons (duplicate records, wrong values, etc.).
5.2 Read
This means reading existing values from the database. It sounds simple enough, but you should know a few little things about Mongoose.
const res = await CompletedSchema.find(info).lean()
Copy the code
- You can see it there
lean()
Function call? It’s super useful for performance. By default, Mongoose processes documents returned from the database and adds them to themmagicMethods (e.g.save
). - When you use
.lean()
“, Mongoose will return normal JSON objects instead of memory – and resource-heavy documents. Faster query and less CPU consumption. - However, if you do want to update the data, you can omit it
.lean()
(As we’ll see)
The 5.3 Update
If you already have a Mongoose document (not triggered with.lean()), you can simply modify the object properties and save it with Object.save ().
const doc = await CompletedSchema.findOne(info)
doc.slug = 'something-else'
await doc.save()
Copy the code
Remember, there are two database calls. The first is on findOne and the second is on doc.save.
If you can, you should always reduce the number of database access requests (because network is almost always the slowest when comparing memory, network, and disk).
In another case, the following query could be used:
const res = await CompletedSchema.updateOne(<condition>, <query>).lean()
Copy the code
And only one call is made to the database.
5.4 the Delete
Deleting using Mongoose is also easy. Let’s see how to delete individual documents:
const res = await CompletedSchema.deleteOne(<condition>)
Copy the code
Like updateOne, deleteOne accepts the first parameter as a matching condition for the document.
There is another method called deleteMany, which is used only if you know you want to delete multiple documents.
In any other case, always use deleteOne to avoid accidental multiple deletions, especially if you’re trying to execute the query yourself.
www.freecodecamp.org: blog.zhangbing.site