Generating RSS feeds from a Backbone.js Collection in Node.js
When designing and developing this site, I wasn't entirely sold on using
Backbone structures on the server-side. The benefits weren't immediately
clear. Although after creating the blog using Models, Collections and
Events, I'm very glad I chose Backbone. Events are very helpful for
re-rendering the RSS feeds as you will see.
No magic is being written here, nor the solving of any new issues, just elegant code organization and implementation.
These are required dependencies:
- Node.js 0.6.3 (now bundles NPM, the package manager)
- Backbone (npm install backbone)
- RSS (npm install rss)
- Express (npm install express)
How to structure the server:
Since this post is specifically about generating RSS, I'll show the shell
of what my server looks like to make it easier to envision implementing in
your own application. I will break down the Model and Collection code after
showing the server.
// Require server dependencies
var Backbone = require("backbone");
var express = require("express");
var RSS = require("rss");
/* Post Model and Posts Collection exist here outside the route, which is
* possible since Node.js is the server and script coupled, and exists
* entirely in memory.
*/
// Route to specific post
site.get("/post/:id", function(req, res) { /* ... */ });
// Currently does nothing, will show soon how to serve this
site.get("/rss.xml", function(req, res) { /* ... */ });
// Render out home page with list of posts
site.get("/", function(req, res) { /* ... */ });
Checking out the Model:
The Post model is responsible for dealing with the slugification, which is
used when constructing unique URLs for each Post. The idAttribute is very
handy here later on when you need to find the Post that corresponds to the
id parameter that is available in the Post entry route that you can see
above in my server.
var Post = Backbone.Model.extend({
// This is a great way to reference the post later, since its unique
idAttribute: "slug",
// When initialized from collection fetch generate the slug
initialize: function() {
this.set({ slug: this.slugify() });
},
// Convert the title attribute into a URL friendly slug
slugify: function(title) {
return this.get("title").toLowerCase().replace(/ /g, "-")
.replace(/[^\w-]+/g, "");
}
});
Setting up the Collection:
When the Posts collection is initialized, a new RSS feed object is created
and attached as a reusable property. The sync method was intentially ommitted
since that is a lot of code not pertinent to this post. Just be aware that I
override sync in order to actually fill the collection from files on the
filesystem.
var Posts = Backbone.Collection.extend({
model: Post,
// Sort by most recent posted date
comparator: function(post) {
return -1 * new Date(post.get("posted"));
},
// Fetches posts from Git folder
sync: function(method, model, options) { /* ... */ },
// Create a new RSS feed and assign it as a property on the Collection
initialize: function() {
this.feed = new RSS({
title: "Tim Branyen @tbranyen",
description: "JavaScript and web technology updates.",
feed_url: "http://tbranyen.com/rss.xml",
site_url: "http://tbranyen.com",
image_url: "",
author: "Tim Branyen @tbranyen"
});
}
});
// Initialize a global reference to all Posts
var posts = new Posts();
Adding items to the Feed:
RSS feed regeneration only needs to happen when the Posts collection is
updated. Since the only mutation I do is reset, that event is
sufficient to add items to the feed.
posts.bind("reset", function() {
// Iterate over all the Posts and set the context to the Collection
posts.each(function(post) {
// Add each post into the feed property on the Collection
this.feed.item({
title: post.get("title"),
description: post.get("title"),
date: post.get("posted"),
url: "http://tbranyen.com/post/" + post.id
});
}, posts);
});
Triggering the reset event:
The reset event is triggered under-the-hood by Backbone, whenever the
Posts collection contents are replaced completely. Since I'm never going to
incrementally add new Post models, this event will be called correctly
whenever I trigger the fetch method. Internally Backbone will reset
the new fetched data into the collection.
This updating is done with an interval that runs once an hour, outside
of this code I have another place that I can manually trigger in the
server to do the same thing (useful for when I push a new Post, like this!)
setInterval(function() {
posts.reset([]);
posts.fetch();
// Refresh Posts Collection once an hour
}, 3600000);
// Always fetch immediately
posts.fetch();
Delivering the RSS:
Coming back to that RSS route, this is how the actual delivery function
looks now. I simply have to set the correct Content-Type and call the xml
method on the feed object.
site.get("/rss.xml", function(req, res) {
res.contentType("rss");
res.send(posts.feed.xml());
});
As seen above the structure is really quite nice, and doesn't turn into callback hell since I'm using events. What do you think?