TJ Holowaychuk

Month

May 2011

2 posts

node recap #2
JSONSelect

This first library is JSONSelect. This library allows you to query JSON using css selectors! this seems like an obvious one, but I haven’t personally seen any other implementations.

It’s not node-specific, however it is JavaScript, so naturally it works with nodejs as well, and over all it’s just a neat concept. Suppose we have the following JSON:

JSONSelect

  {
      "name": {
          "first": "Lloyd",
          "last": "Hilaiel"
      },
      "favoriteColor": "yellow",
      "languagesSpoken": [
          {
              "language": "Bulgarian",
              "level": "advanced"
          },
          {
              "language": "English",
              "level": "native"
          },
          {
              "language": "Spanish",
              "level": "beginner"
          }
      ],
      "seatingPreference": [
          "window",
          "aisle"
      ],
      "drinkPreference": [
          "whiskey",
          "beer",
          "wine"
      ],
      "weight": 172
  }

To grab the languages “Bulgarian”, “English” and “Spanish” we could issue the following query:

.languagesSpoken .language

cool!

node-canvas Image#src=Buffer

A few days ago I added support to node-canvas for Image#src= to accept Buffer objects, not only path strings. This needs to be polished, and to increase format support but we can finally apply image data from arbitrary sources:

 var buf = fs.readFileSync(__dirname + '/textures/jpeg');
 var img = new Image;
 img.src = buf;
 ctx.drawImage(img, 0, 0, 400, 400);

When the Buffer is assigned we can sniff the bytes to determine the format by looking for the “magic number”. For example a PNG starts with the following bytes “89 50 4e 47 0d 0a 1a 0a”, while a JPEG starts with “ff d8”.

Express 2.3.8

A recent Express refactor moved Connect’s router middleware into Express so that I could modify it further. This also means that Connect 2.0 will no longer have router. At first it seemed like a good idea, but routing to specific and it gives people the wrong idea, Connect is meant to be an abstraction layer for higher level frameworks, not to be used directly for application logic. Though this is not true for all cases, such as simply serving static files from a directory or two.

This commit consists of a general refactor of the router, while retaining the public API. What does this mean for Express? flexibility! For example now we may query Express to see which (if any) routes match a specific route path:

 app.get('/user/:id');
 // => [Route]

or routes that match any HTTP method:

 app.all('/user/:id');
 // => [Route, Route, Route]

this is functionally equivalent to:

 app.lookup.all('/user/:id');

Another alternative is “matching” rather than “lookup”. With app.match.VERB() we can query Express to see which routes would match the url passed, for example:

app.match.get('/user/12/edit');
// => [Route]

app.match.del('/user/12');
// => [Route]

app.match.all('/user/12?foo=bar');
// => [Route, Route]

In this commit I’ve introduced the ability to register logic for defining parameters. Within Express core we only support middleware-style param pre-conditions, for example auto-loading a user for any route that includes the :uid param:

app.param('uid', function(req, res, next, uid){
  User.find(uid, function(err, user){
    if (err) return next(err);
    req.user = user;
    next();
  });
});

app.get('/user/:uid, function(){
  // req.user
});

while this is great, some cases can be less verbose, while still expanding to middleware-style functions, this is where express-params comes in. This plugin extends Express with additional app.param() logic, for example via RegExp:

  app.param('uid', /^[0-9]+$/);

  app.get('/user/:uid', function(req, res, next){
    var uid = req.params.uid;
    res.send('user ' + uid);
  });

  app.get('/user/:name', function(req, res, next){
    var name = req.params.name;
    res.send('user ' + name);
  });

or via return value, useful for validations and coercion:

  app.param('id', Number);

  app.get('/user/:id', function(req, res, next){
    var id = req.params.id;
    res.send('typeof ' + typeof id + ' ' + id);
  });
Fabric.js

