Performance Zone is brought to you in partnership with:

Raymond Camden is a developer evangelist for Adobe. His work focuses on web standards, mobile development and Cold Fusion. He's a published author and presents at conferences and user groups on a variety of topics. He is the happily married proud father of three kids and is somewhat of a Star Wars nut. Raymond can be reached via his blog at www.raymondcamden.com or via email at raymondcamden@gmail.com Raymond is a DZone MVB and is not an employee of DZone and has posted 239 posts at DZone. You can read more from them at their website. View Full User Profile

My Thoughts on Node.js and Express

09.04.2012
| 13500 views |
  • submit to reddit

A while back I posted about how I had begun to see Node in a new light. (Do folks refer to it as "just" Node or do you always include the JS at the end?) I had some time this week and decided to try building a real, if simple, application. Since I had plenty of data for my blog, I thought a simple blog viewer would be a good application to try building. Here are some random, scattered thoughts on what the process was like.

First and foremost - I had decided to use Express to build a web application. Express is a Node application framework specifically built for web applications. I had seen Sim Bateman demo this at cfObjective this year and it had looked pretty cool. What I liked right away is how I had complete control over how requests were handled. I was easily able to specify a folder for static crap, like CSS and JavaScript. I then could specify URLs and how to handle them. In ways this is a bit like ColdFusion's onRequestStart, but at a much deeper level. You get the ability to say, for example, when a request comes in for /ray/bio, run so and so code. But you can also easily add dynamic patterns as well. If I wanted to match /display/X where X was dynamic, I could set up the code easy enough and Express would automatically give me access to the dynamic portion as a variable. (Before I go any further - let me point out that I may confuse what Express gives you versus what is just plain there in Node. Please forgive me if I make that mistake.)

Here is an example of all three above. From what I know, the patterns are checked in order of how I coded them.

//To use with static crap, like images, jquery, etc
app.use('/', express.static(__dirname + "/public",{maxAge:86400000}));

//default request for home page
app.get("/", function(req, res) {
   
    blog.getLatestEntries(function(rows) {
        res.render("home",{entries:rows});
    });
    
});

//blog entry by id
app.get("/entry/:id", function(req, res) {
    blog.getBlogEntryById(req.params.id,function(entry) {
        res.render("entry", {entry:entry});
    },function() {
        res.writeHead(302, {
            'Location': '/'
        });
        res.end();
    });
});

As I said, the first block handles my static stuff. I've specified a folder called public and inside of there I'd place my CSS and JS files. Once I drop in a style file I'd address it by just pointing the URL to /public/style.css.

The second block handles the home page request. Don't worry about "blog" just yet, I'll explain it in a bit. But basically, you can see that I'm calling for for some data and then rendering it. (Again, more on that in a second.)

Finally - I added support for loading one particular blog entry. Note that I was able to precisely define a URL pattern of /entry/X. I could have done anything here at all. I love that freedom. To be clear, you can do the exact same in ColdFusion if you add in a URL rewriter like what's built into Apache. But I like having it right here in my application. It makes it a bit easier to mentally grasp what is going on in the application.

If you visit my blog often, you know that I use a slightly slicker URL scheme. It took me then 5 minutes to add this to my application:

//blog entry by fancy SES url
app.get("/:year/:month/:day/:alias", function(req, res) {
    var entryDate = new Date(req.params.year,req.params.month-1,req.params.day);
    blog.getBlogEntry(entryDate,req.params.alias,function(entry,comments) {
        res.render("entry", {entry:entry,comments:comments});
    },function() {
        res.writeHead(302, {
            'Location': '/'
        });
        res.end();
    });
});

Ok, so that's some examples of responding to URLs. I've got more coming up, but let's talk about something else - rendering the pages. I love JavaScript, but there is no way in heck I'm going to build out HTML views in JavaScript. As much as possible I've been trying to use templating engines lately. As I was researching, I discovered that a library called EJS worked with Express. Hooking it up to Express was rather simple. I told my app I needed EJS and told Express to use it to parse HTML files. Honestly I don't 100% understand this portion, but it worked:

