Node.js for Rails developers, Part 6b (MongoDB Datastore)

This post is part of a series for Rails developers who want to get started with Node.js.
After enabling basic navigation, it’s time to enable database functionality.
This post describes the process of connecting the application to a MongoDB datastore.

MongoDB Prerequisites

Installing MongoDB

Follow these instructions to install MongoDB on your local machine using Homebrew, if necessary.

Installing Package Dependencies

To interface with MongoDB, we’ll use the Mongoose module to specify the schema and execute queries. Let’s install it now.

npm install mongoose --save

Mongoose includes Object-Relational Mapping (ORM) capabilities like ActiveRecord does. This means there are models with Mongoose. Let’s create them now.

Creating Models

Create models.

mkdir -p app/models
touch app/models/robot.js

Edit app/models/robot.js according to the following template:

// app/models/robot.jsvarmongoose=require('mongoose');varSchema=mongoose.Schema;varRobotSchema=newSchema({name:{type:String,required:true},description:{type:String,required:true}},{timestamps:{// include timestamp attributes in the schema and automatically assign values on create and update, respectivelycreatedAt:'created_at',// rename from createdAtupdatedAt:'updated_at'// rename from updatedAt}});module.exports=mongoose.model('Robot',RobotSchema);

Configuring Database Connection

Add a new database connection file.

touch db.js

Edit db.js according to the following template:

// db.jsvarmongoose=require('mongoose');varmongoConnectionString=process.env.MONGODB_URI||'mongodb://localhost/robots_dev';mongoose.connect(mongoConnectionString);// establishes a database connection which may in some cases need to be manually closed ... use db.disconnect();module.exports=mongoose;

Tell the web server to start a database connection. Edit app.js to include the following code:

Seeding the Database

Create a new database population script.

touch db/seed.js

Edit db/seed.js according to the following template:

// db/seed.jsvardb=require("../db");// starts a mongoose connectionvarRobot=require("../app/models/robot");varrobots=[{name:"c3po",description:"specializes in language translation"},{name:"r2d2",description:"holds a secret message"},{name:"bb8",description:"rolls around"}];Robot.find(function(err,bots){if(err)returnconsole.error(err);console.log("FOUND",bots.length,"ROBOTS TO BE DELETED")Robot.remove(bots,function(err){if(err)returnconsole.error(err);console.log("DELETED")Robot.create(robots,function(err,new_bots){console.log(new_bots)db.disconnect();// close the connection, else it will keep running, which is appropriate for when the web server runs, but not for a script like this.});});});

Run it.

node db/seed.js

At this point, you should be able to login to MongoDB to confirm existence of a database called robots_dev, a collection called robots, and three example robot records.

Modifying Controller Actions

We want the application to display robots from the database, not from a hard-coded variable. We also want to be able to use our views to add, edit, and delete robots from the database. Let’s connect our robots controller actions to the database.

Modify app/controllers/robots_controller.js according to the following template:

// app/controllers/robots_controller.jsvarexpress=require('express');varrouter=express.Router();varRobot=require("../models/robot");varmongooseError=require("../helpers/mongoose_error")varcreate_robot_path='/robots/';functionupdateRobotPath(robot_id){return'/robots/'+robot_id+'/update';};/* INDEX */router.get('/robots',function(req,res,next){Robot.find(function(err,bots){console.log("LIST",bots.length,"ROBOTS:",bots);res.render('robots/index',{page_title:'Robots',robots:bots.reverse()});});});/* CREATE */router.post('/robots',function(req,res,next){console.log("CAPTURE FORM DATA:",req.body)varrobot_name=req.body.robotName;varrobot_description=req.body.robotDescription;varbot=newRobot({name:robot_name,description:robot_description});bot.save(function(saveErr,bot_id){if(saveErr){console.log(saveErr);varerror_messages=mongooseError.toMessages(saveErr);req.flash('danger',error_messages);res.render('robots/new',{page_title:'Add a new Robot',form_action:create_robot_path,robot:{name:robot_name,description:robot_description}// pass-back attempted values to the form in case one was not blank});}else{console.log("CREATE ROBOT",bot)req.flash('success','Created a New Robot named '+robot_name);res.redirect('/robots')};});});/* NEW */// this must come above the SHOW action else express will think the word 'new' is the :idrouter.get('/robots/new',function(req,res,next){console.log("NEW ROBOT")res.render('robots/new',{page_title:'Add a new Robot',form_action:create_robot_path});});/* SHOW */router.get('/robots/:id',function(req,res,next){varrobot_id=req.params.id;Robot.findById(robot_id,function(err,bot){if(err){console.log("COULDN'T SHOW ROBOT #"+robot_id);console.log(err);varerror_messages=mongooseError.toMessages(err);req.flash('danger',error_messages);res.redirect('/robots');}else{console.log("SHOW ROBOT:",bot);res.render('robots/show',{page_title:'Robot #'+bot.id,robot:bot});};});});/* EDIT */router.get('/robots/:id/edit',function(req,res,next){varrobot_id=req.params.id;Robot.findById(robot_id,function(err,bot){console.log("EDIT ROBOT",bot);res.render('robots/edit',{page_title:'Edit Robot #'+bot.id,robot:bot,form_action:updateRobotPath(bot.id)});});});/* UPDATE */router.post('/robots/:id/update',function(req,res,next){console.log("CATURED FORM DATA",req.body)varrobot_id=req.params.id;varrobot_name=req.body.robotName;varrobot_description=req.body.robotDescription;Robot.findById(robot_id,function(err,bot){bot.name=req.body.robotNamebot.description=req.body.robotDescriptionbot.save(function(saveErr,new_bot){if(saveErr){console.log(saveErr)varerror_messages=mongooseError.toMessages(saveErr)req.flash('danger',error_messages);res.render('robots/edit',{page_title:'Edit Robot #'+robot_id,form_action:updateRobotPath(robot_id),robot:{name:robot_name,description:robot_description}// pass-back attempted values to the form in case one was not blank});}else{console.log("UPDATED ROBOT",new_bot)req.flash('success','Updated Robot #'+new_bot._id);res.redirect('/robots')};});});});/* DESTROY */router.post('/robots/:id/destroy',function(req,res,next){varrobot_id=req.params.id;Robot.findById(robot_id,function(err,bot){bot.remove(function(rmErr,removed_bot){if(rmErr){console.log("COULDN'T DELETE ROBOT #",bot_id);req.flash('danger',"Couldn't delete Robot #"+bot_id);varerror_messages=mongooseError.toMessages(rmErr);req.flash("danger",error_messages)}else{console.log("DELETED ROBOT",removed_bot);req.flash('success','Deleted Robot #'+removed_bot._id);}res.redirect('/robots');});});});module.exports=router;

You’ll notice references to a helper file called mongoose_error. Create it now.

touch app/helpers/mongoose_error.js

Edit app/helpers/mongoose_error.js according to the following template: