File Structure of Mongoose & NodeJS Project

node.jsMongoose

node.js Problem Overview


I currently have all my models (Schema definitions) in the /models/models.js file for my Mongoose/NodeJS application.

I'd like to break these apart into different files as such: user_account.js, profile.js, etc. However I cannot seem to do so as my controllers break and report back "cannot find module" once I pull these classes apart.

My project structure is as follows:

/MyProject
  /controllers
    user.js
    foo.js
    bar.js
    // ... etc, etc
  /models
    models.js
  server.js

The contents of my models.js file looks like this:

var mongoose = require('mongoose'),
	Schema = mongoose.Schema,
	ObjectId = Schema.ObjectId;
	
mongoose.connect('mongodb://localhost/mydb');

var UserAccount = new Schema({
	user_name		: { type: String, required: true, lowercase: true, trim: true, index: { unique: true } }, 
	password		: { type: String, required: true },
	date_created	: { type: Date, required: true, default: Date.now }
}); 

var Product = new Schema({
	upc				: { type: String, required: true, index: { unique: true } },
	description		: { type: String, trim: true },
	size_weight		: { type: String, trim: true }
});
 

My user.js file (controller) looks like this:

var mongoose 	= require('mongoose'), 
	UserAccount = mongoose.model('user_account', UserAccount);

exports.create = function(req, res, next) {

	var username = req.body.username; 
	var password = req.body.password;

	// Do epic sh...what?! :)
}

How can I break the schema definition into multiple files and also reference it from my controller? When I do reference it (after the schema is in a new file) I get this error:

Error: Schema hasn't been registered for model "user_account".

Thoughts?

node.js Solutions


Solution 1 - node.js

Here's a sample app/models/item.js

var mongoose = require("mongoose");

var ItemSchema = new mongoose.Schema({
  name: {
    type: String,
    index: true
  },
  equipped: Boolean,
  owner_id: {
    type: mongoose.Schema.Types.ObjectId,
    index: true
  },
  room_id: {
    type: mongoose.Schema.Types.ObjectId,
    index: true
  }
});

var Item = mongoose.model('Item', ItemSchema);

module.exports = {
  Item: Item
}

To load this from an item controller in app/controllers/items.js I would do

  var Item = require("../models/item").Item;
  //Now you can do Item.find, Item.update, etc

In other words, define both the schema and the model in your model module and then export just the model. Load your model modules into your controller modules using relative require paths.

To make the connection, handle that early in your server startup code (server.js or whatever). Usually you'll want to read the connection parameters either from a configuration file or from environment variables and default to development mode localhost if no configuration is provided.

var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost');

Solution 2 - node.js

A couple answers here really helped me develop an alternative approach. The original question is regarding breaking just the Schema definition out, but I prefer to bundle the Schema and Model definitions in the same file.

This is mostly Peter's idea, only exporting the model definition by overriding module.exports to make accessing the model from your controller a little less verbose:

Project layout:

MyProject
  /controllers
    user.js
    foo.js
    bar.js
    // ... etc, etc
  /models
    Item.js
  server.js

models/Item.js would look like:

var mongoose = require("mongoose");

var ItemSchema = new mongoose.Schema({
  name: {
    type: String,
    index: true
  }
});

module.exports = mongoose.model('Item', ItemSchema); 
// Now `require('Item.js')` will return a mongoose Model,
// without needing to do require('Item.js').Item

And you access the model in a controller, say user.js, like:

var Item = require(__dirname+'/../models/Item')

...

var item = new Item({name:'Foobar'});

Don't forget to call mongoose.connect(..) in server.js, or wherever else you deem appropriate!

Solution 3 - node.js

I recently answered a Quora question with regard to this same problem. http://qr.ae/RoCld1

What I have found very nice and saves on the amount of require calls is to structure your models into a single directory. Make sure you only have a single model per file.

Create an index.js file in the same directory as your models. Add this code to it. Be sure to add the necessary fs require

var fs = require('fs');

/*
 * initializes all models and sources them as .model-name
 */
fs.readdirSync(__dirname).forEach(function(file) {
  if (file !== 'index.js') {
    var moduleName = file.split('.')[0];
    exports[moduleName] = require('./' + moduleName);
  }
});

