Mongoose Many-to-Many related models with NodeJS/Express, MongoDB

mongoose-many-to-many-related-models-with-nodejs-express-mongodb-feature-image

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

nodejs-express-mongodb-mongoosejs-many-to-many-related-documents-init-data

– Get All Subjects
-> http://localhost:8081/api/subjects

nodejs-express-mongodb-mongoosejs-many-to-many-related-documents-get-all-subjects

– Get All Students
-> http://localhost:8081/api/students

nodejs-express-mongodb-mongoosejs-many-to-many-related-documents-get-all-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

nodejs-express-mongodb-mongoosejs-many-to-many-related-documents-find-student-learnt-a-given-subject-id

Sourcecode

Nodejs-Mongoose-Many-To-Many

3 thoughts on “Mongoose Many-to-Many related models with NodeJS/Express, MongoDB”

  1. Great article but I would like to know, how one can do this with the data coming from a form rather than the hard coded data used in this example?

  2. good one.
    how will we findout the students who are attending both maths and computer classes considering the fact that there are theree subjects taken up by students(maths, computer and Physics)

Leave a Reply

Your email address will not be published. Required fields are marked *