Learning Node.js — Part 7
In our applications, we will develop multiple Models to represent our data. Our data, by default, can and will be connected in a multitude of ways, our objects will be related. To support the associations that our Models have with each-other, Mongoose has to be told in what ways our Models interact. In relational databases, like PostgreSQL, there is a concept of relationship (ex: foreign keys). This isn’t the case with NoSQL databases; don’t truly have a relationship.
***Relational and NoSQL database trade off: Query performance vs. consistency
Approaches to Work With Related Objects:
Normalization: Using References
Line 6 does NOT establish a direct relationship between the song and the musician. There is no enforced data integrity¹, the id is being set manually, and there is plenty of room for error doing it this way.
Downside: Slower bc queries don’t run as fast as possible
Denormalization: Using Embedded Documents
With denormalization, we embed the song document inside the musician document.
Upside: If changes are made, they potentially need to be updated in multiple objects
Downside: Fast, single queries, all information is directly in the one object
Setting Up A Relationship — Song belongs to Musician
MongoDB Compass Shows:
! Be careful bc if we store the reference to an invalid musician id, mongo doesn’t know it’s invalid, and it will save it anyways (example in the code above)!
This method follows the path to the given property (argument). The ‘ref’/reference listed as an attribute of musician when we instantiated a new Song:
To specify properties that you want to include, use the 2nd argument of .populate():
- .populate(‘musician’, ‘name’)
To exclude something, for example ‘id’:
- .populate(‘musician’, ‘-_id’)
Embedded Documents can not be saved on their own, they are only saved in the context of their parent.¹
Array of Sub-documents:
Node Package: Fawn
Fawn provides the ability to carry out edits on a mongoDB database as a series of steps. If an error occurs on any of the steps, the database is returned to its initial state (its state before the transaction started). It’s based on the two phase commit system². Terminal command: npm i fawn
- Fawn creates a new collection (ojlinttaskcollections) to perform the two phase commit transactions. When we .run() these types of tasks, it creates a new document in that collection, completing all of the tasks in our transaction independently. Once all of the operations are complete, it deletes the collection.
ObjectID in MongoDB
example: _id: ObjectId(“608ea873f5c4bb0795d18b79”)
- 24 characters — every two characters represent a byte
- 12 bytes to uniquely identify a document in MongoDB
- First 4 bytes represent the time stamp of when document created
- Next 3 bytes: machine identifier — no two machines have the same identifier
- Next 2 bytes: process identifier — same machine, same document but created during different process
- Last 3 bytes represent counter — same machine, same process, same second but generate different docs
1 byte = 8 bits (0 or 1) / 1 byte can represent 256 different numbers ( 2 ^ 8)
In 3 bytes we can store (2 ^ 24) = 16m : Counter will only overflow if on the same machine, during the same process we make more than 16m docs, the ObjectID won’t be unique.
MongoDB Driver generates the unique ObjectID. Mongoose is an abstraction built over MongoDB Driver.
Node Package: Joi-objectid
Adds support for validating ObjectId in Joi. It validates that the value is an alphanumeric string of 24 characters in length³. Terminal command: npm i joi-objectid
- Node.js Tutorial for Beginners | Programming with Mosh | “https://www.youtube.com/watch?v=TlB_eWDSMt4&t=2349s”
- Fawn | “https://www.npmjs.com/package/fawn”
- Joi-objectid | “https://www.npmjs.com/package/joi-objectid”