Persisting a Todo List With MongoDB and Geddy

Persisting a Todo List With MongoDB and Geddy

Tutorial Details
  • Topic: Node.js, Geddy, npm, MongoDB, javascript
  • Difficulty: Medium
  • Estimated Completion Time: 1 hour

In this three part tutorial, we’ll be diving deep into creating a to do list management app in Node.js and Geddy. This is the last entry in the series, where we’ll be persisting our todo items to MongoDB.

As a quick refresher, last time, we created our todo resource and made a working to do list application, but the data only existed in memory. In this tutorial, we’ll fix that!


Intro to MongoDB

MongoDB is a NoSQL document store database created by the folks over at 10gen. It’s a great database for Node apps because it stores its data in a JSON-like format already, and its queries are written in JavaScript. We’ll be using it for our app, so let’s get it set up.

Installing MongoDB

Go to http://www.mongodb.org/downloads and download the latest version for your OS. Follow the instructions in the readme from there. Make sure that you can start mongod (and go ahead and leave it running for the duration of this tutorial)

It’s worth noting that you’ll need to have mongo running any time you want your app running. Most people set this up to start up with their server using an upstart script or something like it.

Done? alright, let’s move on.

MongoDB-Wrapper

For our app, we’ll be using a module that wraps the mongodb-native database driver. This greatly simplifies the code that we’ll be producing, so let’s get it installed. cd into your app and run this command:

npm install mongodb-wrapper

If all goes well you should have a mongodb-wrapper directory in your node_modules directory now.


Setting up Your Database

Mongo is a really easy DB to work with; you don’t have to worry about setting up tables, columns, or databases. Simply by connecting to a database, you create one! And just by adding to a collection, you make one. So let’s set this up for our app.

Editing your init.js file

We’re going to need access to our DB app-wide, so let’s setup our code in config/init.js. Open it up; it should look like this:

// Add uncaught-exception handler in prod-like environments
if (geddy.config.environment != 'development') {
  process.addListener('uncaughtException', function (err) {
    geddy.log.error(JSON.stringify(err));
  });
}
geddy.todos = [];
geddy.model.adapter = {};
geddy.model.adapter.Todo = require(process.cwd() + '/lib/model_adapters/todo').Todo;

Let’s add in our db code at the very top (and remove the geddy.todos array while we’re at it):

var mongo = require('mongodb-wrapper');

geddy.db = mongo.db('localhost', 27017, 'todo');
geddy.db.collection('todos');

// Add uncaught-exception handler in prod-like environments
if (geddy.config.environment != 'development') {
  process.addListener('uncaughtException', function (err) {
    geddy.log.error(JSON.stringify(err));
  });
}
geddy.model.adapter = {};
geddy.model.adapter.Todo = require(process.cwd() + '/lib/model_adapters/todo').Todo;

First, we require the mongodb-wrapper module. Then, we set up our database, and add a collection to it. Hardly any set up at all.


Rewriting Your Model-Adapter

Geddy doesn’t really care what data backend you use, as long as you’ve got a model-adapter written for it. This means that the only code that you’ll have to change in your app to get your todos into a database is in the model-adapter. That said, this will be a complete rewrite of the adapter, so if you want to keep your old in-memory app around, you’ll want to copy the code to another directory.

Editing Your Save Method

Open up your model-adapter (lib/model_adapters/todo.js) and find the save method. It should look something like this:

this.save = function (todo, opts, callback) {
  if (typeof callback != 'function') {
    callback = function(){};
  }
  var todoErrors = null;
  for (var i in geddy.todos) {
    // if it's already there, save it
    if (geddy.todos[i].id == todo.id) {
      geddy.todos[i] = todo;
      todoErrors = geddy.model.Todo.create(todo).errors;
      return callback(todoErrors, todo);
    }
  }
  todo.saved = true;
  geddy.todos.push(todo);
  return callback(null, todo);
}

Make it look like this:

this.save = function (todo, opts, callback) {
  // sometimes we won't need to pass a callback
  if (typeof callback != 'function') {
    callback = function(){};
  }
  // Mongo doesn't like it when you send functions to it
  // so let's make sure we're only using the properties
  cleanTodo = {
    id: todo.id
  , saved: todo.saved
  , title: todo.title
  , status: todo.status
  };
  // Double check to see if this thing is valid
  todo = geddy.model.Todo.create(cleanTodo);
  if (!todo.isValid()) {
    return callback(todo.errors, null);
  }
  // Check to see if we have this to do item already
  geddy.db.todos.findOne({id: todo.id}, function(err, doc){
    if (err) {
      return callback(err, null);
    }
    // if we already have the to do item, update it with the new values
    if (doc) {
      geddy.db.todos.update({id: todo.id}, cleanTodo, function(err, docs){
        return callback(todo.errors, todo);
      });
    }
    // if we don't already have the to do item, save a new one
    else {
      todo.saved = true;
      geddy.db.todos.save(todo, function(err, docs){
        return callback(err, docs);
      });
    }
  });
}

