Fleegix.org

REST easy with Geddy for Node.js

2010-03-25 01:20:00

Geddy is a small Web-app development framework for Node.js. It uses a router syntax similar to Rails or Merb, and now has tools for quickly creating RESTful, resource-based routes as well.

Building and installing Geddy

Prerequisites: An up-to-date version of Node.js. I'm developing Geddy on Linux (and I'm lazy), so I'm not sure yet what adjustments might be needed for it to work smoothly on OS X. I hope one of you Mac guys can tell me what's needed.

Get Geddy from GitHub and install:

git clone git://github.com/mde/geddy.git cd geddy make && sudo make install

There's currently no configure step, because the installation is really just packaging stuff up, and a little file-copying. The installation sticks Geddy into .node_libraries/ in your home directory.

Creating a Geddy app

Geddy comes with a utility called geddy-gen you can use to create an app:

mde@localhost:~/work$ geddy-gen app bytor Created app bytor. mde@localhost:~/work$ cd bytor mde@localhost:~/work/bytor$ geddy Server running at http://127.0.0.1:8000/

Go to http://localhost:8000/, and you should see:

Attention all planets of the Solar Federation

Pretty simple.

What's in a Geddy app? Well, geddy-gen starts you off with a simple app stub. The directory contents look like this:

mde@localhost:~/work$ find bytor bytor bytor/config bytor/config/router.js bytor/app bytor/app/controllers bytor/app/controllers/main.js bytor/public

There's just a single, simple default route in router.js:

var Router = require('geddy/lib/router').Router;

router = new Router();
router.match('/').to({controller: 'Main', action: 'index'});

exports.router = router;

This has the the / path handled by the index action on the Main controller.

Adding resources

Change directories into your new app directory, and use geddy-gen resource to create one:

mde@localhost:~/work/bytor$ geddy-gen resource snow<em>dogs [ADDED] ./app/controllers/snow</em>dogs.js resources snow_dogs route added to ./config/router.js

Restart Geddy, and you'll see the new route working. Hit your new route -- for example, http://localhost:8000/snow_dogs.json, and you should see something like this:

{"method":"index","params":{"extension":"json"}}

Depending on how your browser is set up to deal with the JSON MIME type, it may want to open the response in a separate application.

Resource controllers

Take a look at the new file created, app/controllers/snow_dogs.js to see what actions a resource-based controller has. The actions are methods called on the controller, and get passed a copy of the parsed parameters from the URL and query string.

Okay, so REST isn't quite the same as CRUD, but Geddy's resource-based routes do take the expedient course of creating url/request-method mappings for easy CRUD operations like this:

GET /snow_dogs
(SnowDogs controller, index action)

GET /snow_dogs/add
(SnowDogs controller, add action, for any new-resource template -- "new" is not usable as a JavaScript action name)

POST /snow_dogs
(SnowDogs controller, create action)

GET /snow_dogs/:id
(SnowDogs controller, show action)

PUT /snow_dogs/:id
(SnowDogs controller, update action)

DELETE /snow_dogs/:id
(SnowDogs controller, remove action)

Content-negotiation

Geddy has some built-in ability to perform content-negotiation based on the abilities of the client, and the requested filename-extension. Right now, this feature is very bare-bones, supporting only JSON and plaintext, but can be easily extended to support XML, Atompub, or whatever.

If you have a JSON-serializable JavaScript object you want to return in JSON format, all you have to do is set up a resource with geddy-gen resource, and in the appropriate controller action, pass your JavaScript object to the respond method.

Something like this:

this.respondsWith = ['text', 'json'];

this.show = function (params) {
  // (Fetch some item by params.id)
  item = {foo: 'FOO', bar: 1, baz: false};
  this.respond(item);
};

Geddy will automatically serialize item to JSON, and set the content-type appropriately -- assuming the client can accept JSON.

Eventually it should be possible to register handlers for new content-types on a per-controller, or per-model basis. (Doing this by way of defining a 'to_[format]' method on the objects to be returned isn't a workable solution in JavaScript, when you're likely to be returning built-in types.)

If you want to use a specific format, you can pass it as the second argument to the respond call -- even if it's not one of the listed formats in respondsWith.

Perhaps something like this:

this.show = function (params) {
  // (Fetch some item by params.id)
  item = {foo: 'FOO', bar: 1, baz: false};

  // Serve up some html
  if (params.extension == 'html') {
    var html = '';
    html += '<html><body>';
    html += '<h3>' + item.foo + '</h3>';
    html += '</body></html>';
    this.respond(html, 'html');
  }
  // Otherwise return formatted data
  else {
    this.respond(item);
  }
};

Geddy uses simple strings for the identifiers of the formats, rather than an enumeration, because it's just cleaner-looking. I prefer simply 'json' to this.formats.JSON or something like that.

What's next?

Next is work on easy handling of templates. I'm not sure how far down the HAML/SASS road I want to go initially, but basic EJS, and Moustache seem like a good place to start.


Comments

Tony (2010-03-26)

Nice article. What about models? If create models like in rails I see one problem. For example in rails we call model method and wait when it’s done, in nodejs world we prefer event-oriented actions. But with this approach in controllers if we have many calls to db we will have

var User = require(‘geddy/lib/models/user’).User; var Comment = require(‘geddy/lib/models/comment’).Comment;

this.show = functions(params) { User.findBy(‘id’, params[‘id’], function(err, data) { if (!err) { var user = data; user.comments = Comment.findBy(‘user_id’, params[‘id’], function(err, data) { // and so on. I want to say that more queries to the database so large investments in code } } }); }


mde (2010-03-30)

Tony,

I haven’t even touched models, but ultimately they need to serve two different goals: 1. accommodate non-relational datastores and 2. allow you to work with the non-blocking callback-based coding style in a clean way. This is a hard problem, as you note. :)

There are some different patterns you can use to structure your code better in an async environment. The AsyncChain I’ve added to Geddy is one example. There are some good blog posts on this, but I hope to write a post soon to add my two cents.


About

This is the blog for Matthew Eernisse. I currently work at Yammer as a developer, working mostly with JavaScript. All opinions expressed here are my own, not my employer's.

Related

Previous posts

All previous posts ยป

This blog is a GeddyJS application.