Many-to-many mapping with Mongoose

node.jsMongodbMongoose

node.js Problem Overview


I have FlashcardSchemas and PackageSchemas in my design. One flashcard can belong to different packages and a package can contain different flashcards.

Below you can see a stripped down version of my mongoose schema definitions:

// package-schema.js
var Schema = mongoose.Schema,
  	ObjectId = Schema.ObjectId;

var PackageSchema = new Schema({
	id			: ObjectId,
	title		: { type: String, required: true },
	flashcards	: [ FlashcardSchema ]
});

var exports = module.exports = mongoose.model('Package', PackageSchema);

// flashcard-schema.js
var Schema = mongoose.Schema,
  	ObjectId = Schema.ObjectId;

var FlashcardSchema = new Schema({
	id		: ObjectId,
	type 		: { type: String, default: '' },
	story		: { type: String, default: '' },
	packages	: [ PackageSchema ]
});

var exports = module.exports = mongoose.model('Flashcard', FlashcardSchema);

As you can see from the comments above, these two schema definitions belong to separate files and reference each other.

I get an exception stating that PackageSchema is not defined, as expected. How can I map a many-to-many relation with mongoose?

node.js Solutions


Solution 1 - node.js

I am new to node, mongoDB, and mongoose, but I think the proper way to do this is:

var PackageSchema = new Schema({
    id: ObjectId,
    title: { type: String, required: true },
    flashcards: [ {type : mongoose.Schema.ObjectId, ref : 'Flashcard'} ]
});

var FlashcardSchema = new Schema({
    id: ObjectId,
    type: { type: String, default: '' },
    story: { type: String, default: '' },
    packages: [ {type : mongoose.Schema.ObjectId, ref : 'Package'} ]
});

This way, you only store the object reference and not an embedded object.

Solution 2 - node.js

You are doing it the right way, however the problem is that you have to include PackageSchema in the the flashcard-schema.js, and vice-versa. Otherwise these files have no idea what you are referencing

var Schema = mongoose.Schema,
    ObjectId = Schema.ObjectId;
    PackageSchema = require('./path/to/package-schema.js')

var FlashcardSchema = new Schema({
    id      : ObjectId,
    type        : { type: String, default: '' },
    story       : { type: String, default: '' },
    packages    : [ PackageSchema ]
});

Solution 3 - node.js

You could use the Schema.add() method to avoid the forward referencing problem.

This (untested) solution puts the schema in one .js file

models/index.js

var Schema = mongoose.Schema,
	ObjectId = Schema.ObjectId;

// avoid forward referencing
var PackageSchema = new Schema();
var FlashcardSchema = new Schema();
	
PackageSchema.add({
	id          : ObjectId,
	title       : { type: String, required: true },
	flashcards  : [ FlashcardSchema ]
});

FlashcardSchema.add({
	id      : ObjectId,
	type        : { type: String, default: '' },
	story       : { type: String, default: '' },
	packages    : [ PackageSchema ]
});

// Exports both types
module.exports = {
	Package:   mongoose.model('Package', PackageSchema),
	Flashcard: mongoose.model('Flashcard', FlashcardSchema)
};	

Solution 4 - node.js

You're thinking of this too much like a relational data store. If that's what you want, use MySQL (or another RDBMS)

Failing that, then yes, a third schema could be used, but don't forget it'll still only be the id of each object (no joins, remember) so you'll still have to retrieve each other item in a separate query.

Solution 5 - node.js

https://www.npmjs.com/package/mongoose-relationship

##Many-To-Many with Multiple paths

var mongoose = require("mongoose"),
    Schema = mongoose.Schema,
    relationship = require("mongoose-relationship");
 
var ParentSchema = new Schema({
    children:[{ type:Schema.ObjectId, ref:"Child" }]
});
var Parent = mongoose.models("Parent", ParentSchema);
 
var OtherParentSchema = new Schema({
    children:[{ type:Schema.ObjectId, ref:"Child" }]
});
var OtherParent = mongoose.models("OtherParent", OtherParentSchema);
 
var ChildSchema = new Schema({
    parents: [{ type:Schema.ObjectId, ref:"Parent", childPath:"children" }]
    otherParents: [{ type:Schema.ObjectId, ref:"OtherParent", childPath:"children" }]
});
ChildSchema.plugin(relationship, { relationshipPathName:['parents', 'otherParents'] });
var Child = mongoose.models("Child", ChildSchema)
 
var parent = new Parent({});
parent.save();
var otherParent = new OtherParent({});
otherParent.save();
 
var child = new Child({});
child.parents.push(parent);
child.otherParents.push(otherParent);
child.save() //both parent and otherParent children property will now contain the child's id 
child.remove() 

Solution 6 - node.js

This is the problem of cyclic/circular dependency. This is how you make it work in nodejs. For more detail, check out "Cyclic dependencies in CommonJS" at http://exploringjs.com/es6/ch_modules.html#sec_modules-in-javascript

//------ a.js ------
var b = require('b');
function foo() {
    b.bar();
}
exports.foo = foo;

//------ b.js ------
var a = require('a'); // (i)
function bar() {
    if (Math.random()) {
        a.foo(); // (ii)
    }
}
exports.bar = bar;

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionÉlodie PetitView Question on Stackoverflow
Solution 1 - node.jsgtsoukView Answer on Stackoverflow
Solution 2 - node.jsLast Rose StudiosView Answer on Stackoverflow
Solution 3 - node.jsTony O'HaganView Answer on Stackoverflow
Solution 4 - node.jsAlexView Answer on Stackoverflow
Solution 5 - node.jssudeep_dkView Answer on Stackoverflow
Solution 6 - node.jsLu TranView Answer on Stackoverflow