# WEEK 2 - MongoDB and Mongoose. ###### tags: `WEB` `JS` `DIGITALIZE` `BACKEND` `Express` `MongoDB` ## MongoDB ### What is MongoDB? **MongoDB** is a document-oriented NoSQL database used for high volume data storage. Instead of using tables and rows as in the traditional relational databases, **MongoDB** makes use of collections and documents. Documents consist of key-value pairs which are the basic unit of data in **MongoDB**. Collections contain sets of documents and function which is the equivalent of relational database tables. ### Main Features * Each database contains collections which in turn contains documents. Each document can be different with a varying number of fields. The size and content of each document can be different from each other. * The document structure is more in line with how developers construct their classes and objects in their respective programming languages. Developers will often say that their classes are not rows and columns but have a clear structure with key-value pairs. * The rows (or documents as called in MongoDB) doesn’t need to have a schema defined beforehand. Instead, the fields can be created on the fly. * The data model available within MongoDB allows you to represent hierarchical relationships, to store arrays, and other more complex structures more easily. * Scalability – The MongoDB environments are very scalable. ![](https://www.guru99.com/images/MongoDB/112015_1051_Introductio1.png) ### Common terms used - `_id`: This is a field required in every MongoDB document. The _id field represents a unique value in the MongoDB document. The _id field is like the document’s primary key. If you create a new document without an _id field, MongoDB will automatically create the field. Mongo DB adds a 24 digit unique identifier to each document in the collection. Collection Example: <table class="table table-striped"> <tbody></tbody> <thead> <tr> <th>_Id</th> <th>CustomerID</th> <th>CustomerName</th> <th>OrderID</th> </tr> </thead> <tbody> <tr> <td>563479cc8a8a4246bd27d784</td> <td>11</td> <td>Guru99</td> <td>111</td> </tr> <tr> <td>563479cc7a8a4246bd47d784</td> <td>22</td> <td>Trevor Smith</td> <td>222</td> </tr> <tr> <td>563479cc9a8a4246bd57d784</td> <td>33</td> <td>Nicole</td> <td>333</td> </tr> </tbody> </table> - Collection: a grouping of MongoDB documents. A collection is the equivalent of a table which is created in any other RDMS such as Oracle or MS SQL. - Cursor: This is a pointer to the result set of a query. Clients can iterate through a cursor to retrieve results. - Database: This is a container for collections like in RDMS wherein it is a container for tables. Each database gets its own set of files on the file system. A MongoDB server can store multiple databases. - Document: A record in a MongoDB collection is basically called a document. The document, in turn, will consist of field name and values. - Field – A name-value pair in a document. A document has zero or more fields. Fields are analogous to columns in relational databases. Example below CustomerID and 11 is one of the key value pair’s defined in the document. ![](https://www.guru99.com/images/MongoDB/112015_1051_Introductio2.png) - JSON: This is known as JavaScript Object Notation. This is a human-readable, plain text format for expressing structured data. JSON is currently supported in many programming languages. ### Why MongoDB * Document-oriented – Since MongoDB is a NoSQL type database, instead of having data in a relational type format, it stores the data in documents. This makes MongoDB very flexible and adaptable to real business world situation and requirements. * Ad hoc queries – MongoDB supports searching by field, range queries, and regular expression searches. Queries can be made to return specific fields within documents. * Indexing – Indexes can be created to improve the performance of searches within MongoDB. Any field in a MongoDB document can be indexed. * Replication – MongoDB can provide high availability with replica sets. A replica set consists of two or more mongo DB instances. Each replica set member may act in the role of the primary or secondary replica at any time. The primary replica is the main server which interacts with the client and performs all the read/write operations. The Secondary replicas maintain a copy of the data of the primary using built-in replication. When a primary replica fails, the replica set automatically switches over to the secondary and then it becomes the primary server. * Load balancing – MongoDB uses the concept of sharding to scale horizontally by splitting data across multiple MongoDB instances. MongoDB can run over multiple servers, balancing the load and/or duplicating data to keep the system up and running in case of hardware failure. ### Terminology and Concepts <table cellspacing="0" cellpadding="0" class="leafygreen-ui-7u661v leafygreen-ui-1zvjfk"><thead><tr class="" data-testid="leafygreen-ui-header-row"><th role="columnheader" scope="col" aria-sort="none" class="leafygreen-ui-pwho68 leafygreen-ui-nl948p"><div class="leafygreen-ui-1mwn02k"><span class="leafygreen-ui-nem3xz">SQL Terms/Concepts</span></div></th><th role="columnheader" scope="col" aria-sort="none" class="leafygreen-ui-pwho68 leafygreen-ui-nl948p"><div class="leafygreen-ui-1mwn02k"><span class="leafygreen-ui-nem3xz">MongoDB Terms/Concepts</span></div></th></tr></thead><tbody><tr role="row" class="leafygreen-ui-1qqfxo5" aria-disabled="false"><td class="leafygreen-ui-1959614 leafygreen-ui-15swplv"><div class="leafygreen-ui-1sg2lsz" data-leafygreen-ui="td-inner-div"><span class="leafygreen-ui-1auv844">database</span></div></td><td class="leafygreen-ui-1959614 leafygreen-ui-15swplv"><div class="leafygreen-ui-1sg2lsz" data-leafygreen-ui="td-inner-div"><span class="leafygreen-ui-1auv844"><a class="css-15s9h51 ehugbqd0" href="/docs/manual/reference/glossary/#std-term-database">database</a></span></div></td></tr><tr role="row" class="leafygreen-ui-1qqfxo5" aria-disabled="false"><td class="leafygreen-ui-1959614 leafygreen-ui-15swplv"><div class="leafygreen-ui-1sg2lsz" data-leafygreen-ui="td-inner-div"><span class="leafygreen-ui-1auv844">table</span></div></td><td class="leafygreen-ui-1959614 leafygreen-ui-15swplv"><div class="leafygreen-ui-1sg2lsz" data-leafygreen-ui="td-inner-div"><span class="leafygreen-ui-1auv844"><a class="css-15s9h51 ehugbqd0" href="/docs/manual/reference/glossary/#std-term-collection">collection</a></span></div></td></tr><tr role="row" class="leafygreen-ui-1qqfxo5" aria-disabled="false"><td class="leafygreen-ui-1959614 leafygreen-ui-15swplv"><div class="leafygreen-ui-1sg2lsz" data-leafygreen-ui="td-inner-div"><span class="leafygreen-ui-1auv844">row</span></div></td><td class="leafygreen-ui-1959614 leafygreen-ui-15swplv"><div class="leafygreen-ui-1sg2lsz" data-leafygreen-ui="td-inner-div"><span class="leafygreen-ui-1auv844"><a class="css-15s9h51 ehugbqd0" href="/docs/manual/reference/glossary/#std-term-document">document</a> or <a class="css-15s9h51 ehugbqd0" href="/docs/manual/reference/glossary/#std-term-BSON">BSON</a> document</span></div></td></tr><tr role="row" class="leafygreen-ui-1qqfxo5" aria-disabled="false"><td class="leafygreen-ui-1959614 leafygreen-ui-15swplv"><div class="leafygreen-ui-1sg2lsz" data-leafygreen-ui="td-inner-div"><span class="leafygreen-ui-1auv844">column</span></div></td><td class="leafygreen-ui-1959614 leafygreen-ui-15swplv"><div class="leafygreen-ui-1sg2lsz" data-leafygreen-ui="td-inner-div"><span class="leafygreen-ui-1auv844"><a class="css-15s9h51 ehugbqd0" href="/docs/manual/reference/glossary/#std-term-field">field</a></span></div></td></tr><tr role="row" class="leafygreen-ui-1qqfxo5" aria-disabled="false"><td class="leafygreen-ui-1959614 leafygreen-ui-15swplv"><div class="leafygreen-ui-1sg2lsz" data-leafygreen-ui="td-inner-div"><span class="leafygreen-ui-1auv844">index</span></div></td><td class="leafygreen-ui-1959614 leafygreen-ui-15swplv"><div class="leafygreen-ui-1sg2lsz" data-leafygreen-ui="td-inner-div"><span class="leafygreen-ui-1auv844"><a class="css-15s9h51 ehugbqd0" href="/docs/manual/reference/glossary/#std-term-index">index</a></span></div></td></tr><tr role="row" class="leafygreen-ui-1qqfxo5" aria-disabled="false"><td class="leafygreen-ui-1959614 leafygreen-ui-15swplv"><div class="leafygreen-ui-1sg2lsz" data-leafygreen-ui="td-inner-div"><span class="leafygreen-ui-1auv844">table joins</span></div></td><td class="leafygreen-ui-1959614 leafygreen-ui-15swplv"><div class="leafygreen-ui-1sg2lsz" data-leafygreen-ui="td-inner-div"><span class="leafygreen-ui-1auv844"><a class="css-15s9h51 ehugbqd0" href="/docs/manual/reference/operator/aggregation/lookup/#mongodb-pipeline-pipe.-lookup"><code class="css-1wtyw2q e1wawog1 leafygreen-ui-al0qrg">$lookup</code></a>, embedded documents</span></div></td></tr><tr role="row" class="leafygreen-ui-1qqfxo5" aria-disabled="false"><td class="leafygreen-ui-1959614 leafygreen-ui-15swplv"><div class="leafygreen-ui-1sg2lsz" data-leafygreen-ui="td-inner-div"><span class="leafygreen-ui-1auv844"><p class="css-18uqayh e6p926f0 leafygreen-ui-1wc45bb">primary key</p><p class="css-18uqayh e6p926f0 leafygreen-ui-1wc45bb">Specify any unique column or column combination as primary key.</p></span></div></td><td class="leafygreen-ui-1959614 leafygreen-ui-15swplv"><div class="leafygreen-ui-1sg2lsz" data-leafygreen-ui="td-inner-div"><span class="leafygreen-ui-1auv844"><p class="css-18uqayh e6p926f0 leafygreen-ui-1wc45bb"><a class="css-15s9h51 ehugbqd0" href="/docs/manual/reference/glossary/#std-term-primary-key">primary key</a></p><p class="css-18uqayh e6p926f0 leafygreen-ui-1wc45bb">In MongoDB, the primary key is automatically set to the <a class="css-15s9h51 ehugbqd0" href="/docs/manual/reference/glossary/#std-term-_id">_id</a> field.</p></span></div></td></tr><tr role="row" class="leafygreen-ui-1qqfxo5" aria-disabled="false"><td class="leafygreen-ui-1959614 leafygreen-ui-15swplv"><div class="leafygreen-ui-1sg2lsz" data-leafygreen-ui="td-inner-div"><span class="leafygreen-ui-1auv844">aggregation (e.g. group by)</span></div></td><td class="leafygreen-ui-1959614 leafygreen-ui-15swplv"><div class="leafygreen-ui-1sg2lsz" data-leafygreen-ui="td-inner-div"><span class="leafygreen-ui-1auv844"><p class="css-18uqayh e6p926f0 leafygreen-ui-1wc45bb">aggregation pipeline</p><p class="css-18uqayh e6p926f0 leafygreen-ui-1wc45bb">See the <a class="css-15s9h51 ehugbqd0" href="/docs/manual/reference/sql-aggregation-comparison/">SQL to Aggregation Mapping Chart.</a></p></span></div></td></tr><tr role="row" class="leafygreen-ui-1qqfxo5" aria-disabled="false"><td class="leafygreen-ui-1959614 leafygreen-ui-15swplv"><div class="leafygreen-ui-1sg2lsz" data-leafygreen-ui="td-inner-div"><span class="leafygreen-ui-1auv844">SELECT INTO NEW_TABLE</span></div></td><td class="leafygreen-ui-1959614 leafygreen-ui-15swplv"><div class="leafygreen-ui-1sg2lsz" data-leafygreen-ui="td-inner-div"><span class="leafygreen-ui-1auv844"><p class="css-18uqayh e6p926f0 leafygreen-ui-1wc45bb"><a class="css-15s9h51 ehugbqd0" href="/docs/manual/reference/operator/aggregation/out/#mongodb-pipeline-pipe.-out"><code class="css-1wtyw2q e1wawog1 leafygreen-ui-al0qrg">$out</code></a></p><p class="css-18uqayh e6p926f0 leafygreen-ui-1wc45bb">See the <a class="css-15s9h51 ehugbqd0" href="/docs/manual/reference/sql-aggregation-comparison/">SQL to Aggregation Mapping Chart.</a></p></span></div></td></tr><tr role="row" class="leafygreen-ui-1qqfxo5" aria-disabled="false"><td class="leafygreen-ui-1959614 leafygreen-ui-15swplv"><div class="leafygreen-ui-1sg2lsz" data-leafygreen-ui="td-inner-div"><span class="leafygreen-ui-1auv844">MERGE INTO TABLE</span></div></td><td class="leafygreen-ui-1959614 leafygreen-ui-15swplv"><div class="leafygreen-ui-1sg2lsz" data-leafygreen-ui="td-inner-div"><span class="leafygreen-ui-1auv844"><p class="css-18uqayh e6p926f0 leafygreen-ui-1wc45bb"><a class="css-15s9h51 ehugbqd0" href="/docs/manual/reference/operator/aggregation/merge/#mongodb-pipeline-pipe.-merge"><code class="css-1wtyw2q e1wawog1 leafygreen-ui-al0qrg">$merge</code></a> (Available starting in MongoDB 4.2)</p><p class="css-18uqayh e6p926f0 leafygreen-ui-1wc45bb">See the <a class="css-15s9h51 ehugbqd0" href="/docs/manual/reference/sql-aggregation-comparison/">SQL to Aggregation Mapping Chart.</a></p></span></div></td></tr><tr role="row" class="leafygreen-ui-1qqfxo5" aria-disabled="false"><td class="leafygreen-ui-1959614 leafygreen-ui-15swplv"><div class="leafygreen-ui-1sg2lsz" data-leafygreen-ui="td-inner-div"><span class="leafygreen-ui-1auv844">UNION ALL</span></div></td><td class="leafygreen-ui-1959614 leafygreen-ui-15swplv"><div class="leafygreen-ui-1sg2lsz" data-leafygreen-ui="td-inner-div"><span class="leafygreen-ui-1auv844"><a class="css-15s9h51 ehugbqd0" href="/docs/manual/reference/operator/aggregation/unionWith/#mongodb-pipeline-pipe.-unionWith"><code class="css-1wtyw2q e1wawog1 leafygreen-ui-al0qrg">$unionWith</code></a> (Available starting in MongoDB 4.4)</span></div></td></tr><tr role="row" class="leafygreen-ui-1qqfxo5" aria-disabled="false"><td class="leafygreen-ui-1959614 leafygreen-ui-15swplv"><div class="leafygreen-ui-1sg2lsz" data-leafygreen-ui="td-inner-div"><span class="leafygreen-ui-1auv844">transactions</span></div></td><td class="leafygreen-ui-1959614 leafygreen-ui-15swplv"><div class="leafygreen-ui-1sg2lsz" data-leafygreen-ui="td-inner-div"><span class="leafygreen-ui-1auv844"><p class="css-18uqayh e6p926f0 leafygreen-ui-1wc45bb"><a class="css-15s9h51 ehugbqd0" href="/docs/manual/core/transactions/">transactions</a></p><div role="note" class="leafygreen-ui-11oupyp"><div class="leafygreen-ui-1fuktk9"><svg class="leafygreen-ui-1j5chro leafygreen-ui-1e6hhul" height="16" width="16" role="presentation" aria-hidden="true" alt="" viewBox="0 0 16 16"><path d="M12.3311 8.5C12.7565 7.76457 13 6.91072 13 6C13 3.23858 10.7614 1 8 1C5.23858 1 3 3.23858 3 6C3 6.94628 3.26287 7.83117 3.71958 8.58561L5.40749 11.501C5.58628 11.8099 5.91607 12 6.27291 12H6.5V6C6.5 5.17157 7.17157 4.5 8 4.5C8.82843 4.5 9.5 5.17157 9.5 6V12H9.72368C10.0793 12 10.4082 11.8111 10.5874 11.5039L12.34 8.5H12.3311Z" fill="currentColor"></path><path d="M7.5 6V12H8.5V6C8.5 5.72386 8.27614 5.5 8 5.5C7.72386 5.5 7.5 5.72386 7.5 6Z" fill="currentColor"></path><path d="M10 14V13H6V14C6 14.5523 6.44772 15 7 15H9C9.55228 15 10 14.5523 10 14Z" fill="currentColor"></path></svg><h2 class="leafygreen-ui-4rvd2s">Tip</h2></div><div class="leafygreen-ui-1rlqfkz"><div class="leafygreen-ui-6zh3yw"><p class="css-18uqayh e6p926f0 leafygreen-ui-1wc45bb">For many scenarios, the <a class="css-15s9h51 ehugbqd0" href="/docs/manual/core/data-model-design/#std-label-data-modeling-embedding">denormalized data model (embedded documents and arrays)</a> will continue to be optimal for your data and use cases instead of multi-document transactions. That is, for many scenarios, modeling your data appropriately will minimize the need for multi-document transactions.</p></div></div></div></span></div></td></tr></tbody></table> ### Installation There are 2 methods to connect and work with MongoDB: - Install MongoDB locally - Use MongoDB cloud-based We will start learning with installing mongoDB locally. **Installation and setup:** - Download MongoDB installer from https://www.mongodb.com/try/download/community - Complete installation. - Now to connect to MongoDB, there are 3 methods: - using the Mongo shell While the MongoDB daemon is running - By using a GUI tool called `MongoDB Compass` - By Nodejs server using `mongoose` or `mongodb` packages. **Note:** We will use MongoDB Compass along with our nodejs app development. ### Connect Nodejs server to MongoDB - First create a new database using MongoDB Compass and run the server. - Install mongoose in your project `npm install mongoose` - Create the connection: `const mongoose = require("mongoose");` ``` mongoose.connect('mongodb://localhost:27017/clinic'); ``` - Listen to the connection results: ``` const db = mongoose.connection; db.on("error", console.error.bind(console, "connection error: ")); db.once("open", function () { console.log("Connected to db successfully"); }); ``` ### Mongoose Model in Mongoose, you need to use models to create, read, update, or delete items from a MongoDB collection. To create a Model, you need to create a Schema. A Schema lets you** define the structure of an entry** in the collection. This entry is also called a document. ``` const mongoose = require('mongoose') const Schema = mongoose.Schema const Patient = new Schema({ full_name:String, birth_date:String, phone:String, Gender:String, Code:String }) module.exports = mongoose.model('Patient', Patient) ``` - Create New Patient add the following code at PatientController ``` const PatientModel = require('../../../models/Patient') const newPatient = async (req, res)=>{ let bdy = req.body; console.log(bdy) const P = new PatientModel(bdy) try { const returnedP = await P.save() return res.status(200).json(success(200,returnedP,"Ok")) } catch (error) { return res.status(500).json(error(500,"Something went wrong, "+error.message )) } } ``` Now you can test the request from postman: ``` { "full_name":"Fadi Ramzi", "birth_date":"21/10/2022", "phone":"+9647713451918", "code":"855", "Gender":"m" } ``` - Get All Patients: Changes on old get patients function is ``` const getAllPatients = async (req, res)=>{ const data = await PatientModel.find() return res.status(200).json(success(200,data,"Success")) } ``` - To get one document: ``` const getPatientById = async (req, res)=>{ const id = req.params.id; // TO-DO const p = await PatientModel.findOne({_id:id}) return res.status(200).json(success(200,p,"Success")) } ``` - Update document: either using findOne then .save on the model ``` const updatePatient = async (req, res)=>{ const id = req.params.id; // TO-DO let p = await PatientModel.findOne({_id:id}) p.full_name = req.body.full_name try { p = await p.save() return res.status(200).json(success(200,p,"Success")) } catch (error) { return res.status(500).json(success(500,"Error "+error.message)) } } ``` or by using `findByIdAndUpdate` - Delete a document using findByIdAndDelete ``` const deletePatient = async (req, res)=>{ const id = req.params.id; // TO-DO const deleted = await PatientModel.findOneAndDelete({ _id: id }) return res.status(200).json(success(200,deleted,"Success")) } ``` - Search You can achieve functionality of 'like' search in mongoDB by using $regex as following: ``` const searchPatients = async (req, res)=>{ const full_name = req.query.keyword let data = await Patient.find({ full_name:{ $regex: `.*${full_name}.*` } }) if(data) return res.status(200).json(success(200, data,"OK")) return res.status(404).json(success(404,"No Results")) } ``` ## Relationships Relationships enforce data integrity in relational databases. However, there are no relationships between documents in MongoDB and other NoSQL databases. There are a few differing approaches to modelling these document relationships. ### Relationships Methods The Embedded and Referenced methods are two ways to create such relationships. - Embedded: ideal for one-to-one and one-to-many(few) relationships. - Referenced: ideal for one-to-many(alot) and many-to-many. 1- Embedded Document Model: The documents are embedded within one another in this model. For instance, we have two documents: first one is patients which contain (_id, full_name, birth_date, gender, phone, etc.) and another one is `history` ollection. rather than creating two separate documents, the history documentd are embedded within the patient documents. Retireving operation will be done by single query. 2- Referenced: keep the documents separate in this model, but one document contains the references to the others. For instance: we have two documents: one is a `patient` document (which contains the patient's basic information such as `_id, full_name, phone, gender, birth_date`) and the other is an `history` document (which contains the `history` details of the `patient`). As a result, the _id field of the history document is referenced in the patient document. We can now query the history and get the patient's history details using this reference id. ### Relationship Types #### One to One: - **Referenced**: Referencing A Document in another Document (Normalization) **Example:** ``` // Patient document { _id:"1", full_name: "Ahmed", phone: "+9647788778896", gender:'m', birth_date:"1990/01/01" } // History document { patient_id:"1", date:"1990/01/01", report:"Bla Bla", prescription:[ "", "", "" ] } ``` **Patient Schema** ``` const mongoose = require('mongoose') const Schema = mongoose.Schema const Patient = new Schema({ full_name:String, birth_date: String, phone:String, gender:String, code:String, age:Number }) module.exports = mongoose.model('Patient', Patient); ``` **History Schema** ``` const mongoose = require('mongoose') const Schema = mongoose.Schema const History = new Schema({ date:String, report: String, prescription:[ { type:String } ], patient:{ type: mongoose.Schema.Types.ObjectId, ref: "Patient" } }) module.exports = mongoose.model('History', History); ``` to insert a patient with history: ``` const createPatient = async (req, res)=>{ let bdy = req.body console.log(bdy) let p = new Patient(bdy) try { let returedObject = await p.save(); // for one-to-one referenced, add history to p console.log("saved patient ",returedObject) let h = new History( { ...req.body.history, patient:p._id.toString() } ) await h.save() let result = await h.populate('patient') return res.status(200).json(success(200, result,"OK")) } catch (e) { return res.status(500).json(error(500,"Something went wrong"+e.message)) } } ``` - **Embedded** No need to create another Model for history. The way we define models for Embedded Documents will be different from Referenced Documents. In `models/Patient.js`, we also define Patient like previous code, but also export PatientSchema. ``` const mongoose = require('mongoose'); const { HistorySchema } = require('./History'); const Schema = mongoose.Schema const PatientSchema = new Schema({ full_name:String, birth_date: String, phone:String, gender:String, code:String, age:Number, history:HistorySchema }) const Patient = mongoose.model('Patient', PatientSchema); module.exports = {Patient, PatientSchema} ``` `models/History.js`: ``` const mongoose = require('mongoose'); const Schema = mongoose.Schema const HistorySchema = new Schema({ date:String, report: String, prescription:[ { type:String } ] }) const History = mongoose.model('History', HistorySchema); module.exports = { History, HistorySchema } ``` Now to test insertion but with new design, There is a little change in how we call createIdentifier(). Instead passing customerId, we use patient object instead of id. ``` const createPatient = async (req, res)=>{ let bdy = req.body console.log(bdy) let p = new Patient(bdy) try { let returedObject = await p.save(); return res.status(200).json(success(200, p,"OK")) } catch (e) { return res.status(500).json(error(500,"Something went wrong"+e.message)) } } ``` #### One to Many: There are 3 types of 1-to-many relationships: one-to-many(few) one-to-many(many) one-to-many(alot) - **Referenced** In the MongoDB referenced form, we keep all the documents ‘separated’ which is exactly what ‘normalized’ means. For example, we have documents for Patients and History. Because they are all completely different document, the Patient need a way to know which History it contains. That’s why the IDs come in. We’re gonna use the History’ IDs to make references on Patient document. Patient Document ``` // Patient document { _id:"1", full_name: "Ahmed", phone: "+9647788778896", gender:'m', birth_date:"1990/01/01", history:[ '1', '2', '3' ] } } ``` History Collection ``` { _id:1 date:"10/10/2022", report:"Bla Bla Bla", prescription:[ "A", "B", "C" ] }, { _id:2 date:"10/10/2022", report:"Bla Bla Bla", prescription:[ "A", "B", "C" ] }, { _id:3 date:"10/10/2022", report:"Bla Bla Bla", prescription:[ "A", "B", "C" ] } ``` Schema Definition: ``` // Patient Model { full_name:String, phone: String, gender:String, birth_date:String, history:[ { type: mongoose.Schema.Types.ObjectId, ref: "History" } ] } } ``` Create and add new history Example: ``` const createHistory = function(patientId, history) { return History.create(history).then(h => { console.log(`${h} created`); return Patient.findByIdAndUpdate( patientId, { $push: { history: h._id } }, { new: true, useFindAndModify: false } ); }); }; ``` Embedded(Denormalization) We can also denormalize data into a denormalized form simply by embedding the related documents right into the main document. Because we can get all the data about Patient and History at the same time, our application will need fewer queries to the database which will increase our performance. ``` // Patient document { _id:"1", full_name: "Ahmed", phone: "+9647788778896", gender:'m', birth_date:"1990/01/01", history:[ { date:"1990/01/01", report:"Bla Bla", prescription:[ "", "", "" ] }, { date:"1990/01/01", report:"Bla Bla", prescription:[ "", "", "" ] } ] } } ``` ### When to use References or Embedding for MongoDB One-to-Many Relationships? We will decide how to implement the data model depending on the types of - **Relationships that exists between collections.:** `for one-to-few`, Usually when we have one-to-few relationship, we will embed the related documents into the parent documents. For example, a Patient has few history documents per year. `for one-to-many`, we can either embed or reference according to the other two criteria. `one-to-aLot `, we always use data references. - **On data access patterns** `->a high read/write ratio:` embedded method. `->a high write/read ratio:` referencing - **On data cohesion.** To actually take the decision, we need to combine all of these three criteria, not just use one of them in isolation. **Note** Embedded documents are an efficient and clean way to store related data, especially data that’s regularly accessed together. In general, when designing schemas for MongoDB, you should prefer embedding by default, and use references and application-side or database-side joins only when they’re worthwhile. **Example** `One-to-Few` We will use `One-to-Few` data model for `clinic` application since each patient have several history and not many or a lot. so we will use `Embedded` method. Patient Schema: ``` const mongoose = require('mongoose'); const { HistorySchema } = require('./History'); const Schema = mongoose.Schema const PatientSchema = new Schema({ full_name:String, birth_date: String, phone:String, gender:String, code:String, age:Number, history:[] }) const Patient = mongoose.model('Patient', PatientSchema); module.exports = {Patient, PatientSchema} ``` To add new history to already exists patient: ``` const AddHistoryToPatient = async (req,res)=>{ console.log('Hi') const id = req.params.id // patient ID console.log("_id is",id) //# method 1 // let p = await Patient.findByIdAndUpdate(id,{ // $push:{ // history:req.body // } // }) // # method 2 let p = await Patient.findById(id) p.history.push(req.body) p.save() return res.status(200).json(success(200, p,"OK")) } ``` **Example** `One-to-Many` Patient Schema: ``` const mongoose = require('mongoose'); const { HistorySchema } = require('./History'); const Schema = mongoose.Schema const PatientSchema = new Schema({ full_name:String, birth_date: String, phone:String, gender:String, code:String, age:Number, history:[ { type:mongoose.Schema.Types.ObjectId, ref:'History' } ] }) const Patient = mongoose.model('Patient', PatientSchema); module.exports = {Patient, PatientSchema} ``` - First create child document - Add Id of created child document into parent document ``` const AddHistoryToPatient = async (req,res)=>{ console.log('Hi') const id = req.params.id // patient ID // create child first let h = await History.create(req.body) let p = await Patient.findByIdAndUpdate(id,{ $push:{ history:h._id } }).populate('history','-__v') return res.status(200).json(success(200, p,"OK")) } ``` `populate('history','-__v')`: used to load related embedded document. `-__v` param: used to select all fields except `__v`, `-` prefix of the field used to indicate removing this field. ## Aggregation An aggregation framework query is an array of stages. A stage is an object description of how MongoDB should transform any document coming into the stage. The first stage feeds documents into the second stage, and so on, so you can compose transformations using stages. The array of stages you pass to the aggregate() function is called an aggregation pipeline. ``` let results = [ModelCollection].aggregate([ {},// stage {} // stage ]) ``` **The $match Stage** The $match stage filters out documents that don't match the given filter parameter, similar to filters for Mongoose's find() function. Example: ``` const searchPatients = async (req, res)=>{ let data = await Patient.aggregate([ { $match:{ gender:{ $eq:'f' } } } ]) console.log(data) if(data) return res.status(200).json(success(200, data,"OKlk")) return res.status(404).json(success(404,"No Results")) } ``` output results is filtered to be all the objects has gender equal to `m`/`f`. `$eq`: this is operator used to compare. **The $group Stage** The $group stage behaves like a reduce() function. For example, the $group stage lets you count how many characters have a given age. ``` const searchPatients = async (req, res)=>{ const full_name = req.query.keyword // let data = await Patient.find({ // full_name:{ // $regex: `.*${full_name}.*` // } // }) let data = await Patient.aggregate([ { $match:{ gender:{ $eq:'f' } } }, { $group:{ _id:null, count:{ $sum:1 } } } ]) console.log(data) if(data) return res.status(200).json(success(200, data,"OKlk")) return res.status(404).json(success(404,"No Results")) } ``` output is property `count` that holds the sum of documents. ``` { "code": 200, "data": [ { "_id": null, "count": 1 } ], "success": true, "message": "OKlk" } ``` ## Filter, Sorting and Pagination ### Filter Use query filter object to filter patient according to `gender` field. you can also pass query object to populate function to achieve filtering on child collection. ``` const getAllPatients = async (req, res)=>{ const gender = req.query.gender; const date = req.query.date; const match = { date:date } console.log(match) let data = await Patient.find({ gender:gender }).populate({ path:'history', match }) return res.status(200).json(success(200,data,"Success")) } ``` URL:`http://127.0.0.1:5000/api/v1/patients?gender=m&date=10/10/2022` ### Pagination One of the most important things to make a website friendly is the response time, and pagination comes for this reason. For example, this google.com website returns thousands or millions of results, however display several per page, they don't want to display all of them at once. Pagination means displaying a small number of all, by a page. We will use: - `limit` chaining method on Model: Number of records to fetch - `skip` chaining method on Model: offset number to skip N of records then get remaining records. ``` const getAllPatients = async (req, res)=>{ const gender = req.query.gender; const date = req.query.date; const limit = req.query.limit; const offset = req.query.offset; let parentMatch = {}; if(gender) { parentMatch = { gender:gender } } let childMatch = {}; if(date) { childMatch.date = date } console.log('parent',parentMatch) console.log('child',childMatch) let data = await Patient.find(parentMatch) .populate({ path:'history', match:childMatch }) .limit(limit) .skip(offset) return res.status(200).json(success(200,data,"Success")) } ``` ### Sorting Sorting will help us to sort the data by a specific field and get us the data whether in an ascending order or descending order. Let's sort returned patient list by the name: ``` const getAllPatients = async (req, res)=>{ const gender = req.query.gender; const date = req.query.date; const limit = req.query.limit; const offset = req.query.offset; let parentMatch = {}; if(gender) { parentMatch = { gender:gender } } let childMatch = {}; if(date) { childMatch.date = date } console.log('parent',parentMatch) console.log('child',childMatch) let data = await Patient.find(parentMatch) .populate({ path:'history', match:childMatch }) .limit(limit) .skip(offset) .sort({ full_name:1 }) return res.status(200).json(success(200,data,"Success")) } ``` - `-1`: for desc order. - `1`: for asc order. Also we can sort embedded collection via populate function, let's sort history list of the patient: ``` const getAllPatients = async (req, res)=>{ const gender = req.query.gender; const date = req.query.date; const limit = req.query.limit; const offset = req.query.offset; let parentMatch = {}; if(gender) { parentMatch = { gender:gender } } let childMatch = {}; if(date) { childMatch.date = date } console.log('parent',parentMatch) console.log('child',childMatch) let data = await Patient.find(parentMatch) .populate({ path:'history', match:childMatch, options:{ sort:{ _id:-1 } } }) .limit(limit) .skip(offset) .sort({ full_name:1 }) return res.status(200).json(success(200,data,"Success")) } ``` ## References https://www.mongodb.com/docs/manual/tutorial/getting-started/ https://mongoosejs.com/docs/ Aggregation https://masteringjs.io/tutorials/mongoose/aggregate#:~:text=Mongoose's%20aggregate()%20function%20is,in%20Mongoose%20without%20any%20changes.