NoSQL Zone is brought to you in partnership with:

I enjoy programming in my free time and spend a considerable amount of time hacking away at open source projects as well as implementing those projects for various clients. My recent interests have included messaging (RabbitMQ specifically), Node.js, NoSQL, and alternative JVM languages such as Scala. You can also occasionally find me at several conferences speaking on some of my passions as well. Of course, got to balance all of this out with my wonderful baby girl and beautiful wife. James is a DZone MVB and is not an employee of DZone and has posted 20 posts at DZone. You can read more from them at their website. View Full User Profile

Blog Rolling with MongoDB, Node.js and Coffeescript

01.21.2012
| 12127 views |
  • submit to reddit

This morning I woke up with a lingering thought on my mind that was left over from recent conversations. In the technical community we often get so invested in our work that rather than talk about the simple building blocks that build our success we talk about the huge breakthroughs we make. The problem however is that our breakthroughs most often aren’t accessible to someone who wants to just get started. So today I will give an intro tutorial to using node.js, coffeescript and mongodb to build a simple blog. It builds off the concept in a tutorial I first used to learn node.js more than a year ago, but with a completely from scratch approach. In this tutorial I will also cover practicing Behavior Driven Development using Mocha.

Getting Started

Quite obviously, you’re going to need node.js and mongodb installed. I recommend downloading and installing from the node.js website and following the instructions. I will note that this tutorial covers 0.6.7 so if you come across this post a year from now the API might have changed significantly since then. You will also need mongodb installed, you can download it here. If you use Ubuntu (or some other Debian derivative) you should consider installing from their apt repository. OSX? No problem, you can also install it via homebrew or macports.

Finally, since we’ll be using coffeescript for this tutorial, run npm -g install coffee-script (you might need to sudo) to install coffeescript. Run coffee from the commandline to access the coffeescript REPL. If all works well, install these additional packages listed below via npm that we’ll be using throughout the tutorial.

  • express
  • mocha

Now let’s bootstrap our project structure. Type express coffeepress to generate a skeleton express project structure. You should see output similar to the following:


Notice how at the end it says to cd to the directory and type npm install? Let’s follow those instructions. Let’s run what we have so far by typing node app.js and navigating to http://localhost:3000. This is the default structure that gives a good starting point. Feel free to investigate the files under the directory before moving on. I even suggest poking around by changing the view a bit and changing the title from express to “My Coffeepress Blog”.

Porting to Coffeescript

At this point, let’s port our backend to coffeescript. I used to copy and paste files into the js2coffee website but you can also install js2coffee via npm. So run the following:

npm -g install js2coffee
js2coffee app.js > app.coffee
js2coffee routes/index.js > routes/index.coffee
rm app.js
rm routes/index.js


Now you can run coffee app.coffee to run the same app, but now in coffeescript. Take a look at the resulting files to get a feel for what has changed. New to coffeescript? Then I recommend taking a gander at coffeescript.org before moving on. Here is the project structure so far.

Basic Navigation


I like to try and work my way from the outside in while developing a site or feature, materializing components into existence as I need them. So let’s start by working on the initial navigation of the site with some simple in-memory storage of blog posts. This is a good time to get our test framework setup and write a few simple tests against our routes. Normally I prefer to not write tests against my routes, shoving logic into heavy models or services. However I have come to learn that untested components of a system serve as a gravity well for untested code that eventually leads to clients calling you about broken applications. What follows serves as both an introduction to Mocha as well as express’ routing mechanism.

Let’s edit our package.json to include our test framework dependencies.
{
    "name": "coffeepress"
  , "description":"Wordpress clone written in coffeescript!"
  , "version": "0.0.1"
  , "dependencies": {
      "express": "2.5.4"
    , "jade": ">= 0.0.1"
  }
  , "devDependencies": {
      "mocha": "0.10.0"
    , "should": "0.5.1"
  }
}


We include should so that we can use BDD style assertions (more on this in a bit). Write a simple test case located at test/routes-test.coffee with the following code to get us started with mocha

require "should"

describe "feature", ->
  it "should add two numbers", ->
    (2+2).should.equal 4


Now run this by typing mocha from the root of the project directory. It should pass. Let’s go ahead and make it fail by changing 4 to 5 and rerunning it. Hopefully this gives you a good feel for our test framework before we move on and change this test to reflect our existing index route. Swap the code in this test out with the following.

routes = require "../routes/index"
require "should"

describe "routes", ->
  describe "index", ->
    it "should display index with posts", ->
      req = null
      res = 
        render: (view, vars) ->
          view.should.equal "index"
          vars.title.should.equal "My Coffeepress Blog"
      routes.index(req, res)

 

Here we fake our requests and response in order to capture what is passed into the response. We fake the render method and verify that our rendered view is “index” and that the variable title is equal to what we expect to be passed in. Run the tests and make changes to your route to make it pass.

