In the tutorial, we will show you how to develop Mongoose Many-to-Many related documents with NodeJs/Express, MongoDB.
Related post:
– Crud RestAPIs with NodeJS/Express, MongoDB using Mongoose
– NodeJs/Express MongoDB One-to-Many related documents
Goal
Prerequisites
Crud RestAPIs with NodeJS/Express, MongoDB using Mongoose
In the above tutorial, we show how to build CRUD RestAPIs with NodeJS/Express and MongoDB using Mongoose:
/nodejs-restapi-mongodb /app /config mongodb.config.js /controllers customer.controller.js /models customer.model.js /routes customer.routes.js /node_modules package.json server.js
Objective
In the tutorial, we show how to develop Mongoose Many-to-Many related documents with NodeJS/Express, MongoDB. Project structure:
/Nodejs-Mongoose-Many-To-Many /app /config mongodb.config.js /controllers init.controller.js student.controller.js subject.controller.js /models student.model.js subject.model.js /routes init.routes.js students.routes.js subjects.routes.js /node_modules package.json server.js
Many-to-Many related models
For working with related documents, we use the ObjectId schema field.
-> SubjectSchema:
const mongoose = require('mongoose'), Schema = mongoose.Schema; const SubjectSchema = mongoose.Schema({ code: String, name: String }); module.exports = mongoose.model('Subject', SubjectSchema);
-> StudentSchema:
const Subject = require('../models/subject.model.js'); const mongoose = require('mongoose'), Schema = mongoose.Schema; const StudentSchema = mongoose.Schema({ firstname: String, lastname: String, age: { type: Number, required: true }, subjects : [{ type: Schema.Types.ObjectId, ref: 'Subject' }] }); module.exports = mongoose.model('Student', StudentSchema);
We can save all references to the related documents by assigning the _id value:
// Add Subject to MongoDB var math = new Subject({ code: 'M-01', name: 'Mathematics' }); var computer = new Subject({ code: "C-02", name: 'Computer' }); math.save(function (err){ if(err) return console.error(err.stack) console.log("Math is added") }); computer.save(function(err){ if(err) return console.error(err.stack); console.log("Computer is added") }) // Add Student to MongoDB var peter = new Student({ firstname: 'Peter', lastname: 'Thomas', age: 25 }) peter.subjects.push(math._id, computer._id) peter.save(function(err){ if(err) return console.log(err.stack); console.log("Peter is added") }); ...
We use populate()
to get the Subject information in Student:
Student.findOne({ firstname: req.params.firstname }) .populate('subjects') .exec(function (err, student) { if (err){ ... } res.send(student); });
We didn’t add our students to subjects, how to get all students that learnt the same particular subject?
-> One way, we create a references array field of students in SubjectSchema as below:
const Student = require('../models/student.model.js'); const mongoose = require('mongoose'), Schema = mongoose.Schema; const SubjectSchema = mongoose.Schema({ code: String, name: String, students : [{ type: Schema.Types.ObjectId, ref: 'Student' }] }); module.exports = mongoose.model('Subject', SubjectSchema);
But What is problem with above way? -> We have two places where the information relating students and subjects needs to be maintained.
What is the better solution?
-> We get the _id of our subject, then use find()
to search for this in the subjects field across all students.
exports.findBySubjectId = (req, res) => { Student.find({ subjects : req.params.subjectId }) .exec(function (err, students) { if (err){ ... } res.send(students); }); };
Practice
Create a NodeJS/Express project
Following below guide: Crud RestAPIs with NodeJS/Express, MongoDB using Mongoose
See dependencies in ‘package.json’ file:
"dependencies": { "body-parser": "^1.18.2", "express": "^4.16.3", "mongoose": "^5.0.13", "npm": "^5.8.0" }
Create Model Schemas
Subject Schema
const mongoose = require('mongoose'), Schema = mongoose.Schema; const SubjectSchema = mongoose.Schema({ code: String, name: String }); module.exports = mongoose.model('Subject', SubjectSchema);
Student Schema
const Subject = require('../models/subject.model.js'); const mongoose = require('mongoose'), Schema = mongoose.Schema; const StudentSchema = mongoose.Schema({ firstname: String, lastname: String, age: { type: Number, required: true }, subjects : [{ type: Schema.Types.ObjectId, ref: 'Subject' }] }); module.exports = mongoose.model('Student', StudentSchema);
Route
Init Data Route
module.exports = function(app) { var initialController = require('../controllers/init.controller.js') app.get('/api/data/init', initialController.init); }
Subject Routes
module.exports = function(app) { var subjects = require('../controllers/subject.controller.js') app.get('/api/subjects', subjects.findAll); }
Student Routes
module.exports = function(app) { var students = require('../controllers/student.controller.js'); // Get All Students app.get('/api/students', students.findAll); // Find a single Student by Name app.get('/api/students/:firstname', students.findByName); // Find all Students that learnt a given subject app.get('/api/students/subject/:subjectId', students.findBySubjectId); }
Controller
Init Data Controller
const Subject = require('../models/subject.model.js'); const Student = require('../models/student.model.js'); exports.init = (req, res) => { // Add Subject to MongoDB var math = new Subject({ code: 'M-01', name: 'Mathematics' }); var computer = new Subject({ code: "C-02", name: 'Computer' }); math.save(function (err){ if(err) return console.error(err.stack) console.log("Math is added") }); computer.save(function(err){ if(err) return console.error(err.stack); console.log("Computer is added") }) // Add Students to MongoDB var jack = new Student({ firstname: 'Jack', lastname: 'Davis', age: 21 }); jack.subjects.push(math._id, computer._id); var peter = new Student({ firstname: 'Peter', lastname: 'Thomas', age: 25 }) peter.subjects.push(math._id, computer._id) peter.save(function(err){ if(err) return console.log(err.stack); console.log("Peter is added") }); jack.save(function(err){ if(err) return console.log(err.stack); console.log("Jack is added"); }); // Return Message res.send("Done Initial Data!"); }
Subject Controller
const Subject = require('../models/subject.model.js'); exports.findAll = (req, res) => { Subject.find() .then(subjects => { res.send(subjects); }).catch(err => { res.status(500).send({ message: err.message }); }); };
Student Controller
const Student = require('../models/student.model.js'); const Subject = require('../models/subject.model.js'); // Get All Students exports.findAll = (req, res) => { Student.find() .then(students => { res.send(students); }).catch(err => { res.status(500).send({ message: err.message }) }); }; // Find a Student by firstname exports.findByName = (req, res) => { Student.findOne({ firstname: req.params.firstname }) .populate('subjects') .exec(function (err, student) { if (err){ if(err.kind === 'ObjectId') { return res.status(404).send({ message: "Student not found with given firstname " + req.params.firstname }); } return res.status(500).send({ message: "Error retrieving Student with given firstname" + req.params.firstname }); } res.send(student); }); }; // Find all student learnt a given subject exports.findBySubjectId = (req, res) => { Student.find({ subjects : req.params.subjectId }) .exec(function (err, students) { if (err){ if(err.kind === 'ObjectId') { return res.status(404).send({ message: "Student not found with given Subject Id " + req.params.subjectId }); } return res.status(500).send({ message: "Error retrieving Student with given subject Id " + req.params.subjectId }); } res.send(students); }); };
Run & Check results
Run MongDB server by commandline:
\MongoDB\Server\3.6\bin>mongod.exe 2018-04-13T05:13:11.363+0700 I CONTROL [initandlisten] MongoDB starting : pid=1584 port=27017 dbpath=C:\data\db\ 64-bit host=LOI-COMPUTER 2018-04-13T05:13:11.365+0700 I CONTROL [initandlisten] targetMinOS: Windows 7/Windows Server 2008 R2 2018-04-13T05:13:11.366+0700 I CONTROL [initandlisten] db version v3.6.3 2018-04-13T05:13:11.366+0700 I CONTROL [initandlisten] git version: 9586e557d54ef70f9ca4b43c26892cd55257e1a5 2018-04-13T05:13:11.366+0700 I CONTROL [initandlisten] OpenSSL version: OpenSSL 1.0.1u-fips 22 Sep 2016 ...
Run NodeJS/Express application:
>node server.js App listening at http://:::8081 Successfully connected to MongoDB.
– Initial data
-> http://localhost:8081/api/init
– Get All Subjects
-> http://localhost:8081/api/subjects
– Get All Students
-> http://localhost:8081/api/students
– Find Student by Name
-> http://localhost:8081/api/students/Peter
– Find Students learnt a given subject Id
-> http://localhost:8081/api/students/subject/5ad006faa6996d2634ff65f5