Now you can call all your models as follows:

var models = require('./path/to/models');
var User = models.user;
var OtherModel = models['other-model'];

Solution 4 - node.js

Peter Lyons pretty much covered the basis.
Borrowing from the above example (removing the lines after the schema) I just wanted to add:

app/models/item.js

note: notice where `module.exports` is placed
var mongoose = require("mongoose");

var ItemSchema = module.exports = new mongoose.Schema({
  name: {
    type: String,
    index: true
  },
  ...

});

To load it from the app/controllers/items.js

var mongoose = require('mongoose');
var Item = mongoose.model('Item', require('../models/item'));  

Another way without the module.exports or require:

app/models/item.js

var mongoose = require("mongoose");

var ItemSchema = new mongoose.Schema({
  name: {
    type: String,
    index: true
  },
  ... 

});

mongoose.model('Item', ItemSchema); // register model

In the app/controllers/items.js

var mongoose = require('mongoose')
  , Item = mongoose.model('Item');  // registered model

Solution 5 - node.js

Inspired by sequelize-cli, I have a models directory where i define all schema.

Complete app on github: https://github.com/varunon9/node-starter-app-mongo

models/index.js-

'use strict';

const fs        = require('fs');
const path      = require('path');
const mongoose = require('mongoose');//.set('debug', true);
const basename  = path.basename(__filename);
const env       = process.env.NODE_ENV || 'development';
const config    = require(__dirname + '/../config/config.json')[env];
const db        = {};

const Schema = mongoose.Schema;

fs
    .readdirSync(__dirname)
    .filter(fileName => {
	    return (
	    	fileName.indexOf('.') !== 0) 
	                && (fileName !== basename) 
	                && (fileName.slice(-3) === '.js'
	    );
    })
    .forEach(fileName => {
	    const model = require(path.join(__dirname, fileName));
	    const modelSchema = new Schema(model.schema);

	    modelSchema.methods = model.methods;
	    modelSchema.statics = model.statics;

	    // user.js will be user now
	    fileName = fileName.split('.')[0];
	    db[fileName] = mongoose.model(fileName, modelSchema);
    });

module.exports = db;

models/user.js-

'use strict';

module.exports = {
	schema: {
		email: {
			type: String,
			required: true,
			unique: true,
		},
		mobile: {
			type: String,
			required: false
		},
		name: {
			type: String,
			required: false
		},
		gender: {
			type: String,
			required: false,
			default: 'male'
		},
		password: {
			type: String,
			required: true
		},
		dob: {
			type: Date,
			required: false
		},
		deactivated: {
			type: Boolean,
			required: false,
			default: false
		},
		type: {
			type: String,
			required: false
		}
	},

    // instance methods goes here
	methods: {

	},

    // statics methods goes here
	statics: {
	}
};

Solution 6 - node.js

I like using classes to organize everything, maybe try this:

const mongoose = require('mongoose')

class UserAccount {
    constructor() {
        this.schema = new mongoose.Schema({
            user_name: { type: String, required: true, lowercase: true, trim: true, index: { unique: true } },
            password: { type: String, required: true },
            date_created: { type: Date, required: true, default: Date.now }
        });
        this.model = new mongoose.model('UserAccount', this.schema)
    }

    create = (obj) => {
        return new Promise((resolve, reject) => {
            this.model.create({ ...item })
                .then((result) => {
                    resolve(result)
                }).catch((err) => {
                    reject(err)
                });
        });
    }
}

module.exports = UserAccount;

This approach allows you to add custom methods. Plus it combines the controller/model into a single vector, allowing you to detach the model at any time. This may not scale in enterprise, but it may suite smaller apps.

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
QuestionDonn FelkerView Question on Stackoverflow
Solution 1 - node.jsPeter LyonsView Answer on Stackoverflow
Solution 2 - node.jsNick BoyleView Answer on Stackoverflow
Solution 3 - node.jsJosh HardyView Answer on Stackoverflow
Solution 4 - node.jsuser1460015View Answer on Stackoverflow
Solution 5 - node.jsVarun KumarView Answer on Stackoverflow
Solution 6 - node.jsSolomon BushView Answer on Stackoverflow