Now let’s add a post variable that will be an array of posts we’ll display on the front page. Add the following assertion right after the title assertion:

          vars.posts.should.equal []


Run the tests to see it fail and change the route to have a posts array variable available in the template.

exports.index = (req, res) ->
  res.render "index",
    title: "My Coffeepress Blog"
    posts: []

 

Unfortunately you’ll notice that the test fails. This is due to a subtle difference between equal and eql. The former enforces strict equality while the latter is a bit looser, so we change our assertion to use eql. Take a look at the should documentation for more information.

Next let’s write tests for the “new post” route.

 

  describe "new post", ->
    it "should display the add post page", ->
      res.render = (view, vars) ->
          view.should.equal "add_post"
          vars.title.should.equal "Write New Post"

      routes.newPost req, res


Run it, see the failure, and rework our routes.coffee file to include the route (with no implementation yet)

module.exports = 
  index: (req, res) ->
    res.render "index",
      title: "My Coffeepress Blog"
      posts: []

  newPost: (req, res) ->
    # do nothing


You’ll notice our test passes. That’s not good. Why? Because we put our assertion in our req.render callback, which never gets executed. Doh! How can we make absolutely sure it gets called during our test run? Old school thinking would have you assign a local variable outside the scope of the callback that gets assigned during execution and then can be verified against later on. However we have no guarantee that the routing logic will be synchronous!

Thankfully mocha has a feature that allows for easy testing in these situations. In the method declaration of our test specify a parameter named done. This is a callback that we can call anywhere to indicate the test is done. Basically the test will wait up to a default of 2000ms for it to be called. With this in mind, let’s modify our tests with the following:

routes = require "../routes/index"
require "should"

describe "routes", ->
  req = {}
  res = {}
  describe "index", ->
    it "should display index with posts", (done)->
      res.render = (view, vars) ->
          view.should.equal "index"
          vars.title.should.equal "My Coffeepress Blog"
          vars.posts.should.eql []
          done()
      routes.index(req, res)

  describe "new post", ->
    it "should display the add post page", (done) ->
      res.render = (view, vars) ->
          view.should.equal "add_post"
          vars.title.should.equal "Write New Post"
          done()
      routes.newPost(req, res)


If we run this via mocha now we’ll notice that we have one failure. Let’s go ahead and implement the route and connect it into our router.

  newPost: (req, res) ->
    res.render 'add_post', title:"Write New Post"


And connecting it up in the app.coffee

app.get "/", routes.index
app.get "/post/new", routes.newPost

 

Modifying the Views

This code is useless without views, so let’s modify our views a bit. Let’s modify our layout.jade to link to the new posts page. This layout also makes use of twitter bootstrap because I’m too lazy to design something for this tutorial. :)

 

!!!5
html
  head
    title= title
    link(rel="stylesheet", href="http://twitter.github.com/bootstrap/1.4.0/bootstrap.min.css")
    style(type="text/css")
      #content { padding-top: 60px;}
  body
    .topbar-wrapper
      .topbar
        .topbar-inner
          .container
            h3: a(href="/")= title
            ul.nav
              li: a(href="/post/new") New Post
    #content.container!=body


And create our add_post view at views/add_post.jade. An interesting thing to note here that I’ll touch on in a bit is that I prefix the input names with post.

form(method="post", action="/post/new")
  fieldset
    legend=title
    .clearfix
      label(for="title") Title
      .input
        input.xlarge#title(type="text", name="post[title]")
    .clearfix
      label(for="post") Post
      .input
        textarea#post.xxlarge(name="post[body]",rows="3")
    .actions
      input.btn.primary(type="submit", value="Publish!")
      |   
      a.btn(href="/") Cancel


Now let’s add another route to handle the post. This time I’m going to kind of skip delving into the details of writing the test, but you can look at what I have so far here if you’d like to see it.

posts = []

module.exports =·
  index: (req, res) ->
    res.render "index",
      title: "My Coffeepress Blog"
      posts: posts

  newPost: (req, res) ->
    res.render 'add_post', title:"Write New Post"

  addPost: (req, res) ->
    post = req.body.post
    post.id = posts.length
    posts.push post
    res.redirect "/" 

  viewPost: (req, res) ->
    post = posts[req.params.id]
    res.render 'post', post: post, title: post.title


For now, we’re just going to store each post in an array. Nothing fancy yet. We also add a new route to app.coffee. We could refactor or use some express-mvc plugin to reduce adding each route by hand, but I think it’s good to do it like this to get a feel for express’ low level routing mechanisms.

app.get  "/"        , routes.index
app.get  "/post/new", routes.newPost
app.post "/post/new", routes.addPost
app.get  "/post/:id", routes.viewPost


Finally, we’ll add one last view for viewing a single post:

.page-header
  h1= post.title
.content!=post.body


