NestJS with MongoDB native driver
Let’s build a application with NestJS and MongoDB.
- Install NestJS
- Install Mongoose
Do I really need Mongoose? I see a lot of articles, videos and courses about NodeJS / MongoDB stack applications that apply Mongoose right from the start, sometimes without being clear about the reasons to do so.
Introduction
Coming from a SQL world, using Mongoose can feel more natural as it uses schemas that define a structure for the data. But being schemaless is one of NoSQL main advantages. That is particularly interesting when you don’t know before hand the kind of data you will receive. Also, in the early stages of web development, the business rules may suffer many changes, so having a schemaless database can be an advantage. Mongoose applies the schema in the application layer, although the database itself remains schemaless, its behavior considering the application, isn’t.
It is true that most of the material I referenced earlier had a well defined data structure so using schemas is fine. But it is also true that those applications could easily be build with NodeJS native MongoDB driver. And if you still feel the need for using schemas, the native driver also provides MongoDB JSON Schema, a flexible schema implementation since version 3.6 (2017).
One thing I do not like is that as Mongoose adds an abstraction layer, the developer tends not to know how to use MongoDB native syntax which I believe is important. I do like Mongoose, it certainly is a well written and powerful tool, it provides a fast way to build applications in NodeJS / MongoDB stack, it offers others functionalities apart from schemas, it does facilitates some operations and helped me when starting in NoSQL world. But part of the reason for this last one is that I did not saw a lot of material about using this stack with the native driver as I did with Mongoose. That is what motivated me to write this article.
My goal is to provide the option of using the native driver which can already offer everything you need in your application with better performance, less dependencies and also a simple and nice coding experience. I’m using NestJS as its official documentation only covers integration with Mongoose and TypeORM.
Implementation
1. Install NodeJS native MongoDB driver
npm install mongodb --save
2. Connect to the database
Here I created a database module with a provider DATABASE_CONNECTION
. First I make a instance of MongoClient
that connect to the cluster using the connect
method which receives as parameters a MongoDB connection string and a optional options object. Then I return a database instance db
to a particular database name from this provider.
database.module.ts
3. Import the Database module from other modules
Here I’m importing the database module from a users module.
4. Use the connection in services across the application
Now you can inject the database instance to your services and use native MongoDB queries to interact with the data. Here is an example of a basic users CRUD implementation.
As you can see, the queries are pretty intuitive and similar to Mongoose methods. Here are some differences.
- You need to specify the collection name were the query is to run;
- MongoDB by default stores each document with a ObjectId BSON type primary key named
_id
. When your query uses this key you need to convert the string value to aObjectId
type; - If the
_id
comes in the request, you need to check if is a validObjectId
; - Update queries do not return the updated document. If you need to access it before the query is executed you will have to do a subsequent
find
; - You need to manually throw exceptions based on the return from the query execution;
Validation
As in any API, the requests need to be validated. When the document data comes from the request and the request is validated before processing, you should not need to re-validate it in the schema. In NestJS, you can validate your request data with class-validator as follows.
When the document data or part of if comes from external services you can validate the response before executing database queries. If the request or external response structure is not defined you can use a generic type any
and insert the data as it in a schemaless database. Mongoose also can store generic types with mixed type but generally this is used when your document have defined fields and one or more generic. If all document is generic, as is in standard NoSQL (schemaless), there is no point of using Mongoose, at least in that collection.
Indexes
You can create indexes to your collection using the createIndex
method. Acording to MongoDB documentation:
The
db.collection.createIndex
method only creates an index if an index of the same specification does not already exist.
So you don’t need to worry about executing this method multiple times. Here I’m creating a unique
index on the email field with sparse
to skip documents that do not have the indexed field when the connection is set.
Conclusion
Using NodeJS native driver is easy. Some parts are similar to Mongoose’s implementation with the advantage of using native MongoDB syntax, which you can directly use on MongoDB shell or clients and have a better performance. Also you use less dependencies (3) compared to Mongoose (11 including the native driver) so the application size is smaller and you don’t have to worry about catching a bug in any of those dependencies or theirs dependencies. Here is a repository with the complete application code https://github.com/guizoxxv/nestjs-native-mongodb-driver-test.