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 nodejs@googlegroups.com
To unsubscribe from this group, send email to
nodejs+unsubscribe@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/nodejs?hl=en?hl=en