Don’t be too daunted by this one; we started with the most complex one first. Remember that our save method has to account for both new todos and updating old todos. So let’s walk through this code step by step.

We use the same callback code as we did before – if we don’t have a callback passed to us, just use an empty function.

Then we sanitize our todo item. We have to do this because our todo object has JavaScript methods on it (like save), and Mongo doesn’t like it when you pass it objects with methods on them. So we just create a new object with just the properties that we care about on it.

Then, we check to see if the todo is valid. If it’s not, we call the callback with the validation errors. If it is, we continue on.

In case we already have this todo item in the db, we check the db to see if a todo exists. This is where we start to use the mongodb-wrapper module. It gives us a clean API to work with our db. Here we’re using the db.todos.findOne() method to find a single document that statisfies our query. Our query is a simple js object – we’re looking for a document whose id is the same as our todos id. If we find one and there isn’t an error, we use the db.todos.update() method to update the document with the new data. If we don’t find one, we use the db.todos.save() method to save a new document with the todo item’s data.

In all cases, we call a callback when we’re done, with any errors that we got and the docs that the db returned to us being passed to it.

Editing the all method

Take a look at the all method, it should look like this:

this.all = function (callback) {
  callback(null, geddy.todos);
}

Let’s make it look like this:

this.all = function (callback) {
  var todos = [];
  geddy.db.todos.find().sort({status: -1, title: 1}).toArray(function(err, docs){
    // if there's an error, return early
    if (err) {
      return callback(err, null);
    }
    // iterate through the docs and create models out of them
    for (var i in docs) {
      todos.push( geddy.model.Todo.create(docs[i]) )
    }
    return callback(null, todos);
  });
}

Much simpler than the save method, don’t you think? We use the db.todos.find() method to get all the items in the todos collection. We’re using monogdb-wrapper’s api to sort the results by status (in decending alphabetical order) and by title (in ascending alphabetical order). Then we send that to an array, which triggers the query to start. Once we get our data back, we check to see if there are any errors, if there are, we call the callback with the error. If there aren’t any errors we continue on.

Then, we loop through all the docs (the documents that mongo gave back to us), create new todo model instances for each of them, and push them to a todos array. When we’re done there, we call the callback, passing in the todos.

Editing the load method

Take a look at the ‘load’ method, it should look something like this:

 this.load = function (id, callback) {
  for (var i in geddy.todos) {
    if (geddy.todos[i].id == id) {
      return callback(null, geddy.todos[i]);
    }
  }
  callback({message: "To Do not found"}, null);
};

Let’s make it look like this:

this.load = function (id, callback) {
  var todo;
  // find a todo in the db
  geddy.db.todos.findOne({id: id}, function(err, doc){
    // if there's an error, return early
    if (err) {
      return callback(err, null);
    }
    // if there's a doc, create a model out of it
    if (doc) {
      todo = geddy.model.Todo.create(doc);
    }
    return callback(null, todo);
  });
};

This one is even simpler. We use the db.todos.findOne() method again. This time, that’s all we have to use though. If we have an error, we call the callback with it, if not, we continue on (seeing a pattern here yet?). If we have a doc, we create a new instance of the todo model and call the callback with it. That’s it for that one.

Editing the remove method

Take a look at the remove method now, it should look like this:

this.remove = function(id, callback) {
  if (typeof callback != 'function') {
    callback = function(){};
  }
  for (var i in geddy.todos) {
    if (geddy.todos[i].id == id) {
      geddy.todos.splice(i, 1);
      return callback(null);
    }
  }
  return callback({message: "To Do not found"});
};

Let’s make it look like this:

this.remove = function(id, callback) {
  if (typeof callback != 'function') {
    callback = function(){};
  }
  geddy.db.todos.remove({id: id}, function(err, res){
    callback(err);
  });
}

The remove method is even shorter than it used to be. We use the db.todos.remove() method to remove any documents with the passed in id and call the callback with an error (if any).


Time for the Magic

Let’s go test our app: cd into your project’s directory and start up the server with geddy. Create a new todo. Try editing it, have it fail some validations, and try removing it. It all works!


Conclusion

