#tech
#mongodb
#backend
#node
#mongo
7 min read

Mongoose vs. MongoDB: What Should You Choose for Your Node.js Project?

Andriy Obrizan

Mongoose, a neat ODM library for MongoDB used in Node.js projects, has plenty of useful features that make developers’ lives easier. It manages relationships between data, has schema validation, and overall allows coding 3-5 times faster. Although Mongoose may be on the slower side performance-wise, it does provide a fallback to native MongoDB when needed.

What is Mongoose?

Mongoose is an Object Data Modeling (ODM) library built on top of the MongoDB native driver for Node.js projects. It lets you save time on writing MongoDB validation, casting, and business logic boilerplate thanks to out-of-the-box hooks.

But the main benefit of Mongoose, the one that makes you even consider weighing your Node.js application down with another library, is schema validation at the application layer.

MongoDB Schema Validation

We all love MongoDB for its ability to store documents with a dynamic structure inside collections. And while the flexibility of having various data types is great, it can get out of hand, especially once you move from a POC to a full-blown product. And that’s why we need schema validation.

Native Schema Validation

If you don’t want rogue data slithering into your database, you need schema validation. Sure, you don’t have to think about it while the project is still young. But as the number of data sources grows, you need to set some ground rules.

With schema validation, you can set a specific structure for documents, and if a document doesn’t match the structure, the database will reject the operation right away or issue a warning.

Native MongoDB schema validation is good for three reasons: one, JSON validation is an open standard, which means you’ll be able to connect third-party libraries now or in the future. Secondly, it has fewer dependencies; and thirdly, the performance is great. And did we mention that native validation is applied at the database level?

Schema Validation with Mongoose

Now, with Mongoose, the validation process is quicker and easier since it allows creating a structure for your data with predefined types you can use out of the box. Just imagine how much time you could save with this built-in validation. We’ve estimated that Mongoose does with one string of validation code what we’d manually do with three or five of them.

Speaking of types, Mongoose offers the following SchemaTypes:

  • String
  • Number
  • Date
  • Buffer
  • Boolean
  • Mixed
  • ObjectId
  • Array
  • Decimal128
  • Map
  • Schema

Plus, you can use available plugins for other types or even create your own custom SchemaType.

Mongoose applies schema validation at the application level (which means MongoDB has no idea about it) and does so in two ways: by telling your application what data types and fields are allowed to be in a particular collection and by confirming that data matches the set criteria.

The official MongoDB blog has an article that perfectly illustrates how Mongoose schemas work, using a blog as an example.

Benefits of Mongoose

With Mongoose, creating and managing relationships between data is quick and easy thanks to a strict schema and populate() method. It replaces specific paths, described in the schema with documents from other collections. Needless to say, it’s more powerful than the native “lookup” functionality.

The library can also boast a convenient save() that lets you update documents hassle-free.

Middleware is a nice thing to have when you plan to create a plugin for Mongoose or want to build a modular Data Access Layer (DAL). There are four middleware types, depending on where it’s effective: document, model, aggregate, and query middleware. And there are hundreds of available plugins — feel the power of open-source.

The Discriminator mechanism is another interesting one. It could be quite useful if you need to build SaaS with a shared database. In a nutshell, it allows you to have a shared key in each model within the same collection. Let’s imagine that you have a single collection “Tasks,” but the actual tasks (models) belong to different customers. Having a discriminator for a customer ID will ensure that we don’t forget to filter tasks during a query and accidentally break the data isolation principle.

The use of Timestamps is rather straightforward but still worth mentioning. Turning on is super easy: just set “timestamps” to “true” when describing a schema, and you’ll have an insight about the creation and update time of the model.

Instance and static methods are also very handy since they allow modeling entities and not just data models. Enhancing your models with methods helps to keep your DAL rich and cohesive, and modularity makes testing easy. The difference between instance and static method is almost self-explanatory to the people familiar with object-oriented programming — one is applied to the model instance and the other — to the whole schema.

Mongoose virtuals are another benefit of the library. They’re document properties that aren’t stored in MongoDB: you can still get them and set values, but these virtuals aren’t written to collections. Virtuals are used for computed properties on documents, formatting, combining fields, etc.

Other perks of using Mongoose include chained functions (for a more flexible and readable code), the abstraction layer (for not having to use named collections), and defaults (for improved readability of code as well as time-saving).

Drawbacks of Mongoose

Making an informed decision also requires considering the drawbacks of Mongoose. And though there aren’t many, you should be aware of them.

Too Much Woodoo Under the Hood

Like any other ODM or ORM, Mongoose hides the complexity of persisting data from a developer. Therefore, tinkering with the data without Mongoose could be unpredictable and requires a deep understanding of the library. It’s not just a helper that could be used here and there; you have to conform to its API. Some refer to that as “lack of control,” which is critical in many cases.

Building an app quickly could be crucial for a budding business, but choosing the convenience of Mongoose to build a highly scalable and performance-optimized app isn’t the best idea.

Performance Issues

Mongoose is written in JavaScript on top of the native MongoDB driver. So yeah, using it will give you a performance penalty. But even bigger issues arise when you try to use it as an ORM. Bad design will shoot down the performance of any ODM or database.

But there’s a workaround: MongoDB. Mongoose provides direct access to the native driver used under the hood. If there’s a performance-critical part of your app, you can use the full power of MongoDB while still having an option of Mongoose in the rest of the app.

Other, smaller drawbacks include:

  • Mongoose isn’t an official library developed by MongoDB
  • Rigid schema is more of an anti-pattern in the MongoDB world
  • Performance could be better
  • Not the best choice for large projects and advanced queries
  • Relationships only exist at the application level
  • Only works when your collection holds a predictable schema for documents
  • Might struggle with complex schemas

Sure, Mongoose is convenient for managing data, but it’s not a silver bullet. So in more complicated projects, during active development, and when data formats change often, you’ll need to use the MongoDB Node.js driver.

Conclusion

As you might have noticed, we’re not saying you should use Mongoose for all your Node.js projects — MongoDB provides everything natively, so there’s no immediate need to use Mongoose. Yes, ODMs like Mongoose provide a nice feeling of familiarity and ease, especially when you’re just starting out with relational databases. But they also hold you back from exploring the true power and freedom of MongoDB.

Mongoose isn’t perfect, but it’s still a pretty decent ODM that lets you save time on writing validations and instance methods. So if there’s not too much data processing in your project, Mongoose might just be handy.