Chapter 7. Mongoose
Mongoose is the third and last database mapping tool that we will be covering in this book. It is the best known MongoDB mapping tool in the JavaScript world.
Mongoose is the third and last database mapping tool that we will be covering in this book. It is the best known MongoDB mapping tool in the JavaScript world.
When MongoDB was initially released, in 2009, it took the database world by storm. At that point the vast majority of databases in use were relational, and MongoDB quickly grew to be the most popular non-relational database (also known as “NoSQL”.)
NoSQL databases difer from relational databases (such as MySQL, PostgreSQL, etc.) in that they model the data they store in ways other than tables related one to another.
MongoDB, specifically, is a “document-oriented database.” It saves data in “documents” encoded in BSON format (“Binary JSON”, a JSON extension that includes various data types specific for MongoDB). The MongoDB documents are grouped in “collections.”
Traditional relational databases separate data in tables and columns, similar to a spreadsheet. On the other hand, document-oriented databases store complete data objects in single instances of the database, similar to a text file.
While...
Mongoose is technically not an ORM (Object Relational Mapping) though it’s commonly referred to as one. Rather, it is an ODM (Object Document Mapping) since MongoDB itself is based in documents instead of relational tables. The idea behind ODM’s and ORM’s is the same, though: providing an easy-to-use solution for data modelling.
Mongoose works with the notion of “schemas.” A schema is simply an object that defines a collection (a group of documents) and the properties and allowed types of values that the document instances will have (i.e. what we would call “their shape.”).
Just like we saw in the TypeORM and the Sequelize chapters, Nest.js provides us with a module that we can use with Mongoose.
As a first step, we need to install the Mongoose npm package, as well as the Nest.js/Mongoose npm package.
Run npm install --save mongoose @nestjs/mongoose
in your console, and npm install --save-dev @types/mongoose
inmediately after.
Docker Compose is the easiest way to get started with MongoDB. There’s an official MongoDB image in the Docker registry we recommend that you use. The latest stable version at the moment of writing this is 3.6.4
.
Let’s create a Docker Compose file to build and start both the database we will be using, as well as our Nest.js app, and link them together so that we can access the database later from our code.
version
:
'3'
volumes
:
mongo_data
:
services
:
mongo
:
image
:
mongo
:
latest
ports
:
-
"27017:27017"
volumes
:
-
mongo_data
:
/data/db
api
:
build
:
context
:
.
dockerfile
:
Dockerfile
args
:
-
NODE_ENV
=
development
depends_on
:
...
We already mentioned before that Mongoose works with the concept of “schemas.”
Mongoose schemas play a similar role to TypeORM entities. However, unlike the latter, the former are not classes, but rather plain objects that inherit from the Schema
prototype defined (and exported) by Mongoose.
In any case, schemas need to be instantiated into “models” when you are ready to use them. We like to think about schemas as “blueprints” for objects, and about “models” as object factories.
With that said, let’s create our first entity, which we will name Entry
. We will use this entity to store entries (posts) for our blog. We will create a new file at src/entries/entry.entity.ts
; that way TypeORM will be able to find this entity file since earlier in our configuration we specified that entity files will follow the src/**/*.entity.ts
file naming convention.
Let’s create our first schema. We...
As mentioned before, we will use the schema we just defined to instantiate a new data model that we will be able to use in our code. Mongoose models are the ones that do the heavy lifting in regards to mapping objects to database documents, and also abstract common methods for operating with the data, such as .find()
and .save()
.
If you’ve come from the TypeORM chapter, models in Mongoose are very similar to repositories in TypeORM.
When having to connect requests to data models, the typical approach in Nest.js is building dedicated services, which serve as the “touch point” with each model, and controllers. This links the services to the requests reaching the API. We follow the data model -> service -> controller
approach in the following steps.
Before we create our service and controller, we need to write a small interface for our blog entries. This is because, as mentioned before, Mongoose schemas are not TypeScript classes...
At this point, our Nest.js API is ready to listen to requests (both GET
and POST
) and operate on the data stored in our MongoDB instance based on those requests. In other words, we are ready to read from and write to our database from the API.
Let’s give it a try.
We will start with a GET request to the /entries
endpoint. Obviously, since we haven’t created any entries yet, we should receive an empty array as a response.
> GET /entries HTTP/1.1 > Host: localhost:3000 < HTTP/1.1 200 OK []
Let’s create a new entry by sending a POST
request to the entries
endpoint and including in the request body a JSON object that matches the shape of our previously defined EntrySchema
.
> GET /entries HTTP/1.1 > Host: localhost:3000 | { | "title": "This is our first post", | "body": "Bla bla bla bla bla", | "image": "http://lorempixel.com/400", | "created_at": "2018...
While it’s true that MongoDB is not a relational database, it’s also true that it allows “join-like” operations for retrieving two (or more) related documents at once.
Fortunately for us, Mongoose includes a layer of abstraction for these operations and allows us to set up relationships between objects in a clear, concise way. This is provided by using ref
s in schemas’ properties, as well as the .populate()
method (that triggers something known as the “population” process; more on it later.)
Let’s go back to our blog example. Remember that so far we only had a schema that defined our blog entries. We will create a second schema that will allow us to create comments for each blog entry, and save them to the database in a way that allows us later to retrieve both a blog entry as well as the comments that belong to it, all in a single database operation.
So, first, we create a CommentSchema
like...
NoSQL databases are a powerful alternative to “traditional” relational ones. MongoDB is arguably the best known of the NoSQL databases in use today, and it works with documents encoded in a JSON variant. Using a document-based database such as MongoDB allows developers to use more flexible, loosely-structured data models and can improve iteration time in a fast-moving project.
The well known Mongoose library is an adaptor for MongoDB that works in Node.js and that abstracts quite a lot of complexity when it comes to querying and saving operations.
Over this chapter we’ve covered quite some aspects of working with Mongoose and Nest.js, like: