Rolling Your Own RSS Feed with Express and Jade

By  on  

RSS feeds are a great way to facilitate a loyal readership. In fact, as I write this, the RSS feed in David's sidebar is touting over 11,400 subscribers. Hitting the front page of Hacker News is always nice, but for most sites that's not going to translate into a reliable source of traffic. Getting each and every post in front of thousands of intentional subscribers though (who have their own followers on Twitter, Google+, etc.)? That's a traffic generator.

There's really only one gotcha to RSS feeds - you actually have to have one. A little over a month ago I launched a new blog, DevSmash. My shiny new site had been public for less than 48 hours when I received a tweet asking where my RSS feed was. Not if I had an RSS feed, but where was my RSS feed. You see, I had the good fortune of receiving some decent promotion on one of my first posts. Users started showing up, and evidently some of them looked for some means of subscribing. I have an RSS feed now, of course, but all those users who showed up in that first week or so - they're long gone. Moral of the story: RSS feeds are great, but you need it before your readers show up.

Alright - let's call that sufficient context. DevSmash is built on top of all the newfangled goodness you could ask for: Node.js, Express, Jade, Stylus, MongoDB, Mongoose, etc. It's a stack that I absolutely love hacking on, but it exposes an intentionally lean feature set, so "rolling your own xyz" often comes with the territory. Such was the case with my RSS feed. This post provides an overview of how I built the DevSmash RSS feed, and I hope that it will be useful to others building on this increasingly popular stack.

Cast of Characters

Before we get started, let's do a quick overview of the main technologies we'll be using:

Express

From the Express home page: "Express is a minimal and flexible node.js web application framework, providing a robust set of features for building single and multi-page, and hybrid web applications." What TJ Holowaychuk is too modest to say here, is that Express has become the de facto standard for building web apps on Node. There are of course other options, but you definitely owe it to yourself to check it out if you haven't already.

Website: http://expressjs.com/

Jade

From the Jade readme: "Jade is a high performance template engine heavily influenced by Haml and implemented with JavaScript for node." This is my go-to template engine of choice - terse, feature rich, and a syntax that reads as well as it writes.

Website: http://jade-lang.com/

Mongoose

From the Mongoose GitHub repo: "Mongoose is a MongoDB object modeling tool designed to work in an asynchronous environment." In other words, Mongoose provides a model layer for interacting with your MongoDB collections from Node.

Website: http://mongoosejs.com/

Note: It's relatively inconsequential that we're using Mongoose in this post. The concepts should translate well enough to however you're managing your persistence.

RSS Requirements

