I am using Node.js and Expressjs for my server side coding and MongoDB as back end. I am new to all these technologies. I need to do a list of actions based on the request.
For example in user management
To implement this i wrote the following code :
exports.register = function(req,res,state,_this,callback) {
switch(state) {
case 1: //check user already registered or not
_this.checkUser(req, res, ( state + 1 ), _this, _this.register, callback);
break;
case 2: //Already registered user so resend the activation email
_this.resendEmail(req, res, 200, _this, _this.register, callback);
break;
case 3: //not registered user so get the userId from another table that will maintain the ids for user,assets etc
_this.getSysIds(req, res, ( state + 2 ), _this, _this.register, callback);
break;
case 4: //create the entry in user table
_this.createUser(req, res, ( state + 1 ), _this, _this.register, callback);
break;
case 200: //Create Success Response
callback(true);
break;
case 101://Error
callback(false);
break;
default:
callback(false);
break;
}
};
The check user code is something like this
exports.checkUser = function(req,res,state,_this,next,callback) {
//check user already registered or not
if(user) {//Already registered user so resend the activation email
next(req,res,state,_this,callback);
}
else {//not registered user so get the userId
next(req,res,(state + 1),_this,callback);
}
}
And similarly the other functions.
The first call to the register function will do from the app.get as
user.register(req,res,1,this,function(status) {
//do somthing
});
Is there any better way to do this? The problem I am facing is based on some condition I have to follow a sequence of action. I can write all these in a nested callback structure but in that case I cannot reuse my code.
One of the problem that my boss told me was that in the code I am calling the function register and put it in a stack of callbacks as
state 1: chekuser
state 3: getIds
state 4: create user
And finally in state 200 and here i am just exiting from the stack? It may cause a stack overflow!
Is there any better way to handle the callbacks in Node.js/Expressjs ??
NOTE: The above is a sample code.I have many different situations like this.
Maybe a more elegant way would just be to put all of your various possible functions into a single object and call whichever one you need based on the state you're in. Kind of like
var States = {
1: checkuser,
2: resendEmail,
3: getSysIds,
4: createUser,
5: whateverIsNext
}
Then you just have to have one function that does everything
function exec(req,res,state,_this,callback){
States[state].apply(this, arguments)
}
And each of your functions would just set the state argument to whatever it needed to be, then recall exec, for example
function checkUser(req,res,state,_this,callback){
var doneRight = do_whatever_you_need;
state = doneRight? state++: state; //just set state to whatever number it should be
exec(req,res,state,_this,callback);
}
This way you've got your list of steps nice and readable/changeable. You've got your one function that executes everything, and each step just has to manage what the next step should be. If you wanted, you could also replace the numbered steps with named, which would make it more readable, but you'd lose the ability to increment/decrement your step variable.
You are overengineering here mixing middlewares, database layer and route handlers. Let's make it simpler and more reusable.
First of all, lets make sure, that our middlewares and route handlers can communicate with each other. That could be accomplished with session middleware:
// Somewhere inside configuration:
app.use(express.cookieParser()
app.use(express.session({secret: 'cookie monster'));
// If you don't want sessions, change with this:
app.use(function (req, res, next) {
req.session = {};
next();
});
What you want now is to have user info everywhere during request handling. For now, it's just email and whether it registered or not. That would be first stand-alone middleware. Let's assume for brevity that you detect whether user registred or not by querying db with email from query string.
// Fills `req.session.user` with user info (email, registration).
function userInfo (req, res, next) {
var user = req.session.user = req.session.user || {};
user.email = req.query.email; // I'll skip validation.
// I'm not sure which mongo driver you are using, so this is more of a pseudo-code.
db.users.findOne({email: user.email}, function(err, bson) {
if (err) {
return next(err); // Express 3 will figure that error occured
// and will pass control to error handler:
// http://expressjs.com/guide.html#error-handling
}
// Could remember information from DB, but we'll just set `registred` field.
user.registred = !!bson;
next();
});
}
After determining registration status you should either send email or create new user, that will be request handler (last param for app.get). Prior to executing handler we want our userInfo to run (can pass just userInfo (not array), but I personally like this way more):
app.get('/register', [userInfo], function (req, res) {
var user = req.session.user;
var fn = user.registred ? sendActivationEmail
: registerUser;
// If signatures differs or you need different reply for cases...
// Well, you'll figure.
fn(user.email, function (err) {
return res.send(err ? 500 : 200);
});
});
Request handler should not be bothered with what's going on inside this two functions (how many db queries, who is sending emails and how). All it knows is that first argument of provided callback is not null if error happens.
Now about creating new user. Write registerUser function which will query table with IDs, prepare and save user info in DB. For how to synchronize this two operations, see async and other SO questions. When creation is done or errored, call callback with results. This function should be part of DB layer. At least, create db.js module and export important stuff like registerUser; querying user information by email (inside first middleware) should also live inside DB layer.
Similar applies to sendActivationEmail.
Side Note: you can put whatever you want inside _id in mongo documet, not just ObjectId.