How to build Personal Web API Server using Raspberry PI and NodeJs

I have a various streaming box (Apple TV, Roku, Fire TV, Nexus Player) because i prefer to watch video tutorials on TV rather than watching in laptop. However, we don’t have app for all the sites that provides video tutorials. For example, MSDN channel9 dont have app for any streaming boxes. So, i decided to use roku sample video player app to play channel 9 videos on my roku. This app expects the video content defined in certain XML format.So, I just have to build the web APIs to return the XML output dynamically. I also prefer not to host the Web APIs on the internet. I just want to host it in my home network. So, i need a web server that will be always on so, decided to use my existing Raspberry PI 3 as a web server.

I initially thought of using .Net Core on Windows IOT but then later decided to use Raspian OS and NodeJS becuase i wanted to try NodeJS with some real world application.

Setting up Raspian OS on Raspberry PI was pretty simple. I just followed this excellent article on how to setup Raspian on Raspberry PI. Also, configured the remote connection and shared the work folder to deploy the files so i no longer need my Raspberry PI connected to my TV. I just placed the raspberry PI along with my other stream boxes and connected through my laptop using remote desktop.

I started the Node JS Web API with the following Node Packages to parse the channel 9 feeds.

Express is one of the most famous nodejs web application framework to create Web APIs quick and easy. Cheerio is used for parsing the DOM elements. XMLBuilder is used for construct the xml output in easy way.

To start with, i created the web server with routing as follows

var express = require('express')  
, app = express()
, server = require('http').createServer(app)
, path = require('path')

var parser = require('./lib/parser');

app.set('port', 4567);
app.use(express.static(path.join(__dirname, 'public')));

//Routes
app.get('/', function (req, res) {
res.sendFile(__dirname + '/public/index.html');
});

app.get('/Ch9', function (req, res) {
parser.Ch9(function (result) {
res.set('Content-Type', 'text/xml')
res.send(200, result)
})
});

app.get('/Ch9/:topic/List', function (req, res) {
var topic = req.params.topic;
parser.Ch9List(topic, function (result) {
res.set('Content-Type', 'text/xml')
res.send(200, result)
});
});

app.get('/Ch9/:topic/:title/View', function (req, res) {
var topic = req.params.topic;
var title = req.params.title;
parser.Ch9View(topic, title, function (result) {
res.set('Content-Type', 'text/xml')
res.send(200, result)
});
});

i created the parser library class and added the following code to parse the channel 9 RSS content to fetch the video URLs


const topicLists = ["Shows", "Events", "Series", "Blogs"];
const ch9BaseURL = "http://channel9.msdn.com/Browse/";
exports.Ch9 = function (callback) {
var xml = builder.create('categories');
var cIndex;
for (cIndex in topicLists) {
var category = xml.ele("category");
category.att("title", topicLists[cIndex]);
category.att("description", topicLists[cIndex]);

var catLeaf = category.ele("categoryLeaf");
catLeaf.att("title", topicLists[cIndex]);
catLeaf.att("description", topicLists[cIndex]);
catLeaf.att("feed", appBaseURL + "Ch9/" + topicLists[cIndex] + "/List/");
}
xml.end({ pretty: true });
callback(xml.toString());
}

exports.Ch9List = function (topic, callback) {
var xml;
request(ch9BaseURL + topic + '/rss?sort=recent', function (error, response, html) {
if (!error && response.statusCode == 200) {
var $ = cheerio.load(html, { ignoreWhitespace: true, xmlMode: true });
xml = builder.create('categories');
var currIndex = 0;
$('item').each(function (i, element) {
var category = xml.ele("category");
category.att("title", $(element).find('title').text());
category.att("description", $(element).find('description').text());

var catLeaf = category.ele("categoryLeaf");
catLeaf.att("title", $(element).find('title').text());
catLeaf.att("description", $(element).find('description').text());
catLeaf.att("feed", appBaseURL + "Ch9/" + topic + "/" + encodeURIComponent($(element).find('title').text()) + "/View/");
currIndex++;
});

xml.end({ pretty: true });
callback(xml.toString());
}
});
}

exports.Ch9View = function (topic, title, callback) {
var xml;
request(ch9BaseURL + topic + '/rss?sort=recent', function (error, response, html) {
if (!error && response.statusCode == 200) {
var $ = cheerio.load(html, { ignoreWhitespace: true, xmlMode: true });
var curElement = $('item').filter(function (i, el) {
return $(this).find('title').text() === title;
});
var feedURL = $(curElement).find('c9\\:feed').text();
if (!feedURL)
feedURL = $(curElement).find('c9\\:feed').text() + "/Rss";

request(feedURL, function (error, response, html) {
var $ = cheerio.load(html, { ignoreWhitespace: true, xmlMode: true });
xml = builder.create('feed');
var loopIndex = 0;
$('item').each(function (i, element) {
var url = $(element).find('media\\:content').filter(function (i, el) {
return $(this).attr('url').includes("_high");
}).attr("url");

var item = xml.ele('item');
item.att('sdImg', $(this).children('media\\:thumbnail').attr('url'));
item.att('hdImg', $(this).children('media\\:thumbnail').attr('url'));
item.att('thumbnailURL', $(this).children('media\\:thumbnail').attr('url'));
item.ele('title', $(this).children('title').text());
item.ele('contentId', loopIndex);
item.ele('streamFormat', 'mp4');
var media = item.ele('media');
media.ele('streamUrl', url);
media.ele('thumbnailURL', $(this).children('media\\:thumbnail').attr('url'));
loopIndex++;
});
xml.end({ pretty: true });
callback(xml.toString());
});
}
});
}

Thats it. We have just implemented the web API using NodeJS with just few lines of code. if i run my application using http://localhost:4567/ch9, it responds back with xml output.

The deployment is super easy too. Just copy the folder excluding Node_Modules folder. You dont have to deploy the node_modules folder. You can run the NPM Update command to get the node_modules folder from PI Server after deploying it to save some deployment time.

To run the App Server, just open the terminal from Raspian and run using the following command Node APP.js and your personal web server is ready to serve.

I have modified the UrlCategoryFeed in categoryFeed.brs in the roku app before i deploy it. In order to deploy the roku dev app, just follow the roku official guide . Make sure You enable the development mode in roku before deploying it.

Update : Roku recently updated their video channel sample app and the new app can be found here.