Only looked though this briefly but it looks pretty good to me. Separation
of concerns via middleware, short methods, offhanding messy async logic to
async lib, looks good. Off to a great start if this is your first app. I'd
print this out on some nice 120gsm paper and put it in a frame for the
mantlepiece. Make them grandkids proud.
-Tim
On Sunday, 14 October 2012 19:58:31 UTC+10, Mil Werns wrote:
>
> 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