app.engine('.html', require('ejs').__express);
app.set('view engine', 'html');

If you look at the app.get("/") block above, you can see where I run res.render(). The first argument is the name of a template to run. By default this will be in a subdirectory called views. The second argument is data I'm passing to the view. Here is what home.html looks like - and remember - I did this very fast and kinda ugly. Normally you would have a bit more HTML in there:

<h2>Blog</h2>

<ul>
<% 
    entries.forEach(function(entry) { 
        var d = new Date(entry.posted);
        var year = d.getFullYear();
        var month = d.getMonth()+1;
        var day = d.getDate();
%>
    <li><a href="/<%= year + '/' + month + '/' + day %>/<%= entry.alias %>"><%= entry.title %></a> (<%= entry.posted %>)</li>
<% }) %>
</ul>
      

I am not a fan of the template syntax. Frankly it felt like I was writing classic ASP. That being said - it did work. Note that I'm doing a bit of work to create the fancy URL. EJS supports (I believe) writing your own helper functions. Normally I'd have built something to simplify that so that the view had much less logic in it. Just assume that as my first view I didn't write this as nicely as I would if given more time. As a comparison, here is the view for an individual blog entry:

<h1><%= entry.title %></h1>

<%- entry.body %>
<% if(entry.morebody) { %>
    <%- entry.morebody %>
<% } %>

<% if(comments.length) { %>
    <h2>Comments</h2>
    
    <% comments.forEach(function(comment) { %>
        <p>
        Comment posted by <%= comment.name %><br/>
        Posted at <%= comment.posted %><br/>
        <%= comment.comment %>
        </p>
    <% }) %>
    
<% } %>

As I mentioned, I'm not really a fan of EJS. There are other alternatives. Right now I'm considering Dust, but as I ran into problems with that, I couldn't actually use it.

Hopefully at this point you have a rough feel for how Express lets you handle requests and specify views to actually render them. Let's talk about the database layer. After I figured out how to do my views and pass parameters around, I needed to get database support. This is where I got to play around more with NPM. NPM, or the Node Package Manager, is an incredibly powerful tool and probably one of the main reasons Node is so popular. (Other platforms have similar support.) From the command line you can tell Node to get a package (think open source project focused on adding a particular feature) and install it to your system. You can also tell your application itself that it requires a package. So for example, my application needs Express and MySQL support. I can use a special file (package.json) to note these requirements, run one command, and all the supporting libraries just magically come in.

But... this isn't all rainbows and unicorns. When I decided to add RSS support to my application, I used NPM to search for an RSS library. If I remember right, about 30 or so packages showed up. I froze like a deer in headlights. Don't get me wrong, I like options, but I had absolutely no idea which one to pick. This is very much like the problem you may have with jQuery plugins. You can almost always count on jQuery having a plugin to do X, but finding out what the "best" one is can be a laborious process. I feel like I'm going to get some criticism on this, but I do wish people would keep this in mind when praising Node. For me, I picked the package with the name "rss" just because it had the simplest name. Luckily, the one I chose worked great. I was able to add RSS support soon after:

//RSS
app.get("/blog.rss", function(req, res) {

    /* lets create an rss feed */
    var feed = new rss({
            title: 'My Blog',
            description: 'This is my blog',
            feed_url: 'http://www.raymondcamden.com/rss.xml',
            site_url: 'http://www.raymondcamden.com',
            image_url: 'http://example.com/icon.png',
            author: 'Raymond Camden'
        });


    blog.getLatestEntries(function(rows) {
        rows.forEach(function(entry) {
            feed.item({
                title: entry.title,
                description: entry.body,
                url: 'http://localhost:3000/' + entry.posted.getFullYear() + '/' + ((entry.posted.getMonth())+1) + '/' + (entry.posted.getDate()) + '/' + entry.alias, // This can be done better
                date: entry.posted // any format that js Date can parse.
            });
        });

        // cache the xml
        var xml = feed.xml();
        res.send(xml);
    });
    
});

