4
\$\begingroup\$

I have moved all my web-server code dealing with user management into a single Node.js module that I pasted below. I have spent time polishing it, and churned it through JSHint. I am generally happy with the code, except for the error handling. I find it clunky at best.

How can I improve error handling in the following code? Are there design or API guidelines I have not followed?

// ???
// Deal with the case where an admin no longer has admin rights, but still has a session open
var server = require('./server.js').server;
var User = require('./database.js').User;
// Define the user API
var API = {
 list: [list, 'private'],
 login: [login, 'public'],
 logout: [logout, 'private'],
 add: [add, 'admin'],
 remove: [remove, 'admin'],
 edit: [edit, 'admin']
};
// Attach path handlers
for(var label in API) {
 var denied = 'Permission denied';
 var wrapper = (function (label) {
 return function (req, res) {
 var callback = API[label][0];
 var permission = API[label][1];
 if(!req.session) {
 if(permission !== 'public') {
 res.send(denied);
 return;
 }
 } else if((permission === 'admin') && (req.session.rights !== 'Administrator')) {
 res.send(denied);
 return;
 }
 callback(req, res);
 };
 }(label));
 server.post('/user/' + label, wrapper);
}
function list(req, res) {
 User.find({}, null, {sort: {username: 1}}, function (err, users) {
 res.send(users);
 });
}
function login(req, res) {
 var errorMessage = 'Invalid user/password combination';
 find(req.body.username, function(err, user) {
 if(printError(err)) {
 return;
 }
 // Check we have a user
 if(!user) {
 res.send(errorMessage);
 return;
 }
 // Check for a username/password match
 user.comparePassword(req.body.password, function(err, isMatch) {
 if(printError(err)) {
 return;
 }
 if(isMatch) {
 // Populate the session cookie
 req.session.username = user.username;
 req.session.rights = user.rights;
 res.send('OK');
 } else {
 res.send(errorMessage);
 }
 });
 });
}
function logout(req, res) {
 // Clear session
 req.session.username = undefined;
 req.session.rights = undefined;
 res.send('OK');
}
function add(req, res) {
 find(req.body.username, function (err, user) {
 if(user) {
 res.send('The user already exists');
 } else {
 new User(req.body).save(function (err) {
 if(printError(err)) {
 return;
 }
 res.send('OK');
 });
 }
 });
}
function remove(req, res) {
 find(req.body.username, function (err, user) {
 user.remove(function (err) {
 if(printError(err)) {
 return;
 }
 res.send('OK');
 });
 });
}
function edit(req, res) {
 find(req.body.username, function (err, user) {
 if(printError(err)) {
 return;
 }
 // Populate username and rights
 user.username = req.body.user.username;
 user.rights = req.body.user.rights;
 // Only update nonempty passwords
 if(req.body.user.password !== '') {
 user.password = req.body.user.password;
 }
 user.save(function(err) {
 if(printError(err)) {
 return;
 }
 res.send('OK');
 });
 });
}
function find(username, callback) {
 User.findOne({
 username: username
 }, callback);
}
function printError(err) {
 if(err) {
 console.error('ERROR: ', err.message);
 // console.trace();
 }
 return err;
}
konijn
34.3k5 gold badges70 silver badges267 bronze badges
asked Jan 15, 2013 at 15:47
\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

From a once over

  • Not everybody in CR agrees, but I find tabular data best presented in an aligned format:

    var API = { 
     list: [list , 'private'], 
     login: [login , 'public' ], 
     logout: [logout, 'private'], 
     add: [add , 'admin' ], 
     remove: [remove, 'admin' ], 
     edit: [edit , 'admin' ] 
    }; 
    
  • For your error handing, I would mention that using express is very nice for error handling. If you insist on doing it yourself you could do something like

    function handle( f ){
     return function( err ){
     if(err){
     console.error('ERROR: ', err.message);
     return;
     }
     f.apply( this , arguments );
     }
    }
    

    Then you can

    function remove(req, res) {
     find(req.body.username, handle( function (err, user) {
     user.remove( handle( function (err) {
     res.send('OK');
     }));
     }));
    }
    
  • For your error message constants, I would declare them all together on top
  • I am ambivalent about using an array for your API config instead of an object, if you keep using that, I would suggest you use named constants declared on top:

     var callback = API[label][CALLBACK]; //Where CALLBACK is earlier declared as 0
     var permission = API[label][PERMISSION]; //Where PERMISSION is earlier declared as 1
    
answered Feb 12, 2014 at 2:40
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.