One last thing before we dig into the code - let's identify our basic requirements for our RSS feed:

  1. The feed should include the most recent 20 published posts.
  2. Output should be a valid RSS 2.0 feed (I'm personally using the W3C feed validator to verify).

Simple enough.

The Code

For the sake of this article, we only need to be concerned with three files:

  • blog-post.js: Our BlogPost Mongoose Model (implementation details aren't too important for this article, but it's included for the sake of completeness).
  • feed.js: Our route handler (responsible for fetching posts from the database and feeding them to our view).
  • rss.jade: Our RSS template (responsible for turning our posts into a valid RSS 2.0 feed).

blog-post.js

We won't spend too much time talking about this file - it's purely here for reference since we'll be interacting with it later on.

var mongoose = require('mongoose'),
    Schema = mongoose.Schema;

var BlogPostSchema = new Schema({
    title: { type: String, required: true, trim: true },
    slug: { type: String, required: true, lowercase: true, trim: true, index: { unique: true } },
    body: { type: String, required: true },
    teaser: { type: String, required: true },
    author: { type: String, required: true, trim: true },
    published: { type: Boolean, required: true, default: false },
    createdAt: { type: Number },
    updatedAt: { type: Number }
});

// update timestamps on save
BlogPostSchema.pre('save', function(next) {
    this.updatedAt = Date.now();
    if (this.isNew) this.createdAt = this.updatedAt;
    next();
});

// create and export our model
module.exports = mongoose.model('BlogPost', BlogPostSchema);

feed.js

A common Express convention for route handlers is to put them into a dedicated routes/ folder. In my own apps I generally have my route files export a single function that accepts the Express application instance, like so:

// some-route-handler.js
module.exports = function(app) {
    app.get('/some/path', function(req, res, next) {
        // handler logic
    });
};

With a code structure like this in place, your main app.js file simply needs a line like the following:

require('./routes/some-route-handler')(app);

Alright then, here's what a functional RSS handler actually looks like:

var BlogPost = require('../lib/model/blog-post');
module.exports = function(app) {
    app.get('/feed/rss', function(req, res) {
        BlogPost.find({})
            .sort('-publishedAt')
            .where('published', true)
            .limit(20)
            .select('title slug publishedAt teaser')
            .exec(function(err, posts) {
                if (err) return next(err);
                return res.render('rss' {
                    posts: posts
                });
            });
    });
};

As can be seen, there's not much need for cruft around populating our RSS feed. We simply query for the most recent 20 published posts, and render them with our RSS template. Which brings us to...

rss.jade

While Jade's primary use case is generating HTML output, it is just as handy at generating XML. Here's what our Jade template looks like:

doctype xml
rss(version='2.0', xmlns:atom='<a href="http://www.w3.org/2005/Atom" rel="nofollow">http://www.w3.org/2005/Atom</a>')
    channel
        title DevSmash
        link <a href="http://devsmash.com" rel="nofollow">http://devsmash.com</a>
        atom:link(href='<a href="http://devsmash.com/feed/rss" rel="nofollow">http://devsmash.com/feed/rss</a>', rel='self', type='application/rss+xml')
        description Developers talking about stuff that developers like to talk about.
        language en-US
        if posts.length
            lastBuildDate= new Date(posts[0].publishedAt).toUTCString()
        each post in posts
            item
                title= post.title
                link <a href="http://devsmash.com/blog/#{post.slug}" rel="nofollow">http://devsmash.com/blog/#{post.slug}</a>
                description
                    | <![CDATA[
                    | !{post.teaser}
                    p: a(href='<a href="http://devsmash.com/blog/#{post.slug}')!=" rel="nofollow">http://devsmash.com/blog/#{post.slug}')!=</a> 'Read more &raquo;'
                    | ]]>
                pubDate= new Date(post.publishedAt).toUTCString()
                guid(isPermaLink='false') <a href="http://devsmash.com/blog/#{post.slug}" rel="nofollow">http://devsmash.com/blog/#{post.slug}</a>

The Jade syntax may look a little foreign if this is your first time seeing it, but for the most part things are fairly self-explanatory. There are a few things worth pointing out though:

  • The atom stuff isn't strictly required, but was suggested by the W3C feed validator.
  • Outputting the post body (or teaser in this case) requires some special care. You can't encode the markup, or you'll simply see encoded HTML in your RSS reader, but at the same time we need to protect the XML. The standard solution, then, is to wrap the post markup inside of CDATA tags.

And there you have it! Not even 40 lines of code (not counting the model) for a custom RSS feed. I hope this was helpful, and I'd love to hear any thoughts, questions, or concerns in the comments!

Jeremy Martin

About Jeremy Martin

Jeremy Martin is a software developer and Open Source Evangelist at his day job, a Node.js contributor and MongoDB fan boy in his spare time, and husband to the greatest gal on the planet.

Recent Features

  • By
    I&#8217;m an Impostor

    This is the hardest thing I've ever had to write, much less admit to myself.  I've written resignation letters from jobs I've loved, I've ended relationships, I've failed at a host of tasks, and let myself down in my life.  All of those feelings were very...

  • By
    5 Ways that CSS and JavaScript Interact That You May Not Know About

    CSS and JavaScript:  the lines seemingly get blurred by each browser release.  They have always done a very different job but in the end they are both front-end technologies so they need do need to work closely.  We have our .js files and our .css, but...

Incredible Demos

  • By
    Implementing Basic and Fancy Show/Hide in MooTools 1.2

    One of the great parts of MooTools is that the library itself allows for maximum flexibility within its provided classes. You can see evidence of this in the "Class" class' implement method. Using the implement method, you can add your own methods to...

  • By
    Animated AJAX Record Deletion Using jQuery

    I'm a huge fan of WordPress' method of individual article deletion. You click the delete link, the menu item animates red, and the item disappears. Here's how to achieve that functionality with jQuery JavaScript. The PHP - Content & Header The following snippet goes at the...

Discussion

  1. Thanks for the article – I am working on a site and currently using the node package ‘rss’ for the feeds side of things, but given how simple you make it look, I might try this instead.

    Especially as the package does not support alternate link types – whereas I can do it just by amending the template :)

  2. Thanks for the article, I am working on a site and used the ‘rss’ package, but would be good to extend it to support additional media links – I think I might just do what you have done instead :)

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!