But it didn't always work out well. I mentioned above that I tried to use another templating engine. The first one I tried, Dust, didn't work because it wasn't supported on Windows. That really surprised me. Shouldn't JavaScript work everywhere? To be fair, the reason the project wasn't supported on Windows was because the author didn't have an environment to test with (and he did the right thing then in marking it not supported), but I ended up getting stuck for a while.

So going back to database support, it turns out there are a few options for working with MySQL. Unfortunately, none of them really instilled a great deal of confidence in me. In fact, the solution I went with didn't even support bound parameters! Yes, you could use them in your code, and yes, your data would be escaped, but it wasn't truly using a bound parameter in it's communication to the database. And frankly - as much as I like JavaScript, I'm not sure how much I'd trust a database library written in it. I haven't done any performance tests, but out of everything I did, this was the one area that gave me the most doubt.

With all that being said, using MySQL was pretty easy. I began by just setting up the connection like so:

var mysql = require("mysql");
var con = mysql.createConnection({
        host:"localhost",
        user:"root",
        password:"passwordsareforwimps",
        database:"myblog_dbo"
});
con.connect();

I then created a module called blog that would handle my service layer. Since I had a "con" object that represented my database connection, I exposed an API where I could pass it in to the blog:

var blog = require("./blog.js");
blog.setConnection(con);

Here then is my blog module. A 'real' blog engine would have quite a bit more of course but you get the idea.

var CONN;

exports.setConnection = function(con) {
    CONN=con;
}

exports.getLatestEntries = function(cb) {
    CONN.query("select id, title, alias, posted from tblblogentries order by posted desc limit 0,10", function(err, rows, fields) {
        cb(rows);
    });
}

exports.getBlogEntryById = function(id,success,fail) {
    CONN.query("select id, title, body, morebody, posted from tblblogentries where id = ?",[id], function(err, rows, fields) {
        if(rows.length == 1) success(rows[0]);
        else fail();
    });
}

exports.getBlogEntry = function(date,alias,success,fail) {
    var year = date.getFullYear();
    var month = date.getMonth()+1;
    var day = date.getDate();
    CONN.query("select id, title, body, morebody, posted from tblblogentries where year(posted) = ? and month(posted) = ? and dayofmonth(posted) = ? and alias = ?",
    [year,month,day,alias], function(err, rows, fields) {
        if(rows && rows.length == 1) {
            exports.getCommentsForBlogEntry(rows[0].id, function(comments) {
                success(rows[0],comments);
            });
        }
        else fail();
    });
}

exports.getCommentsForBlogEntry = function(id,success) {
    CONN.query("select id, name, email, comment, posted, website from tblblogcomments where entryidfk = ?", [id], function(err, rows, fields) {
        if(!rows || !rows.length) rows = [];
        success(rows);
    });
}

So what do I think? I love it. Once I got my environment running (and be sure to use nodemon to make reloading automatic) I was able to rapidly build out a simple application. I loved the level of control I had over the request and how quick it was to get up and running. I didn't love the fact that the quality wasn't quite consistent across various modules.

p.s. One more code snippet. I demonstrated the RSS support above. But I also built in a quick JSON view as well. It was incredibly difficult. Honest. This took me hours to write. (Heh...)

//API
app.get("/entries.json", function(req, res) {
    blog.getLatestEntries(function(rows) {
        res.writeHead(200, {'Content-Type':'application/json'});
        res.write(JSON.stringify(rows));
        res.end();
    });    
});

 

 

 

 

 

 

 

 

 

 

Published at DZone with permission of Raymond Camden, author and DZone MVB. (source)

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