Brad Oyler

Lead Developer,  NBCNews.com

@bradoyler
bradoyler@gmail.com
~


Building content sites with


preview.nbcnews.com


the stack


Express.js
+
Handlebars.js
+
Cache service
+
Redis

MODULES

"dependencies": {
    "async": "0.9.0",
    "compression": "1.0.2",
    "cron": "1.0.4",
    "eidetic": "0.1.1",
    "express": "4.0.0",
    "express-handlebars": "0.5.0",
    "moment": "2.6.0",
    "redis-url": "0.2.0",
    "request": "2.34.0",
    "route-cache": "0.1.0"
  }
*use a CDN  for static files

ExPress setup

// npm packages
var express       = require('express');
var compression   = require('compression');
var routeCache    = require('route-cache');
var expressHbs    = require('express-handlebars');

// app code
var helpers       = require('./helpers');
var routes        = require('./routes');

var app = express();



route-cache

 module.exports.cacheSeconds = function(ttl) {

  return function(req, res, next) {
    var cache = cachestore.get(req.path);
    if (cache) {
      res.send(cache);
    } else {
      var send = res.send;
      res.send = function(string) {
        var body = string instanceof Buffer ? string.toString() : string;
        cachestore.put(req.url, body, ttl);
        send.call(this, body);
      };
      next();
    }
  }
};

setup RouteR

app.use('/', require('./routes'));
app.use('/section', require('./routes/section'));

// & handle errorsapp.use(function handle404(req, res) { res.status(404); res.render('error', {layout: false}); }); app.use(function handle500(error, req, res, next) { res.status(500); log.error('500 error: ' + error.message + '; stack: ' + error.stack); res.render('error', {layout: false}); });

index route


var cacheService = require('../CacheService');
var router = express.Router();

router.get('/', (req, res, next) {

  cacheService.get('coverstories', ttl, function(model) {
     res.render('index', model, errorHandler);
  });
};


module.exports = router;

Remember 'route-cache'? 

cache-service

var localcache = require('memory-cache'); 
var redis = require('redis'); exports.get = function (key, expires, callback) { var cachedData = localcache.get(key); if (cachedData) { callback(cachedData); return; } redis.get(key, function(error, value) { localcache.put(key, value, expires); callback(value); }); };
use local + remote cache (redis/memcache)
A worker process updates redis

view Engine

app.engine('.hbs', expressHbs({
    defaultLayout: 'default',
    extname: '.hbs',
    layoutsDir: __dirname + '/views/layouts',
    partialsDir: [__dirname + '/views/partials'],
    helpers: hbsHelpers
}));

Q: Why handlebars? 

A: Familiar syntax, partials & helpers

HBS helpers

 module.exports = function() {
  return {
    ifindexby: this.ifindexby,
    formatdate: this.formatdate,
    decodeuri: this.decodeuri,
    link: this.linkHelper,
    'if-equal': this.ifEqual,
    imgurl: this.imgurl,
    'if-contains': this.ifContains,
    articleurl: this.articleurl,
    routeurl: this.routeurl,
    coverheadline: this.coverheadline,
    plusone: this.plusone
  };
};
this is where the magic happens, as it makes building out your app with templates & partials... dead simple

hoping to publish a npm packge soon

{{if-equal}}

exports.ifEqual = function(val, test, options) {
  var arrTest = test.split('||');
  for (var i = 0; i < arrTest.length; i++) {
    if (val === arrTest[i]) {
      return options.fn(this);
    }
  }
  return options.inverse(this);
};

{{#if-equal type 'photo'}}
  {{> article_photo}}
{{/if-equal}}

{{#if-equal type 'post||externallink'}}
  {{> article_post}}
{{/if-equal}}

true, these aren't logic-less templates, but so easy to work with

{{imgurl}}

function imgurl(url, width, height) {
  imgurl = imgurl.toLowerCase().replace('/i/','/j/')
    .replace('.jpg', '.nbcnews-fp-' + width + '-' + height + '.jpg')
    .replace('.jpeg', '.nbcnews-fp-' + width + '-' + height + '.jpeg')
    .replace('.png', '.nbcnews-fp-' + width + '-' + height + '.png');

  return imgurl;
};

{{#each gallery.photos}}
  <div class="img_hero img_gallery">
    <img class="lazy-lg" data-original="{{imgurl url '1440' '600'}}"/>
    <img class="lazy-sm" data-original="{{imgurl url '320' '240"/>
  </div>
{{/each}}

Why Content sites?

it's the right tool...

and now...


(data science)

benchmarking

apachebench test
ab -n 200 -c 4 http://localhost:9001/

// 1. express(‘view cache’, false) & !routecache
Requests per second:    12.88 [#/sec] (mean)

// 2. express(‘view cache’, true) & !routecache
Requests per second:    21.38 [#/sec] (mean)

// 3. express(‘view cache’, true) & routecache(15)
Requests per second:    530.56 [#/sec] (mean)

 

WebPagetest


test results

shameless plug. Download SnapCard

QUESTIONS?

nodejs content sites

By bradoyler

nodejs content sites

One way to build content sites using Node.js, Express and server-side Handlebars templates

  • 11,189