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
    5 Awesome New Mozilla Technologies You&#8217;ve Never Heard Of

    My trip to Mozilla Summit 2013 was incredible.  I've spent so much time focusing on my project that I had lost sight of all of the great work Mozillians were putting out.  MozSummit provided the perfect reminder of how brilliant my colleagues are and how much...

  • By
    Welcome to My New Office

    My first professional web development was at a small print shop where I sat in a windowless cubical all day. I suffered that boxed in environment for almost five years before I was able to find a remote job where I worked from home. The first...

Incredible Demos

  • By
    &#8220;Top&#8221; Watermark Using MooTools

    Whenever you have a long page worth of content, you generally want to add a "top" anchor link at the bottom of the page so that your user doesn't have to scroll forever to get to the top. The only problem with this method is...

  • By
    Styling CSS Print Page Breaks

    It's important to construct your websites in a fashion that lends well to print. I use a page-break CSS class on my websites to tell the browser to insert a page break at strategic points on the page. During the development of my...

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!