I hope you’ve enjoyed learning about Node.js, MongoDB and especially Geddy. I’m sure by now you’ve got a million ideas for what you could build with it, and I’d love to hear about them. As always, if you have any questions, leave a comment here or open up an issue on github.

Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • http://www.udgwebdev.com Caio Ribeiro Pereira

    This Geddy seems more complex than Rails and Express.JS

    Today I am using very happy Express + Mongoose on my new webapp.

    • http://yammer.com/jobs Daniel Erickson
      Author

      Let us know if we can do anything to make it less complex for you! Geddy’s goal is to be as simple as possible to use without sacrificing the power that Node.js gives us. If you think anything is too complex, open up a ticket at http://github.com/mde/geddy – we’d be happy to work with you to make it better.

  • http://stefan-developer.zapto.org/ Stefan

    Really nice tutorial, i’m planning to port some of my projects from CakePHP to Geddy’s as the Node.js is ultimate fast webserver. Thank you for this tuts ;)

  • http://www.gmtd.de Achim Koellner

    Daniel, man, thanks for taking your time for this great tutorial!
    Very helpful for mongodb and geddy noobs.
    Let me know if you come up with something like a geddy-cookie-based login :)
    Cheers,
    Achim

    • http://aydio.de Adem

      Nice to see another German around here. ;)

      +1 for the request about cookie-based login!

      • Erik

        +2 for cookie based auth!

      • http://yammer.com/jobs Daniel Erickson
        Author

        We’d love to start a discussion about auth strategies for Geddy apps. Open an issue at http://github.com/mde/geddy.

    • http://yammer.com/jobs Daniel Erickson
      Author

      Geddy comes with built in session support – check out the docs here: https://github.com/mde/geddy/wiki/Sessions

  • Steve

    Geddy looks nice, haven’t seen many articles on it but I’m going to try it out for sure.
    Thanks for the tutorial.

  • rcdp

    I can’t install mongodb-wrapper on Windows 7 :(

    Does anyone have any idea how to solve this problem? Please help me.

    Thanks a lot.

    Error log: http://pastebin.com/07r881sE

    • http://yammer.com/jobs Daniel Erickson
      Author

      So this is a little weird, but try this instead (it looks like mongodb-wrapper hasn’t been updated for windows 7 support.):

      Download the source here: https://github.com/idottv/node-mongodb-wrapper/downloads.
      Put it in your app’s node_modules directory.
      Then do this in your app directory:

      $> npm install mongodb

      Let me know if this doesn’t work for you.

      • rcdp

        Thanks for your help.

        I installed mongodb successfully but I still got the same problem when install mongodb-wrapper.

        Can it work if I copy all the mongodb-wrapper source into my node_modules?

        I really love your tutorials. Thank you very much.

    • http://yammer.com/jobs Daniel Erickson
      Author

      Yep, just copy the source into your node_modules folder. It’ll work just fine from there.

  • http://www.gbin1.com Terry

    Very good stuff for Mongdb and Node.js beginer!

  • http://pushinpixels.co.uk Paul

    Fantastic and a great opener for Node, Geddy and MongoDB.
    Can you tell me what these relate to in the app:
    ECONNREFUSED
    ECONNREFUSED
    connect

    • http://yammer.com/jobs Daniel Erickson
      Author

      That means that your app couldn’t connect to something. Can you post the whole error?

      • http://pushinpixels.co.uk Paul

        21 Apr 13:56:29 – !!! Mongo (Error: connect ECONNREFUSED)
        [Sat, 21 Apr 2012 12:56:29 GMT] 127.0.0.1 – - [Sat Apr 21 2012 13:56:29 GMT+0100 (BST)] “POST /todos 1.1″ 200 2710 “http://localhost:4000/todos/add” “Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:11.0) Gecko/20100101 Firefox/11.0″
        [Sat, 21 Apr 2012 12:56:29 GMT] 127.0.0.1 – - [Sat Apr 21 2012 13:56:29 GMT+0100 (BST)] “GET /css/bootstrap.responsive.min.css 1.1″ 200 7680 “http://localhost:4000/todos” “Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:11.0) Gecko/20100101 Firefox/11.0″
        [Sat, 21 Apr 2012 12:56:29 GMT] 127.0.0.1 – - [Sat Apr 21 2012 13:56:29 GMT+0100 (BST)] “GET /css/style.css 1.1″ 200 757 “http://localhost:4000/todos” “Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:11.0) Gecko/20100101 Firefox/11.0″
        [Sat, 21 Apr 2012 12:56:29 GMT] 127.0.0.1 – - [Sat Apr 21 2012 13:56:29 GMT+0100 (BST)] “GET /js/bootstrap.min.js 1.1″ 200 20697 “http://localhost:4000/todos” “Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:11.0) Gecko/20100101 Firefox/11.0″
        [Sat, 21 Apr 2012 12:56:29 GMT] 127.0.0.1 – - [Sat Apr 21 2012 13:56:29 GMT+0100 (BST)] “GET /js/jquery.min.js 1.1″ 200 93868 “http://localhost:4000/todos” “Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:11.0) Gecko/20100101 Firefox/11.0″
        [Sat, 21 Apr 2012 12:56:29 GMT] 127.0.0.1 – - [Sat Apr 21 2012 13:56:29 GMT+0100 (BST)] “GET /css/bootstrap.min.css 1.1″ 200 71385 “http://localhost:4000/todos” “Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:11.0) Gecko/20100101 Firefox/11.0″
        [Sat, 21 Apr 2012 12:56:29 GMT] 127.0.0.1 – - [Sat Apr 21 2012 13:56:29 GMT+0100 (BST)] “GET /img/whitey.png 1.1″ 200 87134 “http://localhost:4000/css/style.css” “Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:11.0) Gecko/20100101 Firefox/11.0″
        [Sat, 21 Apr 2012 12:56:29 GMT] 127.0.0.1 – - [Sat Apr 21 2012 13:56:29 GMT+0100 (BST)] “GET /favicon.ico 1.1″ 200 318 “-” “Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:11.0) Gecko/20100101 Firefox/11.0″

        Firebug gave 200 OK.

    • http://yammer.com/jobs Daniel Erickson
      Author

      Looks like you don’t have mongo running, or it’s running on a non standard port.

      • http://pushinpixels.co.uk Paul

        I’ve realised I was not starting the database with the command ‘mongod’ from the terminal all working perfectly as expected. Yay…

  • Eran

    Very nice, I use Mongo from java and I can’t escape the feeling I’m doing something wrong, seeing it in node.js seems so much more natural

    One comment, and please correct me if I’m wrong, I though MongoDB supports upsert operations, e.g. if you provide an ID it updates and if not it inserts, so you might be able to eliminate some of the code

    http://www.mongodb.org/display/DOCS/Updating#Updating-%7B%7Bsave%28%29%7D%7Dinthemongoshell

    // x is some JSON style object
    db.mycollection.save(x); // updates if exists; inserts if new

    // equivalent to:
    db.mycollection.update( { _id: x._id }, x, /*upsert*/ true );

    • http://yammer.com/jobs Daniel Erickson
      Author

      Yep, you could definitely use upsert here!

  • Seboo

    Great tutorial, thanks!

    I’m just curious about one thing – when I run the app (from downloaded sources) it gives me an error saying:
    “ERROR adapter not found”.

    There is nothing more in logs…. why this error is logged?

    Beside this everything is working just fine. Any ideas?

  • Miguel Madero

    I was trying to follow the three part tutorial, but it’s really out of date. Do you need some help updating it?

    I also found another one here (http://geddyjs.org/tutorial.html) that is also out of date. It would be good to consolidate them or at least to add a big disclaimer at the beginning of both.
    The update wasn’t that complex and it’s actually easier with the latest changes and I would be happy to help.

    Please let me know.

    Regards,
    Miguel

  • Mathias

    Great Tutorial. Thanks a lot. I set it up with Mongo and added a few models, experimenting with them. It even led me to better understand the geddy tutorial.

    However, the model adapters are not created as advertised by the geddy generator “scaffold”. So the folder is empty. Using this tutorial to build it in was also not successful since the controllers are executed instead of the model adapters.

    One curious thing though: in the controllers all methods work except the “remove” method. Trying to delete a “todo” the browser returns “no destroy action on Todos controller”. The debug log shows an error when calling the DELETE method. I compared this with the todo_app example but found no difference.

    It seems I have not fully understood how the “remove” method is called. Any suggestions?

    Here is the method:
    this.remove = function (req, resp, params) {
    var self = this;
    geddy.model.adapter.Todo.remove(params.id, function(err){
    if (err) {
    params.errors = err;
    self.transfer(‘edit’);
    } else {
    self.redirect({controller: self.name});
    }
    });
    };

  • Jesse Kinsman

    Looks like this tutorial is quite out of date if you install the newest version of geddy. Looks as if the model_adaptors don’t even exist in the newest version of geddy. At least I can’t find them where they are described in this tutorial.

    Might want a disclaimer at the top of this tutorial or update for the newest version as I am sure most people that are new to geddy will install the latest version in npm.