O'Reilly

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.

O'Reilly Velocity Conference
Save 20% with discount code AFF20

Recent Features

  • 9 More Mind-Blowing WebGL Demos

    With Firefox OS, asm.js, and the push for browser performance improvements, canvas and WebGL technologies are opening a world of possibilities.  I featured 9 Mind-Blowing Canvas Demos and then took it up a level with 9 Mind-Blowing WebGL Demos, but I want to outdo...

  • Page Visibility API

    One event that's always been lacking within the document is a signal for when the user is looking at a given tab, or another tab. When does the user switch off our site to look at something else? When do they come back?...

Incredible Demos

  • MooTools CountDown Plugin

    There are numerous websites around the internet, RapidShare for example, that make you wait an allotted amount of time before presenting you with your reward. Using MooTools, I've created a CountDown plugin that allows you to easily implement a similar system. The MooTools JavaScript The CountDown class...

  • TextboxList for MooTools and jQuery by Guillermo Rauch

    I'll be honest with you: I still haven't figured out if I like my MooTools teammate Guillermo Rauch. He's got a lot stacked up against him. He's from Argentina so I get IM'ed about 10 times a day about how great Lionel...

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!

Recently on David Walsh Blog

  • Open Files from Command Line on OS X

    I'm as much of a fan of application UIs as anyone else but I'm finding myself working more and more from the command line lately.  Much of that is becoming obsessed with media manipulation but I'm forcing myself to use less UIs so that I...

  • Get Stock Quotes From Command Line

    When I conned my way into my first professional programming gig, I didn't really think much about money -- just that I was getting my foot in the door.  But as my career has gone on, I've been more aware of money, investing, and retirement.  I've recently...

  • Geolocation API

    One interesting aspect of web development is geolocation; where is your user viewing your website from? You can base your language locale on that data or show certain products in your store based on the user's location. Let's examine how you can...

  • Create an Image Preview from a Video

    Visuals are everything when it comes to media.  When I'm trying to decide whether to watch a video on Netflix, it would be awesome to see a trailer of some kind, but alas that isn't available.  When I'm looking to download a video on my computer,...

  • New:  Webdesigner News!

    A new and exciting website has recently been launched for web designers and developers. You likely spend hours every morning browsing through hundreds of posts on your RSS feeds, hoping to stumble across relevant stories. Webdesigner News was built to provide web designers and developers with...