Node.js with Mongoose

Posted on - Last Modified on

Mongoose was written to provide a developer-friendly layer above the standard MongoDB node.js driver, which saves time for developers who want to get rid of boilerplate code writing and concentrate only on application logic.

Mongoose can be installed through npm (added -g flag to install it globally):

sudo npm install mongoose -g

Defining a Schema in mongoose

Mongoose has the concept of Schema which helps mapping and reading, writing and converting application level data from/to MongoDB. If you define the schema correctly, then data conversion is done automatically. Let's assume you want to write an application that handles bank accounts and transactions. For this, I defined two schemas – one for transactions, and one for bank accounts.

Schema for Bank Account

I created a simple bank account object, with only a couple of fields. I added the fields so I can demonstrate the data types that are supported by Mongoose.

var mongoose = require('mongoose');
var transactionsSchema = require('./transaction').TransactionsSchema;

var bankAccountSchema = mongoose.Schema({
	owner : String,
	accountNumber: String,
	currency: String,
	balance: Number,
	createdOn: Date,
	transactions: [transactionsSchema]
});

bankAccountSchema.methods.displayInfo = function () {
	console.log("Owner:" + this.owner 
				+ " | AccountNumber:" + this.accountNumber 
				+ " | Balance:" + this.balance);
	//
	// Log Transactions if these exist
	//
	if(this.transactions && this.transactions.length > 0){
		this.transactions.forEach(function(item, index){
			console.log("TransactionId:" + item.transactionId 
				+ " | From:" + item.fromOwner
				+ " | To:" + item.toOwner
				+ " | Currency:" + item.currency
				+ " | Value:" + item.value);
		});
	}
};

You can define a schema using the mongoose.Schema() method, recieves a JSON object, with the name of the fields and types. The owner, accountNumber, currency, balance, and createdOn fields are self-explanatory.

The transactions field is more interesting since this is defined as an array of transactionSchemas, which is specified in the transaction.js file. This way, Mongoose will enforce the developer to add only models of type transactionSchema to the transactions array.

You can define methods for the new type along with the schema definition. In this case, I defined the displayInfo() method which prints the details of the bank account object and lists all the transactions related to it to the console. You will see later how can this be used.

Schema for Transaction

The schema defined for transactions is self-explanatory:

var transactionSchema = mongoose.Schema({
	transactionId: String,
	from : String,
	to: String,
	fromOwner : String,
	toOwner: String,
	currency: String,
	value: Number,
	createdOn: Date,
	executedOn: Date,
	wasExecuted: Boolean
});

Creating Models

After defining the all the schemas you need, you can create Mongoose models. In Mongoose, the CRUD operations are available on the model level.

var BankAccount = mongoose.model('BankAccount', bankAccountSchema);
var myAccount = new BankAccount({
	owner: "John Doe",
	accountNumber: "1233-4564-4564-5555",
	createdOn: new Date(2014,3,11),
	currency: "USD",
	balance: 2864.53,
	transactions: [
		transaction1, // lets suppose it is defined
		transaction2  // lets suppose it is defined
	]
});

myAccount.save(function(err, account, numberOfItemsAffrected){
	if(err) {
		console.log(err);
		return;
	}
	if(account) {
		console.log("Number of items affected in the database:" + numberOfItemsAffrected);
		account.displayInfo();
	}
}); 

Models can be created using the mongoose.model() method, wherein the first parameter is the name of the collection that stores the model (this gets pluralized) and the schema. The mongoose.model() returns a constructor object that's specific to the schema. Afterwards, you can use this to create models using the new keyword. I created the myAccount model, set the properties, and added two transactions to the transactions array. Since these were created using transactionSchema, Mongoose should handle it without any issues.

Mongoose models have CRUD methods defined (like save() and remove()) and have query methods predefined too, like findById(), find() and findOne().

The save() method needs a callback function with three parameters: first is the error object (if there was any), second parameter is the account object which was saved to the database, and the third is a counter, giving information about how many documents were affected by the operation.

When executing the program, the output is similar to:

greg@earth:~/git/node_projects/node_mongoose $ node app.js 

Connection to the db created.
Number of items affected in the database:1

Owner:John Doe | AccountNumber:1233-4564-4564-5555 | Balance:2864.53

TransactionId:IDGGFFG4579 | From:Jane Doe | To:John Doe | Currency:USD | Value:40.3 | Executed:true
TransactionId:IDABCDF1233 | From:Bob Doe | To:John Doe | Currency:USD | Value:148.49 | Executed:false

Data stored in MongoDB can be seen below. Notice that each document, even the nested ones (like transactions) got an _id field; this was added by mongoose to ensure there is a unique identifier for each object. You can force mongoose to skip adding the _id field to the generated objects using:

// disable _id generation for the schema
	var schema = new Schema({ ... }, { _id: false }); 

 > db.bankaccounts.find().pretty()
{
	"owner" : "John Doe",
	"accountNumber" : "1233-4564-4564-5555",
	"createdOn" : ISODate("2014-04-10T22:00:00Z"),
	"currency" : "USD",
	"balance" : 2864.53,
	"_id" : ObjectId("54ffde26437dba800e3e0dd7"),
	"transactions" : [
		{
			"transactionId" : "IDGGFFG4579",
			"from" : "3122-4456-4444-5546",
			"to" : "1233-4564-4564-5555",
			"fromOwner" : "Jane Doe",
			"toOwner" : "John Doe",
			"currency" : "USD",
			"value" : 40.3,
			"createdOn" : ISODate("2014-04-11T08:25:23Z"),
			"executedOn" : ISODate("2014-04-11T12:00:00Z"),
			"wasExecuted" : true,
			"_id" : ObjectId("54ffde26437dba800e3e0dd5")
		},
		{
			"transactionId" : "IDABCDF1233",
			"from" : "8877-4111-4999-5555",
			"to" : "1233-4564-4564-5555",
			"fromOwner" : "Bob Doe",
			"toOwner" : "John Doe",
			"currency" : "USD",
			"value" : 148.49,
			"createdOn" : ISODate("2014-04-10T05:11:05Z"),
			"executedOn" : null,
			"wasExecuted" : false,
			"_id" : ObjectId("54ffde26437dba800e3e0dd6")
		}
	],
	"__v" : 0
}

Using Mongoose for data manipulation is very convenient because you can concentrate on application logic development and not on writing boilerplate code for creating/opening database connections, writing insert statements, and data conversion logic.

The code can be found on GitHub under the node_projects repository in the node_mongoose folder.

Posted 13 March, 2015

Greg Bogdan

Software Engineer, Blogger, Tech Enthusiast

I am a Software Engineer with over 7 years of experience in different domains(ERP, Financial Products and Alerting Systems). My main expertise is .NET, Java, Python and JavaScript. I like technical writing and have good experience in creating tutorials and how to technical articles. I am passionate about technology and I love what I do and I always intend to 100% fulfill the project which I am ...

Next Article

Web Content Optimized for Best Results