Go ahead and start the application up and navigate to http://localhost:3000. Post a few posts and play around a bit. You can see the finished application we have so far here.

Mongoose

Whew. I hope I haven’t lost you yet. Especially with the tests against the routes… I know those are always a bit painful! Now that we have functional blog let’s make it work by storing posts in mongodb using Mongoose.

Let’s add a dependency on mongoose to our project and freeze it at version 2.4.10. As always, run npm install to bring it in. Now we’ll create an initial test to just test mongoose out.

mongoose = require 'mongoose'
Post     = require '../models/Post'

describe 'Post', ->
  before (done) ->
    mongoose.connect 'mongodb://localhost/coffeepress', ->
      Post.remove done
  it 'should create a new post', (done) ->
    post = new Post(title:'First!', body:'First post bastiches!')
    post.save ->
      Post.findOne _id: post._id, (err, retrievedPost) ->
        retrievedPost.title.should.eql "First!"
        retrievedPost.body.should.eql "First post bastiches!"
        done()


Here we import both mongoose and the model object that we’re going to create. Since we want our test to start with a clean slate, we use the before hook (which runs once before anything else in the test runs) to both connect to the database and then remove all of the Post objects from mongodb. We pass the done callback to the remove call so that tests don’t run until all Posts have been removed.

Now we create a new Post instance. You can pass an object literal in to set properties on the model, so we do that here. Finally, in our post.save callback we look the post back up and verify certain attributes have been set. It’s a dumb test (and in fact I rarely test mongoose’s behavior like this), but it does verify that we’ve configured our model correctly.

Now let’s implement our model to make the test pass.

mongoose = require 'mongoose'

Post = new mongoose.Schema(
  title: String
  body: String
)

module.exports = mongoose.model 'Post', Post


Pretty simple. Now let’s refit our routes to use the Post model instead of an in memory array.

Post = require '../models/Post'

module.exports = 
  index: (req, res) ->
    Post.find {}, (err, posts) ->
      res.render "index",
        title: "My Coffeepress Blog"
        posts: posts

  newPost: (req, res) ->
    res.render 'add_post', title:"Write New Post"

  addPost: (req, res) ->
    new Post(req.body.post).save ->
      res.redirect "/"

  viewPost: (req, res) ->
    Post.findById req.params.id, (err, post) ->
      res.render 'post', post: post, title: post.title


That’s all good and dandy, but one last hiccup is our tests for our routes now fail. Chalk this one up to not having any abstraction or dependency injection in place, but that is fine for now, we’ll live with it and change the tests.

routes   = require "../routes/index"
mongoose = require "mongoose"
Post     = require "../models/Post"
require "should"

describe "routes", ->
  req = 
    params: {}
    body: {}
  res = 
    redirect: (route) ->
      # do nothing
    render: (view, vars) -> 
      # do nothing
  before (done) ->
    mongoose.connect 'mongodb://localhost/coffeepress', ->
      Post.remove done

  describe "index", ->
    it "should display index with posts", (done)->
      res.render = (view, vars) ->
          view.should.equal "index"
          vars.title.should.equal "My Coffeepress Blog"
          vars.posts.should.eql []
          done()
      routes.index(req, res)

  describe "new post", ->
    it "should display the add post page", (done) ->
      res.render = (view, vars) ->
          view.should.equal "add_post"
          vars.title.should.equal "Write New Post"
          done()
      routes.newPost(req, res)
    it "should add a new post when posted to", (done) ->
      req.body.post = 
        title: "My Post!"
        body: "My wonderful post."

      routes.addPost req, redirect: (route) ->
        route.should.eql "/"
        routes.index req, render: (view, vars) ->
          view.should.equal "index"
          vars.posts[0].title.should.eql 'My Post!'
          vars.posts[0].body.should.eql "My wonderful post."
          done()
      


Finally we need our app to actually connect to mongoose when we run it. I like to do this based on the express configuration. This is immensely important if you have mongodb running on servers separated from your application. For this example we’ll just use the databases coffeepress-dev and coffeepress-prod.

app.configure "development", ->
  mongoose.connect 'mongodb://localhost/coffeepress-dev'
  app.use express.errorHandler(
    dumpExceptions: true
    showStack: true
  )

app.configure "production", ->
  mongoose.connect 'mongodb://localhost/coffeepress-prod'
  app.use express.errorHandler()


Run it and write a few posts. Restart the app and you’ll see the posts still there. Woot!

Conclusion


Well, that about wraps it up… you can see this tutorial in it’s finished glory on the finished branch of the repository. There’s a bit missing out here that we’d implement in the real world. Obviously some kind of authentication would be in order if we took this further, possibly using mongoose-auth. We’d also want to add some validation when posting. These are all excellent topics for future posts but for now I hope this was enough to help you get going! :)



Source: blog.james-carr.org/wp-content/uploads/2012/01/express.png

Published at DZone with permission of James Carr, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)