This one is not node related, but I wanted to sneak it in :) Fabric is a slick client-side library providing an object model on top of canvas. It has a built-in drawing mode and path smoothing, SVG parsing and some other really cool features. If you do a lot of canvas work it’s worth checking out, the author did a good job at keeping the source clean and commented, which is very important when utilizing open source.

node-migrate

node-migrate is a very small abstract migration framework. All it cares about is that you supply an up() and down() method, and invoke the callback when your migration is complete:

 exports.up = function(next){
   next();
 };

 exports.down = function(next){
   next();
 };

node-migrate comes with the migrate(1) executable, allowing you to run and create migrations, for example here we add two migrations, populating ./migrations/0-add-pets.js etc.

 $ migrate create add-pets
 $ migrate create add-owners

The contents of our badass pet adding migration might look something like this:

 var db = require('./db');

  exports.up = function(next){
    db.rpush('pets', 'tobi');
    db.rpush('pets', 'loki');
    db.rpush('pets', 'jane', next);
  };

  exports.down = function(next){
    db.rpop('pets');
    db.rpop('pets', next);
  };

Now all we have to do is run the migrations:

 $ migrate
 up : migrations/0-add-pets.js
 up : migrations/1-add-jane.js
 up : migrations/2-add-owners.js
 up : migrations/3-coolest-pet.js
 migration : complete

The next time we run migrate, we’ll see that they have already been completed:

 $ migrate
 migration : complete

For more information check out the GitHub repo.

Move.js

The other night I noticed that Apple’s new iMac page showcased some really slick CSS3 animations, and wanted a really simple and intuitive way to create similar effects so I came up with Move.js.

Below is an example of the API:

move('#example-13 .box2')
  .set('background-color', 'red')
  .x(500)
  .scale(.5)
  .rotate(60)
    .then()
      .rotate(30)
      .scale(1.5)
      .set('border-radius', 5)
      .set('background-color', 'white')
      .then()
        .set('opacity', 0)
        .pop()
      .pop()
  .end();

It’s still in it’s early stages, and I don’t have much in the way of documentation yet however feel free to check out the site as well as the examples in the repository.

May 25, 201114 notes
#nodejs #express #canvas #css
node recap

Figured I would whip up a quick blog post during lunch to mention some recent additions / releases to node projects.

Express Mongoose

