Hi,

I want to implement a REST API with node.js and restify. Because it is my 
first node.js project, I spent some time to learn how to structure the 
application to avoid the callback hell. Please have a look to the following 
solution.


*Assumptions*
- This approach is for writing an end-user application, not a module or a 
driver in the sense of a public library.
- REST services are closely related to CRUD, so we have a lot of simple 
sequential/interdependent operations (get_data + validate_data [+ 
change_data] + save_data).


*Goals*
*===> Focus is writing maintainable, clean and easy to understand code! <===
*
Conversely, this means...
- Performance is secondary (scaling horizontally if necessary).
- Avoid "low level" methods like process.nextTick(), instead use "high 
level" methods/libs like middleware and control flow modules.
- Use modules to implement a well known MVC structure.


*Code*
Please ignore the poor logging, the not very useful view tasks etc. in the 
following example. It should only demonstrate the MVC / callback structure.


*/* server.js */*

var restify = require("restify");
var router = require("./router");
...
router.route(server);
server.listen(...);


*/* router.js */*

var userController = require("./controllers/user");
... // more controllers

exports.route = function route(server) {
  server.get("/users",
    [
      userController.checkRead,
      userController.read,
    ]
  );
  server.put("/users",
    [
      userController.checkUpdate,
      userController.update,
    ]
  );
...
};


*/* controllers/user.js */*

var restify = require("restify");
var async = require("async");
var check = require('validator').check;
var sanitize = require('validator').sanitize;
var m = require("../models/user");
var v = require("../views/user");
...

var checkUpdate = function userCheckUpdate(req, res, next) {
  // validate user input
  try {
    check(req.params.id, "id").isInt();
    check(req.params.forename, "forename").len(1,50);
    check(req.params.surname, "surname").len(1,50);
    check(req.params.email, "email").isEmail();
  } catch(err) {
    if(err) {
      return next(new restify.InvalidArgumentError("Invalid arguments"));
    }
  }
  return next();
};


var update = function userUpdate(req, res, next) {
  // create model
  var model = {
    forename: sanitize(req.params.forename).xss(),
    surname: sanitize(req.params.surname).xss(),
    email: sanitize(req.params.email).xss(),
  };

  //*** "sub-methods" part ***

  // _example_ for data check
  function checkOwner(curData, callback) {
    if(curData.ownerId !== req.userId) {
      return callback(new 
restify.InvalidArgumentError("NotAuthorizedError"));
    }
    return callback(null, curData);
  };

  // _example_ for simple model manipulation
  function addSimple(model, callback) {
    model.updateDate = new Date().getTime();
    return callback(null, model);
  };

  // _example_ for complex model manipulation
  function addComplex(model, callback) {
    // use async again to simulate synchronous/parallel/... flow
    async.waterfall(
      [
        // do something
      ]
      function(err, model) {
        if(err) {
          return callback(new restify.InternalError());
        }
        return callback(null, model);
      }
    );
  };


  // *** main part ***
  // use async to simulate synchronous flow
  async.waterfall(
    [
      function(aNext) { m.get(req.params.id, aNext); },
      function(curData, aNext) { checkOwner(curData, aNext); },
      function(curData, aNext) { addSimple(model, aNext); },
      function(model, aNext) { addComplex(model, aNext); },
      function(model, aNext) { m.update(model, aNext); },
      function(model, aNext) { v.update(model, aNext); },
    ],
    function(err, model) {
      if(err) { return next(err); }
      res.send(200, model);
    }
  );
  return next();
};
...


*/* models/user.js */*
...
var get = function userGet(id, callback) {
  var curData = ... // DB query for ID
  if(err) {
    console.log("DB error: " + err);
    return callback(new restify.InternalError());
  };
  return callback(null, curData);
};

var update = ...
...


*/* views/user.js */*
...
var update = function userUpdate(model, callback) {
  // adjust model for output
  delete model.updateDate;
  ...
  callback(null, model);
};


As you can see, this approach uses the middleware concept + async module to 
separate the application in small (MVC) pieces that are hopefully easy to 
understand.
But as I said before: I'm a node.js newbie :)

*What do you think about this structure?
Are there any drawbacks?*

Thanks a lot for your help
Mil


-- 
Job Board: http://jobs.nodejs.org/
Posting guidelines: 
https://github.com/joyent/node/wiki/Mailing-List-Posting-Guidelines
You received this message because you are subscribed to the Google
Groups "nodejs" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to
[email protected]
For more options, visit this group at
http://groups.google.com/group/nodejs?hl=en?hl=en

Reply via email to