Aaron recently released express-mongoose which is a fantastic plugin for any of you using Mongoose allowing you to pass query promises directly to res.render(), res.partial(), and `res.send() calls, deferring rendering until complete.

For example you may currently be nesting as shown below:

var News = db.model('News');
app.get('/dashboard', function (req, res, next) {
  req.user.getLikes(function(err, likes){
    if (err) return next(err);
    News.getLatest(function(err, news){
      if (err) return next(err);
      res.render('dashboard', { likes: likes, latestNews: news });
    });
  });
});

well now you can simply pass the promises, next()ing exceptions. Much cleaner!

var News = db.model('News');
app.get('/dashboard', function (req, res) {
  res.render('dashboard', {
      likes: req.user.getLikes()
    , latestNews: News.getLatest()
  });
});
Asset

asset is a tiny but helpful utility written with node to manage assets such a JavaScript or css libraries. You could think of this as the homebrew or bundler of assets.

Installing assets is extremely simple, and by default will be installed to ./public. For example we can install g.raphael and g.pie to ./public/javascripts which will install the raphael dependency as well:

$ asset g.raphael g.pie -o public/javascripts

   install : raphael@1.4.7
   install : g.raphael@0.4.1
   install : g.pie@0.4.1
  download : raphael@1.4.7
  complete : raphael@1.4.7 public/javascripts/raphael.js
  download : g.raphael@0.4.1
  complete : g.raphael@0.4.1 public/javascripts/g.raphael.js
  download : g.pie@0.4.1
  complete : g.pie@0.4.1 public/javascripts/g.pie.js

Last night I added a tiny patch which adds support for assets.json, which you can add to your project to list dependencies. For example this file might contain something like below

{
     "g.raphael": "0.4.1"
   , "jquery": "1.5.2"
   , "modernizr": "1.7"
 }

Which we can then install with a single command:

$ asset

    install : g.raphael@0.4.1
 dependency : raphael@1.4.7
    install : raphael@1.4.7
    install : jquery@1.5.2
    install : modernizr@1.7
   download : jquery@1.5.2
   complete : jquery@1.5.2 public/jquery.js
   download : raphael@1.4.7
   complete : raphael@1.4.7 public/raphael.js
   download : g.raphael@0.4.1
   complete : g.raphael@0.4.1 public/g.raphael.js
   download : modernizr@1.7
   complete : modernizr@1.7 public/modernizr.js
Cluster updates

Cluster is a multi-process server manager for node. Recent additions now allow you to run cluster without a server, which is great for processing job queues! below is an example of how to utilize cluster for such a task:

var cluster = require('../');

var proc = cluster()
  .set('workers', 4)
  .use(cluster.debug())
  .start();

if (proc.isWorker) {
  var id = process.env.CLUSTER_WORKER;
  console.log('  worker #%d started', id);
  setInterval(function(){
    console.log('  processing job from worker #%d', id);
  }, 1000);
} else {
  setTimeout(function(){
    proc.close();
  }, 10000);
}
Express Resource

For those of you who have been looking for resourceful routing for Express, you may have heard or express-resource already, and if not express-resource utilizes regular Express HTTP verb/pathname routing dressed up in an API better suited for resources.

Installation:

$ npm install express-resource

Below is an example use-case providing actions for forums and their associated threads. By calling forum.add(threads) routes such as GET /forums/:forum/threads/:thread and GET /forums/:forum/threads are mapped to action callbacks. This library also supports auto-loading of resources, providing them as req.forum, req.thread etc instead of manually doing this using the values of req.params.forum or req.params.thread.

  var forum = app.resource('forum', actionsHere)
    , threads = app.resource('thread', actionsHere)

  forum.add(threads);

For more details check out the documentation and tests in the repo.

RedisKit

Week or so ago I started a small high-level redis library called rediskit. I started creating the lower level structures, and will be building up to composite structures and higher level abstractions.

Install with:

$ npm install rediskit

Example usage:

var list = new List('pets')
  , client = list.client
  , tobi = new Hash('pet:tobi')
  , loki = new Hash('pet:loki')
  , jane = new Hash('pet:jane');

list.rpush('tobi');
list.rpush('jane');
list.rpush('loki');

tobi.set('age', 1);
loki.set('age', 0.5);
jane.set('age', 3);

list.sort.by('pet:*->age').get('#').get('pet:*->age').end(function(err, res){
  res.should.eql(['loki', '0.5', 'tobi', '1', 'jane', '3']);
});
RedBack

I also just saw a tweet about a similar library redback which is worth checking out if you are a redis fan.

Installation:

$ npm install redback

Usage:

var redback = require('redback').createClient();

//redback.create<Structure>(key)

var user1 = redback.createHash('user1');
user.set({username:'chris', password:'redisisawesome'}, callback);

var log = redback.createCappedList('log', 1000);
log.push('Log message ...');

var user3 = redback.createSocialGraph(3);
user3.follow(1, callback);
May 11, 201123 notes
#node
Next page →
2012 2013
  • January
  • February 3
  • March
  • April
  • May
  • June
  • July
  • August
  • September
  • October
  • November
  • December
2011 2012 2013
  • January 2
  • February 2
  • March 4
  • April 3
  • May 2
  • June 2
  • July 4
  • August 3
  • September 1
  • October 2
  • November 1
  • December 5
2010 2011 2012
  • January
  • February 3
  • March
  • April 4
  • May 2
  • June 1
  • July 3
  • August 3
  • September 3
  • October
  • November 3
  • December 1
2010 2011
  • January
  • February
  • March
  • April 8
  • May 1
  • June 3
  • July 4
  • August 4
  • September 3
  • October
  • November 1
  • December 1